Listen on a socket for gdb to connect to.
authorTim Newsome <tim@sifive.com>
Sun, 6 Mar 2016 01:35:06 +0000 (17:35 -0800)
committerTim Newsome <tim@sifive.com>
Mon, 23 May 2016 19:12:09 +0000 (12:12 -0700)
So far it just listens, and gdb times out because it's not getting any
messages back.

Receive packets and verify their checksum.

riscv/gdbserver.cc [new file with mode: 0644]
riscv/gdbserver.h [new file with mode: 0644]
riscv/riscv.mk.in
riscv/sim.cc
riscv/sim.h
spike_main/spike.cc

diff --git a/riscv/gdbserver.cc b/riscv/gdbserver.cc
new file mode 100644 (file)
index 0000000..83587f9
--- /dev/null
@@ -0,0 +1,251 @@
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cassert>
+#include <cstdio>
+#include <vector>
+
+#include "gdbserver.h"
+
+template <typename T>
+unsigned int circular_buffer_t<T>::size() const
+{
+  if (end >= start)
+    return end - start;
+  else
+    return end + capacity - start;
+}
+
+template <typename T>
+void circular_buffer_t<T>::consume(unsigned int bytes)
+{
+  start = (start + bytes) % capacity;
+}
+
+template <typename T>
+unsigned int circular_buffer_t<T>::contiguous_space() const
+{
+  if (end >= start)
+    if (start == 0)
+      return capacity - end - 1;
+    else
+      return capacity - end;
+  else
+    return start - end - 1;
+}
+
+template <typename T>
+void circular_buffer_t<T>::data_added(unsigned int bytes)
+{
+  end += bytes;
+  assert(end <= capacity);
+  if (end == capacity)
+    end = 0;
+}
+
+template <typename T>
+void circular_buffer_t<T>::reset()
+{
+  start = 0;
+  end = 0;
+}
+
+// Code inspired by/copied from OpenOCD server/server.c.
+
+gdbserver_t::gdbserver_t(uint16_t port) :
+  recv_buf(64 * 1024),
+  send_start(0), send_end(0)
+{
+  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+  if (socket_fd == -1) {
+    fprintf(stderr, "error creating socket: %s\n", strerror(errno));
+    abort();
+  }
+
+  int so_reuseaddr_option = 1;
+  setsockopt(socket_fd,
+      SOL_SOCKET,
+      SO_REUSEADDR,
+      (void *)&so_reuseaddr_option,
+      sizeof(int));
+
+  int oldopts = fcntl(socket_fd, F_GETFL, 0);
+  fcntl(socket_fd, F_SETFL, oldopts | O_NONBLOCK);
+
+  struct sockaddr_in sin;
+  memset(&sin, 0, sizeof(sin));
+  sin.sin_family = AF_INET;
+  sin.sin_addr.s_addr = INADDR_ANY;
+  sin.sin_port = htons(port);
+
+  if (bind(socket_fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+    fprintf(stderr, "couldn't bind to socket: %s\n", strerror(errno));
+    abort();
+  }
+
+  /* These setsockopt()s must happen before the listen() */
+  int window_size = 128 * 1024;
+  setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF,
+      (char *)&window_size, sizeof(window_size));
+  setsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF,
+      (char *)&window_size, sizeof(window_size));
+
+  if (listen(socket_fd, 1) == -1) {
+    fprintf(stderr, "couldn't listen on socket: %s\n", strerror(errno));
+    abort();
+  }
+}
+
+void gdbserver_t::accept()
+{
+  struct sockaddr client_addr;
+  socklen_t address_size = sizeof(client_addr);
+  client_fd = ::accept(socket_fd, &client_addr, &address_size);
+  if (client_fd == -1) {
+    if (errno == EAGAIN) {
+      // We'll try again in the next call.
+    } else {
+      fprintf(stderr, "failed to accept on socket: %s (%d)\n", strerror(errno), errno);
+      abort();
+    }
+  } else {
+    int oldopts = fcntl(client_fd, F_GETFL, 0);
+    fcntl(client_fd, F_SETFL, oldopts | O_NONBLOCK);
+    ack_mode = true;
+  }
+}
+
+void gdbserver_t::read()
+{
+  // Reading from a non-blocking socket still blocks if there is no data
+  // available.
+
+  size_t count = recv_buf.contiguous_space();
+  assert(count > 0);
+  ssize_t bytes = ::read(client_fd, recv_buf.contiguous_data(), count);
+  if (bytes == -1) {
+    if (errno == EAGAIN) {
+      // We'll try again the next call.
+    } else {
+      fprintf(stderr, "failed to read on socket: %s (%d)\n", strerror(errno), errno);
+      abort();
+    }
+  } else if (bytes == 0) {
+    // The remote disconnected.
+    client_fd = 0;
+    recv_buf.reset();
+    send_start = 0;
+    send_end = 0;
+  } else {
+    printf("read %ld bytes\n", bytes);
+    recv_buf.data_added(bytes);
+  }
+}
+
+void print_packet(const std::vector<uint8_t> &packet)
+{
+  for (uint8_t c : packet) {
+    fprintf(stderr, "%c", c);
+  }
+  fprintf(stderr, "\n");
+}
+
+uint8_t compute_checksum(const std::vector<uint8_t> &packet)
+{
+  uint8_t checksum = 0;
+  for (auto i = packet.begin() + 1; i != packet.end() - 3; i++ ) {
+    checksum += *i;
+  }
+  return checksum;
+}
+
+uint8_t character_hex_value(uint8_t character)
+{
+  if (character >= '0' && character <= '9')
+    return character - '0';
+  if (character >= 'a' && character <= 'f')
+    return 10 + character - 'a';
+  if (character >= 'A' && character <= 'F')
+    return 10 + character - 'A';
+  return 0;
+}
+
+uint8_t extract_checksum(const std::vector<uint8_t> &packet)
+{
+  return character_hex_value(*(packet.end() - 1)) +
+    16 * character_hex_value(*(packet.end() - 2));
+}
+
+void gdbserver_t::process_requests()
+{
+  // See https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
+
+  while (!recv_buf.empty()) {
+    std::vector<uint8_t> packet;
+    for (unsigned int i = 0; i < recv_buf.size(); i++) {
+      uint8_t b = recv_buf[i];
+      if (b == '$') {
+        // Start of new packet.
+        if (!packet.empty()) {
+          fprintf(stderr, "Received malformed %ld-byte packet from debug client\n", packet.size());
+          print_packet(packet);
+          recv_buf.consume(i);
+          break;
+        }
+      }
+
+      packet.push_back(b);
+
+      // Packets consist of $<packet-data>#<checksum>
+      // where <checksum> is 
+      if (packet.size() >= 4 &&
+          packet[packet.size()-3] == '#') {
+        if (compute_checksum(packet) == extract_checksum(packet)) {
+          handle_packet(packet);
+        } else {
+          fprintf(stderr, "Received %ld-byte packet with invalid checksum\n", packet.size());
+          fprintf(stderr, "Computed checksum: %x\n", compute_checksum(packet));
+          print_packet(packet);
+          send("-");
+        }
+        recv_buf.consume(i+1);
+        break;
+      }
+    }
+    // There's a partial packet in the buffer. Wait until we get more data to
+    // process it.
+    if (packet.size())
+      break;
+  }
+}
+
+void gdbserver_t::handle_packet(const std::vector<uint8_t> &packet)
+{
+  fprintf(stderr, "Received %ld-byte packet from debug client\n", packet.size());
+  print_packet(packet);
+  send("+");
+}
+
+void gdbserver_t::handle()
+{
+  if (client_fd > 0) {
+    this->read();
+
+  } else {
+    this->accept();
+  }
+
+  this->process_requests();
+}
+
+void gdbserver_t::send(const char* msg)
+{
+  unsigned int length = strlen(msg);
+  unsigned int count;
+}
diff --git a/riscv/gdbserver.h b/riscv/gdbserver.h
new file mode 100644 (file)
index 0000000..c947936
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef _RISCV_GDBSERVER_H
+#define _RISCV_GDBSERVER_H
+
+#include <stdint.h>
+
+template <typename T>
+class circular_buffer_t
+{
+public:
+  // The buffer can store capacity-1 data elements.
+  circular_buffer_t(unsigned int capacity) : data(new T[capacity]),
+      start(0), end(0), capacity(capacity) {}
+  circular_buffer_t() : start(0), end(0), capacity(0) {}
+  ~circular_buffer_t() { delete data; }
+
+  T *data;
+  unsigned int start;   // Data start, inclusive.
+  unsigned int end;     // Data end, exclusive.
+  unsigned int capacity;    // Size of the buffer.
+  unsigned int size() const;
+  bool empty() const { return start == end; }
+  // Tell the buffer that some bytes were consumed from the start of the
+  // buffer.
+  void consume(unsigned int bytes);
+
+  // Return size and address of the block of RAM where more data can be copied
+  // to be added to the buffer.
+  unsigned int contiguous_space() const;
+  T *contiguous_data() { return data + end; }
+  void data_added(unsigned int bytes);
+
+  void reset();
+
+  T operator[](unsigned int i) {
+    return data[(start + i) % capacity];
+  }
+};
+
+class gdbserver_t
+{
+public:
+  // Create a new server, listening for connections from localhost on the given
+  // port.
+  gdbserver_t(uint16_t port);
+
+  // Process all pending messages from a client.
+  void handle();
+
+  void handle_packet(const std::vector<uint8_t> &packet);
+
+private:
+  int socket_fd;
+  int client_fd;
+  circular_buffer_t<uint8_t> recv_buf;
+  uint8_t send_buf[64 * 1024];          // Circular buffer.
+  unsigned int send_start, send_end;    // Data start (inclusive)/end (exclusive)pointers.
+
+  bool ack_mode;
+
+  // Read pending data from the client.
+  void read();
+  // Accept a new client if there isn't one already connected.
+  void accept();
+  // Process all complete requests in recv_buf.
+  void process_requests();
+  // Add the given message to send_buf.
+  void send(const char* msg);
+};
+
+#endif
index 18d91c569433d2543f34724c4f342f6f0c3cd535..c7d84f794559b48dc7ab76c5b2a93b909f3966b4 100644 (file)
@@ -24,6 +24,7 @@ riscv_hdrs = \
        rocc.h \
        insn_template.h \
        mulhi.h \
+       gdbserver.h \
 
 riscv_precompiled_hdrs = \
        insn_template.h \
@@ -45,6 +46,7 @@ riscv_srcs = \
        devices.cc \
        rom.cc \
        rtc.cc \
+       gdbserver.cc \
        $(riscv_gen_srcs) \
 
 riscv_test_srcs =
index db08cb2cfbf4e154535955b6f3b4be697825e73e..f8564fbd0d266373eac00247e52cdb92840f4b43 100644 (file)
@@ -68,6 +68,9 @@ int sim_t::run()
       interactive();
     else
       step(INTERLEAVE);
+    if (gdbserver) {
+        gdbserver->handle();
+    }
   }
   return htif->exit_code();
 }
index af2d910e44243f82fc52c901be3feb905da54578..cac1dafba4e7970626dbd8efc881abd4bdc1849a 100644 (file)
@@ -8,6 +8,7 @@
 #include <memory>
 #include "processor.h"
 #include "devices.h"
+#include "gdbserver.h"
 
 class htif_isasim_t;
 class mmu_t;
@@ -27,6 +28,7 @@ public:
   void set_log(bool value);
   void set_histogram(bool value);
   void set_procs_debug(bool value);
+  void set_gdbserver(gdbserver_t* gdbserver) { this->gdbserver = gdbserver; }
   htif_isasim_t* get_htif() { return htif.get(); }
   const char* get_config_string() { return config_string.c_str(); }
 
@@ -54,6 +56,7 @@ private:
   bool debug;
   bool log;
   bool histogram_enabled; // provide a histogram of PCs
+  gdbserver_t* gdbserver;
 
   // memory-mapped I/O routines
   bool addr_is_mem(reg_t addr) {
index 2f885182cfd0e0d2f3658b32a719817619413061..b7748d6bd098bdc5720ae1ea1d5e8a8f21f4e22a 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "sim.h"
 #include "mmu.h"
+#include "gdbserver.h"
 #include "htif.h"
 #include "cachesim.h"
 #include "extension.h"
@@ -73,6 +74,8 @@ int main(int argc, char** argv)
   auto argv1 = parser.parse(argv);
   std::vector<std::string> htif_args(argv1, (const char*const*)argv + argc);
   sim_t s(isa, nprocs, mem_mb, htif_args);
+  gdbserver_t gdbserver(9824);
+  s.set_gdbserver(&gdbserver);
 
   if (dump_config_string) {
     printf("%s", s.get_config_string());