[GDBStub] Add MemoryWrite, extra checks in memory cmds

This commit is contained in:
emoose 2024-10-07 20:52:20 +01:00
parent 2ee35a2054
commit 0acc46e52e
2 changed files with 105 additions and 34 deletions

View File

@ -217,18 +217,19 @@ void GDBStub::Listen(std::unique_ptr<Socket>& client) {
}
// No data available, can do other work or sleep
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Check if we need to notify client about anything
{
std::unique_lock<std::mutex> lock(mtx_);
if (cache_.notify_stopped) {
if (cache_.notify_bp_thread_id != -1)
cache_.cur_thread_id = cache_.notify_bp_thread_id;
SendPacket(client, GetThreadStateReply(cache_.notify_bp_thread_id,
kSignalSigtrap));
cache_.notify_bp_thread_id = -1;
cache_.notify_stopped = false;
// Check if we need to notify client about anything
{
std::unique_lock<std::mutex> lock(mtx_);
if (cache_.notify_stopped) {
if (cache_.notify_bp_thread_id != -1) {
cache_.cur_thread_id = cache_.notify_bp_thread_id;
}
SendPacket(client, GetThreadStateReply(cache_.notify_bp_thread_id,
kSignalSigtrap));
cache_.notify_bp_thread_id = -1;
cache_.notify_stopped = false;
}
}
}
}
@ -241,7 +242,9 @@ void GDBStub::SendPacket(std::unique_ptr<Socket>& client,
<< char(GdbStubControl::PacketEnd);
uint8_t checksum = 0;
for (char c : data) checksum += c;
for (char c : data) {
checksum += c;
}
ss << std::hex << std::setw(2) << std::setfill('0') << (checksum & 0xff);
std::string packet = ss.str();
@ -254,17 +257,18 @@ std::string GetPacketFriendlyName(const std::string& packetCommand) {
static const std::unordered_map<std::string, std::string> command_names = {
{"?", "StartupQuery"},
{"!", "EnableExtendedMode"},
{"p", "ReadRegister"},
{"P", "WriteRegister"},
{"g", "ReadAllRegisters"},
{"p", "RegRead"},
{"P", "RegWrite"},
{"g", "RegReadAll"},
{"C", "Continue"},
{"c", "continue"},
{"s", "step"},
{"vAttach", "vAttach"},
{"m", "MemRead"},
{"M", "MemWrite"},
{"H", "SetThreadId"},
{"Z", "CreateCodeBreakpoint"},
{"z", "DeleteCodeBreakpoint"},
{"Z", "BreakpointCreate"},
{"z", "BreakpointDelete"},
{"qXfer", "Xfer"},
{"qSupported", "Supported"},
{"qfThreadInfo", "qfThreadInfo"},
@ -460,7 +464,7 @@ void GDBStub::UpdateCache() {
}
}
std::string GDBStub::ReadRegister(xe::cpu::ThreadDebugInfo* thread,
std::string GDBStub::RegisterRead(xe::cpu::ThreadDebugInfo* thread,
uint32_t rid) {
// Send registers as 32-bit, otherwise some debuggers will switch to 64-bit
// mode (eg. IDA will switch to 64-bit and refuse to allow decompiler to work
@ -520,20 +524,20 @@ std::string GDBStub::ReadRegister(xe::cpu::ThreadDebugInfo* thread,
return string_util::to_hex_string((uint32_t)thread->guest_context.r[rid]);
}
std::string GDBStub::ReadRegister(const std::string& data) {
std::string GDBStub::RegisterRead(const std::string& data) {
auto* thread = cache_.cur_thread_info();
if (!thread) {
return kGdbReplyError;
}
uint32_t rid = string_util::from_string<uint32_t>(data, true);
std::string result = ReadRegister(thread, rid);
std::string result = RegisterRead(thread, rid);
if (result.empty()) {
return kGdbReplyError;
}
return result;
}
std::string GDBStub::ReadRegisters() {
std::string GDBStub::RegisterReadAll() {
auto* thread = cache_.cur_thread_info();
if (!thread) {
return kGdbReplyError;
@ -541,7 +545,7 @@ std::string GDBStub::ReadRegisters() {
std::string result;
result.reserve(68 * 16 + 3 * 8);
for (int i = 0; i < 71; ++i) {
result += ReadRegister(thread, i);
result += RegisterRead(thread, i);
}
return result;
}
@ -571,19 +575,30 @@ std::string GDBStub::ExecutionStep() {
cache_.last_bp_thread_id);
#endif
if (cache_.last_bp_thread_id != -1)
if (cache_.last_bp_thread_id != -1) {
processor_->StepGuestInstruction(cache_.last_bp_thread_id);
}
return kGdbReplyOK;
}
std::string GDBStub::ReadMemory(const std::string& data) {
auto s = data.find(',');
uint32_t addr = string_util::from_string<uint32_t>(data.substr(0, s), true);
uint32_t len = string_util::from_string<uint32_t>(data.substr(s + 1), true);
std::string GDBStub::MemoryRead(const std::string& data) {
auto len_sep = data.find(',');
if (len_sep == std::string::npos) {
return kGdbReplyError;
}
uint32_t addr =
string_util::from_string<uint32_t>(data.substr(0, len_sep), true);
uint32_t len =
string_util::from_string<uint32_t>(data.substr(len_sep + 1), true);
std::string result;
result.reserve(len * 2);
auto global_lock = global_critical_region_.Acquire();
// TODO: is there a better way to check if addr is valid?
auto* heap = processor_->memory()->LookupHeap(addr);
if (!heap) {
@ -608,6 +623,55 @@ std::string GDBStub::ReadMemory(const std::string& data) {
return result;
}
std::string GDBStub::MemoryWrite(const std::string& data) {
auto len_sep = data.find(',');
auto mem_sep = data.find(':');
if (len_sep == std::string::npos || mem_sep == std::string::npos) {
return kGdbReplyError;
}
uint32_t addr =
string_util::from_string<uint32_t>(data.substr(0, len_sep), true);
uint32_t len = string_util::from_string<uint32_t>(
data.substr(len_sep + 1, mem_sep - (len_sep + 1)), true);
auto global_lock = global_critical_region_.Acquire();
auto* heap = processor_->memory()->LookupHeap(addr);
if (!heap) {
return kGdbReplyError;
}
uint32_t protect = 0;
if (!heap->QueryProtect(addr, &protect) ||
(protect & kMemoryProtectRead) != kMemoryProtectRead) {
return kGdbReplyError;
}
if (len == 0) {
return kGdbReplyOK;
}
uint32_t old_protect = 0;
bool mem_unprotected =
heap->Protect(addr, len, protect | kMemoryProtectWrite, &old_protect);
if (!mem_unprotected) {
return kGdbReplyError;
}
std::vector<uint8_t> mem_data;
string_util::hex_string_to_array(mem_data,
std::string_view(data.data() + mem_sep + 1));
auto* mem = processor_->memory()->TranslateVirtual(addr);
for (uint32_t i = 0; i < len; ++i) {
mem[i] = mem_data[i];
}
heap->Protect(addr, len, old_protect);
return kGdbReplyOK;
}
std::string GDBStub::BuildThreadList() {
std::string buffer;
buffer += "l<?xml version=\"1.0\"?>";
@ -830,16 +894,19 @@ std::string GDBStub::HandleGDBCommand(const GDBCommand& command) {
{"\03", [&](const GDBCommand& cmd) { return ExecutionPause(); }},
// Read memory
{"m", [&](const GDBCommand& cmd) { return ReadMemory(cmd.data); }},
{"m", [&](const GDBCommand& cmd) { return MemoryRead(cmd.data); }},
// Write memory
{"M", [&](const GDBCommand& cmd) { return MemoryWrite(cmd.data); }},
// Read register
{"p", [&](const GDBCommand& cmd) { return ReadRegister(cmd.data); }},
{"p", [&](const GDBCommand& cmd) { return RegisterRead(cmd.data); }},
// Write register
{"P",
[&](const GDBCommand& cmd) {
return kGdbReplyOK; // TODO: we'll just tell it write was fine
}},
// Read all registers
{"g", [&](const GDBCommand& cmd) { return ReadRegisters(); }},
{"g", [&](const GDBCommand& cmd) { return RegisterReadAll(); }},
// Attach to specific process ID - IDA used to send this, but doesn't
// after some changes?

View File

@ -62,13 +62,14 @@ class GDBStub : public cpu::DebugListener {
void UpdateCache();
std::string DebuggerDetached();
std::string ReadRegister(xe::cpu::ThreadDebugInfo* thread, uint32_t rid);
std::string ReadRegister(const std::string& data);
std::string ReadRegisters();
std::string RegisterRead(xe::cpu::ThreadDebugInfo* thread, uint32_t rid);
std::string RegisterRead(const std::string& data);
std::string RegisterReadAll();
std::string ExecutionPause();
std::string ExecutionContinue();
std::string ExecutionStep();
std::string ReadMemory(const std::string& data);
std::string MemoryRead(const std::string& data);
std::string MemoryWrite(const std::string& data);
std::string BuildThreadList();
std::string GetThreadStateReply(uint32_t thread_id, uint8_t signal);
@ -88,12 +89,15 @@ class GDBStub : public cpu::DebugListener {
std::condition_variable cv_;
bool stop_thread_ = false;
xe::global_critical_region global_critical_region_;
struct EmulatorStateCache {
uint32_t cur_thread_id = -1;
uint32_t last_bp_thread_id = -1;
uint64_t notify_bp_guest_address = -1;
uint32_t notify_bp_thread_id = -1;
std::vector<std::string> notify_debug_messages;
bool notify_stopped = false;
bool is_stopped = false;