mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-25 20:00:44 +01:00
Extends gdbstub functionality
There are a significant number of changes here that I don't really expect much review on. Adds a bunch of new features and allows us to support GDB, IDA, and Binja through their remote GDB debugging facilities. Ghidra may also work but I haven't tried. This work isn't quite yet complete to do correct thread stepping yet. We need to add support for stopping threads using signals since threads may be stuck inside of a syscall. I have a hack locally that tries pausing all threads and times out after a few seconds which works well enough for local testing.
This commit is contained in:
1 parent
2ecd103aed
commit
95a8aba433
2 files changed
+565
-132
No files matched your search
+536
-126
@@ -7,7 +7,8 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include "Common/NetStream.h"
|
||||
#include "LogManager.h"
|
||||
#include "Common/SoftFloat.h"
|
||||
#include "LogManager.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -15,9 +16,11 @@
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
|
||||
#include "GdbServer.h"
|
||||
#include <FEXCore/Core/CodeLoader.h>
|
||||
#include <FEXCore/Core/X86Enums.h>
|
||||
|
||||
namespace FEXCore
|
||||
{
|
||||
@@ -73,13 +76,28 @@ static std::string encodeHex(unsigned char *data, size_t length) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static std::string getThreadName(uint32_t ThreadID) {
|
||||
std::fstream fs;
|
||||
std::ostringstream ThreadFile;
|
||||
ThreadFile << "/proc/" << getpid() << "/task/" << ThreadID << "/comm";
|
||||
|
||||
fs.open(ThreadFile.str(), std::fstream::in | std::fstream::binary);
|
||||
if (fs.is_open()) {
|
||||
std::string ThreadName;
|
||||
fs >> ThreadName;
|
||||
fs.close();
|
||||
return ThreadName;
|
||||
}
|
||||
|
||||
return "<No Name>";
|
||||
}
|
||||
|
||||
// Packet parser
|
||||
// Takes a serial stream and reads a single packet
|
||||
// Un-escapes chars, checks the checksum and request a retransmit if it fails.
|
||||
// Once the checksum is validated, it acknowledges and returns the packet in a string
|
||||
std::string GdbServer::ReadPacket(std::iostream &stream) {
|
||||
std::string packet;
|
||||
std::string packet{};
|
||||
|
||||
// The GDB "Remote Serial Protocal" was originally 7bit clean for use on serial ports.
|
||||
// Binary data is useally hex encoded. However some later extentions just put
|
||||
@@ -114,12 +132,9 @@ std::string GdbServer::ReadPacket(std::iostream &stream) {
|
||||
int expected_checksum = std::strtoul(hexString, nullptr, 16);
|
||||
|
||||
if (calculateChecksum(packet) == expected_checksum) {
|
||||
LogMan::Msg::I("Received Packet: \"%s\"", packet.c_str());
|
||||
stream << "+" << std::flush;
|
||||
return packet;
|
||||
} else {
|
||||
LogMan::Msg::E("Received Invalid Packet: $%s#%02x %c%c", packet.c_str(), expected_checksum);
|
||||
stream << "-" << std::flush;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -155,54 +170,204 @@ static std::string escapePacket(std::string packet) {
|
||||
}
|
||||
|
||||
void GdbServer::SendPacket(std::ostream &stream, std::string packet) {
|
||||
// In-band signaling, not a great design
|
||||
if (packet == NO_REPLY)
|
||||
return;
|
||||
// In-band signaling, not a great design
|
||||
if (packet == NO_REPLY)
|
||||
return;
|
||||
|
||||
auto escaped = escapePacket(packet);
|
||||
//LogMan::Msg::E("GdbServer Reply: %s", escaped.c_str());
|
||||
stream << '$' << escaped << '#';
|
||||
stream << std::setfill('0') << std::setw(2) << std::hex << (int)calculateChecksum(escaped);
|
||||
stream << std::flush;
|
||||
auto escaped = escapePacket(packet);
|
||||
std::ostringstream ss;
|
||||
|
||||
ss << '$' << escaped << '#';
|
||||
ss << std::setfill('0') << std::setw(2) << std::hex << (int)calculateChecksum(escaped);
|
||||
stream << ss.str() << std::flush;
|
||||
}
|
||||
|
||||
std::string GdbServer::readRegs() {
|
||||
auto state = CTX->GetCPUState();
|
||||
// state.rip = 0x47c990;
|
||||
void GdbServer::SendACK(std::ostream &stream, bool NACK) {
|
||||
if (NoAckMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
return encodeHex((unsigned char *)&state, sizeof(state)).substr(0, 572*2);
|
||||
if (NACK) {
|
||||
stream << "-" << std::flush;
|
||||
}
|
||||
else {
|
||||
stream << "+" << std::flush;
|
||||
}
|
||||
|
||||
if (SettingNoAckMode) {
|
||||
NoAckMode = true;
|
||||
SettingNoAckMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
struct __attribute__((packed)) GDBContextDefinition {
|
||||
uint64_t gregs[16];
|
||||
uint64_t rip;
|
||||
uint32_t eflags;
|
||||
uint32_t cs, ss, ds, es, fs, gs;
|
||||
X80SoftFloat mm[8];
|
||||
uint32_t fctrl;
|
||||
uint32_t fstat;
|
||||
uint32_t dummies[6];
|
||||
uint64_t xmm[16][2];
|
||||
uint32_t mxcsr;
|
||||
};
|
||||
|
||||
std::string GdbServer::readRegs() {
|
||||
GDBContextDefinition GDB{};
|
||||
FEXCore::Core::CPUState state{};
|
||||
|
||||
auto Threads = CTX->GetThreads();
|
||||
bool Found = false;
|
||||
|
||||
for (auto &Thread : *Threads) {
|
||||
if (Thread->State.ThreadManager.GetTID() != CurrentDebuggingThread) {
|
||||
continue;
|
||||
}
|
||||
state = Thread->State.State;
|
||||
Found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Found) {
|
||||
// If set to an invalid thread then just get the parent thread ID
|
||||
state = CTX->GetCPUState();
|
||||
}
|
||||
|
||||
// Encode the GDB context definition
|
||||
memcpy(&GDB.gregs[0], &state.gregs[0], sizeof(GDB.gregs));
|
||||
memcpy(&GDB.rip, &state.rip, sizeof(GDB.rip));
|
||||
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
uint64_t Flag = state.flags[i];
|
||||
GDB.eflags |= (Flag << i);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
memcpy(&GDB.mm[i], &state.mm[i], sizeof(GDB.mm));
|
||||
}
|
||||
|
||||
// Currently unsupported
|
||||
GDB.fctrl = 0x37F;
|
||||
|
||||
GDB.fstat = static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11;
|
||||
GDB.fstat |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8;
|
||||
GDB.fstat |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C1_LOC]) << 9;
|
||||
GDB.fstat |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C2_LOC]) << 10;
|
||||
GDB.fstat |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C3_LOC]) << 14;
|
||||
|
||||
memcpy(&GDB.xmm[0], &state.xmm[0], sizeof(GDB.xmm));
|
||||
|
||||
return encodeHex((unsigned char *)&GDB, sizeof(GDBContextDefinition));
|
||||
}
|
||||
|
||||
GdbServer::HandledPacketType GdbServer::readReg(std::string& packet) {
|
||||
size_t addr;
|
||||
auto ss = std::istringstream(packet);
|
||||
ss.get(); // Drop first letter
|
||||
ss >> std::hex >> addr;
|
||||
|
||||
FEXCore::Core::CPUState state{};
|
||||
|
||||
auto Threads = CTX->GetThreads();
|
||||
bool Found = false;
|
||||
|
||||
for (auto &Thread : *Threads) {
|
||||
if (Thread->State.ThreadManager.GetTID() != CurrentDebuggingThread) {
|
||||
continue;
|
||||
}
|
||||
state = Thread->State.State;
|
||||
Found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Found) {
|
||||
// If set to an invalid thread then just get the parent thread ID
|
||||
state = CTX->GetCPUState();
|
||||
}
|
||||
|
||||
|
||||
if (addr >= offsetof(GDBContextDefinition, gregs[0]) &&
|
||||
addr < offsetof(GDBContextDefinition, gregs[16])) {
|
||||
return {encodeHex((unsigned char *)(&state.gregs[addr / sizeof(uint64_t)]), sizeof(uint64_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr == offsetof(GDBContextDefinition, rip)) {
|
||||
return {encodeHex((unsigned char *)(&state.rip), sizeof(uint64_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr == offsetof(GDBContextDefinition, eflags)) {
|
||||
uint32_t eflags{};
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
uint64_t Flag = state.flags[i];
|
||||
eflags |= (Flag << i);
|
||||
}
|
||||
return {encodeHex((unsigned char *)(&eflags), sizeof(uint32_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr >= offsetof(GDBContextDefinition, cs) &&
|
||||
addr < offsetof(GDBContextDefinition, mm[0])) {
|
||||
uint32_t Empty{};
|
||||
return {encodeHex((unsigned char *)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr >= offsetof(GDBContextDefinition, mm[0]) &&
|
||||
addr < offsetof(GDBContextDefinition, mm[8])) {
|
||||
return {encodeHex((unsigned char *)(&state.mm[(addr - offsetof(GDBContextDefinition, mm[0])) / sizeof(X80SoftFloat)]), sizeof(X80SoftFloat)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr == offsetof(GDBContextDefinition, fctrl)) {
|
||||
// XXX: We don't support this yet
|
||||
uint32_t FCW = 0x37F;
|
||||
return {encodeHex((unsigned char *)(&FCW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr == offsetof(GDBContextDefinition, fstat)) {
|
||||
uint32_t FSW{};
|
||||
FSW = static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_TOP_LOC]) << 11;
|
||||
FSW |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C0_LOC]) << 8;
|
||||
FSW |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C1_LOC]) << 9;
|
||||
FSW |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C2_LOC]) << 10;
|
||||
FSW |= static_cast<uint32_t>(state.flags[FEXCore::X86State::X87FLAG_C3_LOC]) << 14;
|
||||
return {encodeHex((unsigned char *)(&FSW), sizeof(uint32_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr >= offsetof(GDBContextDefinition, dummies[0]) &&
|
||||
addr < offsetof(GDBContextDefinition, dummies[6])) {
|
||||
uint32_t Empty{};
|
||||
return {encodeHex((unsigned char *)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr >= offsetof(GDBContextDefinition, xmm[0][0]) &&
|
||||
addr < offsetof(GDBContextDefinition, xmm[16][0])) {
|
||||
return {encodeHex((unsigned char *)(&state.xmm[(addr - offsetof(GDBContextDefinition, xmm[0][0])) / 16][0]), 16), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
else if (addr == offsetof(GDBContextDefinition, mxcsr)) {
|
||||
uint32_t Empty{};
|
||||
return {encodeHex((unsigned char *)(&Empty), sizeof(uint32_t)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
LogMan::Msg::E("Unknown GDB register 0x%lx", addr);
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
std::string buildTargetXML() {
|
||||
using FEXCore::Core::CPUState;
|
||||
|
||||
std::ostringstream xml;
|
||||
|
||||
int dummy_offset = sizeof(CPUState);
|
||||
int dummy_num = sizeof(CPUState) / 8;
|
||||
auto dummy = [&](int size) { dummy_offset+= size; return dummy_num++; };
|
||||
|
||||
xml << "<?xml version='1.0'?>\n";
|
||||
xml << "<!DOCTYPE target SYSTEM 'gdb-target.dtd'>\n";\
|
||||
xml << "<target version='1.0'>\n";
|
||||
xml << "<target>\n";
|
||||
xml << "<architecture>i386:x86-64</architecture>\n";
|
||||
xml << "<osabi>GNU/Linux</osabi>\n";
|
||||
xml << "<feature name='org.gnu.gdb.i386.core'>\n";
|
||||
|
||||
xml << "<flags id='fex_eflags' size='32'>\n";
|
||||
xml << "<flags id='fex_eflags' size='4'>\n";
|
||||
// flags register
|
||||
for(int i=0; i <= 32; i++) {
|
||||
for(int i = 0; i < 22; i++) {
|
||||
auto name = FEXCore::Core::GetFlagName(i);
|
||||
xml << "<field name='" << name << "' start='" << i << "' end='" << i << "' />\n";
|
||||
if (name.empty()) {
|
||||
continue;
|
||||
}
|
||||
xml << "\t<field name='" << name << "' start='" << i << "' end='" << i << "' />\n";
|
||||
}
|
||||
xml << "</flags>";
|
||||
xml << "</flags>\n";
|
||||
|
||||
auto reg_numbered = [&](std::string_view name, int num, std::string_view type, int size) {
|
||||
xml << "<reg name='" << name << "' bitsize='" << size << "' type='" << type;
|
||||
xml << "' regnum='" << num << "' />" << std::endl;
|
||||
};
|
||||
auto reg = [&](std::string_view name, int offset, std::string_view type, int size) {
|
||||
reg_numbered(name, offset / 8, type, size);
|
||||
int32_t TargetSize{};
|
||||
auto reg = [&](std::string_view name, std::string_view type, int size) {
|
||||
TargetSize += size;
|
||||
xml << "<reg name='" << name << "' bitsize='" << size << "' type='" << type << "' />" << std::endl;
|
||||
};
|
||||
|
||||
// Register ordering.
|
||||
@@ -210,62 +375,80 @@ std::string buildTargetXML() {
|
||||
|
||||
// GPRs
|
||||
for (int i=0; i < 16; i++) {
|
||||
reg(FEXCore::Core::GetGRegName(i), offsetof(CPUState, gregs[i]), "int64", 64);
|
||||
reg(FEXCore::Core::GetGRegName(i), "int64", 64);
|
||||
}
|
||||
|
||||
reg("rip", offsetof(CPUState, rip), "code_ptr", 64);
|
||||
reg("eflags", offsetof(CPUState, flags), "fex_eflags", 8*32);
|
||||
reg("gs", offsetof(CPUState, gs), "int64", 64);
|
||||
reg("fs", offsetof(CPUState, fs), "int64", 64);
|
||||
reg("rip", "code_ptr", 64);
|
||||
|
||||
reg("eflags", "fex_eflags", 32);
|
||||
|
||||
// Fake registers which GDB requires, but we don't support;
|
||||
// We stick them past the end of our cpu state.
|
||||
|
||||
// non-userspace segment registers
|
||||
reg_numbered("cs", dummy(4), "int32", 32);
|
||||
reg_numbered("ss", dummy(4), "int32", 32);
|
||||
reg_numbered("ds", dummy(4), "int32", 32);
|
||||
reg_numbered("es", dummy(4), "int32", 32);
|
||||
reg("cs", "int32", 32);
|
||||
reg("ss", "int32", 32);
|
||||
reg("ds", "int32", 32);
|
||||
reg("es", "int32", 32);
|
||||
|
||||
reg("fs", "int32", 32);
|
||||
reg("gs", "int32", 32);
|
||||
|
||||
// x87 stack
|
||||
for (int i=0; i < 8; i++) {
|
||||
reg_numbered("st" + std::to_string(i), dummy(10), "i387_ext", 80);
|
||||
reg("st" + std::to_string(i), "i387_ext", 80);
|
||||
}
|
||||
|
||||
// x87 control
|
||||
reg_numbered("fctrl", dummy(4), "int32", 32);
|
||||
reg_numbered("fstat", dummy(4), "int32", 32);
|
||||
reg_numbered("ftag", dummy(4), "int32", 32);
|
||||
reg_numbered("fiseg", dummy(4), "int32", 32);
|
||||
reg_numbered("fioff", dummy(4), "int32", 32);
|
||||
reg_numbered("foseg", dummy(4), "int32", 32);
|
||||
reg_numbered("fooff", dummy(4), "int32", 32);
|
||||
reg_numbered("fop", dummy(4), "int32", 32);
|
||||
reg("fctrl", "int32", 32);
|
||||
reg("fstat", "int32", 32);
|
||||
reg("ftag", "int32", 32);
|
||||
reg("fiseg", "int32", 32);
|
||||
reg("fioff", "int32", 32);
|
||||
reg("foseg", "int32", 32);
|
||||
reg("fooff", "int32", 32);
|
||||
reg("fop", "int32", 32);
|
||||
|
||||
|
||||
xml << "</feature>\n";
|
||||
xml << "<feature name='org.gnu.gdb.i386.sse'>\n";
|
||||
xml << "<vector id='sse' type='int32' count='4' />";
|
||||
xml <<
|
||||
R"(<vector id="v4f" type="ieee_single" count="4"/>
|
||||
<vector id="v2d" type="ieee_double" count="2"/>
|
||||
<vector id="v16i8" type="int8" count="16"/>
|
||||
<vector id="v8i16" type="int16" count="8"/>
|
||||
<vector id="v4i32" type="int32" count="4"/>
|
||||
<vector id="v2i64" type="int64" count="2"/>
|
||||
<union id="vec128">
|
||||
<field name="v4_float" type="v4f"/>
|
||||
<field name="v2_double" type="v2d"/>
|
||||
<field name="v16_int8" type="v16i8"/>
|
||||
<field name="v8_int16" type="v8i16"/>
|
||||
<field name="v4_int32" type="v4i32"/>
|
||||
<field name="v2_int64" type="v2i64"/>
|
||||
<field name="uint128" type="uint128"/>
|
||||
</union>
|
||||
)";
|
||||
|
||||
// SSE regs
|
||||
for (int i=0; i < 16; i++) {
|
||||
reg("xmm" + std::to_string(i), offsetof(CPUState, xmm[i]), "sse", 128);
|
||||
reg("xmm" + std::to_string(i), "vec128", 128);
|
||||
}
|
||||
|
||||
reg("mxcsr", dummy(4), "int", 32);
|
||||
reg("mxcsr", "int", 32);
|
||||
|
||||
xml << "</feature>\n";
|
||||
xml << "</target>\n";
|
||||
xml << "</target>";
|
||||
xml << std::flush;
|
||||
|
||||
return xml.str();
|
||||
}
|
||||
|
||||
std::string GdbServer::handleXfer(std::string &packet) {
|
||||
|
||||
GdbServer::HandledPacketType GdbServer::handleXfer(std::string &packet) {
|
||||
std::string object;
|
||||
std::string rw;
|
||||
std::string annex;
|
||||
int annex_pid;
|
||||
int offset;
|
||||
int length;
|
||||
|
||||
@@ -279,13 +462,20 @@ std::string GdbServer::handleXfer(std::string &packet) {
|
||||
std::getline(ss, object, ':');
|
||||
std::getline(ss, rw, ':');
|
||||
std::getline(ss, annex, ':');
|
||||
if (annex == "") {
|
||||
annex_pid = getpid();
|
||||
}
|
||||
else {
|
||||
auto ss_pid = std::istringstream(annex);
|
||||
ss_pid >> std::hex >> annex_pid;
|
||||
}
|
||||
ss >> std::hex >> offset;
|
||||
ss.get(expectComma);
|
||||
ss >> std::hex >> length;
|
||||
|
||||
// Bail on any errors
|
||||
if (ss.fail() || !ss.eof() || expectXfer != "qXfer" || rw != "read" || expectComma != ',')
|
||||
return "E00";
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
// Lambda to correctly encode any reply
|
||||
@@ -300,19 +490,91 @@ std::string GdbServer::handleXfer(std::string &packet) {
|
||||
};
|
||||
|
||||
if (object == "exec-file") {
|
||||
if (annex == "")
|
||||
return encode(CTX->SyscallHandler->GetFilename());
|
||||
return "E00";
|
||||
if (annex_pid == getpid())
|
||||
return {encode(CTX->SyscallHandler->GetFilename()), HandledPacketType::TYPE_ACK};
|
||||
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
if (object == "features") {
|
||||
if (annex == "target.xml")
|
||||
return encode(buildTargetXML());
|
||||
return "E00";
|
||||
if (annex == "target.xml")
|
||||
return {encode(buildTargetXML()), HandledPacketType::TYPE_ACK};
|
||||
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
return "";
|
||||
|
||||
if (object == "threads") {
|
||||
if (offset == 0) {
|
||||
auto Threads = CTX->GetThreads();
|
||||
|
||||
ThreadString.clear();
|
||||
std::ostringstream ss;
|
||||
ss << "<?xml version=\"1.0\?>\n";
|
||||
ss << "<threads>\n";
|
||||
for (size_t i = 0; i < Threads->size(); ++i) {
|
||||
auto Thread = Threads->at(i);
|
||||
ss << "\t<thread id=\"" << std::hex << Thread->State.ThreadManager.GetTID() << "\" core=\"" << i << "\" name=\"" << getThreadName(Thread->State.ThreadManager.GetTID()) << "\">\n";
|
||||
ss << "\t</thread>\n";
|
||||
}
|
||||
|
||||
ss << "</threads>";
|
||||
ss << std::flush;
|
||||
ThreadString = ss.str();
|
||||
}
|
||||
|
||||
return {encode(ThreadString.substr(offset, length)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
return {"", HandledPacketType::TYPE_UNKNOWN};
|
||||
}
|
||||
|
||||
std::string GdbServer::handleMemory(std::string &packet) {
|
||||
static size_t CheckMemMapping(uint64_t Address, size_t Size) {
|
||||
uint64_t AddressEnd = Address + Size;
|
||||
|
||||
std::fstream fs;
|
||||
fs.open("/proc/self/maps", std::fstream::in | std::fstream::binary);
|
||||
std::string Line;
|
||||
while (std::getline(fs, Line)) {
|
||||
if (fs.eof()) break;
|
||||
uint64_t Begin, End;
|
||||
char r,w,x,p;
|
||||
if (sscanf(Line.c_str(), "%lx-%lx %c%c%c%c", &Begin, &End, &r, &w, &x, &p) == 6) {
|
||||
if (Begin <= Address &&
|
||||
End > Address) {
|
||||
ssize_t Overrun{};
|
||||
if (AddressEnd > End) {
|
||||
Overrun = AddressEnd - End;
|
||||
}
|
||||
return Size - Overrun;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fs.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
GdbServer::HandledPacketType GdbServer::handleProgramOffsets() {
|
||||
std::fstream fs;
|
||||
fs.open("/proc/self/maps", std::fstream::in | std::fstream::binary);
|
||||
std::string Line;
|
||||
std::string const &RuntimeExecutable = CTX->SyscallHandler->GetFilename();
|
||||
while (std::getline(fs, Line)) {
|
||||
uint64_t Begin, End;
|
||||
char Filename[255];
|
||||
if (sscanf(Line.c_str(), "%lx-%lx %*c%*c%*c%*c %*x %*x:%*x %*d%s", &Begin, &End, Filename) == 3) {
|
||||
if (RuntimeExecutable == Filename) {
|
||||
std::ostringstream ss;
|
||||
ss << "Text=" << std::hex << Begin << ";Data=" << std::hex << Begin << ";Bss=" << std::hex << Begin;
|
||||
ss << std::flush;
|
||||
return {ss.str(), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.close();
|
||||
return {"Text=0;Data=0;Bss=0", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
GdbServer::HandledPacketType GdbServer::handleMemory(std::string &packet) {
|
||||
bool write;
|
||||
size_t addr;
|
||||
size_t length;
|
||||
@@ -331,42 +593,91 @@ std::string GdbServer::handleMemory(std::string &packet) {
|
||||
|
||||
// validate packet
|
||||
if (ss.fail() || !ss.eof() || (write && (data.length() != length))) {
|
||||
return "E00";
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
length = CheckMemMapping(addr, length);
|
||||
if (length == 0) {
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
// TODO: check we are in a valid memory range
|
||||
// Also, clamp length
|
||||
void* ptr = CTX->MemoryMapper.GetPointer(addr);
|
||||
void* ptr = reinterpret_cast<void*>(addr);
|
||||
if (!CTX->Config.UnifiedMemory) {
|
||||
ptr = CTX->MemoryMapper.GetPointer(addr);
|
||||
}
|
||||
|
||||
if (write) {
|
||||
std::memcpy(ptr, data.data(), data.length());
|
||||
// TODO: invalidate any code
|
||||
return "OK";
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
} else {
|
||||
return encodeHex((unsigned char*)ptr, length);
|
||||
return {encodeHex((unsigned char*)ptr, length), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string GdbServer::handleQuery(std::string &packet) {
|
||||
auto match = [&](const char *str) -> bool { return packet.rfind(str, 0) == 0; };
|
||||
GdbServer::HandledPacketType GdbServer::handleQuery(std::string &packet) {
|
||||
auto match = [&](const char *str) -> bool { return packet.rfind(str, 0) == 0; };
|
||||
|
||||
if (match("qSupported")) {
|
||||
return "PacketSize=5000;xmlRegisters=i386;qXfer:exec-file:read+;qXfer:features:read+";
|
||||
if (match("qSupported")) {
|
||||
return {"PacketSize=5000;xmlRegisters=i386;qXfer:exec-file:read+;qXfer:features:read+;", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("qAttached")) {
|
||||
return {"1", HandledPacketType::TYPE_ACK}; // We don't currently support launching executables from gdb.
|
||||
}
|
||||
if (match("qXfer")) {
|
||||
return handleXfer(packet);
|
||||
}
|
||||
if (match("qOffsets")) {
|
||||
return handleProgramOffsets();
|
||||
}
|
||||
if (match("qTStatus")) {
|
||||
// We don't support trace experiments
|
||||
return {"", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("qfThreadInfo")) {
|
||||
auto Threads = CTX->GetThreads();
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << "m";
|
||||
for (size_t i = 0; i < Threads->size(); ++i) {
|
||||
auto Thread = Threads->at(i);
|
||||
ss << std::hex << Thread->State.ThreadManager.TID << ",";
|
||||
}
|
||||
if (match("qAttached")) {
|
||||
return "1"; // We don't currently support launching executables from gdb.
|
||||
}
|
||||
if (match("qXfer")) {
|
||||
return handleXfer(packet);
|
||||
}
|
||||
if (match("qOffsets")) {
|
||||
return "Text=0;Data=0;Bss=0";
|
||||
}
|
||||
return "";
|
||||
return {ss.str(), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("qsThreadInfo")) {
|
||||
return {"l", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("qThreadExtraInfo")) {
|
||||
auto ss = std::istringstream(packet);
|
||||
ss.seekg(std::string("qThreadExtraInfo").size());
|
||||
ss.get(); // discard comma
|
||||
uint32_t ThreadID;
|
||||
ss >> std::hex >> ThreadID;
|
||||
auto ThreadName = getThreadName(ThreadID);
|
||||
return {encodeHex((unsigned char*)ThreadName.data(), ThreadName.size()), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("qC")) {
|
||||
// Returns the current Thread ID
|
||||
std::ostringstream ss;
|
||||
ss << "m" << std::hex << CTX->ParentThread->State.ThreadManager.TID;
|
||||
return {ss.str(), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("QStartNoAckMode")) {
|
||||
SettingNoAckMode = true;
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if (match("qSymbol")) {
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
return {"", HandledPacketType::TYPE_UNKNOWN};
|
||||
}
|
||||
|
||||
std::string GdbServer::handleV(std::string& packet) {
|
||||
GdbServer::HandledPacketType GdbServer::handleV(std::string& packet) {
|
||||
auto match = [&](std::string str) -> std::optional<std::istringstream> {
|
||||
if (packet.rfind(str, 0) == 0) {
|
||||
auto ss = std::istringstream(packet);
|
||||
@@ -400,14 +711,20 @@ std::string GdbServer::handleV(std::string& packet) {
|
||||
ss->get(); // discard comma
|
||||
*ss >> std::hex >> mode;
|
||||
|
||||
return F(open(filename.c_str(), flags, mode));
|
||||
return {F(open(filename.c_str(), flags, mode)), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if((ss = match("vFile:setfs:"))) {
|
||||
int pid;
|
||||
*ss >> pid;
|
||||
|
||||
F(pid == 0 ? 0 : -1); // Only support the common filesystem
|
||||
return {F(pid == 0 ? 0 : -1), HandledPacketType::TYPE_ACK}; // Only support the common filesystem
|
||||
}
|
||||
if((ss = match("vFile:close:"))) {
|
||||
int fd;
|
||||
*ss >> std::hex >> fd;
|
||||
close(fd);
|
||||
return {F(0), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if((ss = match("vFile:pread:"))) {
|
||||
int fd, count, offset;
|
||||
|
||||
@@ -419,17 +736,17 @@ std::string GdbServer::handleV(std::string& packet) {
|
||||
|
||||
std::string data(count, '\0');
|
||||
if (lseek(fd, offset, SEEK_SET) < 0) {
|
||||
return F_error();
|
||||
return {F_error(), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
int ret = read(fd, data.data(), count);
|
||||
if (ret < 0) {
|
||||
return F_error();
|
||||
return {F_error(), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
data.resize(ret);
|
||||
return F_data(ret, data);
|
||||
return {F_data(ret, data), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
if ((ss = match("vCont?"))) {
|
||||
return "vCont;c;C;s;t"; // We support continue, step and terminate
|
||||
return {"vCont;c;C;t;s;S;r", HandledPacketType::TYPE_ACK}; // We support continue, step and terminate
|
||||
// FIXME: We also claim to support continue with signal... because it's compulsory
|
||||
}
|
||||
if ((ss = match("vCont;"))) {
|
||||
@@ -443,51 +760,145 @@ std::string GdbServer::handleV(std::string& packet) {
|
||||
*ss >> std::hex >> thread;
|
||||
}
|
||||
|
||||
if (ss->fail() || !ss->eof()) {
|
||||
return "E00";
|
||||
if (ss->fail()) {
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'c':
|
||||
case 'c': {
|
||||
CTX->Run();
|
||||
return NO_REPLY;
|
||||
case 's':
|
||||
return {"", HandledPacketType::TYPE_ONLYACK};
|
||||
}
|
||||
case 's': {
|
||||
CTX->Step();
|
||||
return NO_REPLY;
|
||||
SendPacketPair({"OK", HandledPacketType::TYPE_ACK});
|
||||
std::ostringstream ss;
|
||||
ss << "T05thread:" << std::setfill('0') << std::setw(2) << std::hex << getpid() << ";core:2c;";
|
||||
|
||||
SendPacketPair({ss.str(), HandledPacketType::TYPE_ACK});
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
case 't':
|
||||
CTX->ShouldStop = true;
|
||||
return NO_REPLY;
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
default:
|
||||
return "E00";
|
||||
return {"E00", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
}
|
||||
return "";
|
||||
return {"", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
std::string GdbServer::ProcessPacket(std::string &packet) {
|
||||
switch (packet[0]) {
|
||||
case '?':
|
||||
return "S00";
|
||||
GdbServer::HandledPacketType GdbServer::handleThreadOp(std::string &packet) {
|
||||
auto match = [&](const char *str) -> bool { return packet.rfind(str, 0) == 0; };
|
||||
|
||||
if (match("Hc")) {
|
||||
// Sets thread to this ID for stepping
|
||||
// This is deprecated and vCont should be used instead
|
||||
auto ss = std::istringstream(packet);
|
||||
ss.seekg(std::string("Hc").size());
|
||||
ss >> std::hex >> CurrentDebuggingThread;
|
||||
|
||||
CTX->Pause();
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
if (match("Hg")) {
|
||||
// Sets thread for "other" operations
|
||||
auto ss = std::istringstream(packet);
|
||||
ss.seekg(std::string("Hg").size());
|
||||
ss >> std::hex >> CurrentDebuggingThread;
|
||||
|
||||
// This must return quick otherwise IDA complains
|
||||
CTX->Pause();
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
return {"", HandledPacketType::TYPE_UNKNOWN};
|
||||
}
|
||||
|
||||
GdbServer::HandledPacketType GdbServer::handleBreakpoint(std::string &packet) {
|
||||
auto ss = std::istringstream(packet);
|
||||
|
||||
bool Set{};
|
||||
uint64_t Addr;
|
||||
uint64_t Type;
|
||||
Set = ss.get() == 'Z';
|
||||
|
||||
ss >> std::hex >> Addr;
|
||||
ss.get(); // discard comma
|
||||
ss >> std::hex >> Type;
|
||||
|
||||
CTX->Pause();
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
|
||||
GdbServer::HandledPacketType GdbServer::ProcessPacket(std::string &packet) {
|
||||
switch (packet[0]) {
|
||||
case '?': {
|
||||
// Indicates the reason that the thread has stopped
|
||||
// Behaviour changes if the target is in non-stop mode
|
||||
// Binja doesn't support S response here
|
||||
//return {"S00", HandledPacketType::TYPE_ACK};
|
||||
std::ostringstream ss;
|
||||
ss << "T00thread:" << std::setfill('0') << std::setw(2) << std::hex << getpid() << ";core:2c;";
|
||||
|
||||
return {ss.str(), HandledPacketType::TYPE_ACK};
|
||||
}
|
||||
case 'g':
|
||||
return readRegs();
|
||||
return {readRegs(), HandledPacketType::TYPE_ACK};
|
||||
case 'p':
|
||||
return readReg(packet);
|
||||
case 'q':
|
||||
return handleQuery(packet);
|
||||
case 'Q':
|
||||
return handleQuery(packet);
|
||||
case 'v':
|
||||
return handleV(packet);
|
||||
return handleV(packet);
|
||||
case 'm': // Memory read
|
||||
case 'M': // Memory write
|
||||
return handleMemory(packet);
|
||||
return handleMemory(packet);
|
||||
case 'H': // Sets thread for subsequent operations
|
||||
return handleThreadOp(packet);
|
||||
case '!': // Enable extended mode
|
||||
case 'T': // Is a thread alive?
|
||||
return {"OK", HandledPacketType::TYPE_ACK};
|
||||
case 'Z': // Inserts breakpoint or watchpoint
|
||||
return handleBreakpoint(packet);
|
||||
case 'k': // Kill the process
|
||||
CTX->ShouldStop = true;
|
||||
CTX->Pause(); // Block until exit
|
||||
return {"", HandledPacketType::TYPE_NONE};
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
return {"", HandledPacketType::TYPE_UNKNOWN};
|
||||
}
|
||||
}
|
||||
|
||||
void GdbServer::SendPacketPair(HandledPacketType response) {
|
||||
std::lock_guard lk(sendMutex);
|
||||
if (response.TypeResponse == HandledPacketType::TYPE_ACK ||
|
||||
response.TypeResponse == HandledPacketType::TYPE_ONLYACK) {
|
||||
SendACK(*CommsStream, false);
|
||||
}
|
||||
else if (response.TypeResponse == HandledPacketType::TYPE_NACK ||
|
||||
response.TypeResponse == HandledPacketType::TYPE_ONLYNACK) {
|
||||
SendACK(*CommsStream, true);
|
||||
}
|
||||
|
||||
if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) {
|
||||
SendPacket(*CommsStream, "");
|
||||
}
|
||||
else if (response.TypeResponse != HandledPacketType::TYPE_ONLYNACK &&
|
||||
response.TypeResponse != HandledPacketType::TYPE_ONLYACK &&
|
||||
response.TypeResponse != HandledPacketType::TYPE_NONE) {
|
||||
SendPacket(*CommsStream, response.Response);
|
||||
}
|
||||
}
|
||||
|
||||
void GdbServer::GdbServerLoop() {
|
||||
while (!CTX->ShouldStop) {
|
||||
CommsStream = OpenSocket();
|
||||
|
||||
std::string responce;
|
||||
HandledPacketType response{};
|
||||
|
||||
// Outer server loop. Handles packet start, ACK/NAK and break
|
||||
|
||||
@@ -496,13 +907,10 @@ void GdbServer::GdbServerLoop() {
|
||||
switch (c) {
|
||||
case '$': {
|
||||
std::string packet = ReadPacket(*CommsStream);
|
||||
responce = ProcessPacket(packet);
|
||||
{
|
||||
std::lock_guard lk(sendMutex);
|
||||
SendPacket(*CommsStream, responce);
|
||||
}
|
||||
if (responce == "") {
|
||||
LogMan::Msg::D("Unknown packet %s", packet.c_str());
|
||||
response = ProcessPacket(packet);
|
||||
SendPacketPair(response);
|
||||
if (response.TypeResponse == HandledPacketType::TYPE_UNKNOWN) {
|
||||
LogMan::Msg::D("Unknown packet %s", packet.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -512,14 +920,17 @@ void GdbServer::GdbServerLoop() {
|
||||
case '-':
|
||||
// NAK, Resend requested
|
||||
{
|
||||
std::lock_guard lk(sendMutex);
|
||||
SendPacket(*CommsStream, responce);
|
||||
std::lock_guard lk(sendMutex);
|
||||
SendPacket(*CommsStream, response.Response);
|
||||
}
|
||||
break;
|
||||
case '\x03': // ASCII EOT
|
||||
LogMan::Msg::D("GdbServer: Break");
|
||||
case '\x03': { // ASCII EOT
|
||||
CTX->Pause();
|
||||
std::ostringstream ss;
|
||||
ss << "T02thread:" << std::setfill('0') << std::setw(2) << std::hex << getpid() << ";core:2c;";
|
||||
SendPacketPair({ss.str(), HandledPacketType::TYPE_ACK});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogMan::Msg::D("GdbServer: Unexpected byte %c (%02x)", c, c);
|
||||
}
|
||||
@@ -529,7 +940,7 @@ void GdbServer::GdbServerLoop() {
|
||||
std::lock_guard lk(sendMutex);
|
||||
CommsStream.release();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GdbServer::StartThread() {
|
||||
@@ -570,11 +981,10 @@ std::unique_ptr<std::iostream> GdbServer::OpenSocket() {
|
||||
|
||||
// Block until a connection arrives
|
||||
|
||||
LogMan::Msg::E("GdbServer, waiting for connection on localhost:8086");
|
||||
LogMan::Msg::I("GdbServer, waiting for connection on localhost:8086");
|
||||
listen(sockfd, 1);
|
||||
|
||||
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
|
||||
LogMan::Msg::E("Connected");
|
||||
|
||||
return std::make_unique<NetStream>(new_fd);
|
||||
}
|
||||
|
||||
+29
-6
@@ -21,19 +21,42 @@ private:
|
||||
std::string ReadPacket(std::iostream &stream);
|
||||
void SendPacket(std::ostream &stream, std::string packet);
|
||||
|
||||
std::string ProcessPacket(std::string &packet);
|
||||
std::string handleQuery(std::string &packet);
|
||||
std::string handleXfer(std::string &packet);
|
||||
std::string handleMemory(std::string &packet);
|
||||
std::string handleV(std::string& packet);
|
||||
void SendACK(std::ostream &stream, bool NACK);
|
||||
|
||||
struct HandledPacketType {
|
||||
std::string Response{};
|
||||
enum ResponseType {
|
||||
TYPE_NONE,
|
||||
TYPE_UNKNOWN,
|
||||
TYPE_ACK,
|
||||
TYPE_NACK,
|
||||
TYPE_ONLYACK,
|
||||
TYPE_ONLYNACK,
|
||||
};
|
||||
ResponseType TypeResponse{};
|
||||
};
|
||||
|
||||
void SendPacketPair(HandledPacketType packetPair);
|
||||
HandledPacketType ProcessPacket(std::string &packet);
|
||||
HandledPacketType handleQuery(std::string &packet);
|
||||
HandledPacketType handleXfer(std::string &packet);
|
||||
HandledPacketType handleMemory(std::string &packet);
|
||||
HandledPacketType handleV(std::string& packet);
|
||||
HandledPacketType handleThreadOp(std::string &packet);
|
||||
HandledPacketType handleBreakpoint(std::string &packet);
|
||||
HandledPacketType handleProgramOffsets();
|
||||
|
||||
std::string readRegs();
|
||||
HandledPacketType readReg(std::string& packet);
|
||||
|
||||
FEXCore::Context::Context *CTX;
|
||||
std::thread gdbServerThread;
|
||||
uint64_t data_offset;
|
||||
std::unique_ptr<std::iostream> CommsStream;
|
||||
std::mutex sendMutex;
|
||||
bool SettingNoAckMode{false};
|
||||
bool NoAckMode{false};
|
||||
std::string ThreadString{};
|
||||
uint32_t CurrentDebuggingThread{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user