446 lines
14 KiB
C
446 lines
14 KiB
C
/** \file
|
|
* \brief Architecture-independent parts of in-kernel GDB stub.
|
|
*
|
|
* This file implements the kernel-side GDB stubs for remote debugging when
|
|
* running on hardware. It is loosely based on the public domain i386-stub.c
|
|
* which is part of the GDB sources.
|
|
*
|
|
* The following gdb commands are supported:
|
|
*
|
|
*<table>
|
|
*<tr><td>command</td><td>function</td><td>Return value</td></tr>
|
|
*<tr><td>?</td><td>What was the last sigval ?</td><td>SNN (signal NN)</td></tr>
|
|
*<tr><td>g</td><td>return the value of the CPU registers</td><td>hex data or ENN</td></tr>
|
|
*<tr><td>G</td><td>set the value of the CPU registers</td><td>OK or ENN</td></tr>
|
|
*<tr><td>p</td><td>read the value of a single CPU register</td><td>hex data or ENN</td></tr>
|
|
*<tr><td>P</td><td>set the value of a single CPU register</td><td>OK or ENN</td></tr>
|
|
*<tr><td>mAA..AA,LLLL</td><td>Read LLLL bytes at address AA..AA</td><td>hex data or ENN</td></tr>
|
|
*<tr><td>MAA..AA,LLLL:</td><td>Write LLLL bytes at address AA.AA</td><td>OK or ENN</td></tr>
|
|
*<tr><td>c</td><td>Resume at current address</td><td>SNN (signal NN)</td></tr>
|
|
*<tr><td>cAA..AA</td><td>Continue at address AA..AA</td><td>SNN</td></tr>
|
|
*<tr><td>s</td><td>Step one instruction</td><td>SNN</td></tr>
|
|
*<tr><td>sAA..AA</td><td>Step one instruction from AA..AA</td><td>SNN</td></tr>
|
|
*<tr><td>D</td><td>GDB detached -- attempt to resume</td><td>(no reply)</td></tr>
|
|
*<tr><td>k</td><td>kill -- reboots the system</td><td>(no reply)</td></tr>
|
|
*</table>
|
|
*
|
|
* All commands and responses are sent with a packet which includes a
|
|
* checksum. A packet consists of $\<packet info\>#\<checksum\> where:
|
|
*
|
|
* - \<packet info\> :: characters representing the command or response
|
|
* - \<checksum\> :: two hex digits computed as mod-256 sum of \<packet info\>
|
|
*
|
|
* When a packet is received, it is first acknowledged with either '+' or '-'.
|
|
* '+' indicates a successful transfer. '-' indicates a failed transfer.
|
|
*
|
|
* Example:
|
|
* - Host: $m0,10#2a
|
|
* - Reply: +$00010203040506070809101112131415#42
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2007, ETH Zurich.
|
|
* All rights reserved.
|
|
*
|
|
* This file is distributed under the terms in the attached LICENSE file.
|
|
* If you do not find this file, copies can be found by writing to:
|
|
* ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
|
|
*/
|
|
|
|
#include <kernel.h>
|
|
#include <barrelfish_kpi/cpu.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <serial.h>
|
|
#include <exec.h>
|
|
#include <gdb_stub.h>
|
|
#include <arch_gdb_stub.h>
|
|
|
|
/** \brief Flag. Enables debug messages from the stub itself. */
|
|
#define DEBUG_ENABLED true
|
|
|
|
/** \brief Quick-and-dirty debug message macro. */
|
|
#define DEBUG(...) \
|
|
if (DEBUG_ENABLED) { \
|
|
debug(SUBSYS_GDB, __VA_ARGS__); \
|
|
}
|
|
|
|
/** \brief Defines the maximum number of characters in in/outbound buffers.
|
|
*
|
|
* At least NUMREGBYTES * 2 are needed for register packets. */
|
|
#define BUFMAX 512
|
|
|
|
/** \brief Wrapper for debugger output. */
|
|
static inline void myputchar(char c)
|
|
{
|
|
serial_debug_putchar(c);
|
|
}
|
|
|
|
/** \brief Wrapper for debugger input. */
|
|
static inline char mygetchar(void)
|
|
{
|
|
return serial_debug_getchar();
|
|
}
|
|
|
|
/** \brief Convert an ASCII hex character to its numeric value.
|
|
* \return -1 if the character is invalid. */
|
|
static int fromhex(char ch)
|
|
{
|
|
if ((ch >= 'a') && (ch <= 'f')) {
|
|
return ch - 'a' + 10;
|
|
} else if ((ch >= '0') && (ch <= '9')) {
|
|
return ch - '0';
|
|
} else if ((ch >= 'A') && (ch <= 'F')) {
|
|
return ch - 'A' + 10;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/** \brief Convert a number 0..15 to the corresponding hex character. */
|
|
static char tohex(int i)
|
|
{
|
|
static const char hexchars[] = "0123456789abcdef";
|
|
assert(i >= 0 && i <= 0xf);
|
|
return hexchars[i];
|
|
}
|
|
|
|
/** \brief Parse a hex-encoded integer
|
|
*
|
|
* Reads a hex-encoded integer into val, stopping at the first non-hex character
|
|
* \return first position after the end of the consumed characters
|
|
*/
|
|
static const char * NONNULL NTS parse_hex_val(const char * NONNULL NTS str,
|
|
uintptr_t * NONNULL val)
|
|
{
|
|
uintptr_t ret = 0;
|
|
|
|
for (int pos = 0; true; pos++) {
|
|
int value = fromhex(str[pos]);
|
|
if (value >= 0) {
|
|
assert(pos < sizeof(uintptr_t) * 2); // overflow
|
|
ret = (ret << 4) | value;
|
|
} else {
|
|
*val = ret;
|
|
return &str[pos];
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \brief Read memory region into a buffer as a hex string.
|
|
*
|
|
* Converts the memory pointed to by mem (of length memlen) into a hex string,
|
|
* placing the result into buf (of length buflen), and terminating with a \\0.
|
|
* \return Number of characters written on success, negative on failure.
|
|
* May fail if a page fault would be incurred.
|
|
*/
|
|
static int mem_to_hex(lvaddr_t mem, size_t memlen,
|
|
char * NONNULL COUNT(buflen) buf, size_t buflen)
|
|
{
|
|
int bufpos = 0;
|
|
uint8_t ch;
|
|
int r;
|
|
|
|
if (buflen < memlen * 2 + 1) {
|
|
return -1; // not enough space in output buffer
|
|
}
|
|
|
|
for (int mempos = 0; mempos < memlen; mempos++) {
|
|
r = gdb_arch_read_byte((uint8_t * SAFE NONNULL)TC(mem + mempos), &ch);
|
|
if (r != 0) {
|
|
return r;
|
|
}
|
|
|
|
buf[bufpos++] = tohex(ch >> 4); // top bits of char
|
|
buf[bufpos++] = tohex(ch & 0xf); // bottom bits of char
|
|
}
|
|
|
|
buf[bufpos++] = '\0';
|
|
return bufpos;
|
|
}
|
|
|
|
/** \brief Copy data from hex string in buffer to raw memory region.
|
|
*
|
|
* Writes data in the provided hex string buffer into the given memory region.
|
|
* \return zero on success, negative on failure.
|
|
* May fail if a page fault would be incurred.
|
|
*/
|
|
static int hex_to_mem(const char * NONNULL NT COUNT(memlen * 2) buf,
|
|
lvaddr_t mem, size_t memlen)
|
|
{
|
|
for (int mempos = 0; mempos < memlen; mempos++) {
|
|
uint8_t ch;
|
|
|
|
assert(*buf != '\0');
|
|
ch = fromhex(*buf++) << 4;
|
|
assert(*buf != '\0');
|
|
ch += fromhex(*buf++);
|
|
|
|
int r = gdb_arch_write_byte(&((uint8_t * SAFE NONNULL)TC(mem))[mempos],
|
|
ch);
|
|
if (r != 0) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** \brief Wait to receive a valid checksummed packet from GDB.
|
|
*
|
|
* Scans input for the sequence $\<packet info\>#\<checksum\>. If the
|
|
* checksum is valid, acknowledges it to the sender and returns the buffer.
|
|
* If invalid, reports failure to the sender and waits for the retransmission.
|
|
*/
|
|
static const char * NONNULL NTS getpacket(void)
|
|
{
|
|
static char (NT buffer)[BUFMAX];
|
|
uint8_t checksum, xmitcsum;
|
|
int bufpos;
|
|
char ch = 0; /* shut up a useless deputy warning */
|
|
|
|
while (true) {
|
|
/* wait around for the start character, ignore all other characters */
|
|
while (mygetchar() != '$') {}
|
|
|
|
retry:
|
|
checksum = 0;
|
|
bufpos = 0;
|
|
|
|
/* now, read until a # or end of buffer is found */
|
|
while (bufpos < BUFMAX - 1) {
|
|
ch = mygetchar();
|
|
if (ch == '$') {
|
|
goto retry; // invalid
|
|
} else if (ch == '#') {
|
|
break; // found start of checksum
|
|
}
|
|
checksum += ch;
|
|
buffer[bufpos++] = ch;
|
|
}
|
|
buffer[bufpos] = 0;
|
|
|
|
if (bufpos == BUFMAX - 1) {
|
|
DEBUG("Warning: Incoming buffer full in getpacket()");
|
|
}
|
|
|
|
if (ch == '#') {
|
|
xmitcsum = fromhex(mygetchar()) << 4;
|
|
xmitcsum += fromhex(mygetchar());
|
|
|
|
if (checksum != xmitcsum) {
|
|
DEBUG("bad checksum. My count=0x%x, sent=0x%x. buf='%s'",
|
|
checksum, xmitcsum, buffer);
|
|
myputchar('-'); // failed checksum
|
|
} else {
|
|
myputchar('+'); // successful transfer
|
|
return buffer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \brief Send the packet in the buffer, adding a checksum.
|
|
*
|
|
* Sends the packet, using the format: $\<packet info\>#\<checksum\>.
|
|
* Loops until it receives an acknowledgement response ('+') from GDB.
|
|
*/
|
|
static void putpacket(char * NONNULL NTS buffer)
|
|
{
|
|
do {
|
|
uint8_t checksum = 0;
|
|
myputchar('$');
|
|
|
|
for (int pos = 0; buffer[pos] != '\0'; pos++) {
|
|
char ch = buffer[pos];
|
|
myputchar(ch);
|
|
checksum += ch;
|
|
}
|
|
|
|
myputchar('#');
|
|
myputchar(tohex(checksum >> 4)); // top bits of checksum
|
|
myputchar(tohex(checksum & 0xf)); // bottom bits of checksum
|
|
} while (mygetchar() != '+');
|
|
}
|
|
|
|
/** \brief Generic entry point to the GDB stub.
|
|
*
|
|
* Wait for a remote GDB to start talking to us, and then service its requests.
|
|
* This function should run on a different stack the main kernel, and does not
|
|
* return.
|
|
* \param signal_code Unix-style signal value indicating reason for interruption
|
|
* \param init_message Optional initial message to send to the remote GDB
|
|
*/
|
|
void gdb_stub_entry(int signal_code, char * OPT NTS init_message)
|
|
{
|
|
const char * NONNULL NTS request;
|
|
const char * NONNULL NTS newpos;
|
|
static char (NT buffer)[BUFMAX];
|
|
static bool gdb_connected = false;
|
|
char *reply;
|
|
uintptr_t addr, length, regnum, value;
|
|
int r;
|
|
|
|
/* send initial message, if gdb is listening for it */
|
|
if (init_message && gdb_connected) {
|
|
putpacket(init_message);
|
|
} else if (!gdb_connected) {
|
|
printk(LOG_NOTE, "Waiting for GDB connection...\n");
|
|
}
|
|
|
|
/* loop handling requests */
|
|
while (true) {
|
|
request = getpacket();
|
|
gdb_connected = true;
|
|
reply = buffer;
|
|
|
|
switch (*request++) {
|
|
|
|
case '?':
|
|
/* Indicate reason target halted */
|
|
r = snprintf(buffer, sizeof(buffer), "S%02hhx", signal_code);
|
|
assert(r < sizeof(buffer));
|
|
if (r >= sizeof(buffer)) {
|
|
/* ensure termination */
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
}
|
|
break;
|
|
|
|
case 'g':
|
|
/* Read general registers */
|
|
r = mem_to_hex((lvaddr_t)GDB_ARCH_REGADDR, GDB_ARCH_REGBYTES,
|
|
buffer, sizeof(buffer));
|
|
if (r < 0) {
|
|
reply = "E03";
|
|
}
|
|
break;
|
|
|
|
case 'G':
|
|
/* Write general registers */
|
|
r = hex_to_mem(request, (lvaddr_t)GDB_ARCH_REGADDR,
|
|
GDB_ARCH_REGBYTES);
|
|
if (r == 0) {
|
|
reply = "OK";
|
|
} else {
|
|
reply = "E03";
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
/* Read a single register */
|
|
newpos = parse_hex_val(request, ®num);
|
|
if (newpos == request) {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
|
|
r = gdb_arch_get_register(regnum, &value);
|
|
if (r != 0) {
|
|
reply = "E02";
|
|
break;
|
|
}
|
|
|
|
r = mem_to_hex((lvaddr_t)&value, sizeof(uintptr_t), buffer,
|
|
sizeof(buffer));
|
|
if (r < 0) {
|
|
reply = "E03";
|
|
}
|
|
break;
|
|
|
|
case 'P':
|
|
/* Write a single register */
|
|
newpos = parse_hex_val(request, ®num);
|
|
if (newpos == request || *newpos != '=') {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
request = newpos + 1;
|
|
newpos = parse_hex_val(request, &value);
|
|
if (newpos == request) {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
|
|
r = gdb_arch_set_register(regnum, value);
|
|
if (r == 0) {
|
|
reply = "OK";
|
|
} else {
|
|
reply = "E02";
|
|
}
|
|
break;
|
|
|
|
case 'm':
|
|
/* mAA..AA,LLLL Read LLLL bytes at address AA..AA */
|
|
request = parse_hex_val(request, &addr);
|
|
if (addr == 0 || *(request++) != ',') {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
request = parse_hex_val(request, &length);
|
|
if (length == 0) {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
r = mem_to_hex(addr, length, buffer, sizeof(buffer));
|
|
if (r < 0) {
|
|
reply = "E03";
|
|
}
|
|
break;
|
|
|
|
case 'M':
|
|
/* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
|
|
request = parse_hex_val(request, &addr);
|
|
if (addr == 0 || *request++ != ',') {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
request = parse_hex_val(request, &length);
|
|
if (length == 0 || *request++ != ':') {
|
|
reply = "E01";
|
|
break;
|
|
}
|
|
r = hex_to_mem(request, addr, length);
|
|
if (r == 0) {
|
|
reply = "OK";
|
|
} else {
|
|
reply = "E03";
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
/* Single step */
|
|
parse_hex_val(request, &addr); // try to read optional parameter
|
|
gdb_arch_single_step(addr); // should not return
|
|
reply = "E02";
|
|
break;
|
|
|
|
case 'c':
|
|
/* Continue execution */
|
|
parse_hex_val(request, &addr); // try to read optional parameter
|
|
gdb_arch_continue(addr); // should not return
|
|
reply = "E02";
|
|
break;
|
|
|
|
case 'D':
|
|
/* GDB detached, try to continue */
|
|
printk(LOG_NOTE, "GDB has detached. Attempting to continue...");
|
|
gdb_connected = false;
|
|
gdb_arch_continue(0);
|
|
break;
|
|
|
|
case 'k':
|
|
/* Kill the program */
|
|
printk(LOG_NOTE, "Kill requested by remote GDB.\n");
|
|
reboot();
|
|
break;
|
|
|
|
default:
|
|
/* unsupported command */
|
|
reply = "";
|
|
break;
|
|
}
|
|
|
|
putpacket(reply);
|
|
}
|
|
}
|