Adds gdbserver to simulators and makes config general.

PiperOrigin-RevId: 890418526
Change-Id: Icb7b88981eec72797a5df5b69c2059cb3d883ddd
diff --git a/riscv/BUILD b/riscv/BUILD
index ad224eb..cb65100 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -1253,8 +1253,6 @@
     srcs = [
         "riscv_debug_info.cc",
         "riscv_gdb_debug_info.cc",
-        "riscv_gdb_debug_info_32.inc",
-        "riscv_gdb_debug_info_64.inc",
     ],
     hdrs = [
         "riscv_debug_info.h",
@@ -1340,6 +1338,7 @@
         ":riscv32g_vec_decoder",
         ":riscv32gzb_vec_decoder",
         ":riscv_arm_semihost",
+        ":riscv_debug_info",
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
@@ -1352,6 +1351,7 @@
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_protobuf//:protobuf",
         "@com_googlesource_code_re2//:re2",
         "@mpact-sim//mpact/sim/generic:core",
@@ -1359,6 +1359,7 @@
         "@mpact-sim//mpact/sim/generic:counters",
         "@mpact-sim//mpact/sim/generic:instruction",
         "@mpact-sim//mpact/sim/proto:component_data_cc_proto",
+        "@mpact-sim//mpact/sim/util/gdbserver",
         "@mpact-sim//mpact/sim/util/memory",
         "@mpact-sim//mpact/sim/util/program_loader:elf_loader",
     ],
@@ -1374,6 +1375,7 @@
         ":debug_command_shell",
         ":riscv64g_decoder",
         ":riscv_arm_semihost",
+        ":riscv_debug_info",
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
@@ -1386,12 +1388,14 @@
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_protobuf//:protobuf",
         "@com_googlesource_code_re2//:re2",
         "@mpact-sim//mpact/sim/generic:core_debug_interface",
         "@mpact-sim//mpact/sim/generic:counters",
         "@mpact-sim//mpact/sim/generic:instruction",
         "@mpact-sim//mpact/sim/proto:component_data_cc_proto",
+        "@mpact-sim//mpact/sim/util/gdbserver",
         "@mpact-sim//mpact/sim/util/memory",
         "@mpact-sim//mpact/sim/util/program_loader:elf_loader",
     ],
@@ -1408,6 +1412,7 @@
         ":riscv64g_vec_decoder",
         ":riscv64gzb_vec_decoder",
         ":riscv_arm_semihost",
+        ":riscv_debug_info",
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
@@ -1420,6 +1425,7 @@
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_protobuf//:protobuf",
         "@com_googlesource_code_re2//:re2",
         "@mpact-sim//mpact/sim/generic:core",
@@ -1427,6 +1433,7 @@
         "@mpact-sim//mpact/sim/generic:counters",
         "@mpact-sim//mpact/sim/generic:instruction",
         "@mpact-sim//mpact/sim/proto:component_data_cc_proto",
+        "@mpact-sim//mpact/sim/util/gdbserver",
         "@mpact-sim//mpact/sim/util/memory",
         "@mpact-sim//mpact/sim/util/program_loader:elf_loader",
     ],
diff --git a/riscv/riscv_gdb_debug_info.cc b/riscv/riscv_gdb_debug_info.cc
index 81cdee0..e9f5c06 100644
--- a/riscv/riscv_gdb_debug_info.cc
+++ b/riscv/riscv_gdb_debug_info.cc
@@ -14,35 +14,59 @@
 
 #include "riscv/riscv_gdb_debug_info.h"
 
+#include <string>
+#include <string_view>
+
+#include "absl/container/flat_hash_map.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "mpact/sim/generic/type_helpers.h"
 
+namespace {
+
+std::string EscapeString(std::string_view str) {
+  std::string escaped_string;
+  escaped_string.reserve(str.size() * 2);
+  for (char c : str) {
+    if ((c == '$') || (c == '#') || (c == '*') || (c == '}')) {
+      escaped_string.push_back('}');
+      escaped_string.push_back(c ^ 0x20);
+    } else {
+      escaped_string.push_back(c);
+    }
+  }
+  return escaped_string;
+}
+
+}  // namespace
+
 namespace mpact::sim::riscv {
 
 using ::mpact::sim::generic::operator*;  // NOLINT
 
-RiscVGdbDebugInfo* RiscVGdbDebugInfo::Instance(int gpr_width) {
-  if (gpr_width != 32 && gpr_width != 64) {
+RiscVGdbDebugInfo* RiscVGdbDebugInfo::Instance(int gpr_width, int fp_width,
+                                               int vec_width) {
+  static absl::flat_hash_map<int, RiscVGdbDebugInfo*> instance_map;
+  // Check for valid widths.
+  if (gpr_width != 32 && gpr_width != 64) return nullptr;
+  if (fp_width != 0 && fp_width != 32 && fp_width != 64) return nullptr;
+  if (vec_width != 0 && vec_width != 128 && vec_width != 256 &&
+      vec_width != 512 && vec_width != 1024) {
     return nullptr;
   }
-  static RiscVGdbDebugInfo* instance32 = nullptr;
-  static RiscVGdbDebugInfo* instance64 = nullptr;
-  if (gpr_width == 32) {
-    if (instance32 == nullptr) {
-      instance32 = new RiscVGdbDebugInfo(gpr_width);
-    }
-    return instance32;
-  } else {
-    if (instance64 == nullptr) {
-      instance64 = new RiscVGdbDebugInfo(gpr_width);
-    }
-    return instance64;
+  int key = gpr_width | (fp_width << 16) | (vec_width << 24);
+  auto it = instance_map.find(key);
+  if (it != instance_map.end()) {
+    return it->second;
   }
-  return nullptr;
+  RiscVGdbDebugInfo* instance =
+      new RiscVGdbDebugInfo(gpr_width, fp_width, vec_width);
+  instance_map[key] = instance;
+  return instance;
 }
 
-RiscVGdbDebugInfo::RiscVGdbDebugInfo(int gpr_width) : gpr_width_(gpr_width) {
+RiscVGdbDebugInfo::RiscVGdbDebugInfo(int gpr_width, int fp_width, int vec_width)
+    : gpr_width_(gpr_width), fp_width_(fp_width), vec_width_(vec_width) {
   host_info_ = absl::StrFormat(
       "triple:riscv%d-unknown-elf;"
       "endian:little;"
@@ -57,29 +81,111 @@
     debug_register_map_.emplace(
         i, absl::StrCat("x", i - *RiscVGdbRegisterEnum::kGprX0));
   }
-  // Floating point registers.
-  for (int i = *RiscVGdbRegisterEnum::kFprFirst;
-       i <= *RiscVGdbRegisterEnum::kFprLast; ++i) {
-    debug_register_map_.emplace(
-        i, absl::StrCat("f", i - *RiscVGdbRegisterEnum::kFprFirst));
+  if (fp_width != 0) {
+    // Floating point registers.
+    for (int i = *RiscVGdbRegisterEnum::kFprFirst;
+         i <= *RiscVGdbRegisterEnum::kFprLast; ++i) {
+      debug_register_map_.emplace(
+          i, absl::StrCat("f", i - *RiscVGdbRegisterEnum::kFprFirst));
+    }
+    // CSRs.
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kFprFcsr, "fcsr");
   }
-  // CSRs.
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kFprFcsr, "fcsr");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVstart, "vstart");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVxsat, "vxsat");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVxrm, "vxrm");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVcsr, "vcsr");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVl, "vl");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVType, "vtype");
-  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVlenb, "vlenb");
-  // xml
-  if (gpr_width == 32) {
-    gdb_target_xml_ =
-#include "riscv/riscv_gdb_debug_info_32.inc"
-  } else {
-    gdb_target_xml_ =
-#include "riscv/riscv_gdb_debug_info_64.inc"
+  if (vec_width != 0) {
+    // Vector control registers.
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVstart, "vstart");
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVxsat, "vxsat");
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVxrm, "vxrm");
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVcsr, "vcsr");
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVl, "vl");
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVtype, "vtype");
+    debug_register_map_.emplace(*RiscVGdbRegisterEnum::kVprVlenb, "vlenb");
   }
+  // Construct the gdb target xml string.
+  gdb_target_xml_ = R"xml(<?xml version="1.0"?>
+<!DOCTYPE target SYSTEM "gdb-target.dtd">
+<target version="1.0">
+  <architecture>riscv</architecture>)xml";
+  absl::StrAppend(&gdb_target_xml_,
+                  "\n  <feature name=\"org.gnu.gdb.riscv.cpu\">\n");
+  // GPR registers.
+  int regnum = 0;
+  for (int i = 0; i < 32; ++i) {
+    absl::StrAppend(
+        &gdb_target_xml_,
+        absl::StrFormat("    <reg name=\"x%d\" bitsize=\"%d\" regnum=\"%d\" "
+                        "type=\"uint%d\" group=\"general\"/>\n",
+                        i, gpr_width_, regnum++, gpr_width_));
+  }
+  absl::StrAppend(
+      &gdb_target_xml_,
+      absl::StrFormat("    <reg name=\"pc\" bitsize=\"%d\" regnum=\"%d\" "
+                      "type=\"code_ptr\" group=\"general\"/>\n",
+                      gpr_width_, regnum++));
+  absl::StrAppend(&gdb_target_xml_, "  </feature>\n");
+
+  // FP registers.
+  if (fp_width != 0) {
+    absl::StrAppend(&gdb_target_xml_,
+                    "  <feature name=\"org.gnu.gdb.riscv.fpu\">\n");
+    std::string fp_type = fp_width == 64 ? "ieee_double" : "ieee_float";
+    for (int i = 0; i < 32; ++i) {
+      absl::StrAppend(
+          &gdb_target_xml_,
+          absl::StrFormat("    <reg name=\"f%d\" bitsize=\"%d\" "
+                          "regnum=\"%d\" type=\"%s\" group=\"float\"/>\n",
+                          i, fp_width_, regnum++, fp_type));
+    }
+    absl::StrAppend(
+        &gdb_target_xml_,
+        absl::StrFormat("    <reg name=\"fcsr\" bitsize=\"%d\" regnum=\"%d\" "
+                        "type=\"uint32\" group=\"float\"/>\n",
+                        32, regnum++));
+    absl::StrAppend(&gdb_target_xml_, "  </feature>\n");
+  }
+  // Vector registers.
+  if (vec_width != 0) {
+    absl::StrAppend(&gdb_target_xml_,
+                    "  <feature name=\"org.gnu.gdb.riscv.vector\">\n");
+    // Add vector data type.
+    absl::StrAppend(
+        &gdb_target_xml_,
+        absl::StrFormat(
+            "    <vector id=\"v%d\" type=\"uint8\" count=\"%d\"/>\n",
+            vec_width_, vec_width_ / 8));
+    for (int i = 0; i < 32; ++i) {
+      absl::StrAppend(&gdb_target_xml_,
+                      absl::StrFormat("    <reg name=\"v%d\" bitsize=\"%d\" "
+                                      "regnum=\"%d\" type=\"v%d\" "
+                                      "group=\"vector\"/>\n",
+                                      i, vec_width_, regnum++, vec_width_));
+    }
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vstart\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vxsat\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vxrm\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vcsr\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vl\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vtype\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_,
+                    "    <reg name=\"vlenb\" bitsize=\"32\" type=\"uint32\" "
+                    "group=\"vector\"/>\n");
+    absl::StrAppend(&gdb_target_xml_, "  </feature>\n");
+  }
+  absl::StrAppend(&gdb_target_xml_, "</target>\n");
+  // Escape the string for GDB
+  gdb_target_xml_ = EscapeString(gdb_target_xml_);
 }
 
 }  // namespace mpact::sim::riscv
diff --git a/riscv/riscv_gdb_debug_info.h b/riscv/riscv_gdb_debug_info.h
index 0e6b14d..0d0055b 100644
--- a/riscv/riscv_gdb_debug_info.h
+++ b/riscv/riscv_gdb_debug_info.h
@@ -30,8 +30,7 @@
 enum class RiscVGdbRegisterEnum : int {
   // Integer registers.
   kGprFirst = 0,
-  kGprPc = kGprFirst,
-  kGprX0,
+  kGprX0 = kGprFirst,
   kGprX1,
   kGprX2,
   kGprX3,
@@ -63,7 +62,8 @@
   kGprX29,
   kGprX30,
   kGprX31,
-  kGprLast = kGprX31,
+  kGprPc,
+  kGprLast = kGprPc,
   // Floating point registers.
   kFprFirst = kGprLast + 1,
   kFprF0 = kFprFirst,
@@ -99,23 +99,57 @@
   kFprF30,
   kFprF31,
   kFprLast = kFprF31,
-  // CSRs.
-  kCsrFirst = 4096,
-  kFprFcsr = kCsrFirst + 0x003,
-  kVprVstart = kCsrFirst + 0x008,
-  kVprVxsat = kCsrFirst + 0x009,
-  kVprVxrm = kCsrFirst + 0x00a,
-  kVprVcsr = kCsrFirst + 0x00f,
-  kVprVl = kCsrFirst + 0xc20,
-  kVprVType = kCsrFirst + 0xc21,
-  kVprVlenb = kCsrFirst + 0xc22,
+  kFprFcsr,
+  // Vector registers.
+  kVprFirst = kFprFcsr + 1,
+  kVprV0 = kVprFirst,
+  kVprV1,
+  kVprV2,
+  kVprV3,
+  kVprV4,
+  kVprV5,
+  kVprV6,
+  kVprV7,
+  kVprV8,
+  kVprV9,
+  kVprV10,
+  kVprV11,
+  kVprV12,
+  kVprV13,
+  kVprV14,
+  kVprV15,
+  kVprV16,
+  kVprV17,
+  kVprV18,
+  kVprV19,
+  kVprV20,
+  kVprV21,
+  kVprV22,
+  kVprV23,
+  kVprV24,
+  kVprV25,
+  kVprV26,
+  kVprV27,
+  kVprV28,
+  kVprV29,
+  kVprV30,
+  kVprV31,
+  kVprLast = kVprV31,
+  kVprVstart,
+  kVprVxsat,
+  kVprVxrm,
+  kVprVcsr,
+  kVprVl,
+  kVprVtype,
+  kVprVlenb,
 };
 
 class RiscVGdbDebugInfo : public generic::DebugInfo {
  public:
   using DebugRegisterMap = generic::DebugInfo::DebugRegisterMap;
 
-  static RiscVGdbDebugInfo* Instance(int gpr_width);
+  static RiscVGdbDebugInfo* Instance(int gpr_width, int fp_width,
+                                     int vec_width);
 
   const DebugRegisterMap& debug_register_map() const override {
     return debug_register_map_;
@@ -139,11 +173,14 @@
   }
 
  private:
-  explicit RiscVGdbDebugInfo(int gpr_width);
+  RiscVGdbDebugInfo(int gpr_width, int fp_width, int vec_width);
   int gpr_width_;
+  int fp_width_;
+  int vec_width_;
   std::string host_info_;
   DebugRegisterMap debug_register_map_;
-  std::string_view gdb_target_xml_;
+  // "Escaped" GDB target XML string.
+  std::string gdb_target_xml_;
 };
 
 }  // namespace mpact::sim::riscv
diff --git a/riscv/rv32g_sim.cc b/riscv/rv32g_sim.cc
index 43f18e8..4692495 100644
--- a/riscv/rv32g_sim.cc
+++ b/riscv/rv32g_sim.cc
@@ -503,7 +503,8 @@
   }
   if (gdbserver_port > 0) {
     std::array<CoreDebugInterface*, 1> core_debug_interfaces = {&riscv_top};
-    RiscVGdbDebugInfo* debug_info = RiscVGdbDebugInfo::Instance(32);
+    RiscVGdbDebugInfo* debug_info = RiscVGdbDebugInfo::Instance(
+        /*gpr_width=*/32, /*fp_width=*/64, /*vec_width=*/0);
     GdbServer gdb_server(absl::MakeSpan(core_debug_interfaces), *debug_info);
     std::cerr << "Starting gdbserver on port " << gdbserver_port << std::endl;
     gdb_server.Connect(gdbserver_port);
diff --git a/riscv/rv32gv_sim.cc b/riscv/rv32gv_sim.cc
index 14a3a8f..0ac0013 100644
--- a/riscv/rv32gv_sim.cc
+++ b/riscv/rv32gv_sim.cc
@@ -14,6 +14,7 @@
 
 #include <signal.h>
 
+#include <array>
 #include <cstdint>
 #include <cstdlib>
 #include <fstream>
@@ -37,11 +38,13 @@
 #include "absl/strings/string_view.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/counters.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/proto/component_data.pb.h"
+#include "mpact/sim/util/gdbserver/gdbserver.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 #include "mpact/sim/util/memory/memory_interface.h"
@@ -55,6 +58,7 @@
 #include "riscv/riscv_arm_semihost.h"
 #include "riscv/riscv_csr.h"
 #include "riscv/riscv_fp_state.h"
+#include "riscv/riscv_gdb_debug_info.h"
 #include "riscv/riscv_register.h"
 #include "riscv/riscv_register_aliases.h"
 #include "riscv/riscv_state.h"
@@ -62,6 +66,7 @@
 #include "riscv/riscv_vector_state.h"
 #include "src/google/protobuf/text_format.h"
 
+using ::mpact::sim::generic::CoreDebugInterface;
 using ::mpact::sim::generic::Instruction;
 using ::mpact::sim::proto::ComponentData;
 using ::mpact::sim::riscv::RiscV32GVecDecoder;
@@ -69,11 +74,13 @@
 using ::mpact::sim::riscv::RiscV32HtifSemiHost;
 using ::mpact::sim::riscv::RiscVArmSemihost;
 using ::mpact::sim::riscv::RiscVFPState;
+using ::mpact::sim::riscv::RiscVGdbDebugInfo;
 using ::mpact::sim::riscv::RiscVState;
 using ::mpact::sim::riscv::RiscVVectorState;
 using ::mpact::sim::riscv::RiscVXlen;
 using ::mpact::sim::riscv::RV32Register;
 using ::mpact::sim::riscv::RVFpRegister;
+using ::mpact::sim::util::gdbserver::GdbServer;
 
 using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
 
@@ -164,6 +171,10 @@
 // Flag to set the vector length in bytes (VLENB).
 ABSL_FLAG(int, vlen, 16, "Vector length in bytes (VLENB)");
 
+// Flag to run the simulator with a gdbserver listening for connections on the
+// given port.
+ABSL_FLAG(int, gdbserver, -1, "Run simulator in gdbserver mode");
+
 constexpr char kStackEndSymbolName[] = "__stack_end";
 constexpr char kStackSizeSymbolName[] = "__stack_size";
 
@@ -466,9 +477,24 @@
   sa.sa_handler = &sim_sigint_handler;
   sigaction(SIGINT, &sa, nullptr);
 
-  // Determine if this is being run interactively or as a batch job.
+  // Determine if this is being run interactively, as gdbserver, or as a batch
+  // job.
   bool interactive = absl::GetFlag(FLAGS_i) || absl::GetFlag(FLAGS_interactive);
-  if (interactive) {
+  int gdbserver_port = absl::GetFlag(FLAGS_gdbserver);
+  if (interactive && gdbserver_port > 0) {
+    std::cerr << "Gdbserver cannot be used in interactive mode\n";
+    return -1;
+  }
+  if (gdbserver_port > 0) {
+    std::array<CoreDebugInterface*, 1> core_debug_interfaces = {&riscv_top};
+    RiscVGdbDebugInfo* debug_info = RiscVGdbDebugInfo::Instance(
+        /*gpr_width=*/32,
+        /*fp_width=*/64, /*vec_width=*/absl::GetFlag(FLAGS_vlen) * 8);
+    GdbServer gdb_server(absl::MakeSpan(core_debug_interfaces), *debug_info);
+    std::cerr << "Starting gdbserver on port " << gdbserver_port << std::endl;
+    gdb_server.Connect(gdbserver_port);
+    std::cerr << "Gdbserver disconnected" << std::endl;
+  } else if (interactive) {
     mpact::sim::riscv::DebugCommandShell cmd_shell;
     cmd_shell.AddCore({&riscv_top, [&elf_loader]() { return &elf_loader; }});
     // Add custom command to interactive debug command shell.
diff --git a/riscv/rv64g_sim.cc b/riscv/rv64g_sim.cc
index fe80fcc..e0e75fe 100644
--- a/riscv/rv64g_sim.cc
+++ b/riscv/rv64g_sim.cc
@@ -14,6 +14,7 @@
 
 #include <signal.h>
 
+#include <array>
 #include <cstdint>
 #include <cstdlib>
 #include <fstream>
@@ -37,10 +38,12 @@
 #include "absl/strings/string_view.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/counters.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/proto/component_data.pb.h"
+#include "mpact/sim/util/gdbserver/gdbserver.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 #include "mpact/sim/util/memory/memory_interface.h"
@@ -52,21 +55,25 @@
 #include "riscv/riscv_arm_semihost.h"
 #include "riscv/riscv_csr.h"
 #include "riscv/riscv_fp_state.h"
+#include "riscv/riscv_gdb_debug_info.h"
 #include "riscv/riscv_register.h"
 #include "riscv/riscv_register_aliases.h"
 #include "riscv/riscv_state.h"
 #include "riscv/riscv_top.h"
 #include "src/google/protobuf/text_format.h"
 
+using ::mpact::sim::generic::CoreDebugInterface;
 using ::mpact::sim::generic::Instruction;
 using ::mpact::sim::proto::ComponentData;
 using ::mpact::sim::riscv::RiscV64Decoder;
 using ::mpact::sim::riscv::RiscVArmSemihost;
 using ::mpact::sim::riscv::RiscVFPState;
+using ::mpact::sim::riscv::RiscVGdbDebugInfo;
 using ::mpact::sim::riscv::RiscVState;
 using ::mpact::sim::riscv::RiscVXlen;
 using ::mpact::sim::riscv::RV64Register;
 using ::mpact::sim::riscv::RVFpRegister;
+using ::mpact::sim::util::gdbserver::GdbServer;
 using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
 
 // Flags for specifying interactive mode.
@@ -150,6 +157,10 @@
 // Flag to set the default value for the misa CSR.
 ABSL_FLAG(std::optional<uint64_t>, misa, std::nullopt, "misa value");
 
+// Flag to run the simulator with a gdbserver listening for connections on the
+// given port.
+ABSL_FLAG(int, gdbserver, -1, "Run simulator in gdbserver mode");
+
 constexpr char kStackEndSymbolName[] = "__stack_end";
 constexpr char kStackSizeSymbolName[] = "__stack_size";
 
@@ -405,9 +416,23 @@
   sa.sa_handler = &sim_sigint_handler;
   sigaction(SIGINT, &sa, nullptr);
 
-  // Determine if this is being run interactively or as a batch job.
+  // Determine if this is being run interactively, as gdbserver, or as a batch
+  // job.
   bool interactive = absl::GetFlag(FLAGS_i) || absl::GetFlag(FLAGS_interactive);
-  if (interactive) {
+  int gdbserver_port = absl::GetFlag(FLAGS_gdbserver);
+  if (interactive && gdbserver_port > 0) {
+    std::cerr << "Gdbserver cannot be used in interactive mode\n";
+    return -1;
+  }
+  if (gdbserver_port > 0) {
+    std::array<CoreDebugInterface*, 1> core_debug_interfaces = {&riscv_top};
+    RiscVGdbDebugInfo* debug_info = RiscVGdbDebugInfo::Instance(
+        /*gpr_width=*/64, /*fp_width=*/64, /*vec_width=*/0);
+    GdbServer gdb_server(absl::MakeSpan(core_debug_interfaces), *debug_info);
+    std::cerr << "Starting gdbserver on port " << gdbserver_port << std::endl;
+    gdb_server.Connect(gdbserver_port);
+    std::cerr << "Gdbserver disconnected" << std::endl;
+  } else if (interactive) {
     mpact::sim::riscv::DebugCommandShell cmd_shell;
     cmd_shell.AddCore({&riscv_top, [&elf_loader]() { return &elf_loader; }});
     // Add custom command to interactive debug command shell.
diff --git a/riscv/rv64gv_sim.cc b/riscv/rv64gv_sim.cc
index 992ed7f..6556a4d 100644
--- a/riscv/rv64gv_sim.cc
+++ b/riscv/rv64gv_sim.cc
@@ -14,6 +14,7 @@
 
 #include <signal.h>
 
+#include <array>
 #include <cstdint>
 #include <cstdlib>
 #include <fstream>
@@ -37,11 +38,13 @@
 #include "absl/strings/string_view.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/counters.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/proto/component_data.pb.h"
+#include "mpact/sim/util/gdbserver/gdbserver.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 #include "mpact/sim/util/memory/memory_interface.h"
@@ -54,6 +57,7 @@
 #include "riscv/riscv_arm_semihost.h"
 #include "riscv/riscv_csr.h"
 #include "riscv/riscv_fp_state.h"
+#include "riscv/riscv_gdb_debug_info.h"
 #include "riscv/riscv_register.h"
 #include "riscv/riscv_register_aliases.h"
 #include "riscv/riscv_state.h"
@@ -61,17 +65,20 @@
 #include "riscv/riscv_vector_state.h"
 #include "src/google/protobuf/text_format.h"
 
+using ::mpact::sim::generic::CoreDebugInterface;
 using ::mpact::sim::generic::Instruction;
 using ::mpact::sim::proto::ComponentData;
 using ::mpact::sim::riscv::RiscV64GVecDecoder;
 using ::mpact::sim::riscv::RiscV64GZBVecDecoder;
 using ::mpact::sim::riscv::RiscVArmSemihost;
 using ::mpact::sim::riscv::RiscVFPState;
+using ::mpact::sim::riscv::RiscVGdbDebugInfo;
 using ::mpact::sim::riscv::RiscVState;
 using ::mpact::sim::riscv::RiscVVectorState;
 using ::mpact::sim::riscv::RiscVXlen;
 using ::mpact::sim::riscv::RV64Register;
 using ::mpact::sim::riscv::RVFpRegister;
+using ::mpact::sim::util::gdbserver::GdbServer;
 using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
 
 // Flags for specifying interactive mode.
@@ -158,6 +165,10 @@
 // Flag to set the vector length in bytes (VLENB).
 ABSL_FLAG(int, vlen, 64, "Vector length in bytes (VLENB)");
 
+// Flag to run the simulator with a gdbserver listening for connections on the
+// given port.
+ABSL_FLAG(int, gdbserver, -1, "Run simulator in gdbserver mode");
+
 constexpr char kStackEndSymbolName[] = "__stack_end";
 constexpr char kStackSizeSymbolName[] = "__stack_size";
 
@@ -420,9 +431,24 @@
   sa.sa_handler = &sim_sigint_handler;
   sigaction(SIGINT, &sa, nullptr);
 
-  // Determine if this is being run interactively or as a batch job.
+  // Determine if this is being run interactively, as gdbserver, or as a batch
+  // job.
   bool interactive = absl::GetFlag(FLAGS_i) || absl::GetFlag(FLAGS_interactive);
-  if (interactive) {
+  int gdbserver_port = absl::GetFlag(FLAGS_gdbserver);
+  if (interactive && gdbserver_port > 0) {
+    std::cerr << "Gdbserver cannot be used in interactive mode\n";
+    return -1;
+  }
+  if (gdbserver_port > 0) {
+    std::array<CoreDebugInterface*, 1> core_debug_interfaces = {&riscv_top};
+    RiscVGdbDebugInfo* debug_info = RiscVGdbDebugInfo::Instance(
+        /*gpr_width=*/64,
+        /*fp_width=*/64, /*vec_width=*/absl::GetFlag(FLAGS_vlen) * 8);
+    GdbServer gdb_server(absl::MakeSpan(core_debug_interfaces), *debug_info);
+    std::cerr << "Starting gdbserver on port " << gdbserver_port << std::endl;
+    gdb_server.Connect(gdbserver_port);
+    std::cerr << "Gdbserver disconnected" << std::endl;
+  } else if (interactive) {
     mpact::sim::riscv::DebugCommandShell cmd_shell;
     cmd_shell.AddCore({&riscv_top, [&elf_loader]() { return &elf_loader; }});
     // Add custom command to interactive debug command shell.