/** \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: * * * * * * * * * * * * * * * * *
commandfunctionReturn value
?What was the last sigval ?SNN (signal NN)
greturn the value of the CPU registershex data or ENN
Gset the value of the CPU registersOK or ENN
pread the value of a single CPU registerhex data or ENN
Pset the value of a single CPU registerOK or ENN
mAA..AA,LLLLRead LLLL bytes at address AA..AAhex data or ENN
MAA..AA,LLLL:Write LLLL bytes at address AA.AAOK or ENN
cResume at current addressSNN (signal NN)
cAA..AAContinue at address AA..AASNN
sStep one instructionSNN
sAA..AAStep one instruction from AA..AASNN
DGDB detached -- attempt to resume(no reply)
kkill -- reboots the system(no reply)
* * All commands and responses are sent with a packet which includes a * checksum. A packet consists of $\#\ where: * * - \ :: characters representing the command or response * - \ :: two hex digits computed as mod-256 sum of \ * * 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 #include #include #include #include #include #include #include /** \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 $\#\. 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: $\#\. * 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); } }