| // 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 <cstdio> |
| #include <istream> |
| #include <ostream> |
| #include <streambuf> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #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 "re2/re2.h" |
| |
| namespace mpact::sim::util::gdbserver { |
| |
| using ::mpact::sim::generic::DebugInfo; |
| |
| // This class is used to provide a streambuf interface to a socket file |
| // descriptor, so that it can be used to access a socket as an istream/ostream. |
| // The overflow and underflow methods are the bare minimum that's required to |
| // implement to make this happen. No buffering is performed. This streambuf |
| // does not expand \n to \r\n or vice versa. |
| class GdbSocketStreambuf : public std::streambuf { |
| public: |
| explicit GdbSocketStreambuf(int fd) : fd_(fd) {} |
| ~GdbSocketStreambuf() override { close(fd_); } |
| |
| // On overflow write character to the socket fd. |
| int overflow(int c) override { |
| if (c == EOF) { |
| close(fd_); |
| return c; |
| } |
| write(fd_, &c, 1); |
| return c; |
| } |
| |
| // On underflow read character from the socket fd. |
| std::streambuf::int_type underflow() override { |
| int count = read(fd_, &ch_, 1); |
| if (count == 0) return traits_type::eof(); |
| setg(&ch_, &ch_, &ch_ + 1); |
| return traits_type::to_int_type(ch_); |
| } |
| |
| private: |
| char ch_; |
| int fd_; |
| }; |
| |
| 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. |
| |
| // For now, only one core is supported (core 0) - the others are ignored. |
| // Multiple cores will be supported in the future, but that requires that |
| // the cores can be set to operate in stop mode. Then, after that we can |
| // add support for non-stop mode with multiple cores. |
| |
| 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); |
| |
| int GetThreadId(char command_type, std::string_view command); |
| // 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); |
| |
| std::string HexEncodeString(std::string_view str); |
| std::string HexEncodeNumberInTargetEndianness(int bit_width, uint64_t number); |
| // GDB command handlers. |
| |
| // Halt the simulator. |
| void GdbHalt(); |
| // Return the halt reason. |
| std::string GetHaltReason(int thread_id); |
| void GdbHaltReason(); |
| // Continue the simulation. |
| void GdbContinue(int thread_id, std::string_view command); |
| void ContinueThread(int thread_id); |
| void GdbVContinue(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(int thread_id); |
| // Write GPR registers to the simulator. |
| void GdbWriteGprRegisters(int thread_id, std::string_view data); |
| // Read a register from the simulator. |
| void GdbReadRegister(int thread_id, std::string_view register_number_str); |
| // Write a register to the simulator. |
| void GdbWriteRegister(int thread_id, 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(int thread_id); |
| void StepThread(int thread_id); |
| // 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); |
| // Get the host info. |
| void GdbHostInfo(); |
| void GdbXferReadTarget(std::string_view offset_str, |
| std::string_view length_str); |
| |
| GdbSocketStreambuf* out_buf_ = nullptr; |
| GdbSocketStreambuf* in_buf_ = nullptr; |
| std::ostream* os_ = nullptr; |
| std::istream* is_ = nullptr; |
| |
| bool log_packets_ = false; |
| bool no_ack_mode_ = false; |
| bool no_ack_mode_latch_ = false; |
| bool error_message_supported_ = false; |
| bool thread_suffix_ = false; |
| uint8_t buffer_[16 * 1024]; |
| bool good_ = false; |
| int server_socket_ = -1; |
| int cli_fd_ = -1; |
| int current_thread_id_ = 1; |
| absl::flat_hash_map<char, int> thread_select_; |
| absl::Span<generic::CoreDebugInterface*> core_debug_interfaces_; |
| std::vector<int> halt_reasons_; |
| const DebugInfo& debug_info_; |
| // Regular expressions used to parse GDB commands. |
| // These are static since they are immutable once initialized. |
| static LazyRE2 gdb_command_re_; |
| static LazyRE2 thread_re_; |
| static LazyRE2 xfer_read_target_re_; |
| static LazyRE2 swbreak_set_re_; |
| static LazyRE2 swbreak_clear_re_; |
| }; |
| |
| } // namespace mpact::sim::util::gdbserver |
| |
| #endif // MPACT_SIM_UTIL_GDBSERVER_GDBSERVER_H_ |