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.