aos/kernel/gdb_stub.c
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

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, &regnum);
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, &regnum);
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);
}
}