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: I4285e8f1b52cd799523b70ed5a21918d36808fb2
diff --git a/mpact/sim/generic/debug_info.h b/mpact/sim/generic/debug_info.h index d1406a1..9635dd2 100644 --- a/mpact/sim/generic/debug_info.h +++ b/mpact/sim/generic/debug_info.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// This file contains the definition of the DebugInfo base class. +// This file contains the definition of the DebugInfo base class. The DebugInfo +// class is used to provide information about the registers and target XML to +// gdbserver. #ifndef MPACT_SIM_GENERIC_DEBUG_INFO_H_ #define MPACT_SIM_GENERIC_DEBUG_INFO_H_ @@ -31,11 +33,21 @@ virtual ~DebugInfo() = default; + // Returns the map of register numbers to register names. This is used so that + // gdbserver can convert from the register numbers used by the debugger to + // the register names used by the simulator. virtual const DebugRegisterMap& debug_register_map() const = 0; + // Returns the first and last general purpose register numbers. virtual int GetFirstGpr() const = 0; virtual int GetLastGpr() const = 0; + // Returns the byte width of the general purpose registers. virtual int GetGprWidth() const = 0; + // Returns the byte width of the register with the given number. + virtual int GetRegisterByteWidth(int) const = 0; + // Returns the XML file describing the target for gdb (or lldb). + virtual std::string_view GetGdbTargetXml() const = 0; + // Returns the host info string for gdb/lldb. virtual std::string_view GetLLDBHostInfo() const = 0; };
diff --git a/mpact/sim/util/gdbserver/BUILD b/mpact/sim/util/gdbserver/BUILD index 6ca29a5..8f06489 100644 --- a/mpact/sim/util/gdbserver/BUILD +++ b/mpact/sim/util/gdbserver/BUILD
@@ -30,11 +30,13 @@ "//mpact/sim/generic:type_helpers", "//mpact/sim/util/renode:socket_cli", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/log", "@com_google_absl//absl/status", "@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", + "@com_googlesource_code_re2//:re2", ], )
diff --git a/mpact/sim/util/gdbserver/gdbserver.cc b/mpact/sim/util/gdbserver/gdbserver.cc index c15a353..1088a51 100644 --- a/mpact/sim/util/gdbserver/gdbserver.cc +++ b/mpact/sim/util/gdbserver/gdbserver.cc
@@ -29,6 +29,7 @@ #include <vector> #include "absl/container/flat_hash_map.h" +#include "absl/flags/flag.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/strings/match.h" @@ -40,6 +41,9 @@ #include "absl/types/span.h" #include "mpact/sim/generic/core_debug_interface.h" #include "mpact/sim/generic/type_helpers.h" +#include "re2/re2.h" + +ABSL_FLAG(bool, log_packets, false, "Enables logging of GDB server packets."); namespace { @@ -61,6 +65,20 @@ return command; } +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::util::gdbserver { @@ -71,7 +89,14 @@ GdbServer::GdbServer( absl::Span<generic::CoreDebugInterface*> core_debug_interfaces, const DebugInfo& debug_info) - : core_debug_interfaces_(core_debug_interfaces), debug_info_(debug_info) { + : core_debug_interfaces_(core_debug_interfaces), + debug_info_(debug_info), + gdb_command_re_(R"(\$([^#]*)#([0-9a-fA-F]{2}))"), + thread_re_(R"(;?thread\:(\d+);?)"), + xfer_read_target_re_( + R"(Xfer\:features\:read\:target\.xml\:([0-9a-fA-F]+),([0-9a-fA-F]+))"), + swbreak_set_re_(R"(Z0,([0-9a-fA-F]+),(.+))"), + swbreak_clear_re_(R"(z0,([0-9a-fA-F]+),(.+))") { if (core_debug_interfaces.size() > 1) { LOG(WARNING) << "MPACT GdbServer only supports one core for now - " "ignoring extra cores."; @@ -167,7 +192,6 @@ Terminate(); return false; } - good_ = false; LOG(ERROR) << absl::StrFormat( "Failed to receive command on port %d (expected '$', but received " "'%c' - 0x%x)", @@ -180,7 +204,6 @@ do { val = is_->get(); if (!is_->good()) { - good_ = false; LOG(ERROR) << absl::StrFormat( "Failed to receive complete command on port %d received: '%s'", port, @@ -233,14 +256,15 @@ // 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); + if (log_packets_) { + 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(); if (no_ack_mode_) break; - LOG(INFO) << "Waiting for ACK"; ack = is_->get(); if (ack == '+') break; LOG(WARNING) << absl::StrFormat("Response not acknowledged ('%c' received)", @@ -257,15 +281,40 @@ Respond(absl::StrCat("E.", error)); } +int GdbServer::GetThreadId(char command_type, std::string_view command) { + if (!thread_suffix_ || command.empty()) { + auto iter = thread_select_.find(command_type); + if (iter == thread_select_.end()) { + // If it hasn't been set, default to thread id 1. + return 1; + } + return iter->second; + } else { + std::string thread_id_str; + if (RE2::FullMatch(command, *thread_re_, &thread_id_str)) { + int thread_id; + bool success = absl::SimpleAtoi(thread_id_str, &thread_id); + if (!success) { + LOG(ERROR) << absl::StrFormat("Invalid thread id: '%s'", thread_id_str); + return 1; + } + return thread_id; + } + LOG(ERROR) << absl::StrFormat("Invalid thread id: '%s'", command); + return 1; + } +} + void GdbServer::AcceptGdbCommand(std::string_view command) { - LOG(INFO) << absl::StrFormat("Received: '%s'", command); + if (log_packets_) { + 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)) { + std::string command_str; + std::string checksum_str; + if (!RE2::FullMatch(command, *gdb_command_re_, &command_str, &checksum_str)) { LOG(ERROR) << "Invalid GDB command syntax"; if (!no_ack_mode_) { - LOG(INFO) << "Sending NACK"; os_->put('-'); os_->flush(); } else { @@ -274,12 +323,8 @@ 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) { + for (char c : command_str) { checksum += c; } int orig_checksum; @@ -289,7 +334,6 @@ checksum_str); // Request a retransmission. if (!no_ack_mode_) { - LOG(INFO) << "Sending NACK"; os_->put('-'); os_->flush(); } else { @@ -303,7 +347,6 @@ // Request a retransmission. if (!no_ack_mode_) { - LOG(INFO) << "Sending NACK"; os_->put('-'); os_->flush(); } else { @@ -313,12 +356,11 @@ } // Acknowledge the command. if (!no_ack_mode_) { - LOG(INFO) << "Sending ACK"; os_->put('+'); os_->flush(); } - std::string clean_command = UnescapeCommand(command); - ParseGdbCommand(command); + std::string clean_command = UnescapeCommand(command_str); + ParseGdbCommand(clean_command); } void GdbServer::ParseGdbCommand(std::string_view command) { @@ -329,13 +371,30 @@ return Respond(""); case '?': // Inquire about the halt reason. return GdbHaltReason(); - case 'c': // Continue packet. + case 'c': { // Continue packet. command.remove_prefix(1); - return GdbContinue(command); + size_t pos = command.find(";thread:"); + if (pos == std::string_view::npos) { + return GdbContinue(GetThreadId('c', ""), command); + } + std::string_view thread_str = command.substr(pos); + return GdbContinue(GetThreadId('c', thread_str), command.substr(0, pos)); + } case 'D': // Detach packet. return GdbDetach(); - case 'g': // Read registers. - return GdbReadGprRegisters(); + case 'g': // Read gp registers. + command.remove_prefix(1); + return GdbReadGprRegisters(GetThreadId('g', command)); + case 'G': { // Write gp registers. + command.remove_prefix(1); + size_t pos = command.find(";thread:"); + if (pos == std::string_view::npos) { + return GdbWriteGprRegisters(GetThreadId('g', ""), command); + } + std::string_view thread_str = command.substr(pos); + return GdbWriteGprRegisters(GetThreadId('g', thread_str), + command.substr(0, pos)); + } case 'H': // Select thread command.remove_prefix(1); return GdbSelectThread(command); @@ -376,17 +435,30 @@ } case 'p': { // Read register. command.remove_prefix(1); - return GdbReadRegister(command); + size_t pos = command.find(";thread:"); + absl::string_view thread_str = ""; + if (pos != std::string_view::npos) { + thread_str = command.substr(pos); + command.remove_suffix(command.size() - pos); + } + return GdbReadRegister(GetThreadId('g', thread_str), command); } case 'P': { // Write register. command.remove_prefix(1); - size_t pos = command.find('='); + size_t pos = command.find(";thread:"); + absl::string_view thread_str = ""; + if (pos != std::string_view::npos) { + thread_str = command.substr(pos); + command.remove_suffix(command.size() - pos); + } + pos = command.find('='); if (pos == std::string_view::npos) { return SendError("invalid register write format"); } std::string_view register_name = command.substr(0, pos); std::string_view value = command.substr(pos + 1); - return GdbWriteRegister(register_name, value); + return GdbWriteRegister(GetThreadId('g', thread_str), register_name, + value); } case 'q': { // Query. command.remove_prefix(1); @@ -398,7 +470,7 @@ } case 's': // Step. command.remove_prefix(1); - return GdbStep(command); + return GdbStep(GetThreadId('c', command)); case 'v': // Verbose commands. if (absl::StartsWith(command, "vCont")) { command.remove_prefix(5); @@ -412,31 +484,28 @@ case 'z': case 'Z': { // Add or remove breakpoint or watchpoint. // First make sure it's not a conditional breakpoint/watchpoint. - if (command.find(';')) { - Respond(""); - return; + std::string_view address_str; + std::string_view kind_str; + if (RE2::FullMatch(command, *swbreak_set_re_, &address_str, &kind_str)) { + return GdbAddBreakpoint('0', address_str, kind_str); } - command.remove_prefix(1); - char type = command.front(); - size_t address_pos = command.find_first_of(','); - if (address_pos == std::string_view::npos) { - return SendError("invalid remove breakpoint format"); + if (RE2::FullMatch(command, *swbreak_clear_re_, &address_str, + &kind_str)) { + return GdbRemoveBreakpoint('0', address_str, kind_str); } - size_t kind_pos = command.find_last_of(','); - if (kind_pos == std::string_view::npos) { - return SendError("invalid remove breakpoint format"); - } - 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') { - return GdbRemoveBreakpoint(type, address, kind); - } - return GdbAddBreakpoint(type, address, kind); + return Respond(""); } } } +std::string GdbServer::HexEncodeString(std::string_view str) { + std::string encoded_str; + for (char c : str) { + absl::StrAppend(&encoded_str, absl::Hex(c, absl::kZeroPad2)); + } + return encoded_str; +} + std::string GdbServer::HexEncodeNumberInTargetEndianness(uint64_t number) { std::string encoded_number; // Encode the number into a hex string in little endian format. @@ -468,72 +537,76 @@ halt_reasons_[0] = result.value(); switch (result.value()) { default: - return "T05"; + return "T05thread:1"; case *HaltReason::kSoftwareBreakpoint: case *HaltReason::kHardwareBreakpoint: case *HaltReason::kDataWatchPoint: case *HaltReason::kActionPoint: - return "T02"; + return "T02thread:1"; case *HaltReason::kSimulatorError: - return "T06"; + return "T06thread:1"; case *HaltReason::kUserRequest: - return "T03"; + return "T03thread:1"; case *HaltReason::kProgramDone: - return "W00"; + return "W00thread:1"; } } void GdbServer::GdbHaltReason() { Respond(GetHaltReason(0)); } -void GdbServer::GdbContinue(std::string_view command) { +void GdbServer::GdbContinue(int thread_id, std::string_view command) { + int thread_index = thread_id - 1; // If the core is halted due to program done, then just respond with W00. - if (halt_reasons_[0] == *HaltReason::kProgramDone) { + if (halt_reasons_[thread_index] == *HaltReason::kProgramDone) { Respond("W00"); return; } - auto result = core_debug_interfaces_[0]->GetRunStatus(); - if (!result.ok()) { - return SendError(result.status().message()); + auto run_status_res = core_debug_interfaces_[thread_index]->GetRunStatus(); + if (!run_status_res.ok()) { + return SendError(run_status_res.status().message()); } - if (result.value() == generic::CoreDebugInterface::RunStatus::kHalted) { + if (run_status_res.value() == + generic::CoreDebugInterface::RunStatus::kHalted) { if (!command.empty()) { uint64_t address; bool success = absl::SimpleHexAtoi(command, &address); if (!success) { return SendError("invalid address"); } - auto status = core_debug_interfaces_[0]->WriteRegister("pc", address); + auto status = + core_debug_interfaces_[thread_index]->WriteRegister("pc", address); if (!status.ok()) { return SendError(status.message()); } } - result = core_debug_interfaces_[0]->Run(); - if (!result.ok()) { - return SendError(result.status().message()); + auto status = core_debug_interfaces_[thread_index]->Run(); + if (!status.ok()) { + return SendError(status.message()); } // Now wait for the core to halt. - result = core_debug_interfaces_[0]->Wait(); - if (!result.ok()) { - return SendError(result.status().message()); + status = core_debug_interfaces_[thread_index]->Wait(); + if (!status.ok()) { + return SendError(status.message()); } // Get the halt reason. - Respond(GetHaltReason(0)); + Respond(GetHaltReason(thread_index)); } } void GdbServer::ContinueThread(int thread_id) { + int thread_index = thread_id - 1; // If the program is done, do nothing. - if (halt_reasons_[thread_id] == *HaltReason::kProgramDone) { + if (halt_reasons_[thread_index] == *HaltReason::kProgramDone) { return; } - auto result = core_debug_interfaces_[0]->GetRunStatus(); + auto result = core_debug_interfaces_[thread_index]->GetRunStatus(); if (!result.ok()) { LOG(ERROR) << "Failed to get run status for thread " << thread_id << ": " << result.status().message(); return; } if (result.value() == generic::CoreDebugInterface::RunStatus::kHalted) { - auto status = core_debug_interfaces_[0]->Run(); + auto status = core_debug_interfaces_[thread_index]->Run(); if (!status.ok()) { LOG(ERROR) << "Continue on thread " << thread_id << " failed: " << result.status().message(); @@ -543,18 +616,19 @@ } void GdbServer::StepThread(int thread_id) { + int thread_index = thread_id - 1; // If the program is done, do nothing. - if (halt_reasons_[thread_id] == *HaltReason::kProgramDone) { + if (halt_reasons_[thread_index] == *HaltReason::kProgramDone) { return; } - auto result = core_debug_interfaces_[0]->GetRunStatus(); + auto result = core_debug_interfaces_[thread_index]->GetRunStatus(); if (!result.ok()) { LOG(ERROR) << "Failed to get run status for thread " << thread_id << ": " << result.status().message(); return; } if (result.value() == generic::CoreDebugInterface::RunStatus::kHalted) { - auto result = core_debug_interfaces_[0]->Step(1); + auto result = core_debug_interfaces_[thread_index]->Step(1); if (!result.ok()) { LOG(ERROR) << "Step on thread " << thread_id << " failed: " << result.status().message(); @@ -580,11 +654,11 @@ // If the tread id is -1, then apply the action to all threads. if (tid_str == "-1") { // all threads if (action == "c") { // continue - for (int tid = 0; tid < core_debug_interfaces_.size(); ++tid) { + for (int tid = 1; tid <= core_debug_interfaces_.size(); ++tid) { ContinueThread(tid); } } else if (action == "s") { // step - for (int tid = 0; tid < core_debug_interfaces_.size(); ++tid) { + for (int tid = 1; tid <= core_debug_interfaces_.size(); ++tid) { StepThread(tid); } } @@ -593,7 +667,7 @@ // Otherwise, apply the action to the specified thread. int tid; bool success = absl::SimpleHexAtoi(tid_str, &tid); - if (!success || (tid >= core_debug_interfaces_.size())) { + if (!success || (tid > core_debug_interfaces_.size())) { SendError("invalid thread id"); return; } @@ -604,21 +678,21 @@ } } // Wait for all threads that haven't already finished to halt. - for (int tid = 0; tid < core_debug_interfaces_.size(); ++tid) { - if (halt_reasons_[tid] == *HaltReason::kProgramDone) { + for (int tid = 1; tid <= core_debug_interfaces_.size(); ++tid) { + if (halt_reasons_[tid - 1] == *HaltReason::kProgramDone) { continue; } - (void)core_debug_interfaces_[tid]->Wait(); - auto result = core_debug_interfaces_[tid]->GetLastHaltReason(); + (void)core_debug_interfaces_[tid - 1]->Wait(); + auto result = core_debug_interfaces_[tid - 1]->GetLastHaltReason(); if (!result.ok()) continue; - halt_reasons_[tid] = result.value(); + halt_reasons_[tid - 1] = result.value(); } // If the program is done we respond with W00, otherwise T05 and add the // halt reason for each thread. We only respond with W00 if all threads are // done. bool all_done = true; - for (int tid = 0; tid < core_debug_interfaces_.size(); ++tid) { - all_done &= (halt_reasons_[tid] == *HaltReason::kProgramDone); + for (int tid = 1; tid <= core_debug_interfaces_.size(); ++tid) { + all_done &= (halt_reasons_[tid - 1] == *HaltReason::kProgramDone); } if (all_done) { return Respond("W00"); @@ -628,20 +702,20 @@ // 0. auto result = core_debug_interfaces_[0]->GetLastHaltReason(); if (!result.ok()) { - return Respond("T05"); + return Respond("T05thread:1"); } halt_reasons_[0] = result.value(); uint64_t address = 0; generic::AccessType access_type = generic::AccessType::kNone; switch (halt_reasons_[0]) { default: - return Respond("T05"); + return Respond("T05thread:1"); case *HaltReason::kSoftwareBreakpoint: address = core_debug_interfaces_[0]->GetSwBreakpointInfo(); - return Respond(absl::StrCat("T05thread:0;swbreak:", + return Respond(absl::StrCat("T05thread:1;swbreak:", HexEncodeNumberInTargetEndianness(address))); case *HaltReason::kHardwareBreakpoint: - return Respond("T05thread:0;hwbreak:"); + return Respond("T05thread:1;hwbreak:"); case *HaltReason::kDataWatchPoint: { core_debug_interfaces_[0]->GetWatchpointInfo(address, access_type); std::string encoded_address = HexEncodeNumberInTargetEndianness(address); @@ -649,22 +723,22 @@ // read/write (awatch) watch points. switch (access_type) { case generic::AccessType::kLoad: - return Respond(absl::StrCat("T05thread:0;rwatch:", encoded_address)); + return Respond(absl::StrCat("T05thread:1;rwatch:", encoded_address)); case generic::AccessType::kStore: - return Respond(absl::StrCat("T05thread:0;watch:", encoded_address)); + return Respond(absl::StrCat("T05thread:1;watch:", encoded_address)); case generic::AccessType::kLoadStore: - return Respond(absl::StrCat("T05thread:0;awatch:", encoded_address)); + return Respond(absl::StrCat("T05thread:1;awatch:", encoded_address)); default: LOG(ERROR) << "Invalid access type: " << static_cast<int>(access_type); - return Respond("T05thread:0;"); + return Respond("T05thread:1;"); } } case *HaltReason::kActionPoint: case *HaltReason::kSimulatorError: - return Respond("T06thread:0;"); + return Respond("T06thread:1;"); case *HaltReason::kUserRequest: - return Respond("T03"); + return Respond("T03thread:1"); case *HaltReason::kProgramDone: return Respond("W00"); } @@ -692,9 +766,9 @@ } void GdbServer::GdbThreadInfo() { - std::string response = "m0"; + std::string response = "m1"; - for (int i = 1; i < core_debug_interfaces_.size(); ++i) { + for (int i = 2; i <= core_debug_interfaces_.size(); ++i) { absl::StrAppend(&response, ",", absl::Hex(i)); } Respond(response); @@ -796,7 +870,8 @@ Respond("OK"); } -void GdbServer::GdbReadGprRegisters() { +void GdbServer::GdbReadGprRegisters(int thread_id) { + int thread_index = thread_id - 1; std::string response; for (int i = debug_info_.GetFirstGpr(); i <= debug_info_.GetLastGpr(); ++i) { auto it = debug_info_.debug_register_map().find(i); @@ -804,12 +879,11 @@ // gap in the register numbers). if (it == debug_info_.debug_register_map().end()) continue; const std::string& register_name = it->second; - auto result = core_debug_interfaces_[0]->ReadRegister(register_name); + auto result = + core_debug_interfaces_[thread_index]->ReadRegister(register_name); if (!result.ok()) { return SendError(result.status().message()); } - 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)); @@ -819,7 +893,8 @@ Respond(response); } -void GdbServer::GdbWriteGprRegisters(std::string_view data) { +void GdbServer::GdbWriteGprRegisters(int thread_id, std::string_view data) { + int thread_index = thread_id - 1; int num_bytes = 0; for (int i = debug_info_.GetFirstGpr(); i <= debug_info_.GetLastGpr(); ++i) { auto it = debug_info_.debug_register_map().find(i); @@ -837,8 +912,8 @@ value |= byte_value << (j * 8); num_bytes++; } - auto status = - core_debug_interfaces_[0]->WriteRegister(register_name, value); + auto status = core_debug_interfaces_[thread_index]->WriteRegister( + register_name, value); if (!status.ok()) { return SendError(status.message()); } @@ -846,42 +921,48 @@ Respond("OK"); } -void GdbServer::GdbReadRegister(std::string_view register_number_str) { +void GdbServer::GdbReadRegister(int thread_id, + std::string_view register_number_str) { + int thread_index = thread_id - 1; uint64_t register_number; bool success = absl::SimpleHexAtoi(register_number_str, ®ister_number); if (!success) { - return SendError("invalid register number"); + return SendError( + absl::StrCat("invalid register number: ", register_number_str)); } auto it = debug_info_.debug_register_map().find(register_number); if (it == debug_info_.debug_register_map().end()) { - return SendError("invalid register number"); + return SendError(absl::StrCat("Register not found: ", register_number)); } const std::string& register_name = it->second; - auto result = core_debug_interfaces_[0]->ReadRegister(register_name); + auto result = + core_debug_interfaces_[thread_index]->ReadRegister(register_name); if (!result.ok()) { return SendError(result.status().message()); } std::string response; uint64_t value = result.value(); - while (value > 0) { + for (int i = 0; i < debug_info_.GetRegisterByteWidth(register_number); i++) { absl::StrAppend(&response, absl::Hex(value & 0xff, absl::kZeroPad2)); value >>= 8; } Respond(response); } -void GdbServer::GdbWriteRegister(std::string_view register_number_str, +void GdbServer::GdbWriteRegister(int thread_id, + std::string_view register_number_str, std::string_view register_value_str) { + int thread_index = thread_id - 1; uint64_t register_number; bool success = absl::SimpleHexAtoi(register_number_str, ®ister_number); if (!success) { - SendError("invalid register number"); + return SendError( + absl::StrCat("invalid register number: ", register_number_str)); return; } auto it = debug_info_.debug_register_map().find(register_number); if (it == debug_info_.debug_register_map().end()) { - SendError("invalid register number"); - return; + return SendError(absl::StrCat("Register not found: ", register_number)); } const std::string& register_name = it->second; uint64_t value = 0; @@ -895,7 +976,8 @@ value |= byte_value << (count * 8); count++; } - auto status = core_debug_interfaces_[0]->WriteRegister(register_name, value); + auto status = + core_debug_interfaces_[thread_index]->WriteRegister(register_name, value); if (!status.ok()) { return SendError(status.message()); } @@ -949,23 +1031,42 @@ command.remove_prefix(9); return GdbSupported(command); } + break; + } + case 'X': { + std::string offset_str, length_str; + if (RE2::FullMatch(command, *xfer_read_target_re_, &offset_str, + &length_str)) { + return GdbXferReadTarget(offset_str, length_str); + } + break; } } Respond(""); // Not supported for now. } void GdbServer::GdbSet(std::string_view command) { - LOG(INFO) << "GdbSet: " << command; if (command == "StartNoAckMode") { no_ack_mode_latch_ = true; return Respond("OK"); } + if (command == "ThreadSuffixSupported") { + thread_suffix_ = true; + return Respond("OK"); + } + if (command == "EnableErrorStrings") { + return Respond("OK"); + } + if (command == "ListThreadsInStopReply") { + return Respond("OK"); + } Respond(""); // TODO(torerik): Implement. } -void GdbServer::GdbStep(std::string_view command) { - auto result = core_debug_interfaces_[0]->Step(1); +void GdbServer::GdbStep(int thread_id) { + int thread_index = thread_id - 1; + auto result = core_debug_interfaces_[thread_index]->Step(1); if (!result.ok()) { SendError(result.status().message()); return; @@ -1118,10 +1219,11 @@ } std::string response; absl::StrAppend( - &response, "PacketSize=8192;multi-wp-addr-", + &response, "PacketSize=2000;multi-wp-addr-", ";multiprocess-;hwbreak-;qRelocInsn-;fork-events-;exec-events-" - ";vContSupported-;QThreadEvents-;QThreadOptions-;no-resumed-" - ";memory-tagging-;vfork-events-;QStartNoAckMode+;swbreak+;watch+"); + ";vContSupported+;QThreadEvents-;QThreadOptions-;no-resumed-" + ";memory-tagging-;vfork-events-;QStartNoAckMode+;swbreak+;watch+" + ";qXfer:features:read+"); Respond(response); } @@ -1130,6 +1232,57 @@ Respond(file_name); } -void GdbServer::GdbHostInfo() { Respond(debug_info_.GetLLDBHostInfo()); } +void GdbServer::GdbHostInfo() { + // The host info has to be recoded, as some fields have to be hex encoded, + // and not left as plan strings. + std::string host_info = std::string(debug_info_.GetLLDBHostInfo()); + std::vector<std::string> parts = absl::StrSplit(host_info, ';'); + std::string response; + for (const std::string& part : parts) { + if (part.empty()) continue; + std::vector<std::string> key_value = absl::StrSplit(part, ':'); + std::string_view key = key_value[0]; + std::string_view value = key_value[1]; + if (key == "triple") { + absl::StrAppend(&response, key, ":", HexEncodeString(value), ";"); + } else if (key == "distribution_id") { + absl::StrAppend(&response, key, ":", HexEncodeString(value), ";"); + } else if (key == "os_build") { + absl::StrAppend(&response, key, ":", HexEncodeString(value), ";"); + } else if (key == "hostname") { + absl::StrAppend(&response, key, ":", HexEncodeString(value), ";"); + } else if (key == "os_kernel") { + absl::StrAppend(&response, key, ":", HexEncodeString(value), ";"); + } else { + absl::StrAppend(&response, key, ":", value, ";"); + } + } + Respond(response); +} + +void GdbServer::GdbXferReadTarget(std::string_view offset_str, + std::string_view length_str) { + uint64_t offset; + bool success = absl::SimpleHexAtoi(offset_str, &offset); + if (!success) { + return SendError("invalid offset"); + } + uint64_t length; + success = absl::SimpleHexAtoi(length_str, &length); + if (!success) { + return SendError("invalid length"); + } + if (offset >= debug_info_.GetGdbTargetXml().size()) { + return Respond("l"); + } + std::string packet_type = "m"; + if (offset + length > debug_info_.GetGdbTargetXml().size()) { + length = debug_info_.GetGdbTargetXml().size() - offset; + packet_type = "l"; + } + std::string_view response = + debug_info_.GetGdbTargetXml().substr(offset, length); + Respond(absl::StrCat(packet_type, EscapeString(response))); +} } // namespace mpact::sim::util::gdbserver
diff --git a/mpact/sim/util/gdbserver/gdbserver.h b/mpact/sim/util/gdbserver/gdbserver.h index aea988d..8561c63 100644 --- a/mpact/sim/util/gdbserver/gdbserver.h +++ b/mpact/sim/util/gdbserver/gdbserver.h
@@ -31,6 +31,7 @@ #include "mpact/sim/generic/core_debug_interface.h" #include "mpact/sim/generic/debug_info.h" #include "mpact/sim/util/renode/socket_streambuf.h" +#include "re2/re2.h" namespace mpact::sim::util::gdbserver { @@ -67,12 +68,14 @@ // 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(uint64_t number); // GDB command handlers. @@ -82,7 +85,7 @@ std::string GetHaltReason(int thread_id); void GdbHaltReason(); // Continue the simulation. - void GdbContinue(std::string_view command); + void GdbContinue(int thread_id, std::string_view command); void ContinueThread(int thread_id); void GdbVContinue(std::string_view command); // Detach from the simulator. @@ -97,20 +100,20 @@ void GdbWriteMemory(std::string_view address, std::string_view length, std::string_view data); // Read GPR registers from the simulator. - void GdbReadGprRegisters(); + void GdbReadGprRegisters(int thread_id); // Write GPR registers to the simulator. - void GdbWriteGprRegisters(std::string_view data); + void GdbWriteGprRegisters(int thread_id, std::string_view data); // Read a register from the simulator. - void GdbReadRegister(std::string_view register_number_str); + void GdbReadRegister(int thread_id, std::string_view register_number_str); // Write a register to the simulator. - void GdbWriteRegister(std::string_view register_number_str, + 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(std::string_view command); + 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, @@ -124,24 +127,33 @@ void GdbExecAndArgs(std::string_view command); // Get the host info. void GdbHostInfo(); + void GdbXferReadTarget(std::string_view offset_str, + std::string_view length_str); SocketStreambuf* out_buf_ = nullptr; SocketStreambuf* 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_ = 0; + 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_; + LazyRE2 gdb_command_re_; + LazyRE2 thread_re_; + LazyRE2 xfer_read_target_re_; + LazyRE2 swbreak_set_re_; + LazyRE2 swbreak_clear_re_; }; } // namespace mpact::sim::util::gdbserver