Updated gdbserver to handle more package types. PiperOrigin-RevId: 886374647 Change-Id: Idf1b377e74a832ac86d8f9bb7fb13f36132cc947
diff --git a/mpact/sim/generic/breakpoint_manager.cc b/mpact/sim/generic/breakpoint_manager.cc index 7967d47..d942e98 100644 --- a/mpact/sim/generic/breakpoint_manager.cc +++ b/mpact/sim/generic/breakpoint_manager.cc
@@ -120,7 +120,8 @@ return action_point_manager_->IsActionEnabled(address, iter->second->id); } -void BreakpointManager::DoBreakpointAction(uint64_t, int) { +void BreakpointManager::DoBreakpointAction(uint64_t address, int) { + last_breakpoint_address_ = address; req_halt_function_(); }
diff --git a/mpact/sim/generic/breakpoint_manager.h b/mpact/sim/generic/breakpoint_manager.h index f0dca00..d6d3784 100644 --- a/mpact/sim/generic/breakpoint_manager.h +++ b/mpact/sim/generic/breakpoint_manager.h
@@ -58,6 +58,8 @@ ActionPointManagerBase* action_point_manager() const { return action_point_manager_; } + // Return the address of the most recently triggered breakpoint. + uint64_t last_breakpoint_address() const { return last_breakpoint_address_; } private: // Structure keeping track of breakpoint information. @@ -71,6 +73,7 @@ RequestHaltFunction req_halt_function_; void DoBreakpointAction(uint64_t, int); + uint64_t last_breakpoint_address_ = 0xffff'ffff'ffff'ffff; ActionPointManagerBase* action_point_manager_; absl::btree_map<uint64_t, BreakpointInfo*> breakpoint_map_; };
diff --git a/mpact/sim/generic/core_debug_interface.h b/mpact/sim/generic/core_debug_interface.h index 0071b6a..e6e544b 100644 --- a/mpact/sim/generic/core_debug_interface.h +++ b/mpact/sim/generic/core_debug_interface.h
@@ -30,6 +30,7 @@ namespace generic { enum class AccessType { + kNone = 0, kLoad = 1, kStore = 2, kLoadStore = 3, @@ -119,6 +120,10 @@ // Set/Clear software breakpoints at the given addresses. virtual absl::Status SetSwBreakpoint(uint64_t address) = 0; virtual absl::Status ClearSwBreakpoint(uint64_t address) = 0; + // Return the address of the most recently triggered software breakpoint. + // This method is used by the GdbServer to determine the address of the + // breakpoint that triggered the halt. + virtual uint64_t GetSwBreakpointInfo() const { return 0xffff'ffff'ffff'ffff; } // Remove all software breakpoints. virtual absl::Status ClearAllSwBreakpoints() = 0; @@ -134,6 +139,12 @@ AccessType access_type) { return absl::UnimplementedError("Not implemented"); } + // Set the address and the configured access type for the most recently + // triggered watchpoint. If there has been no watchpoint triggered, this + // method will not change the given arguments. + // This method is used by the GdbServer to determine the address and access + // type of the watchpoint that triggered the halt. + virtual void GetWatchpointInfo(uint64_t& address, AccessType& access_type) {} // Return the instruction object for the instruction at the given address. virtual absl::StatusOr<Instruction*> GetInstruction(uint64_t address) = 0;
diff --git a/mpact/sim/generic/debug_info.h b/mpact/sim/generic/debug_info.h index 9b7d607..d1406a1 100644 --- a/mpact/sim/generic/debug_info.h +++ b/mpact/sim/generic/debug_info.h
@@ -19,6 +19,7 @@ #include <cstdint> #include <string> +#include <string_view> #include "absl/container/flat_hash_map.h" @@ -35,6 +36,7 @@ virtual int GetFirstGpr() const = 0; virtual int GetLastGpr() const = 0; virtual int GetGprWidth() const = 0; + virtual std::string_view GetLLDBHostInfo() const = 0; }; } // namespace mpact::sim::generic
diff --git a/mpact/sim/util/gdbserver/BUILD b/mpact/sim/util/gdbserver/BUILD index fdfbe85..1123141 100644 --- a/mpact/sim/util/gdbserver/BUILD +++ b/mpact/sim/util/gdbserver/BUILD
@@ -31,6 +31,7 @@ "//mpact/sim/util/renode:socket_cli", "@com_google_absl//absl/container:flat_hash_map", "@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",
diff --git a/mpact/sim/util/gdbserver/gdbserver.cpp b/mpact/sim/util/gdbserver/gdbserver.cpp index c5b5d5a..84bc6f9 100644 --- a/mpact/sim/util/gdbserver/gdbserver.cpp +++ b/mpact/sim/util/gdbserver/gdbserver.cpp
@@ -30,6 +30,7 @@ #include "third_party/absl/container/flat_hash_map.h" #include "third_party/absl/log/log.h" +#include "third_party/absl/status/status.h" #include "third_party/absl/strings/numbers.h" #include "third_party/absl/strings/str_cat.h" #include "third_party/absl/strings/str_format.h" @@ -41,6 +42,9 @@ namespace { +constexpr std::string_view kGDBServerVersion = + "name:mpact-sim-gdbserver;version:1.0"; + // 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) { @@ -61,11 +65,22 @@ namespace mpact::sim::util::gdbserver { using ::mpact::sim::generic::operator*; // NOLINT +using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason; 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) { + if (core_debug_interfaces.size() > 1) { + LOG(WARNING) << "MPACT GdbServer only supports one core for now - " + "ignoring extra cores."; + } + // Remove the extra cores from the core_debug_interfaces_. + core_debug_interfaces_.remove_suffix(core_debug_interfaces.size() - 1); + for (int i = 0; i < core_debug_interfaces_.size(); ++i) { + halt_reasons_.push_back(*generic::CoreDebugInterface::HaltReason::kNone); + } +} GdbServer::~GdbServer() { Terminate(); @@ -223,15 +238,18 @@ 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)", ack); } - if (ack != '+') { + if (!no_ack_mode_ && (ack != '+')) { LOG(ERROR) << "Failed to send response after 5 attempts"; Terminate(); } + no_ack_mode_ = no_ack_mode_latch_; } void GdbServer::SendError(std::string_view error) { @@ -245,8 +263,13 @@ if ((command.front() != '$') || (pos == std::string_view::npos) || (pos != command.size() - 3)) { LOG(ERROR) << "Invalid GDB command syntax"; - os_->put('-'); - os_->flush(); + if (!no_ack_mode_) { + LOG(INFO) << "Sending NACK"; + os_->put('-'); + os_->flush(); + } else { + SendError("invalid syntax"); + } return; } // Verify the checksum. @@ -264,8 +287,13 @@ LOG(ERROR) << absl::StrFormat("Invalid original checksum: '%s'", checksum_str); // Request a retransmission. - os_->put('-'); - os_->flush(); + if (!no_ack_mode_) { + LOG(INFO) << "Sending NACK"; + os_->put('-'); + os_->flush(); + } else { + SendError("invalid checksum"); + } return; } if (checksum != orig_checksum) { @@ -273,13 +301,21 @@ static_cast<int>(checksum), orig_checksum); // Request a retransmission. - os_->put('-'); - os_->flush(); + if (!no_ack_mode_) { + LOG(INFO) << "Sending NACK"; + os_->put('-'); + os_->flush(); + } else { + SendError("invalid checksum"); + } return; } // Acknowledge the command. - os_->put('+'); - os_->flush(); + if (!no_ack_mode_) { + LOG(INFO) << "Sending ACK"; + os_->put('+'); + os_->flush(); + } std::string clean_command = UnescapeCommand(command); ParseGdbCommand(command); } @@ -289,38 +325,29 @@ default: // The command is not handled, so respond with an empty string which will // indicate that the command is not supported. - Respond(""); - break; + return Respond(""); case '?': // Inquire about the halt reason. - GdbHaltReason(); - break; + return GdbHaltReason(); case 'c': // Continue packet. command.remove_prefix(1); - GdbContinue(command); - break; + return GdbContinue(command); case 'D': // Detach packet. - GdbDetach(); - break; + return GdbDetach(); case 'g': // Read registers. - GdbReadGprRegisters(); - break; + return GdbReadGprRegisters(); case 'H': // Select thread command.remove_prefix(1); - GdbSelectThread(command); - break; + return GdbSelectThread(command); case 'k': // Kill packet. - Terminate(); - break; + return Terminate(); 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 SendError("invalid memory read format"); } - return; + return Respond("E01"); } std::string_view address = command.substr(0, pos); std::string_view length = command.substr(pos + 1); @@ -332,166 +359,316 @@ 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 SendError("invalid memory write format"); } - return; + return Respond("E01"); } size_t colon_pos = command.find(':'); if (colon_pos == std::string_view::npos) { - SendError("invalid memory write format"); - return; + return SendError("invalid memory write format"); } 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; + return GdbWriteMemory(address, length, data); } case 'p': { // Read register. command.remove_prefix(1); - GdbReadRegister(command); - break; + return GdbReadRegister(command); } 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; + return SendError("invalid register write format"); } std::string_view register_name = command.substr(0, pos); std::string_view value = command.substr(pos + 1); - GdbWriteRegister(register_name, value); - break; + return GdbWriteRegister(register_name, value); } case 'q': { // Query. command.remove_prefix(1); - GdbQuery(command); - break; + return GdbQuery(command); } case 'Q': { // Set. command.remove_prefix(1); - GdbSet(command); - break; + return GdbSet(command); } case 's': // Step. command.remove_prefix(1); - GdbStep(command); - break; + return GdbStep(command); + case 'v': // Verbose commands. + if (command.starts_with("vCont")) { + command.remove_prefix(5); + return GdbVContinue(command); + } + if (command == "vMustReplyEmpty") { + return Respond(""); + } + return SendError("invalid verbose command"); + case 'z': case 'Z': { // Add or remove breakpoint or watchpoint. - // First make sure it's not a conditional breakpoint. + // First make sure it's not a conditional breakpoint/watchpoint. if (command.find(';')) { Respond(""); return; } - char type = command.front(); command.remove_prefix(1); + char type = command.front(); size_t address_pos = command.find_first_of(','); if (address_pos == std::string_view::npos) { - SendError("invalid remove breakpoint format"); - return; + return SendError("invalid remove breakpoint format"); } size_t kind_pos = command.find_last_of(','); if (kind_pos == std::string_view::npos) { - SendError("invalid remove breakpoint format"); - return; + 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') { - GdbRemoveBreakpoint(type, address, kind); - } else { - GdbAddBreakpoint(type, address, kind); + return GdbRemoveBreakpoint(type, address, kind); } - break; + return GdbAddBreakpoint(type, address, kind); } } } +std::string GdbServer::HexEncodeNumberInTargetEndianness(uint64_t number) { + std::string encoded_number; + // Encode the number into a hex string in little endian format. + do { + absl::StrAppend(&encoded_number, absl::Hex(number & 0xff, absl::kZeroPad2)); + number >>= 8; + } while (number > 0); + return encoded_number; +} + void GdbServer::GdbHalt() { auto status = core_debug_interfaces_[0]->Halt( generic::CoreDebugInterface::HaltReason::kUserRequest); if (!status.ok()) { - SendError(status.message()); - return; + return SendError(status.message()); } status = core_debug_interfaces_[0]->Wait(); if (!status.ok()) { - SendError(status.message()); - return; + return SendError(status.message()); } Respond(absl::StrCat("T03")); } -void GdbServer::GdbHaltReason() { - auto result = core_debug_interfaces_[0]->GetRunStatus(); +std::string GdbServer::GetHaltReason(int thread_id) { + auto result = core_debug_interfaces_[thread_id]->GetLastHaltReason(); if (!result.ok()) { - SendError(result.status().message()); - return; + return "E01"; } - 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; - } + halt_reasons_[0] = result.value(); + switch (result.value()) { + default: + return "T05"; + case *HaltReason::kSoftwareBreakpoint: + case *HaltReason::kHardwareBreakpoint: + case *HaltReason::kDataWatchPoint: + case *HaltReason::kActionPoint: + return "T02"; + case *HaltReason::kSimulatorError: + return "T06"; + case *HaltReason::kUserRequest: + return "T03"; + case *HaltReason::kProgramDone: + return "W00"; } - Respond("E01"); } +void GdbServer::GdbHaltReason() { Respond(GetHaltReason(0)); } + void GdbServer::GdbContinue(std::string_view command) { + // If the core is halted due to program done, then just respond with W00. + if (halt_reasons_[0] == *HaltReason::kProgramDone) { + Respond("W00"); + return; + } auto result = core_debug_interfaces_[0]->GetRunStatus(); if (!result.ok()) { - SendError(result.status().message()); - return; + return SendError(result.status().message()); } 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; + return SendError("invalid address"); } auto status = core_debug_interfaces_[0]->WriteRegister("pc", address); if (!status.ok()) { - SendError(status.message()); - return; + return SendError(status.message()); } } result = core_debug_interfaces_[0]->Run(); if (!result.ok()) { - SendError(result.status().message()); + return SendError(result.status().message()); + } + // Now wait for the core to halt. + result = core_debug_interfaces_[0]->Wait(); + if (!result.ok()) { + return SendError(result.status().message()); + } + // Get the halt reason. + Respond(GetHaltReason(0)); + } +} + +void GdbServer::ContinueThread(int thread_id) { + // If the program is done, do nothing. + if (halt_reasons_[thread_id] == *HaltReason::kProgramDone) { + return; + } + auto result = core_debug_interfaces_[0]->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(); + if (!status.ok()) { + LOG(ERROR) << "Continue on thread " << thread_id + << " failed: " << result.status().message(); return; } } } +void GdbServer::StepThread(int thread_id) { + // If the program is done, do nothing. + if (halt_reasons_[thread_id] == *HaltReason::kProgramDone) { + return; + } + auto result = core_debug_interfaces_[0]->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); + if (!result.ok()) { + LOG(ERROR) << "Step on thread " << thread_id + << " failed: " << result.status().message(); + return; + } + } +} + +void GdbServer::GdbVContinue(std::string_view command) { + if (command == "?") { + return Respond("vCont;c;s;"); + } + if (command.front() != ';') { + return SendError("invalid vCont command"); + } + command.remove_prefix(1); + std::vector<std::string> parts = absl::StrSplit(command, ';'); + for (std::string& part : parts) { + std::string_view part_view = part; + std::string_view action = part_view.substr(0, 1); + // Get the thread id. + std::string_view tid_str = part_view.substr(part.find(':') + 1); + // 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) { + ContinueThread(tid); + } + } else if (action == "s") { // step + for (int tid = 0; tid < core_debug_interfaces_.size(); ++tid) { + StepThread(tid); + } + } + continue; + } + // Otherwise, apply the action to the specified thread. + int tid; + bool success = absl::SimpleHexAtoi(tid_str, &tid); + if (!success || (tid >= core_debug_interfaces_.size())) { + SendError("invalid thread id"); + return; + } + if (action == "c") { + ContinueThread(tid); + } else if (action == "s") { + StepThread(tid); + } + } + // 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) { + continue; + } + (void)core_debug_interfaces_[tid]->Wait(); + auto result = core_debug_interfaces_[tid]->GetLastHaltReason(); + if (!result.ok()) continue; + halt_reasons_[tid] = 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); + } + if (all_done) { + return Respond("W00"); + } + // TODO(torerik): Add support for multiple threads in the response. For now, + // knowing we only support one core, we just use the halt reason from core + // 0. + auto result = core_debug_interfaces_[0]->GetLastHaltReason(); + if (!result.ok()) { + return Respond("T05"); + } + halt_reasons_[0] = result.value(); + uint64_t address = 0; + generic::AccessType access_type = generic::AccessType::kNone; + switch (halt_reasons_[0]) { + default: + return Respond("T05"); + case *HaltReason::kSoftwareBreakpoint: + address = core_debug_interfaces_[0]->GetSwBreakpointInfo(); + return Respond(absl::StrCat("T05thread:0;swbreak:", + HexEncodeNumberInTargetEndianness(address))); + case *HaltReason::kHardwareBreakpoint: + return Respond("T05thread:0;hwbreak:"); + case *HaltReason::kDataWatchPoint: { + core_debug_interfaces_[0]->GetWatchpointInfo(address, access_type); + std::string encoded_address = HexEncodeNumberInTargetEndianness(address); + // Need to differentiate between write (watch), read (rwatch), and + // read/write (awatch) watch points. + switch (access_type) { + case generic::AccessType::kLoad: + return Respond(absl::StrCat("T05thread:0;rwatch:", encoded_address)); + case generic::AccessType::kStore: + return Respond(absl::StrCat("T05thread:0;watch:", encoded_address)); + case generic::AccessType::kLoadStore: + return Respond(absl::StrCat("T05thread:0;awatch:", encoded_address)); + default: + LOG(ERROR) << "Invalid access type: " + << static_cast<int>(access_type); + return Respond("T05thread:0;"); + } + } + case *HaltReason::kActionPoint: + case *HaltReason::kSimulatorError: + return Respond("T06thread:0;"); + case *HaltReason::kUserRequest: + return Respond("T03"); + case *HaltReason::kProgramDone: + return Respond("W00"); + } +} + void GdbServer::GdbDetach() { Respond("OK"); Terminate(); @@ -506,8 +683,7 @@ } else { bool success = absl::SimpleHexAtoi(command, &thread_id); if (!success) { - SendError("invalid thread id"); - return; + return SendError("invalid thread id"); } } thread_select_[op] = thread_id; @@ -529,38 +705,30 @@ bool success = absl::SimpleHexAtoi(address_str, &address); if (!success) { if (error_message_supported_) { - SendError("invalid memory read format"); - } else { - Respond("E01"); + return SendError("invalid memory read format"); } - return; + return Respond("E01"); } uint64_t length; success = absl::SimpleHexAtoi(length_str, &length); if (!success) { if (error_message_supported_) { - SendError("invalid memory read format"); - } else { - Respond("E01"); + return SendError("invalid memory read format"); } - return; + return Respond("E01"); } if (length > sizeof(buffer_)) { if (error_message_supported_) { - SendError("length exceeds buffer size of 4096"); - } else { - Respond("E01"); + return SendError("length exceeds buffer size of 4096"); } - return; + return Respond("E01"); } 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 SendError(result.status().message()); } - return; + return Respond("E01"); } std::string response; for (int i = 0; i < result.value(); ++i) { @@ -576,29 +744,23 @@ bool success = absl::SimpleHexAtoi(address_str, &address); if (!success) { if (error_message_supported_) { - SendError("invalid memory read format"); - } else { - Respond("E01"); + return SendError("invalid memory read format"); } - return; + return Respond("E01"); } uint64_t length; success = absl::SimpleHexAtoi(length_str, &length); if (!success) { if (error_message_supported_) { - SendError("invalid memory read format"); - } else { - Respond("E01"); + return SendError("invalid memory read format"); } - return; + return Respond("E01"); } if (length > sizeof(buffer_)) { if (error_message_supported_) { - SendError("length exceeds buffer size of 4096"); - } else { - Respond("E01"); + return SendError("length exceeds buffer size of 4096"); } - return; + return Respond("E01"); } int num_bytes = 0; for (int i = 0; (i < length) && (data.size() >= 2); ++i) { @@ -611,29 +773,23 @@ } if (num_bytes != length) { if (error_message_supported_) { - SendError("length does not match data size"); - } else { - Respond("E01"); + return SendError("length does not match data size"); } - return; + return Respond("E01"); } 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 SendError(result.status().message()); } - return; + return Respond("E01"); } if (result.value() != length) { if (error_message_supported_) { - SendError("length does not match number of bytes written"); - } else { - Respond("E01"); + return SendError("length does not match number of bytes written"); } - return; + return Respond("E01"); } Respond("OK"); } @@ -642,15 +798,13 @@ 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; - } + // If the register is not found, go to the next one (assume there is a + // 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); if (!result.ok()) { - SendError(result.status().message()); - return; + return SendError(result.status().message()); } LOG(INFO) << absl::StrFormat("Register %s = %x", register_name, result.value()); @@ -668,8 +822,7 @@ 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; + return SendError("Internal error - failed to find register"); } const std::string& register_name = it->second; uint64_t value = 0; @@ -684,8 +837,7 @@ auto status = core_debug_interfaces_[0]->WriteRegister(register_name, value); if (!status.ok()) { - SendError(status.message()); - return; + return SendError(status.message()); } } Respond("OK"); @@ -695,19 +847,16 @@ uint64_t register_number; bool success = absl::SimpleHexAtoi(register_number_str, ®ister_number); if (!success) { - SendError("invalid register number"); - return; + return SendError("invalid register number"); } 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("invalid register number"); } const std::string& register_name = it->second; auto result = core_debug_interfaces_[0]->ReadRegister(register_name); if (!result.ok()) { - SendError(result.status().message()); - return; + return SendError(result.status().message()); } std::string response; uint64_t value = result.value(); @@ -734,7 +883,7 @@ const std::string& register_name = it->second; uint64_t value = 0; int count = 0; - while (register_value_str.size() > 0) { + while (!register_value_str.empty()) { std::string_view byte = register_value_str.substr(0, 2); register_value_str.remove_prefix(2); uint8_t byte_value; @@ -744,8 +893,7 @@ } auto status = core_debug_interfaces_[0]->WriteRegister(register_name, value); if (!status.ok()) { - SendError(status.message()); - return; + return SendError(status.message()); } Respond("OK"); } @@ -756,33 +904,38 @@ break; case 'A': // Attached? if (command.starts_with("Attached")) { - Respond("0"); - return; + return Respond("0"); } break; case 'C': // Current thread ID. if (command == "C") { - Respond(absl::StrCat("QC", current_thread_id_)); - return; + return Respond(absl::StrCat("QC", current_thread_id_)); } break; case 'E': // ExecAndArgs? if (command.starts_with("ExecAndArgs")) { command.remove_prefix(11); - GdbExecAndArgs(command); - return; + return GdbExecAndArgs(command); } break; case 'f': if (command.starts_with("fThreadInfo")) { - GdbThreadInfo(); - return; + return GdbThreadInfo(); + } + break; + case 'G': + if (command.starts_with("GDBServerVersion")) { + return Respond(kGDBServerVersion); + } + break; + case 'H': + if (command.starts_with("HostInfo")) { + return GdbHostInfo(); } break; case 's': if (command.starts_with("sThreadInfo")) { - Respond("l"); - return; + return Respond("l"); } break; case 'S': { // Search, Supported, or Symbol. @@ -790,8 +943,7 @@ if (command.starts_with("Symbol")) break; if (command.starts_with("Supported")) { command.remove_prefix(9); - GdbSupported(command); - return; + return GdbSupported(command); } } } @@ -799,6 +951,11 @@ } void GdbServer::GdbSet(std::string_view command) { + LOG(INFO) << "GdbSet: " << command; + if (command == "StartNoAckMode") { + no_ack_mode_latch_ = true; + return Respond("OK"); + } Respond(""); // TODO(torerik): Implement. } @@ -823,46 +980,47 @@ size_t kind; switch (type) { default: - SendError("invalid breakpoint type"); - return; + return SendError("invalid breakpoint type"); case '0': { // Software breakpoint. auto status = core_debug_interfaces_[0]->SetSwBreakpoint(address); if (!status.ok()) { - SendError(status.message()); - return; + if (absl::IsUnimplemented(status)) { + return Respond(""); + } + return SendError(status.message()); } - Respond("OK"); - return; + return Respond("OK"); } case '2': { // Data watchpoint write only. bool success = absl::SimpleHexAtoi(kind_str, &kind); if (!success) { - SendError("invalid watchpoint kind"); - return; + return SendError("invalid watchpoint kind"); } auto status = core_debug_interfaces_[0]->SetDataWatchpoint( address, kind, generic::AccessType::kStore); if (!status.ok()) { - SendError(status.message()); - return; + if (absl::IsUnimplemented(status)) { + return Respond(""); + } + return SendError(status.message()); } - Respond("OK"); - return; + return Respond("OK"); } case '3': { // Data watchpoint read-only. bool success = absl::SimpleHexAtoi(kind_str, &kind); if (!success) { - SendError("invalid watchpoint kind"); - return; + return SendError("invalid watchpoint kind"); } auto status = core_debug_interfaces_[0]->SetDataWatchpoint( address, kind, generic::AccessType::kLoad); if (!status.ok()) { + if (absl::IsUnimplemented(status)) { + return Respond(""); + } SendError(status.message()); return; } - Respond("OK"); - return; + return Respond("OK"); } case '4': { // Data watchpoint read or write. bool success = absl::SimpleHexAtoi(kind_str, &kind); @@ -873,6 +1031,9 @@ auto status = core_debug_interfaces_[0]->SetDataWatchpoint( address, kind, generic::AccessType::kLoadStore); if (!status.ok()) { + if (absl::IsUnimplemented(status)) { + return Respond(""); + } SendError(status.message()); return; } @@ -880,7 +1041,6 @@ break; } } - // TODO(torerik): Implement. Respond(""); } @@ -894,49 +1054,51 @@ } switch (type) { default: - SendError("invalid breakpoint type"); - return; + return SendError("invalid breakpoint type"); case '0': { // Software breakpoint. auto status = core_debug_interfaces_[0]->ClearSwBreakpoint(address); if (!status.ok()) { - SendError(status.message()); - return; + if (absl::IsUnimplemented(status)) { + return Respond(""); + } + return SendError(status.message()); } - Respond("OK"); - break; + return Respond("OK"); } case 2: { // Data watchpoint write only. auto status = core_debug_interfaces_[0]->ClearDataWatchpoint( address, generic::AccessType::kStore); if (!status.ok()) { - SendError(status.message()); - return; + if (absl::IsUnimplemented(status)) { + return Respond(""); + } + return SendError(status.message()); } - Respond("OK"); - return; + return Respond("OK"); } case 3: { // Data watchpoint read-only. auto status = core_debug_interfaces_[0]->ClearDataWatchpoint( address, generic::AccessType::kLoad); if (!status.ok()) { - SendError(status.message()); - return; + if (absl::IsUnimplemented(status)) { + return Respond(""); + } + return SendError(status.message()); } - Respond("OK"); - return; + return Respond("OK"); } 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; + if (absl::IsUnimplemented(status)) { + return Respond(""); + } + return SendError(status.message()); } - Respond("OK"); - return; + return Respond("OK"); } } - Respond(""); } void GdbServer::GdbSupported(std::string_view command) { @@ -955,7 +1117,7 @@ &response, "PacketSize=8192;multi-wp-addr-", ";multiprocess-;hwbreak-;qRelocInsn-;fork-events-;exec-events-" ";vContSupported-;QThreadEvents-;QThreadOptions-;no-resumed-" - ";memory-tagging-;vfork-events-"); + ";memory-tagging-;vfork-events-;QStartNoAckMode+;swbreak+;watch+"); Respond(response); } @@ -964,4 +1126,6 @@ Respond(file_name); } +void GdbServer::GdbHostInfo() { Respond(debug_info_.GetLLDBHostInfo()); } + } // namespace mpact::sim::util::gdbserver
diff --git a/mpact/sim/util/gdbserver/gdbserver.h b/mpact/sim/util/gdbserver/gdbserver.h index da3de18..aea988d 100644 --- a/mpact/sim/util/gdbserver/gdbserver.h +++ b/mpact/sim/util/gdbserver/gdbserver.h
@@ -22,7 +22,9 @@ #include <cstdint> #include <istream> #include <ostream> +#include <string> #include <string_view> +#include <vector> #include "absl/container/flat_hash_map.h" #include "absl/types/span.h" @@ -42,6 +44,11 @@ // 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); @@ -66,14 +73,18 @@ // Parses the given GDB command and calls the appropriate command handler. void ParseGdbCommand(std::string_view command); + std::string HexEncodeNumberInTargetEndianness(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(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. @@ -100,6 +111,7 @@ void GdbSet(std::string_view command); // Step the simulator. void GdbStep(std::string_view command); + 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); @@ -110,13 +122,16 @@ 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(); SocketStreambuf* out_buf_ = nullptr; SocketStreambuf* in_buf_ = nullptr; std::ostream* os_ = nullptr; std::istream* is_ = nullptr; - bool connection_active_ = false; + bool no_ack_mode_ = false; + bool no_ack_mode_latch_ = false; bool error_message_supported_ = false; uint8_t buffer_[16 * 1024]; bool good_ = false; @@ -125,6 +140,7 @@ int current_thread_id_ = 0; absl::flat_hash_map<char, int> thread_select_; absl::Span<generic::CoreDebugInterface*> core_debug_interfaces_; + std::vector<int> halt_reasons_; const DebugInfo& debug_info_; };