Initial commit for adding gdbserver to mpact-sim and vxc_sim PiperOrigin-RevId: 886288912 Change-Id: I258a6ed1a2ca4df8746936d8fe9e43a9a23c2da5
diff --git a/mpact/sim/generic/BUILD b/mpact/sim/generic/BUILD index f1b3884..f3f419d 100644 --- a/mpact/sim/generic/BUILD +++ b/mpact/sim/generic/BUILD
@@ -127,10 +127,12 @@ srcs = [], hdrs = [ "core_debug_interface.h", + "debug_info.h", ], deps = [ ":core", ":instruction", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", ], @@ -216,7 +218,6 @@ "//mpact/sim/proto:component_data_cc_proto", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:variant", ], )
diff --git a/mpact/sim/generic/core_debug_interface.h b/mpact/sim/generic/core_debug_interface.h index 81d3762..0071b6a 100644 --- a/mpact/sim/generic/core_debug_interface.h +++ b/mpact/sim/generic/core_debug_interface.h
@@ -122,10 +122,26 @@ // Remove all software breakpoints. virtual absl::Status ClearAllSwBreakpoints() = 0; + // Set a data watchpoint for the given memory range. Any access matching the + // given access type (load/store) will halt execution following the completion + // of that access. + virtual absl::Status SetDataWatchpoint(uint64_t address, size_t length, + AccessType access_type) { + return absl::UnimplementedError("Not implemented"); + } + // Clear data watchpoint for the given memory address and access type. + virtual absl::Status ClearDataWatchpoint(uint64_t address, + AccessType access_type) { + return absl::UnimplementedError("Not implemented"); + } + // Return the instruction object for the instruction at the given address. virtual absl::StatusOr<Instruction*> GetInstruction(uint64_t address) = 0; // Return the string representation for the instruction at the given address. virtual absl::StatusOr<std::string> GetDisassembly(uint64_t address) = 0; + + // Returns the executable file name. + virtual std::string GetExecutableFileName() { return ""; }; }; } // namespace generic
diff --git a/mpact/sim/generic/debug_info.h b/mpact/sim/generic/debug_info.h new file mode 100644 index 0000000..9b7d607 --- /dev/null +++ b/mpact/sim/generic/debug_info.h
@@ -0,0 +1,42 @@ +// 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 DebugInfo base class. + +#ifndef MPACT_SIM_GENERIC_DEBUG_INFO_H_ +#define MPACT_SIM_GENERIC_DEBUG_INFO_H_ + +#include <cstdint> +#include <string> + +#include "absl/container/flat_hash_map.h" + +namespace mpact::sim::generic { + +class DebugInfo { + public: + using DebugRegisterMap = absl::flat_hash_map<uint64_t, std::string>; + + virtual ~DebugInfo() = default; + + virtual const DebugRegisterMap& debug_register_map() const = 0; + + virtual int GetFirstGpr() const = 0; + virtual int GetLastGpr() const = 0; + virtual int GetGprWidth() const = 0; +}; + +} // namespace mpact::sim::generic + +#endif // MPACT_SIM_GENERIC_DEBUG_INFO_H_
diff --git a/mpact/sim/util/gdbserver/BUILD b/mpact/sim/util/gdbserver/BUILD new file mode 100644 index 0000000..fdfbe85 --- /dev/null +++ b/mpact/sim/util/gdbserver/BUILD
@@ -0,0 +1,39 @@ +# 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 build rules for the gdbserver utility. + +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//visibility:public"], +) + +cc_library( + name = "gdbserver", + srcs = ["gdbserver.cpp"], + hdrs = ["gdbserver.h"], + deps = [ + "//mpact/sim/generic:core_debug_interface", + "//mpact/sim/generic:type_helpers", + "//mpact/sim/util/renode:socket_cli", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/log", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:span", + ], +)
diff --git a/mpact/sim/util/gdbserver/gdbserver.cpp b/mpact/sim/util/gdbserver/gdbserver.cpp new file mode 100644 index 0000000..c5b5d5a --- /dev/null +++ b/mpact/sim/util/gdbserver/gdbserver.cpp
@@ -0,0 +1,967 @@ +// 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 "third_party/mpact_sim/util/gdbserver/gdbserver.h" + +#include <fcntl.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <cerrno> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <istream> +#include <ostream> +#include <string> +#include <vector> + +#include "third_party/absl/container/flat_hash_map.h" +#include "third_party/absl/log/log.h" +#include "third_party/absl/strings/numbers.h" +#include "third_party/absl/strings/str_cat.h" +#include "third_party/absl/strings/str_format.h" +#include "third_party/absl/strings/str_split.h" +#include "third_party/absl/strings/string_view.h" +#include "third_party/absl/types/span.h" +#include "third_party/mpact_sim/generic/core_debug_interface.h" +#include "third_party/mpact_sim/generic/type_helpers.h" + +namespace { + +// Some binary data may be escaped in the GDB command. This removes such +// escapes after the command has been "unwrapped". +std::string UnescapeCommand(std::string_view escaped_command) { + std::string command; + for (int i = 0; i < escaped_command.size(); ++i) { + char c = escaped_command[i]; + if (c == 0x7d) { + c = escaped_command[++i] ^ 0x20; + command.push_back(c); + } + command.push_back(c); + } + return command; +} + +} // namespace + +namespace mpact::sim::util::gdbserver { + +using ::mpact::sim::generic::operator*; // NOLINT + +GdbServer::GdbServer( + absl::Span<generic::CoreDebugInterface*> core_debug_interfaces, + const DebugInfo& debug_info) + : core_debug_interfaces_(core_debug_interfaces), debug_info_(debug_info) {} + +GdbServer::~GdbServer() { + Terminate(); + delete os_; + delete is_; + delete out_buf_; + delete in_buf_; +} + +bool GdbServer::Connect(int port) { + good_ = true; + if (port <= 0) { + return false; + } + // Create the socket on the given port. + server_socket_ = + socket(/*domain=*/AF_INET, /*type=*/SOCK_STREAM, /*protocol=*/0); + int one = 1; + int err = + setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (err != 0) { + LOG(ERROR) << absl::StrFormat("Failed to set socket options: %s", + strerror(errno)); + good_ = false; + return false; + } + sockaddr_in server_address_int; + server_address_int.sin_family = AF_INET; + server_address_int.sin_addr.s_addr = INADDR_ANY; + server_address_int.sin_port = htons(port); + std::memset(&server_address_int.sin_zero, 0, + sizeof(server_address_int.sin_zero)); + int res = bind(server_socket_, + reinterpret_cast<const sockaddr*>(&server_address_int), + sizeof(server_address_int)); + if (res != 0) { + good_ = false; + LOG(ERROR) << absl::StrFormat("Failed to bind to port %d: %s", port, + strerror(errno)); + return false; + } + res = listen(server_socket_, /*backlog=*/1); + if (res != 0) { + good_ = false; + LOG(ERROR) << absl::StrFormat("Failed to listen on port %d: %s", port, + strerror(errno)); + return false; + } + + // Accept the connection. + cli_fd_ = accept(server_socket_, /*addr=*/nullptr, /*addrlen=*/nullptr); + if (cli_fd_ == -1) { + good_ = false; + LOG(ERROR) << absl::StrFormat("Failed to accept connection on port %d: %s", + port, strerror(errno)); + return false; + } + good_ = true; + // Create the input and output buffers and streams to handle reads and writes + // to the socket. + out_buf_ = new SocketStreambuf(cli_fd_); + in_buf_ = new SocketStreambuf(cli_fd_); + os_ = new std::ostream(out_buf_); + is_ = new std::istream(in_buf_); + + // First expect a plus. + uint8_t val = is_->get(); + if (val != '+') { + good_ = false; + LOG(ERROR) << absl::StrFormat("Failed handshake with GDB client on port %d", + port); + Terminate(); + return false; + } + // Now read commands from the gdbserver client and process them. + while (good_) { + int buffer_pos = 0; + // First character is always '$'. + val = is_->get(); + if (val != '$') { + if (val == 0xff) { + LOG(INFO) << "Client disconnected"; + Terminate(); + return false; + } + good_ = false; + LOG(ERROR) << absl::StrFormat( + "Failed to receive command on port %d (expected '$', but received " + "'%c' - 0x%x)", + port, val, static_cast<int>(val)); + Terminate(); + return false; + } + buffer_[buffer_pos++] = val; + // Next read the command until we see a '#'. + do { + val = is_->get(); + if (!is_->good()) { + good_ = false; + LOG(ERROR) << absl::StrFormat( + "Failed to receive complete command on port %d received: '%s'", + port, + std::string_view(reinterpret_cast<char*>(buffer_), buffer_pos)); + Terminate(); + return false; + } + buffer_[buffer_pos++] = val; + } while (val != '#'); + // Next read the checksum. + buffer_[buffer_pos++] = is_->get(); + if (!is_->good()) break; + buffer_[buffer_pos++] = is_->get(); + if (!is_->good()) break; + if ((buffer_pos < 3) || (buffer_[buffer_pos - 3] != '#')) continue; + + AcceptGdbCommand( + std::string_view(reinterpret_cast<char*>(buffer_), buffer_pos)); + buffer_pos = 0; + } + good_ = false; + return true; +} + +void GdbServer::Terminate() { + // Shutdown the connection. + if (cli_fd_ != -1) { + int res = fcntl(cli_fd_, F_GETFD); + if (res >= 0) { + (void)shutdown(cli_fd_, SHUT_RDWR); + (void)close(cli_fd_); + } + } + if (server_socket_ != -1) { + int res = shutdown(server_socket_, SHUT_RDWR); + res = fcntl(server_socket_, F_GETFD); + if (res >= 0) { + (void)close(server_socket_); + } + } + good_ = false; +} + +void GdbServer::Respond(std::string_view response) { + // Compute the checksum of the response. + uint8_t checksum = 0; + for (char c : response) { + checksum += c; + } + // Format the response string. + std::string response_str = absl::StrCat( + "$", response, "#", absl::StrFormat("%02x", static_cast<int>(checksum))); + LOG(INFO) << absl::StrFormat("Response: '%s'", response_str); + uint8_t ack = 0; + // Send the response to the GDB client. + for (int i = 0; i < 5; ++i) { + os_->write(response_str.data(), response_str.size()); + os_->flush(); + ack = is_->get(); + if (ack == '+') break; + LOG(WARNING) << absl::StrFormat("Response not acknowledged ('%c' received)", + ack); + } + if (ack != '+') { + LOG(ERROR) << "Failed to send response after 5 attempts"; + Terminate(); + } +} + +void GdbServer::SendError(std::string_view error) { + Respond(absl::StrCat("E.", error)); +} + +void GdbServer::AcceptGdbCommand(std::string_view command) { + LOG(INFO) << absl::StrFormat("Received: '%s'", command); + // The command is on the form "$<command>#<2 digit checksum>". + size_t pos = command.find_last_of('#'); + if ((command.front() != '$') || (pos == std::string_view::npos) || + (pos != command.size() - 3)) { + LOG(ERROR) << "Invalid GDB command syntax"; + os_->put('-'); + os_->flush(); + return; + } + // Verify the checksum. + std::string_view checksum_str = command.substr(pos + 1, 2); + // Trim the command string. + command.remove_prefix(1); + command.remove_suffix(3); + uint8_t checksum = 0; + for (char c : command) { + checksum += c; + } + int orig_checksum; + bool success = absl::SimpleHexAtoi(checksum_str, &orig_checksum); + if (!success) { + LOG(ERROR) << absl::StrFormat("Invalid original checksum: '%s'", + checksum_str); + // Request a retransmission. + os_->put('-'); + os_->flush(); + return; + } + if (checksum != orig_checksum) { + LOG(ERROR) << absl::StrFormat("Invalid checksum: %x expected: %x", + static_cast<int>(checksum), orig_checksum); + + // Request a retransmission. + os_->put('-'); + os_->flush(); + return; + } + // Acknowledge the command. + os_->put('+'); + os_->flush(); + std::string clean_command = UnescapeCommand(command); + ParseGdbCommand(command); +} + +void GdbServer::ParseGdbCommand(std::string_view command) { + switch (command.front()) { + default: + // The command is not handled, so respond with an empty string which will + // indicate that the command is not supported. + Respond(""); + break; + case '?': // Inquire about the halt reason. + GdbHaltReason(); + break; + case 'c': // Continue packet. + command.remove_prefix(1); + GdbContinue(command); + break; + case 'D': // Detach packet. + GdbDetach(); + break; + case 'g': // Read registers. + GdbReadGprRegisters(); + break; + case 'H': // Select thread + command.remove_prefix(1); + GdbSelectThread(command); + break; + case 'k': // Kill packet. + Terminate(); + break; + case 'm': { // Read memory. + command.remove_prefix(1); + size_t pos = command.find(','); + if (pos == std::string_view::npos) { + if (error_message_supported_) { + SendError("invalid memory read format"); + } else { + Respond("E01"); + } + return; + } + std::string_view address = command.substr(0, pos); + std::string_view length = command.substr(pos + 1); + GdbReadMemory(address, length); + break; + } + case 'M': { // Write memory. + command.remove_prefix(1); + size_t comma_pos = command.find(','); + if (comma_pos == std::string_view::npos) { + if (error_message_supported_) { + SendError("invalid memory write format"); + } else { + Respond("E01"); + } + return; + } + size_t colon_pos = command.find(':'); + if (colon_pos == std::string_view::npos) { + SendError("invalid memory write format"); + return; + } + std::string_view address = command.substr(0, comma_pos); + std::string_view length = + command.substr(comma_pos + 1, colon_pos - comma_pos - 1); + std::string_view data = command.substr(colon_pos + 1); + GdbWriteMemory(address, length, data); + break; + } + case 'p': { // Read register. + command.remove_prefix(1); + GdbReadRegister(command); + break; + } + case 'P': { // Write register. + command.remove_prefix(1); + size_t pos = command.find('='); + if (pos == std::string_view::npos) { + SendError("invalid register write format"); + return; + } + std::string_view register_name = command.substr(0, pos); + std::string_view value = command.substr(pos + 1); + GdbWriteRegister(register_name, value); + break; + } + case 'q': { // Query. + command.remove_prefix(1); + GdbQuery(command); + break; + } + case 'Q': { // Set. + command.remove_prefix(1); + GdbSet(command); + break; + } + case 's': // Step. + command.remove_prefix(1); + GdbStep(command); + break; + case 'z': + case 'Z': { // Add or remove breakpoint or watchpoint. + // First make sure it's not a conditional breakpoint. + if (command.find(';')) { + Respond(""); + return; + } + char type = command.front(); + command.remove_prefix(1); + size_t address_pos = command.find_first_of(','); + if (address_pos == std::string_view::npos) { + SendError("invalid remove breakpoint format"); + return; + } + size_t kind_pos = command.find_last_of(','); + if (kind_pos == std::string_view::npos) { + SendError("invalid remove breakpoint format"); + return; + } + std::string_view address = + command.substr(address_pos + 1, kind_pos - address_pos - 1); + std::string_view kind = command.substr(kind_pos + 1); + if (type == 'z') { + GdbRemoveBreakpoint(type, address, kind); + } else { + GdbAddBreakpoint(type, address, kind); + } + break; + } + } +} + +void GdbServer::GdbHalt() { + auto status = core_debug_interfaces_[0]->Halt( + generic::CoreDebugInterface::HaltReason::kUserRequest); + if (!status.ok()) { + SendError(status.message()); + return; + } + status = core_debug_interfaces_[0]->Wait(); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond(absl::StrCat("T03")); +} + +void GdbServer::GdbHaltReason() { + auto result = core_debug_interfaces_[0]->GetRunStatus(); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + if (result.value() == generic::CoreDebugInterface::RunStatus::kHalted) { + auto result = core_debug_interfaces_[0]->GetLastHaltReason(); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + switch (result.value()) { + default: + Respond("T05"); + return; + case *generic::CoreDebugInterface::HaltReason::kSoftwareBreakpoint: + case *generic::CoreDebugInterface::HaltReason::kHardwareBreakpoint: + case *generic::CoreDebugInterface::HaltReason::kDataWatchPoint: + case *generic::CoreDebugInterface::HaltReason::kActionPoint: + Respond("T02"); + return; + case *generic::CoreDebugInterface::HaltReason::kSimulatorError: + Respond("T06"); + return; + case *generic::CoreDebugInterface::HaltReason::kUserRequest: + Respond("T03"); + return; + case *generic::CoreDebugInterface::HaltReason::kProgramDone: + Respond("W00"); + return; + } + } + Respond("E01"); +} + +void GdbServer::GdbContinue(std::string_view command) { + auto result = core_debug_interfaces_[0]->GetRunStatus(); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + if (result.value() == generic::CoreDebugInterface::RunStatus::kHalted) { + if (!command.empty()) { + uint64_t address; + bool success = absl::SimpleHexAtoi(command, &address); + if (!success) { + SendError("invalid address"); + return; + } + auto status = core_debug_interfaces_[0]->WriteRegister("pc", address); + if (!status.ok()) { + SendError(status.message()); + return; + } + } + result = core_debug_interfaces_[0]->Run(); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + } +} + +void GdbServer::GdbDetach() { + Respond("OK"); + Terminate(); +} + +void GdbServer::GdbSelectThread(std::string_view command) { + int thread_id; + char op = command.front(); + command.remove_prefix(1); + if (command == "-1") { + thread_id = -1; + } else { + bool success = absl::SimpleHexAtoi(command, &thread_id); + if (!success) { + SendError("invalid thread id"); + return; + } + } + thread_select_[op] = thread_id; + Respond("OK"); +} + +void GdbServer::GdbThreadInfo() { + std::string response = "m0"; + + for (int i = 1; i < core_debug_interfaces_.size(); ++i) { + absl::StrAppend(&response, ",", absl::Hex(i)); + } + Respond(response); +} + +void GdbServer::GdbReadMemory(std::string_view address_str, + std::string_view length_str) { + uint64_t address; + bool success = absl::SimpleHexAtoi(address_str, &address); + if (!success) { + if (error_message_supported_) { + SendError("invalid memory read format"); + } else { + Respond("E01"); + } + return; + } + uint64_t length; + success = absl::SimpleHexAtoi(length_str, &length); + if (!success) { + if (error_message_supported_) { + SendError("invalid memory read format"); + } else { + Respond("E01"); + } + return; + } + if (length > sizeof(buffer_)) { + if (error_message_supported_) { + SendError("length exceeds buffer size of 4096"); + } else { + Respond("E01"); + } + return; + } + auto result = core_debug_interfaces_[0]->ReadMemory(address, buffer_, length); + if (!result.ok()) { + if (error_message_supported_) { + SendError(result.status().message()); + } else { + Respond("E01"); + } + return; + } + std::string response; + for (int i = 0; i < result.value(); ++i) { + absl::StrAppend(&response, absl::Hex(buffer_[i], absl::kZeroPad2)); + } + Respond(response); +} + +void GdbServer::GdbWriteMemory(std::string_view address_str, + std::string_view length_str, + std::string_view data) { + uint64_t address; + bool success = absl::SimpleHexAtoi(address_str, &address); + if (!success) { + if (error_message_supported_) { + SendError("invalid memory read format"); + } else { + Respond("E01"); + } + return; + } + uint64_t length; + success = absl::SimpleHexAtoi(length_str, &length); + if (!success) { + if (error_message_supported_) { + SendError("invalid memory read format"); + } else { + Respond("E01"); + } + return; + } + if (length > sizeof(buffer_)) { + if (error_message_supported_) { + SendError("length exceeds buffer size of 4096"); + } else { + Respond("E01"); + } + return; + } + int num_bytes = 0; + for (int i = 0; (i < length) && (data.size() >= 2); ++i) { + num_bytes++; + std::string_view byte = data.substr(0, 2); + data.remove_prefix(2); + uint8_t value; + (void)absl::SimpleHexAtoi(byte, &value); + buffer_[i] = value; + } + if (num_bytes != length) { + if (error_message_supported_) { + SendError("length does not match data size"); + } else { + Respond("E01"); + } + return; + } + auto result = + core_debug_interfaces_[0]->WriteMemory(address, buffer_, num_bytes); + if (!result.ok()) { + if (error_message_supported_) { + SendError(result.status().message()); + } else { + Respond("E01"); + } + return; + } + if (result.value() != length) { + if (error_message_supported_) { + SendError("length does not match number of bytes written"); + } else { + Respond("E01"); + } + return; + } + Respond("OK"); +} + +void GdbServer::GdbReadGprRegisters() { + std::string response; + for (int i = debug_info_.GetFirstGpr(); i <= debug_info_.GetLastGpr(); ++i) { + auto it = debug_info_.debug_register_map().find(i); + if (it == debug_info_.debug_register_map().end()) { + SendError("Internal error - failed to find register"); + return; + } + const std::string& register_name = it->second; + auto result = core_debug_interfaces_[0]->ReadRegister(register_name); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + LOG(INFO) << absl::StrFormat("Register %s = %x", register_name, + result.value()); + uint64_t value = result.value(); + for (int j = 0; j < debug_info_.GetGprWidth(); j += 8) { + absl::StrAppend(&response, absl::Hex(value & 0xff, absl::kZeroPad2)); + value >>= 8; + } + } + Respond(response); +} + +void GdbServer::GdbWriteGprRegisters(std::string_view data) { + int num_bytes = 0; + for (int i = debug_info_.GetFirstGpr(); i <= debug_info_.GetLastGpr(); ++i) { + auto it = debug_info_.debug_register_map().find(i); + if (it == debug_info_.debug_register_map().end()) { + SendError("Internal error - failed to find register"); + return; + } + const std::string& register_name = it->second; + uint64_t value = 0; + for (int j = 0; j < debug_info_.GetGprWidth(); j += 8) { + std::string_view byte = data.substr(0, 2); + data.remove_prefix(2); + uint8_t byte_value; + (void)absl::SimpleHexAtoi(byte, &byte_value); + value |= byte_value << (j * 8); + num_bytes++; + } + auto status = + core_debug_interfaces_[0]->WriteRegister(register_name, value); + if (!status.ok()) { + SendError(status.message()); + return; + } + } + Respond("OK"); +} + +void GdbServer::GdbReadRegister(std::string_view register_number_str) { + uint64_t register_number; + bool success = absl::SimpleHexAtoi(register_number_str, ®ister_number); + if (!success) { + SendError("invalid register number"); + return; + } + auto it = debug_info_.debug_register_map().find(register_number); + if (it == debug_info_.debug_register_map().end()) { + SendError("invalid register number"); + return; + } + const std::string& register_name = it->second; + auto result = core_debug_interfaces_[0]->ReadRegister(register_name); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + std::string response; + uint64_t value = result.value(); + while (value > 0) { + absl::StrAppend(&response, absl::Hex(value & 0xff, absl::kZeroPad2)); + value >>= 8; + } + Respond(response); +} + +void GdbServer::GdbWriteRegister(std::string_view register_number_str, + std::string_view register_value_str) { + uint64_t register_number; + bool success = absl::SimpleHexAtoi(register_number_str, ®ister_number); + if (!success) { + SendError("invalid register number"); + return; + } + auto it = debug_info_.debug_register_map().find(register_number); + if (it == debug_info_.debug_register_map().end()) { + SendError("invalid register number"); + return; + } + const std::string& register_name = it->second; + uint64_t value = 0; + int count = 0; + while (register_value_str.size() > 0) { + std::string_view byte = register_value_str.substr(0, 2); + register_value_str.remove_prefix(2); + uint8_t byte_value; + (void)absl::SimpleHexAtoi(byte, &byte_value); + value |= byte_value << (count * 8); + count++; + } + auto status = core_debug_interfaces_[0]->WriteRegister(register_name, value); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); +} + +void GdbServer::GdbQuery(std::string_view command) { + switch (command.front()) { + default: + break; + case 'A': // Attached? + if (command.starts_with("Attached")) { + Respond("0"); + return; + } + break; + case 'C': // Current thread ID. + if (command == "C") { + Respond(absl::StrCat("QC", current_thread_id_)); + return; + } + break; + case 'E': // ExecAndArgs? + if (command.starts_with("ExecAndArgs")) { + command.remove_prefix(11); + GdbExecAndArgs(command); + return; + } + break; + case 'f': + if (command.starts_with("fThreadInfo")) { + GdbThreadInfo(); + return; + } + break; + case 's': + if (command.starts_with("sThreadInfo")) { + Respond("l"); + return; + } + break; + case 'S': { // Search, Supported, or Symbol. + if (command.starts_with("Search")) break; + if (command.starts_with("Symbol")) break; + if (command.starts_with("Supported")) { + command.remove_prefix(9); + GdbSupported(command); + return; + } + } + } + Respond(""); // Not supported for now. +} + +void GdbServer::GdbSet(std::string_view command) { + Respond(""); + // TODO(torerik): Implement. +} + +void GdbServer::GdbStep(std::string_view command) { + auto result = core_debug_interfaces_[0]->Step(1); + if (!result.ok()) { + SendError(result.status().message()); + return; + } + Respond("T05"); +} + +void GdbServer::GdbAddBreakpoint(char type, std::string_view address_str, + std::string_view kind_str) { + uint64_t address; + bool success = absl::SimpleHexAtoi(address_str, &address); + if (!success) { + SendError("invalid breakpoint address"); + return; + } + size_t kind; + switch (type) { + default: + SendError("invalid breakpoint type"); + return; + case '0': { // Software breakpoint. + auto status = core_debug_interfaces_[0]->SetSwBreakpoint(address); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + return; + } + case '2': { // Data watchpoint write only. + bool success = absl::SimpleHexAtoi(kind_str, &kind); + if (!success) { + SendError("invalid watchpoint kind"); + return; + } + auto status = core_debug_interfaces_[0]->SetDataWatchpoint( + address, kind, generic::AccessType::kStore); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + return; + } + case '3': { // Data watchpoint read-only. + bool success = absl::SimpleHexAtoi(kind_str, &kind); + if (!success) { + SendError("invalid watchpoint kind"); + return; + } + auto status = core_debug_interfaces_[0]->SetDataWatchpoint( + address, kind, generic::AccessType::kLoad); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + return; + } + case '4': { // Data watchpoint read or write. + bool success = absl::SimpleHexAtoi(kind_str, &kind); + if (!success) { + SendError("invalid watchpoint kind"); + return; + } + auto status = core_debug_interfaces_[0]->SetDataWatchpoint( + address, kind, generic::AccessType::kLoadStore); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + break; + } + } + // TODO(torerik): Implement. + Respond(""); +} + +void GdbServer::GdbRemoveBreakpoint(char type, std::string_view address_str, + std::string_view kind_str) { + uint64_t address; + bool success = absl::SimpleHexAtoi(address_str, &address); + if (!success) { + SendError("invalid breakpoint address"); + return; + } + switch (type) { + default: + SendError("invalid breakpoint type"); + return; + case '0': { // Software breakpoint. + auto status = core_debug_interfaces_[0]->ClearSwBreakpoint(address); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + break; + } + case 2: { // Data watchpoint write only. + auto status = core_debug_interfaces_[0]->ClearDataWatchpoint( + address, generic::AccessType::kStore); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + return; + } + case 3: { // Data watchpoint read-only. + auto status = core_debug_interfaces_[0]->ClearDataWatchpoint( + address, generic::AccessType::kLoad); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + return; + } + case 4: { // Data watchpoint read or write. + auto status = core_debug_interfaces_[0]->ClearDataWatchpoint( + address, generic::AccessType::kLoadStore); + if (!status.ok()) { + SendError(status.message()); + return; + } + Respond("OK"); + return; + } + } + Respond(""); +} + +void GdbServer::GdbSupported(std::string_view command) { + // Read supported GDB features. + if (command.front() == ':') { + command.remove_prefix(1); + std::vector<std::string> features = absl::StrSplit(command, ';'); + for (const std::string& feature : features) { + if (feature == "error-message+") { + error_message_supported_ = true; + } + } + } + std::string response; + absl::StrAppend( + &response, "PacketSize=8192;multi-wp-addr-", + ";multiprocess-;hwbreak-;qRelocInsn-;fork-events-;exec-events-" + ";vContSupported-;QThreadEvents-;QThreadOptions-;no-resumed-" + ";memory-tagging-;vfork-events-"); + Respond(response); +} + +void GdbServer::GdbExecAndArgs(std::string_view command) { + std::string file_name = core_debug_interfaces_[0]->GetExecutableFileName(); + Respond(file_name); +} + +} // namespace mpact::sim::util::gdbserver
diff --git a/mpact/sim/util/gdbserver/gdbserver.h b/mpact/sim/util/gdbserver/gdbserver.h new file mode 100644 index 0000000..da3de18 --- /dev/null +++ b/mpact/sim/util/gdbserver/gdbserver.h
@@ -0,0 +1,133 @@ +// 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 a GDB server that can be used to with MPACT-Sim based +// simulators. It requires that the debugger (gdb, lldb, etc.) already +// supports your architecture. + +#ifndef MPACT_SIM_UTIL_GDBSERVER_GDBSERVER_H_ +#define MPACT_SIM_UTIL_GDBSERVER_GDBSERVER_H_ + +#include <cstdint> +#include <istream> +#include <ostream> +#include <string_view> + +#include "absl/container/flat_hash_map.h" +#include "absl/types/span.h" +#include "mpact/sim/generic/core_debug_interface.h" +#include "mpact/sim/generic/debug_info.h" +#include "mpact/sim/util/renode/socket_streambuf.h" + +namespace mpact::sim::util::gdbserver { + +using ::mpact::sim::generic::DebugInfo; +using ::mpact::sim::util::renode::SocketStreambuf; + +class GdbServer { + public: + // The constructor takes a span of core debug interfaces (usually the sim + // top objects), and a debug info object to provide information about the + // registers. Programs should already be loaded onto the core debug + // interfaces before the Gdb server is connected. + + explicit GdbServer( + absl::Span<generic::CoreDebugInterface*> core_debug_interfaces, + const DebugInfo& debug_info); + ~GdbServer(); + + // Open the GDB server on the given port, wait for a connection, and process + // the GDB commands. + bool Connect(int port); + + private: + // Terminate the connection to the GDB client. + void Terminate(); + // Sends the given response to the GDB client. Wrapping it in the proper + // packet format and adding the checksum. + void Respond(std::string_view response); + // Sends the given error message to the GDB client in an error packet. + void SendError(std::string_view error); + + // Takes a GDB command string, verifies the checksum, and if it is valid, + // then submits the command to be parsed. + void AcceptGdbCommand(std::string_view command); + // Parses the given GDB command and calls the appropriate command handler. + void ParseGdbCommand(std::string_view command); + + // GDB command handlers. + + // Halt the simulator. + void GdbHalt(); + // Return the halt reason. + void GdbHaltReason(); + // Continue the simulation. + void GdbContinue(std::string_view command); + // Detach from the simulator. + void GdbDetach(); + // Select the thread to operate on. + void GdbSelectThread(std::string_view command); + // Return the thread info. + void GdbThreadInfo(); + // Read memory from the simulator. + void GdbReadMemory(std::string_view address, std::string_view length); + // Write memory to the simulator. + void GdbWriteMemory(std::string_view address, std::string_view length, + std::string_view data); + // Read GPR registers from the simulator. + void GdbReadGprRegisters(); + // Write GPR registers to the simulator. + void GdbWriteGprRegisters(std::string_view data); + // Read a register from the simulator. + void GdbReadRegister(std::string_view register_number_str); + // Write a register to the simulator. + void GdbWriteRegister(std::string_view register_number_str, + std::string_view register_value_str); + // Query gdbserver features. + void GdbQuery(std::string_view command); + // Set gdbserver features. + void GdbSet(std::string_view command); + // Step the simulator. + void GdbStep(std::string_view command); + // Add a breakpoint to the simulator. + void GdbAddBreakpoint(char type, std::string_view address_str, + std::string_view kind_str); + // Remove a breakpoint from the simulator. + void GdbRemoveBreakpoint(char type, std::string_view address_str, + std::string_view kind_str); + // Respond with the supported GDB features. + void GdbSupported(std::string_view command); + // Get the executable file name and program arguments. + void GdbExecAndArgs(std::string_view command); + + SocketStreambuf* out_buf_ = nullptr; + SocketStreambuf* in_buf_ = nullptr; + std::ostream* os_ = nullptr; + std::istream* is_ = nullptr; + + bool connection_active_ = false; + bool error_message_supported_ = false; + uint8_t buffer_[16 * 1024]; + bool good_ = false; + int server_socket_ = -1; + int cli_fd_ = -1; + int current_thread_id_ = 0; + absl::flat_hash_map<char, int> thread_select_; + absl::Span<generic::CoreDebugInterface*> core_debug_interfaces_; + const DebugInfo& debug_info_; +}; + +} // namespace mpact::sim::util::gdbserver + +#endif // MPACT_SIM_UTIL_GDBSERVER_GDBSERVER_H_
diff --git a/mpact/sim/util/renode/socket_cli.cc b/mpact/sim/util/renode/socket_cli.cc index 17a158b..4185f38 100644 --- a/mpact/sim/util/renode/socket_cli.cc +++ b/mpact/sim/util/renode/socket_cli.cc
@@ -24,7 +24,7 @@ #include <iostream> #include <istream> #include <ostream> -#include <thread> +#include <thread> // NOLINT #include <utility> #include "absl/functional/any_invocable.h"
diff --git a/mpact/sim/util/renode/socket_cli.h b/mpact/sim/util/renode/socket_cli.h index 56d4807..74124a8 100644 --- a/mpact/sim/util/renode/socket_cli.h +++ b/mpact/sim/util/renode/socket_cli.h
@@ -5,7 +5,7 @@ #include <sys/socket.h> #include <sys/types.h> -#include <thread> +#include <thread> // NOLINT #include "absl/functional/any_invocable.h" #include "mpact/sim/generic/debug_command_shell_interface.h"