[GDBStub] Return error for code / unimpl. reg writes, small fixups

This commit is contained in:
emoose 2024-10-08 22:10:57 +01:00
parent e260d0a110
commit c17261abf7
2 changed files with 90 additions and 71 deletions

View File

@ -40,21 +40,9 @@ using xe::kernel::XModule;
using xe::kernel::XObject; using xe::kernel::XObject;
using xe::kernel::XThread; using xe::kernel::XThread;
enum class GdbStubControl : char {
Ack = '+',
Nack = '-',
PacketStart = '$',
PacketEnd = '#',
Interrupt = '\03',
};
constexpr const char* kGdbReplyOK = "OK"; constexpr const char* kGdbReplyOK = "OK";
constexpr const char* kGdbReplyError = "E01"; constexpr const char* kGdbReplyError = "E01";
constexpr int kSignalSigill = 4; // Illegal instruction
constexpr int kSignalSigtrap = 5; // Trace trap
constexpr int kSignalSigsegv = 11; // Segmentation fault
// must start with l for debugger to accept it // must start with l for debugger to accept it
constexpr char kMemoryMapXml[] = constexpr char kMemoryMapXml[] =
R"(l<?xml version="1.0"?> R"(l<?xml version="1.0"?>
@ -70,7 +58,7 @@ constexpr char kMemoryMapXml[] =
</memory-map> </memory-map>
)"; )";
// TODO: add power-altivec.xml (and update ReadRegister to support it) // TODO: add power-altivec.xml (and update RegisterRead to support it)
constexpr char kTargetXml[] = constexpr char kTargetXml[] =
R"(l<?xml version="1.0"?> R"(l<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd"> <!DOCTYPE target SYSTEM "gdb-target.dtd">
@ -229,13 +217,13 @@ void GDBStub::Listen(std::unique_ptr<Socket>& client) {
cache_.cur_thread_id = cache_.notify_thread_id; cache_.cur_thread_id = cache_.notify_thread_id;
} }
int sig_num = kSignalSigtrap; SignalCode signal = SignalCode::SIGTRAP;
if (cache_.notify_exception_code.has_value()) { if (cache_.notify_exception_code.has_value()) {
if (cache_.notify_exception_code == if (cache_.notify_exception_code ==
xe::Exception::Code::kIllegalInstruction) { xe::Exception::Code::kIllegalInstruction) {
sig_num = kSignalSigill; signal = SignalCode::SIGILL;
} else { } else {
sig_num = kSignalSigsegv; signal = SignalCode::SIGSEGV;
} }
cache_.notify_exception_code.reset(); cache_.notify_exception_code.reset();
@ -243,7 +231,7 @@ void GDBStub::Listen(std::unique_ptr<Socket>& client) {
} }
SendPacket(client, SendPacket(client,
GetThreadStateReply(cache_.notify_thread_id, sig_num)); GetThreadStateReply(cache_.notify_thread_id, signal));
cache_.notify_thread_id = -1; cache_.notify_thread_id = -1;
cache_.notify_stopped = false; cache_.notify_stopped = false;
} }
@ -255,8 +243,7 @@ void GDBStub::Listen(std::unique_ptr<Socket>& client) {
void GDBStub::SendPacket(std::unique_ptr<Socket>& client, void GDBStub::SendPacket(std::unique_ptr<Socket>& client,
const std::string& data) { const std::string& data) {
std::stringstream ss; std::stringstream ss;
ss << char(GdbStubControl::PacketStart) << data ss << char(ControlCode::PacketStart) << data << char(ControlCode::PacketEnd);
<< char(GdbStubControl::PacketEnd);
uint8_t checksum = 0; uint8_t checksum = 0;
for (char c : data) { for (char c : data) {
@ -271,7 +258,7 @@ void GDBStub::SendPacket(std::unique_ptr<Socket>& client,
#ifdef DEBUG #ifdef DEBUG
std::string GetPacketFriendlyName(const std::string& packetCommand) { std::string GetPacketFriendlyName(const std::string& packetCommand) {
static const std::unordered_map<std::string, std::string> command_names = { static const std::unordered_map<std::string, std::string> kGdbCommandNames = {
{"?", "StartupQuery"}, {"?", "StartupQuery"},
{"!", "EnableExtendedMode"}, {"!", "EnableExtendedMode"},
{"p", "RegRead"}, {"p", "RegRead"},
@ -297,8 +284,8 @@ std::string GetPacketFriendlyName(const std::string& packetCommand) {
}; };
std::string packet_name = ""; std::string packet_name = "";
auto it = command_names.find(packetCommand); auto it = kGdbCommandNames.find(packetCommand);
if (it != command_names.end()) { if (it != kGdbCommandNames.end()) {
packet_name = it->second; packet_name = it->second;
} }
@ -319,8 +306,7 @@ bool GDBStub::ProcessIncomingData(std::unique_ptr<Socket>& client,
// Hacky interrupt '\03' packet handling, some reason checksum isn't // Hacky interrupt '\03' packet handling, some reason checksum isn't
// attached to this? // attached to this?
bool isInterrupt = bool isInterrupt = buffer[0] == char(ControlCode::Interrupt) && received == 1;
buffer[0] == char(GdbStubControl::Interrupt) && received == 1;
// Check if we've received a full packet yet, if not exit and allow caller // Check if we've received a full packet yet, if not exit and allow caller
// to try again // to try again
@ -330,7 +316,7 @@ bool GDBStub::ProcessIncomingData(std::unique_ptr<Socket>& client,
if (isInterrupt || packet_end + 2 < receive_buffer.length()) { if (isInterrupt || packet_end + 2 < receive_buffer.length()) {
std::string current_packet; std::string current_packet;
if (isInterrupt) { if (isInterrupt) {
current_packet = char(GdbStubControl::Interrupt); current_packet = char(ControlCode::Interrupt);
receive_buffer = ""; receive_buffer = "";
isInterrupt = false; isInterrupt = false;
} else { } else {
@ -348,12 +334,12 @@ bool GDBStub::ProcessIncomingData(std::unique_ptr<Socket>& client,
command.data); command.data);
#endif #endif
GdbStubControl result = GdbStubControl::Ack; ControlCode result = ControlCode::Ack;
client->Send(&result, 1); client->Send(&result, 1);
std::string response = HandleGDBCommand(command); std::string response = HandleGDBCommand(command);
SendPacket(client, response); SendPacket(client, response);
} else { } else {
GdbStubControl result = GdbStubControl::Nack; ControlCode result = ControlCode::Nack;
client->Send(&result, 1); client->Send(&result, 1);
} }
} else { } else {
@ -390,23 +376,23 @@ bool GDBStub::ParsePacket(const std::string& packet, GDBCommand& out_cmd) {
char c = ReadCharFromBuffer(); char c = ReadCharFromBuffer();
// Expecting start of packet '$' // Expecting start of packet '$'
if (c != char(GdbStubControl::PacketStart)) { if (c != char(ControlCode::PacketStart)) {
// gdb starts conversation with + for some reason // gdb starts conversation with + for some reason
if (c == char(GdbStubControl::Ack)) { if (c == char(ControlCode::Ack)) {
c = ReadCharFromBuffer(); c = ReadCharFromBuffer();
} }
// and IDA sometimes has double +, grr // and IDA sometimes has double +, grr
if (c == char(GdbStubControl::Ack)) { if (c == char(ControlCode::Ack)) {
c = ReadCharFromBuffer(); c = ReadCharFromBuffer();
} }
// Interrupt is special, handle it without checking checksum // Interrupt is special, handle it without checking checksum
if (c == char(GdbStubControl::Interrupt)) { if (c == char(ControlCode::Interrupt)) {
out_cmd.cmd = char(GdbStubControl::Interrupt); out_cmd.cmd = char(ControlCode::Interrupt);
out_cmd.data = ""; out_cmd.data = "";
out_cmd.checksum = 0; out_cmd.checksum = 0;
return true; return true;
} }
if (c != char(GdbStubControl::PacketStart)) { if (c != char(ControlCode::PacketStart)) {
return false; return false;
} }
} }
@ -423,7 +409,7 @@ bool GDBStub::ParsePacket(const std::string& packet, GDBCommand& out_cmd) {
c = ReadCharFromBuffer(); c = ReadCharFromBuffer();
// If we reach the end of the buffer or hit '#', stop // If we reach the end of the buffer or hit '#', stop
if (c == '\0' || c == char(GdbStubControl::PacketEnd)) { if (c == '\0' || c == char(ControlCode::PacketEnd)) {
break; break;
} }
@ -488,12 +474,14 @@ std::string GDBStub::RegisterRead(xe::cpu::ThreadDebugInfo* thread,
// mode (eg. IDA will switch to 64-bit and refuse to allow decompiler to work // mode (eg. IDA will switch to 64-bit and refuse to allow decompiler to work
// with it) // with it)
// //
// TODO: add altivec/VMX registers here... // TODO: add altivec/VMX registers here
// TODO: add cvar to allow switch to 64-bit mode? (unsure if any x360 opcodes
// use the upper 32-bits?)
// //
// ids from gdb/features/rs6000/powerpc-64.c // ids from gdb/features/rs6000/powerpc-64.c
switch (rid) { switch (RegisterIndex(rid)) {
// pc case RegisterIndex::PC: {
case 64: { //
// If we recently hit a BP then debugger is likely asking for registers // If we recently hit a BP then debugger is likely asking for registers
// for it // for it
// //
@ -514,71 +502,69 @@ std::string GDBStub::RegisterRead(xe::cpu::ThreadDebugInfo* thread,
} }
return string_util::to_hex_string((uint32_t)0); return string_util::to_hex_string((uint32_t)0);
} }
case 65: case RegisterIndex::MSR:
return string_util::to_hex_string((uint32_t)thread->guest_context.msr); return string_util::to_hex_string((uint32_t)thread->guest_context.msr);
case 66: case RegisterIndex::CR:
return string_util::to_hex_string((uint32_t)thread->guest_context.cr()); return string_util::to_hex_string((uint32_t)thread->guest_context.cr());
case 67: case RegisterIndex::LR:
return string_util::to_hex_string((uint32_t)thread->guest_context.lr); return string_util::to_hex_string((uint32_t)thread->guest_context.lr);
case 68: case RegisterIndex::CTR:
return string_util::to_hex_string((uint32_t)thread->guest_context.ctr); return string_util::to_hex_string((uint32_t)thread->guest_context.ctr);
// xer case RegisterIndex::XER:
case 69:
return std::string(8, 'x'); return std::string(8, 'x');
case 70: case RegisterIndex::FPSCR:
return string_util::to_hex_string(thread->guest_context.fpscr.value); return string_util::to_hex_string(thread->guest_context.fpscr.value);
} }
if (rid > 70) { if (rid >= int(RegisterIndex::PC)) {
return ""; return "";
} }
// fpr // fpr
if (rid > 31) { if (rid >= int(RegisterIndex::FPR0)) {
return string_util::to_hex_string(thread->guest_context.f[rid - 32]); return string_util::to_hex_string(thread->guest_context.f[rid - 32]);
} }
// gpr // gpr
return string_util::to_hex_string((uint32_t)thread->guest_context.r[rid]); return string_util::to_hex_string((uint32_t)thread->guest_context.r[rid]);
} }
std::string GDBStub::RegisterWrite(xe::cpu::ThreadDebugInfo* thread, std::string GDBStub::RegisterWrite(xe::cpu::ThreadDebugInfo* thread,
uint32_t rid, const std::string_view value) { uint32_t rid, const std::string_view value) {
// Have to update the main thread context, and the ThreadDebugInfo context
auto* guest_context = thread->thread->thread_state()->context(); auto* guest_context = thread->thread->thread_state()->context();
switch (rid) { switch (RegisterIndex(rid)) {
// pc case RegisterIndex::PC:
case 64: return kGdbReplyError; // TODO: figure a way to change this
return kGdbReplyOK; // TODO: figure a way to change this case RegisterIndex::MSR:
case 65:
guest_context->msr = string_util::from_string<uint32_t>(value, true); guest_context->msr = string_util::from_string<uint32_t>(value, true);
thread->guest_context.msr = guest_context->msr; thread->guest_context.msr = guest_context->msr;
return kGdbReplyOK; return kGdbReplyOK;
case 66: case RegisterIndex::CR:
// CR return kGdbReplyError; // TODO: figure a way to change this
return kGdbReplyOK; // TODO: figure a way to change this case RegisterIndex::LR:
case 67:
guest_context->lr = string_util::from_string<uint32_t>(value, true); guest_context->lr = string_util::from_string<uint32_t>(value, true);
thread->guest_context.lr = guest_context->lr; thread->guest_context.lr = guest_context->lr;
return kGdbReplyOK; return kGdbReplyOK;
case 68: case RegisterIndex::CTR:
guest_context->ctr = string_util::from_string<uint32_t>(value, true); guest_context->ctr = string_util::from_string<uint32_t>(value, true);
thread->guest_context.ctr = guest_context->ctr; thread->guest_context.ctr = guest_context->ctr;
return kGdbReplyOK; return kGdbReplyOK;
// xer case RegisterIndex::XER:
case 69: return kGdbReplyError;
return kGdbReplyOK; case RegisterIndex::FPSCR:
case 70:
guest_context->fpscr.value = guest_context->fpscr.value =
string_util::from_string<uint32_t>(value, true); string_util::from_string<uint32_t>(value, true);
thread->guest_context.fpscr.value = guest_context->fpscr.value; thread->guest_context.fpscr.value = guest_context->fpscr.value;
return kGdbReplyOK; return kGdbReplyOK;
} }
if (rid > 70) { if (rid >= int(RegisterIndex::PC)) {
return kGdbReplyError; return kGdbReplyError;
} }
// fpr // fpr
if (rid > 31) { if (rid >= int(RegisterIndex::FPR0)) {
guest_context->f[rid - 32] = string_util::from_string<double>(value, true); guest_context->f[rid - 32] = string_util::from_string<double>(value, true);
thread->guest_context.f[rid - 32] = guest_context->f[rid - 32]; thread->guest_context.f[rid - 32] = guest_context->f[rid - 32];
return kGdbReplyOK; return kGdbReplyOK;
@ -625,7 +611,7 @@ std::string GDBStub::RegisterReadAll() {
return kGdbReplyError; return kGdbReplyError;
} }
std::string result; std::string result;
result.reserve(68 * 16 + 3 * 8); result.reserve((39 * 8) + (32 * 16));
for (int i = 0; i < 71; ++i) { for (int i = 0; i < 71; ++i) {
result += RegisterRead(thread, i); result += RegisterRead(thread, i);
} }
@ -638,6 +624,11 @@ std::string GDBStub::RegisterWriteAll(const std::string& data) {
return kGdbReplyError; return kGdbReplyError;
} }
int expected_size = (39 * 8) + (32 * 16);
if (data.length() != expected_size) {
return kGdbReplyError;
}
int string_offset = 0; int string_offset = 0;
for (int i = 0; i < 71; ++i) { for (int i = 0; i < 71; ++i) {
int reg_size = 8; // 8 hex-nibbles per 32-bit register int reg_size = 8; // 8 hex-nibbles per 32-bit register
@ -746,6 +737,14 @@ std::string GDBStub::MemoryWrite(const std::string& data) {
if (!heap) { if (!heap) {
return kGdbReplyError; return kGdbReplyError;
} }
// Check if they're trying to write to an executable function
if (auto* exe_addr = processor_->LookupFunction(addr)) {
// TODO: allow the write and ask processor to recompile if no breakpoints
// are set there?
return kGdbReplyError; // error for now as writes here won't have an effect
}
uint32_t protect = 0; uint32_t protect = 0;
if (!heap->QueryProtect(addr, &protect) || if (!heap->QueryProtect(addr, &protect) ||
(protect & kMemoryProtectRead) != kMemoryProtectRead) { (protect & kMemoryProtectRead) != kMemoryProtectRead) {
@ -791,10 +790,8 @@ std::string GDBStub::BuildThreadList() {
return buffer; return buffer;
} }
std::string GDBStub::GetThreadStateReply(uint32_t thread_id, uint8_t signal) { std::string GDBStub::GetThreadStateReply(uint32_t thread_id,
constexpr int PC_REGISTER = 64; SignalCode signal) {
constexpr int LR_REGISTER = 67;
auto* thread = cache_.thread_info(thread_id); auto* thread = cache_.thread_info(thread_id);
if (thread_id != -1 && thread) { if (thread_id != -1 && thread) {
@ -813,7 +810,8 @@ std::string GDBStub::GetThreadStateReply(uint32_t thread_id, uint8_t signal) {
} }
return fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};thread:{:x};", return fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};thread:{:x};",
signal, PC_REGISTER, uint32_t(pc_value), LR_REGISTER, uint8_t(signal), int(RegisterIndex::PC),
uint32_t(pc_value), int(RegisterIndex::LR),
uint32_t(thread->guest_context.lr), thread_id); uint32_t(thread->guest_context.lr), thread_id);
} }
return "S05"; return "S05";

View File

@ -26,6 +26,28 @@ namespace debug {
namespace gdb { namespace gdb {
class GDBStub : public cpu::DebugListener { class GDBStub : public cpu::DebugListener {
enum class ControlCode : char {
Ack = '+',
Nack = '-',
PacketStart = '$',
PacketEnd = '#',
Interrupt = '\03',
};
enum class SignalCode : uint8_t { SIGILL = 4, SIGTRAP = 5, SIGSEGV = 11 };
enum class RegisterIndex : int {
GPR0 = 0,
FPR0 = 32,
PC = 64,
MSR = 65,
CR = 66,
LR = 67,
CTR = 68,
XER = 69,
FPSCR = 70
};
public: public:
virtual ~GDBStub(); virtual ~GDBStub();
@ -77,7 +99,7 @@ class GDBStub : public cpu::DebugListener {
std::string MemoryWrite(const std::string& data); std::string MemoryWrite(const std::string& data);
std::string BuildThreadList(); std::string BuildThreadList();
std::string GetThreadStateReply(uint32_t thread_id, uint8_t signal); std::string GetThreadStateReply(uint32_t thread_id, SignalCode signal);
bool CreateCodeBreakpoint(uint64_t address); bool CreateCodeBreakpoint(uint64_t address);
void DeleteCodeBreakpoint(uint64_t address); void DeleteCodeBreakpoint(uint64_t address);
@ -112,7 +134,6 @@ class GDBStub : public cpu::DebugListener {
std::vector<cpu::ThreadDebugInfo*> thread_debug_infos; std::vector<cpu::ThreadDebugInfo*> thread_debug_infos;
struct { struct {
char kernel_call_filter[64] = {0};
std::vector<std::unique_ptr<cpu::Breakpoint>> all_breakpoints; std::vector<std::unique_ptr<cpu::Breakpoint>> all_breakpoints;
std::unordered_map<uint32_t, cpu::Breakpoint*> std::unordered_map<uint32_t, cpu::Breakpoint*>
code_breakpoints_by_guest_address; code_breakpoints_by_guest_address;