Adds functionality to gdb server.
Adds gdbserver to rv32g_sim
- allows it to work with lldb (gdb not tested yet)

PiperOrigin-RevId: 889448457
Change-Id: I0d73a06ac65dc157efbd0e11454124604820a4c7
diff --git a/riscv/BUILD b/riscv/BUILD
index a7820c0..ad224eb 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -1250,11 +1250,22 @@
 
 cc_library(
     name = "riscv_debug_info",
-    srcs = ["riscv_debug_info.cc"],
-    hdrs = ["riscv_debug_info.h"],
+    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",
+        "riscv_gdb_debug_info.h",
+    ],
     copts = ["-O3"],
     deps = [
         "@abseil-cpp//absl/container:flat_hash_map",
+        "@abseil-cpp//absl/strings",
+        "@abseil-cpp//absl/strings:str_format",
+        "@mpact-sim//mpact/sim/generic:core_debug_interface",
         "@mpact-sim//mpact/sim/generic:type_helpers",
     ],
 )
@@ -1290,6 +1301,7 @@
         ":riscv32g_bitmanip_decoder",
         ":riscv32g_decoder",
         ":riscv_arm_semihost",
+        ":riscv_debug_info",
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
@@ -1302,6 +1314,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",
@@ -1309,6 +1322,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/debug_command_shell.h b/riscv/debug_command_shell.h
index eba421d..bc8e786 100644
--- a/riscv/debug_command_shell.h
+++ b/riscv/debug_command_shell.h
@@ -29,7 +29,7 @@
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
-#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/debug_command_shell_interface.h"
 #include "re2/re2.h"
 
diff --git a/riscv/riscv_gdb_debug_info.cc b/riscv/riscv_gdb_debug_info.cc
new file mode 100644
index 0000000..81cdee0
--- /dev/null
+++ b/riscv/riscv_gdb_debug_info.cc
@@ -0,0 +1,85 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "riscv/riscv_gdb_debug_info.h"
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace mpact::sim::riscv {
+
+using ::mpact::sim::generic::operator*;  // NOLINT
+
+RiscVGdbDebugInfo* RiscVGdbDebugInfo::Instance(int gpr_width) {
+  if (gpr_width != 32 && gpr_width != 64) {
+    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;
+  }
+  return nullptr;
+}
+
+RiscVGdbDebugInfo::RiscVGdbDebugInfo(int gpr_width) : gpr_width_(gpr_width) {
+  host_info_ = absl::StrFormat(
+      "triple:riscv%d-unknown-elf;"
+      "endian:little;"
+      "ptrsize:%d;"
+      "vendor:riscv;",
+      gpr_width_, gpr_width_ / 8);
+  // PC register.
+  debug_register_map_.emplace(*RiscVGdbRegisterEnum::kGprPc, "pc");
+  // Integer registers.
+  for (int i = *RiscVGdbRegisterEnum::kGprX0;
+       i <= *RiscVGdbRegisterEnum::kGprX31; ++i) {
+    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));
+  }
+  // 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"
+  }
+}
+
+}  // namespace mpact::sim::riscv
diff --git a/riscv/riscv_gdb_debug_info.h b/riscv/riscv_gdb_debug_info.h
new file mode 100644
index 0000000..0e6b14d
--- /dev/null
+++ b/riscv/riscv_gdb_debug_info.h
@@ -0,0 +1,151 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file contains the definition of the RiscVGdbDebugInfo class. The
+// RiscVGdbDebugInfo class is used to provide information about the registers
+// and target XML to gdbserver.
+
+#ifndef THIRD_PARTY_MPACT_RISCV_RISCV_GDB_DEBUG_INFO_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_GDB_DEBUG_INFO_H_
+
+#include <string>
+#include <string_view>
+
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/debug_info.h"
+
+namespace mpact::sim::riscv {
+
+enum class RiscVGdbRegisterEnum : int {
+  // Integer registers.
+  kGprFirst = 0,
+  kGprPc = kGprFirst,
+  kGprX0,
+  kGprX1,
+  kGprX2,
+  kGprX3,
+  kGprX4,
+  kGprX5,
+  kGprX6,
+  kGprX7,
+  kGprX8,
+  kGprX9,
+  kGprX10,
+  kGprX11,
+  kGprX12,
+  kGprX13,
+  kGprX14,
+  kGprX15,
+  kGprX16,
+  kGprX17,
+  kGprX18,
+  kGprX19,
+  kGprX20,
+  kGprX21,
+  kGprX22,
+  kGprX23,
+  kGprX24,
+  kGprX25,
+  kGprX26,
+  kGprX27,
+  kGprX28,
+  kGprX29,
+  kGprX30,
+  kGprX31,
+  kGprLast = kGprX31,
+  // Floating point registers.
+  kFprFirst = kGprLast + 1,
+  kFprF0 = kFprFirst,
+  kFprF1,
+  kFprF2,
+  kFprF3,
+  kFprF4,
+  kFprF5,
+  kFprF6,
+  kFprF7,
+  kFprF8,
+  kFprF9,
+  kFprF10,
+  kFprF11,
+  kFprF12,
+  kFprF13,
+  kFprF14,
+  kFprF15,
+  kFprF16,
+  kFprF17,
+  kFprF18,
+  kFprF19,
+  kFprF20,
+  kFprF21,
+  kFprF22,
+  kFprF23,
+  kFprF24,
+  kFprF25,
+  kFprF26,
+  kFprF27,
+  kFprF28,
+  kFprF29,
+  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,
+};
+
+class RiscVGdbDebugInfo : public generic::DebugInfo {
+ public:
+  using DebugRegisterMap = generic::DebugInfo::DebugRegisterMap;
+
+  static RiscVGdbDebugInfo* Instance(int gpr_width);
+
+  const DebugRegisterMap& debug_register_map() const override {
+    return debug_register_map_;
+  }
+  int GetFirstGpr() const override {
+    return static_cast<int>(RiscVGdbRegisterEnum::kGprFirst);
+  };
+  int GetLastGpr() const override {
+    return static_cast<int>(RiscVGdbRegisterEnum::kGprLast);
+  }
+  int GetGprWidth() const override { return gpr_width_; };
+  // For now assume that all registers have the same width as the GPRs.
+  int GetRegisterByteWidth(int register_number) const override {
+    return gpr_width_ / 8;
+  }
+  std::string_view GetLLDBHostInfo() const override {
+    return absl::string_view(host_info_);
+  }
+  std::string_view GetGdbTargetXml() const override {
+    return absl::string_view(gdb_target_xml_);
+  }
+
+ private:
+  explicit RiscVGdbDebugInfo(int gpr_width);
+  int gpr_width_;
+  std::string host_info_;
+  DebugRegisterMap debug_register_map_;
+  std::string_view gdb_target_xml_;
+};
+
+}  // namespace mpact::sim::riscv
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_GDB_DEBUG_INFO_H_
diff --git a/riscv/riscv_top.cc b/riscv/riscv_top.cc
index 0dfaed6..6158243 100644
--- a/riscv/riscv_top.cc
+++ b/riscv/riscv_top.cc
@@ -705,6 +705,10 @@
   return rv_breakpoint_manager_->ClearBreakpoint(address);
 }
 
+uint64_t RiscVTop::GetSwBreakpointInfo() const {
+  return rv_breakpoint_manager_->last_breakpoint_address();
+}
+
 absl::Status RiscVTop::ClearAllSwBreakpoints() {
   // Don't try if the simulator is running.
   if (run_status_ != RunStatus::kHalted) {
@@ -756,7 +760,9 @@
       (access_type == AccessType::kLoadStore)) {
     auto rd_memory_status = memory_watcher_->SetLoadWatchCallback(
         util::MemoryWatcher::AddressRange(address, address + length - 1),
-        [this](uint64_t address, int size) {
+        [this, access_type](uint64_t address, int size) {
+          last_watchpoint_address_ = address;
+          last_watchpoint_access_type_ = access_type;
           set_halt_string(absl::StrFormat(
               "Watchpoint triggered due to load from %08x", address));
           RequestHalt(*HaltReason::kDataWatchPoint, nullptr);
@@ -767,7 +773,9 @@
       (access_type == AccessType::kLoadStore)) {
     auto wr_memory_status = memory_watcher_->SetStoreWatchCallback(
         util::MemoryWatcher::AddressRange(address, address + length - 1),
-        [this](uint64_t address, int size) {
+        [this, access_type](uint64_t address, int size) {
+          last_watchpoint_address_ = address;
+          last_watchpoint_access_type_ = access_type;
           set_halt_string(absl::StrFormat(
               "Watchpoint triggered due to store to %08x", address));
           RequestHalt(*HaltReason::kDataWatchPoint, nullptr);
@@ -798,6 +806,11 @@
   return absl::OkStatus();
 }
 
+void RiscVTop::GetWatchpointInfo(uint64_t& address, AccessType& access_type) {
+  address = last_watchpoint_address_;
+  access_type = last_watchpoint_access_type_;
+}
+
 absl::StatusOr<Instruction*> RiscVTop::GetInstruction(uint64_t address) {
   // If requesting the instruction at an action point, we need to write the
   // original instruction back to memory before getting the disassembly.
diff --git a/riscv/riscv_top.h b/riscv/riscv_top.h
index 028f9a7..ed638f3 100644
--- a/riscv/riscv_top.h
+++ b/riscv/riscv_top.h
@@ -102,6 +102,7 @@
   absl::Status SetSwBreakpoint(uint64_t address) override;
   absl::Status ClearSwBreakpoint(uint64_t address) override;
   absl::Status ClearAllSwBreakpoints() override;
+  uint64_t GetSwBreakpointInfo() const override;
 
   // Action points.
   absl::StatusOr<int> SetActionPoint(
@@ -116,6 +117,8 @@
   absl::Status ClearDataWatchpoint(uint64_t address,
                                    AccessType access_type) override;
 
+  void GetWatchpointInfo(uint64_t& address, AccessType& access_type) override;
+
   // If successful, returns a pointer to the instruction at the given address.
   // The instruction object is IncRef'ed, and the caller must DecRef the object
   // when it is done with it.
@@ -235,6 +238,9 @@
   Cache* dcache_ = nullptr;
   Cache* icache_ = nullptr;
   DataBuffer* inst_db_ = nullptr;
+  // Watchpoint info.
+  uint64_t last_watchpoint_address_ = 0;
+  generic::AccessType last_watchpoint_access_type_ = AccessType::kNone;
 };
 
 }  // namespace riscv
diff --git a/riscv/rv32g_sim.cc b/riscv/rv32g_sim.cc
index a45c698..43f18e8 100644
--- a/riscv/rv32g_sim.cc
+++ b/riscv/rv32g_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,12 +58,14 @@
 #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::proto::ComponentValueEntry;
@@ -69,10 +74,12 @@
 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::RiscVXlen;
 using ::mpact::sim::riscv::RV32Register;
 using ::mpact::sim::riscv::RVFpRegister;
+using ::mpact::sim::util::gdbserver::GdbServer;
 
 using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
 
@@ -160,6 +167,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";
 
@@ -482,9 +493,22 @@
   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(32);
+    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.