Add some debugger memory utilities (#109)

* add some memory utilities

* run waitpid in a separate thread and support very simple breakpoints

* fix breakpoints

* add missing windows stub function

* fix error message on exit
This commit is contained in:
water111
2020-11-06 13:59:39 -05:00
committed by GitHub
parent f5ed2e6ad7
commit c09541fa98
20 changed files with 1430 additions and 57 deletions
+88 -28
View File
@@ -1,6 +1,7 @@
/*!
* @file xdbg.cpp
* Debugging utility library. This hides the platform specific details of the debugger.
* Nothing in here should hold state, that should all be managed in Debugger.
*/
#include <cstring>
@@ -44,6 +45,7 @@ std::string ThreadID::to_string() const {
/*!
* Get the ThreadID of whatever called this function.
* The runtime calls this to get the Thread to be debugged.
*/
ThreadID get_current_thread_id() {
return ThreadID(syscall(SYS_gettid));
@@ -78,26 +80,52 @@ bool attach_and_break(const ThreadID& tid) {
return false;
}
// we could technically hang here forever if runtime ignores the signal.
int status;
if (waitpid(tid.id, &status, 0) < 0) {
printf("[Debugger] Failed to waitpid: %s. The runtime is probably in a bad state now.\n",
strerror(errno));
return false;
}
// double check that we stopped for the right reason
if (!WIFSTOPPED(status)) {
printf("[Debugger] Failed to STOP: %s. The runtime is probably in a bad state now.\n",
strerror(errno));
return false;
}
return true;
}
}
/*!
* Has the given thread transitioned from running to stopped?
* If the thread has transitioned to stop, check_stopped should only return true once.
* If true, populates out with information about why it stopped.
* This shouldn't hang if the thread doesn't stop.
*/
bool check_stopped(const ThreadID& tid, SignalInfo* out) {
int status;
if (waitpid(tid.id, &status, WNOHANG) < 0) {
printf("[Debugger] Failed to waitpid: %s.\n", strerror(errno));
// assert(false); // todo, temp because I think we should never hit this.
return false;
}
if (WIFSTOPPED(status)) {
auto sig = WSTOPSIG(status);
if (out) {
switch (sig) {
case SIGSEGV:
out->kind = SignalInfo::SEGFAULT;
break;
case SIGFPE:
out->kind = SignalInfo::MATH_EXCEPTION;
break;
case SIGTRAP:
out->kind = SignalInfo::BREAK;
break;
default:
out->kind = SignalInfo::UNKNOWN;
}
}
return true;
}
return false;
}
/*!
* Open memory of target. Assumes we are already connected and halted.
* If successful returns true and populates out with a "handle" to the memory.
*/
bool open_memory(const ThreadID& tid, MemoryHandle* out) {
int fd = open(fmt::format("/proc/{}/mem", tid.id).c_str(), O_RDWR);
@@ -194,9 +222,47 @@ bool get_regs_now(const ThreadID& tid, Regs* out) {
return true;
}
/*!
* Set all registers now. Must be attached and stopped
*/
bool set_regs_now(const ThreadID& tid, const Regs& out) {
user regs = {};
if (ptrace(PTRACE_GETREGS, tid.id, nullptr, &regs) < 0) {
printf("[Debugger] Failed to PTRACE_GETREGS %s\n", strerror(errno));
return false;
}
regs.regs.rax = out.gprs[0];
regs.regs.rcx = out.gprs[1];
regs.regs.rdx = out.gprs[2];
regs.regs.rbx = out.gprs[3];
regs.regs.rsp = out.gprs[4];
regs.regs.rbp = out.gprs[5];
regs.regs.rsi = out.gprs[6];
regs.regs.rdi = out.gprs[7];
regs.regs.r8 = out.gprs[8];
regs.regs.r9 = out.gprs[9];
regs.regs.r10 = out.gprs[10];
regs.regs.r11 = out.gprs[11];
regs.regs.r12 = out.gprs[12];
regs.regs.r13 = out.gprs[13];
regs.regs.r14 = out.gprs[14];
regs.regs.r15 = out.gprs[15];
regs.regs.rip = out.rip;
if (ptrace(PTRACE_SETREGS, tid.id, nullptr, &regs) < 0) {
printf("[Debugger] Failed to PTRACE_SETREGS %s\n", strerror(errno));
return false;
}
// todo, set fprs.
return true;
}
/*!
* Break the given thread. Must be attached and running.
* Waits for the given thread to actually stop first.
* Does not wait for the thread to stop.
* Eventually check_stop should return true with a reason of BREAK, unless the target gets really
* lucky and manages to crash before the SIGTRAP reaches the target
*/
bool break_now(const ThreadID& tid) {
if (ptrace(PTRACE_INTERRUPT, tid.id, nullptr, nullptr) < 0) {
@@ -204,19 +270,6 @@ bool break_now(const ThreadID& tid) {
return false;
}
int status;
if (waitpid(tid.id, &status, 0) < 0) {
printf("[Debugger] Failed to waitpid: %s. The runtime is probably in a bad state now.\n",
strerror(errno));
return false;
}
if (!WIFSTOPPED(status)) {
printf("[Debugger] Failed to STOP: %s. The runtime is probably in a bad state now.\n",
strerror(errno));
return false;
}
return true;
}
@@ -295,6 +348,13 @@ bool write_goal_memory(const u8* src_buffer,
return false;
}
bool check_stopped(const ThreadID& tid, SignalInfo* out) {
return false;
}
bool set_regs_now(const ThreadID& tid, const Regs& out) {
return false;
}
#endif
const char* gpr_names[] = {"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
+30 -3
View File
@@ -19,6 +19,9 @@
namespace xdbg {
#ifdef __linux
/*!
* Identification for a thread.
*/
struct ThreadID {
pid_t id = 0;
@@ -28,6 +31,9 @@ struct ThreadID {
ThreadID() = default;
};
/*!
* Handle for the memory of a process.
*/
struct MemoryHandle {
int fd;
};
@@ -45,12 +51,18 @@ struct MemoryHandle {
};
#endif
/*!
* The info required to debug the target.
*/
struct DebugContext {
ThreadID tid;
uintptr_t base;
uint32_t s7;
ThreadID tid; //! The target's GOAL thread
uintptr_t base; //! The base address for the GOAL memory
uint32_t s7; //! The value of s7 (GOAL address)
};
/*!
* The x86-64 registers, including rip.
*/
struct Regs {
u64 gprs[16];
u128 xmms[16];
@@ -63,12 +75,25 @@ struct Regs {
std::string print_xmms_as_flt_vec() const;
};
/*!
* Information about why the target has stopped.
*/
struct SignalInfo {
enum Kind {
SEGFAULT, // access bad memory
BREAK, // hit a breakpoint or execute int3
MATH_EXCEPTION, // divide by zero
UNKNOWN // some other signal that is unsupported
} kind = UNKNOWN;
};
// Functions
ThreadID get_current_thread_id();
bool attach_and_break(const ThreadID& tid);
void allow_debugging();
bool detach_and_resume(const ThreadID& tid);
bool get_regs_now(const ThreadID& tid, Regs* out);
bool set_regs_now(const ThreadID& tid, const Regs& in);
bool break_now(const ThreadID& tid);
bool cont_now(const ThreadID& tid);
bool open_memory(const ThreadID& tid, MemoryHandle* out);
@@ -93,4 +118,6 @@ bool write_goal_value(T& value,
return write_goal_memory(&value, sizeof(value), goal_addr, context, handle);
}
bool check_stopped(const ThreadID& tid, SignalInfo* out);
} // namespace xdbg
+3
View File
@@ -11,6 +11,9 @@ constexpr int POINTER_SIZE = 4;
constexpr int BASIC_OFFSET = 4;
constexpr int STRUCTURE_ALIGNMENT = 16;
constexpr s32 GOAL_MAX_SYMBOLS = 0x2000;
constexpr s32 SYM_INFO_OFFSET = 0xff34;
enum class RegKind { GPR_64, FLOAT, INT_128, FLOAT_4X, INVALID };
constexpr u32 GOAL_NEW_METHOD = 0; // method ID of GOAL new