text: /** \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);
      }
  }
  