From 2d52f710e49d60dea5cb9304dc86bc6a00c63f5b Mon Sep 17 00:00:00 2001 From: dd86k Date: Sat, 14 Sep 2024 16:06:43 -0400 Subject: [PATCH] Rework Context API (+) - shell: Remove "regs" command - shell: Add "registers" subcommand for "thread" - Remove register module and merge it into thread module - utils: Add adbg_list_clear --- debugger/shell.d | 136 ++++------ src/adbg/process/base.d | 23 +- src/adbg/process/package.d | 1 - src/adbg/process/register.d | 392 ---------------------------- src/adbg/process/thread.d | 501 ++++++++++++++++++++++++++++++++---- src/adbg/utils/list.d | 32 ++- 6 files changed, 546 insertions(+), 539 deletions(-) delete mode 100644 src/adbg/process/register.d diff --git a/debugger/shell.d b/debugger/shell.d index 88f2889..e6ef921 100644 --- a/debugger/shell.d +++ b/debugger/shell.d @@ -165,7 +165,6 @@ __gshared: adbg_process_t *process; adbg_disassembler_t *dis; -adbg_registers_t *registers; const(char)* last_spawn_exec; const(char)** last_spawn_argv; @@ -378,22 +377,6 @@ immutable command2_t[] shell_commands = [ &command_stepi, }, // - // Context - // - { - [ "regs" ], - "Lists register values.", - [ "[NAME]" ], - MODULE_DEBUGGER, CATEGORY_CONTEXT, - [ - { - SECTION_DESCRIPTION, - [ "Get list of registers and values from process." ] - } - ], - &command_regs, - }, - // // Memory // { @@ -476,7 +459,7 @@ immutable command2_t[] shell_commands = [ { [ "t", "thread" ], "Manage process threads.", - [ "ACTION" ], + [ "list", "TID registers" ], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, @@ -797,8 +780,6 @@ int command_detach(int argc, const(char) **argv) { return ShellError.alicedbg; } adbg_dis_close(dis); - free(registers); - return 0; } @@ -843,7 +824,6 @@ int command_kill(int argc, const(char) **argv) { return ShellError.alicedbg; puts("Process killed"); adbg_dis_close(dis); - free(registers); return 0; } @@ -856,42 +836,6 @@ int command_stepi(int argc, const(char) **argv) { return 0; } -int command_regs(int argc, const(char) **argv) { - if (process == null) - return ShellError.pauseRequired; - - if (registers == null) { - registers = adbg_registers_new(adbg_process_get_machine(process)); - if (registers == null) - return ShellError.alicedbg; - } - - adbg_registers_fill(registers, process); - - if (registers.count == 0) { - logerror("No registers available"); - return ShellError.unavailable; - } - - adbg_register_t *reg = registers.items.ptr; - const(char) *rselect = argc >= 2 ? argv[1] : null; - bool found; - for (size_t i; i < registers.count; ++i, ++reg) { - bool show = rselect == null || strcmp(rselect, reg.info.name) == 0; - if (show == false) continue; - char[20] normal = void, hexdec = void; - adbg_register_format(normal.ptr, 20, reg, AdbgRegFormat.dec); - adbg_register_format(hexdec.ptr, 20, reg, AdbgRegFormat.hexPadded); - printf("%-8s 0x%8s %s\n", reg.info.name, hexdec.ptr, normal.ptr); - found = true; - } - if (rselect && found == false) { - logerror("Register not found"); - return ShellError.invalidParameter; - } - return 0; -} - int command_memory(int argc, const(char) **argv) { if (argc < 2) { return ShellError.missingOption; @@ -991,27 +935,15 @@ int command_disassemble(int argc, const(char) **argv) { if (dis == null) return ShellError.unavailable; + // Need address + if (argc < 2) + return ShellError.missingArgument; + long uaddress = void; - if (argc > 1) { // Address given - if (unformat64(&uaddress, argv[1])) - return ShellError.unformat; - } else { // Address not given, get PC/eIP value - //TODO: dedicated function to get PC/IP/EIP/RIP value - // HACK: register list should be allocated on-demand in global space - AdbgMachine mach = adbg_process_get_machine(process); - adbg_registers_t regs = void; - //if (adbg_registers_config(®s, )) - // return ShellError.alicedbg; - if (adbg_registers_fill(®s, process)) - return ShellError.alicedbg; - // Assume EIP/RIP is in regs[0] - switch (mach) with (AdbgMachine) { - case i386: uaddress = regs.items[0].u32; break; - case amd64: uaddress = regs.items[0].u64; break; - default: return ShellError.unavailable; - } - } + if (unformat64(&uaddress, argv[1])) + return ShellError.unformat; + // Number of instruction, default to 10 int ucount = 10; if (argc >= 3) { if (unformat(&ucount, argv[2])) @@ -1226,21 +1158,57 @@ int command_thread(int argc, const(char) **argv) { if (argc < 2) return ShellError.missingArgument; + adbg_thread_t *thread = void; + const(char) *action = argv[1]; + // thread list - get a list of threads if (strcmp(action, "list") == 0) { - void *list = adbg_thread_list(process); - if (list == null) + if (adbg_thread_list_update(process)) return ShellError.alicedbg; puts("Threads:"); - int tid = void; - for (size_t i; (tid = adbg_thread_list_get(list, i)) != 0; ++i) - printf("%d\n", tid); + size_t i; + while ((thread = adbg_thread_list_get(process, i++)) != null) { + printf("%*d\n", -10, adbg_thread_id(thread)); + } + return 0; + } + + // Else: thread TID subcommand + if (argc < 3) // thread id + return ShellError.missingArgument; + + // Select thread + int tid = atoi(argv[1]); + size_t i; + adbg_thread_t *selected; + while ((thread = adbg_thread_list_get(process, i++)) != null) { + if (adbg_thread_id(thread) == tid) { + selected = thread; + break; + } + } + if (selected == null) + return ShellError.alicedbg; + + action = argv[2]; + if (strcmp(action, "registers") == 0) { + // Update its context + adbg_thread_context_update(process, selected); - adbg_thread_list_free(list); - } else { + i = 0; + adbg_register_t *register = void; + while ((register = adbg_register_get(thread, i++)) != null) { + char[20] dec = void, hex = void; + adbg_register_format(dec.ptr, 20, register, AdbgRegisterFormat.dec); + adbg_register_format(hex.ptr, 20, register, AdbgRegisterFormat.hexPadded); + printf("%*s 0x%*s %s\n", + -8, adbg_register_name(register), + 8, hex.ptr, + dec.ptr); + } + } else return ShellError.invalidParameter; - } return 0; } diff --git a/src/adbg/process/base.d b/src/adbg/process/base.d index f17787a..25ef6f9 100644 --- a/src/adbg/process/base.d +++ b/src/adbg/process/base.d @@ -5,17 +5,18 @@ /// License: BSD-3-Clause-Clear module adbg.process.base; -//TODO: Process Pause/Resume -// Windows: NtSuspendProcess/NtResumeProcess or SuspendThread/ResumeThread -// Linux: Send SIGSTOP/SIGCONT signals via kill(2) -//TODO: List threads of process (maybe in a module called threading.d) +// TODO: Process Pause/Resume +// Windows: NtSuspendProcess/NtResumeProcess or SuspendThread/ResumeThread +// Linux: Send SIGSTOP/SIGCONT signals via kill(2) +// TODO: List threads of process (maybe in a module called threading.d) import adbg.include.c.stdlib; // malloc, calloc, free, exit; import adbg.include.c.stdarg; import adbg.error; -import adbg.utils.strings; import adbg.process.exception : adbg_exception_t, adbg_exception_translate; import adbg.machines; +import adbg.utils.strings; +import adbg.utils.list : list_t, adbg_list_free; import core.stdc.string; version (Windows) { @@ -68,13 +69,13 @@ enum ADBG_PROCESS_NAME_LENGTH = 256; struct adbg_process_t { version (Windows) { // Original identifiers; Otherwise informal int pid; /// Process identificiation number - int tid; /// Thread identification number + deprecated int tid; /// Thread identification number HANDLE hpid; /// Process handle - HANDLE htid; /// Thread handle + deprecated HANDLE htid; /// Thread handle char *args; /// Saved arguments when process was launched // TODO: Deprecate and remove wow64 field // Make function to query process machine type - version (Win64) int wow64; + version (Win64) deprecated int wow64; } version (Posix) { pid_t pid; /// Process ID @@ -88,6 +89,8 @@ struct adbg_process_t { AdbgProcStatus status; /// Process' creation source. AdbgCreation creation; + /// List of threads + list_t *thread_list; //TODO: Deprecate and remove static buffer in process struct /// Process base module name. char[ADBG_PROCESS_NAME_LENGTH] name; @@ -102,6 +105,7 @@ void adbg_process_free(adbg_process_t *proc) { version (Posix) { if (proc.argv) free(proc.argv); } + adbg_list_free(proc.thread_list); free(proc); } @@ -283,9 +287,6 @@ version (Windows) { return 0; } } -unittest { - //TODO: Test one character buffers -} /// Get the current runtime machine platform. /// diff --git a/src/adbg/process/package.d b/src/adbg/process/package.d index 3f01295..0c6e4fb 100644 --- a/src/adbg/process/package.d +++ b/src/adbg/process/package.d @@ -7,7 +7,6 @@ module adbg.process; public import adbg.process.base, - adbg.process.register, adbg.process.exception, adbg.process.breakpoint, adbg.process.memory, diff --git a/src/adbg/process/register.d b/src/adbg/process/register.d deleted file mode 100644 index bbc91d4..0000000 --- a/src/adbg/process/register.d +++ /dev/null @@ -1,392 +0,0 @@ -/// Thread register and context handling. -/// -/// Authors: dd86k -/// Copyright: © dd86k -/// License: BSD-3-Clause-Clear -module adbg.process.register; - -import adbg.process.base : AdbgCreation, adbg_process_t; -import adbg.include.c.stdio : snprintf; -import adbg.include.c.stdlib; -import adbg.error; -import adbg.machines; - -// TODO: Support FPU registers (f32, f64, f80 types) -// TODO: Thread struct type, to hold its context, and other info - -version (Windows) { - import adbg.include.windows.wow64apiset; - import adbg.include.windows.winnt; - import core.sys.windows.winbase; -} else version (Posix) { - import adbg.include.linux.user; - import adbg.include.posix.ptrace; -} - -/// Number of registers, used for buffer -private enum REG_COUNT = 18; // Only x86 support for now - -extern (C): - -/* -enum AdbgRegister { - unknown, - pc, - ip = pc, - eip = pc, - rip = pc, -} -*/ - -/// Register size -enum AdbgRegType : ubyte { - u8, u16, u32, u64, - f32, f64 -} - -/// Register -enum AdbgRegFormat { - dec, - hex, - hexPadded, -} - -/// Register name and type. -struct adbg_register_info_t { - const(char) *name; /// Register name - AdbgRegType type; /// Register type (size) -} - -/// Register structure, designs a single register for UI ends to understand -struct adbg_register_t { - /// Register name and type. - adbg_register_info_t info; - union { // Data - ulong u64; /// Register data: ulong (u64) - uint u32; /// Register data: uint (u32) - ushort u16; /// Register data: ushort (u16) - ubyte u8; /// Register data: ubyte (u8) - double f64; /// Register data: double (f64) - float f32; /// Register data: float (f32) - } -} - -/// Represents a thread context structure with the register values once a -/// process is paused. -struct adbg_registers_t { - /// Register count in registers field. - ushort count; - /// Register population, this may depends by platform. - adbg_register_t[REG_COUNT] items; - // Associated machine with register set. - //AdbgMachine machine; -} - -/// Allocate a new set of registers to be populated. -/// Params: mach = Machine type, determining which set to allocate. -/// Returns: Register set, or null on errror. -adbg_registers_t* adbg_registers_new(AdbgMachine mach) { - adbg_registers_t* regs = cast(adbg_registers_t*)malloc(adbg_registers_t.sizeof); - if (regs == null) { - adbg_oops(AdbgError.crt); - return null; - } - if (adbg_registers_config(regs, mach)) { - free(regs); - return null; - } - return regs; -} - -void adbg_registers_free(adbg_registers_t *regs) { - if (regs) free(regs); -} - -int adbg_registers_config(adbg_registers_t *ctx, AdbgMachine mach) { - if (ctx == null) - return adbg_oops(AdbgError.invalidArgument); - - static immutable adbg_register_info_t[] regs_x86 = [ - { "eip", AdbgRegType.u32 }, - { "eflags", AdbgRegType.u32 }, - { "eax", AdbgRegType.u32 }, - { "ebx", AdbgRegType.u32 }, - { "ecx", AdbgRegType.u32 }, - { "edx", AdbgRegType.u32 }, - { "esp", AdbgRegType.u32 }, - { "ebp", AdbgRegType.u32 }, - { "esi", AdbgRegType.u32 }, - { "edi", AdbgRegType.u32 }, - ]; - static immutable adbg_register_info_t[] regs_x86_64 = [ - { "rip", AdbgRegType.u64 }, - { "rflags", AdbgRegType.u64 }, - { "rax", AdbgRegType.u64 }, - { "rbx", AdbgRegType.u64 }, - { "rcx", AdbgRegType.u64 }, - { "rdx", AdbgRegType.u64 }, - { "rsp", AdbgRegType.u64 }, - { "rbp", AdbgRegType.u64 }, - { "rsi", AdbgRegType.u64 }, - { "rdi", AdbgRegType.u64 }, - { "r8", AdbgRegType.u64 }, - { "r9", AdbgRegType.u64 }, - { "r10", AdbgRegType.u64 }, - { "r11", AdbgRegType.u64 }, - { "r12", AdbgRegType.u64 }, - { "r13", AdbgRegType.u64 }, - { "r14", AdbgRegType.u64 }, - { "r15", AdbgRegType.u64 }, - ]; - - immutable(adbg_register_info_t)[] regs = void; - - switch (mach) with (AdbgMachine) { - case i386: - regs = regs_x86; - break; - case amd64: - regs = regs_x86_64; - break; - default: - return adbg_oops(AdbgError.unimplemented); - } - - version (Trace) trace("regs.length=%d", cast(int)regs.length); - - for (size_t i; i < regs.length; ++i) - ctx.items[i].info = regs[i]; - ctx.count = cast(ushort)regs.length; - - return 0; -} - -/* -ulong* adbg_registers_get_u64(adbg_registers_t *ctx, AdbgRegister reg) { - -} -*/ - -int adbg_registers_fill(adbg_registers_t *ctx, adbg_process_t *tracee) { - version (Trace) trace("tracee=%p ctx=%p", ctx, tracee); - - if (tracee == null || ctx == null) - return adbg_oops(AdbgError.invalidArgument); - - if (tracee.creation == AdbgCreation.unloaded) - return adbg_oops(AdbgError.debuggerUnattached); - -version (Windows) { - CONTEXT winctx = void; - version (Win64) { - WOW64_CONTEXT winctxwow64 = void; - if (tracee.wow64) { - winctxwow64.ContextFlags = CONTEXT_ALL; - if (Wow64GetThreadContext(tracee.htid, &winctxwow64) == FALSE) { - return adbg_oops(AdbgError.os); - } - adbg_context_fill_wow64(ctx, &winctxwow64); - } else { - winctx.ContextFlags = CONTEXT_ALL; - if (GetThreadContext(tracee.htid, cast(LPCONTEXT)&winctx) == FALSE) { - return adbg_oops(AdbgError.os); - } - adbg_context_fill_win(ctx, &winctx); - } - } else { - winctx.ContextFlags = CONTEXT_ALL; - if (GetThreadContext(tracee.htid, cast(LPCONTEXT)&winctx) == FALSE) { - return adbg_oops(AdbgError.os); - } - adbg_context_fill_win(ctx, &winctx); - } - return 0; -} else version (linux) { - //TODO: PT_GETFPREGS - // PT_GETWMMXREGS - // PT_GET_THREAD_AREA - // PT_GETCRUNCHREGS - // PT_GETVFPREGS - // PT_GETHBPREGS - user_regs_struct u = void; - if (ptrace(PT_GETREGS, tracee.pid, null, &u) < 0) - return adbg_oops(AdbgError.os); - - adbg_context_fill_linux(ctx, &u); - return 0; -} else { - return adbg_oops(AdbgError.unimplemented); -} -} - -/// Format a register's value into a string buffer. -/// Errors: invalidOption for format. -/// Params: -/// buffer = Reference to text buffer. -/// len = Size of buffer. -/// reg = Register. -/// format = String format. -/// Returns: Number of characters written. -int adbg_register_format(char *buffer, size_t len, adbg_register_t *reg, AdbgRegFormat format) { - if (reg == null || buffer == null || len == 0) - return 0; - - // Get value - ulong n = void; - switch (reg.info.type) with (AdbgRegType) { - case u8: n = reg.u8; break; - case u16: n = reg.u16; break; - case u32: n = reg.u32; break; - case u64: n = reg.u64; break; - case f32: *cast(double*)n = reg.f32; break; - case f64: *cast(double*)n = reg.f64; break; - default: - adbg_oops(AdbgError.invalidOption); - return 0; - } - - // Get format - const(char) *sformat = void; - switch (format) with (AdbgRegFormat) { - case dec: - switch (reg.info.type) with (AdbgRegType) { - case u8, u16, u32, u64: - sformat = "%llu"; break; - case f32, f64: - sformat = "%f"; break; - default: - adbg_oops(AdbgError.assertion); - return 0; - } - break; - case hex: - sformat = "%llx"; - break; - case hexPadded: - switch (reg.info.type) with (AdbgRegType) { - case u8: sformat = "%02x"; break; - case u16: sformat = "%04x"; break; - case u32, f32: sformat = "%08x"; break; - case u64, f64: sformat = "%016llx"; break; - default: - adbg_oops(AdbgError.assertion); - return 0; - } - break; - default: - adbg_oops(AdbgError.invalidOption); - return 0; - } - - return snprintf(buffer, len, sformat, n); -} -unittest { - adbg_register_t reg = void; - reg.info.type = AdbgRegType.u16; - reg.u16 = 0x1234; - enum BUFSZ = 16; - char[BUFSZ] buffer = void; - int r = adbg_register_format(buffer.ptr, BUFSZ, ®, AdbgRegFormat.hex); - assert(r == 4); - assert(buffer[r] == 0); - assert(buffer[0..r] == "1234"); -} - -private: - -version (Windows) { - -// Populate exception_t.registers array from Windows' CONTEXT -void adbg_context_fill_win(adbg_registers_t *ctx, CONTEXT *winctx) { - version (Trace) trace("ctx=%p win=%p", ctx, winctx); - version (X86) { - ctx.items[0].u32 = winctx.Eip; - ctx.items[1].u32 = winctx.EFlags; - ctx.items[2].u32 = winctx.Eax; - ctx.items[3].u32 = winctx.Ebx; - ctx.items[4].u32 = winctx.Ecx; - ctx.items[5].u32 = winctx.Edx; - ctx.items[6].u32 = winctx.Esp; - ctx.items[7].u32 = winctx.Ebp; - ctx.items[8].u32 = winctx.Esi; - ctx.items[9].u32 = winctx.Edi; - } else version (X86_64) { - ctx.items[0].u64 = winctx.Rip; - ctx.items[1].u64 = winctx.EFlags; - ctx.items[2].u64 = winctx.Rax; - ctx.items[3].u64 = winctx.Rbx; - ctx.items[4].u64 = winctx.Rcx; - ctx.items[5].u64 = winctx.Rdx; - ctx.items[6].u64 = winctx.Rsp; - ctx.items[7].u64 = winctx.Rbp; - ctx.items[8].u64 = winctx.Rsi; - ctx.items[9].u64 = winctx.Rdi; - ctx.items[10].u64 = winctx.R8; - ctx.items[11].u64 = winctx.R9; - ctx.items[12].u64 = winctx.R10; - ctx.items[13].u64 = winctx.R11; - ctx.items[14].u64 = winctx.R12; - ctx.items[15].u64 = winctx.R13; - ctx.items[16].u64 = winctx.R14; - ctx.items[17].u64 = winctx.R15; - } -} - -version (Win64) { - version (X86_64) - void adbg_context_fill_wow64(adbg_registers_t *ctx, WOW64_CONTEXT *winctx) { - version (Trace) trace("ctx=%p win=%p", ctx, winctx); - ctx.items[0].u32 = winctx.Eip; - ctx.items[1].u32 = winctx.EFlags; - ctx.items[2].u32 = winctx.Eax; - ctx.items[3].u32 = winctx.Ebx; - ctx.items[4].u32 = winctx.Ecx; - ctx.items[5].u32 = winctx.Edx; - ctx.items[6].u32 = winctx.Esp; - ctx.items[7].u32 = winctx.Ebp; - ctx.items[8].u32 = winctx.Esi; - ctx.items[9].u32 = winctx.Edi; - } - - //TODO: Windows WoW64 AArch64 filler -} - -} else version (linux) { - -/// Populate exception_t.registers array from user_regs_struct -void adbg_context_fill_linux(adbg_registers_t *ctx, user_regs_struct *u) { - version (Trace) trace("ctx=%p u=%p", ctx, u); - version (X86) { - ctx.items[0].u32 = u.eip; - ctx.items[1].u32 = u.eflags; - ctx.items[2].u32 = u.eax; - ctx.items[3].u32 = u.ebx; - ctx.items[4].u32 = u.ecx; - ctx.items[5].u32 = u.edx; - ctx.items[6].u32 = u.esp; - ctx.items[7].u32 = u.ebp; - ctx.items[8].u32 = u.esi; - ctx.items[9].u32 = u.edi; - } else version (X86_64) { - ctx.items[0].u64 = u.rip; - ctx.items[1].u64 = u.eflags; - ctx.items[2].u64 = u.rax; - ctx.items[3].u64 = u.rbx; - ctx.items[4].u64 = u.rcx; - ctx.items[5].u64 = u.rdx; - ctx.items[6].u64 = u.rsp; - ctx.items[7].u64 = u.rbp; - ctx.items[8].u64 = u.rsi; - ctx.items[9].u64 = u.rdi; - ctx.items[10].u64 = u.r8; - ctx.items[11].u64 = u.r9; - ctx.items[12].u64 = u.r10; - ctx.items[13].u64 = u.r11; - ctx.items[14].u64 = u.r12; - ctx.items[15].u64 = u.r13; - ctx.items[16].u64 = u.r14; - ctx.items[17].u64 = u.r15; - } -} - -} // version linux diff --git a/src/adbg/process/thread.d b/src/adbg/process/thread.d index 31958ad..d512c52 100644 --- a/src/adbg/process/thread.d +++ b/src/adbg/process/thread.d @@ -1,76 +1,106 @@ /// Thread management. /// +/// Including stack frame and context information. +/// /// Authors: dd86k /// Copyright: © dd86k /// License: BSD-3-Clause-Clear module adbg.process.thread; +import adbg.machines : AdbgMachine; import adbg.error; import adbg.process.base; import adbg.utils.list; +import core.stdc.stdlib : malloc, free; +import core.stdc.stdio : snprintf; version (Windows) { import adbg.include.windows.tlhelp32; - import core.sys.windows.basetsd : HANDLE; - import core.sys.windows.winbase : INVALID_HANDLE_VALUE, CloseHandle; - import core.sys.windows.windef : FALSE; + import adbg.include.windows.wow64apiset; + import adbg.include.windows.winnt; + import core.sys.windows.basetsd; + import core.sys.windows.winbase; + import core.sys.windows.windef; } else version (linux) { import core.stdc.ctype : isdigit; import core.stdc.stdio : snprintf; import core.stdc.stdlib : atoi; import core.sys.posix.dirent; import core.sys.posix.libgen : basename; + import adbg.include.linux.user; + import adbg.include.posix.ptrace; +} + +extern (C): + +struct adbg_thread_t { +version (Windows) { + HANDLE handle; + int id; +} +version (linux) { + pid_t handle; +} + adbg_thread_context_t context; } /// Get a list of threads for target process. /// Params: process = Process. /// Returns: Thread list. -void* adbg_thread_list(adbg_process_t *process) { +int adbg_thread_list_update(adbg_process_t *process) { version (Windows) { - if (process == null) { - adbg_oops(AdbgError.invalidArgument); - return null; - } - if (process.pid == 0) { - adbg_oops(AdbgError.assertion); - return null; - } + if (process == null) + return adbg_oops(AdbgError.invalidArgument); + if (process.pid == 0) + return adbg_oops(AdbgError.uninitiated); - list_t *list = adbg_list_new(int.sizeof, 16); - if (list == null) { - adbg_oops(AdbgError.crt); - return null; - } + if (process.thread_list == null) + process.thread_list = adbg_list_new(adbg_thread_t.sizeof, 32); + if (process.thread_list == null) + return adbg_errno(); HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, process.pid); if (snap == INVALID_HANDLE_VALUE) { - adbg_oops(AdbgError.os); - adbg_list_free(list); - return null; + adbg_list_free(process.thread_list); + return adbg_oops(AdbgError.os); } scope(exit) CloseHandle(snap); THREADENTRY32 te32 = void; te32.dwSize = THREADENTRY32.sizeof; if (Thread32First(snap, &te32) == FALSE) { - adbg_oops(AdbgError.os); - adbg_list_free(list); - return null; + adbg_list_free(process.thread_list); + return adbg_oops(AdbgError.os); } + adbg_list_clear(process.thread_list); + adbg_thread_t t = void; + adbg_thread_context_config(&t.context, adbg_process_get_machine(process)); do { if (te32.th32OwnerProcessID != process.pid) continue; - list = adbg_list_add(list, &te32.th32ThreadID); - if (list == null) { - adbg_oops(AdbgError.crt); - adbg_list_free(list); - return null; + enum THREAD_ACCESS = SYNCHRONIZE | THREAD_QUERY_INFORMATION | + THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | + THREAD_SUSPEND_RESUME; + + // Get thread handle + t.handle = OpenThread(THREAD_ACCESS, FALSE, te32.th32ThreadID); + if (t.handle == INVALID_HANDLE_VALUE) { + adbg_list_free(process.thread_list); + return adbg_oops(AdbgError.os); + } + + // Set thread ID + t.id = te32.th32ThreadID; + process.thread_list = adbg_list_add(process.thread_list, &t); + if (process.thread_list == null) { + adbg_list_free(process.thread_list); + return adbg_errno(); } } while (Thread32Next(snap, &te32)); - return list; + return 0; } else version (linux) { if (process == null) { adbg_oops(AdbgError.invalidArgument); @@ -81,8 +111,9 @@ version (Windows) { return null; } - list_t *list = adbg_list_new(int.sizeof, 16); - if (list == null) { + if (process.thread_list == null) + process.thread_list = adbg_list_new(adbg_thread_t.sizeof, 32); + if (process.thread_list == null) { adbg_oops(AdbgError.crt); return null; } @@ -100,42 +131,61 @@ version (Windows) { adbg_oops(AdbgError.crt); return null; } - scope (exit) closedir(procfd); + scope(exit) closedir(procfd); // Go through kernel thread IDs + adbg_list_clear(process.thread_list); + adbg_thread_t t = void; + adbg_thread_context_config(&t.context, adbg_process_get_machine(process)); for (dirent *entry = void; (entry = readdir(procfd)) != null;) { version (Trace) trace("entry=%s", entry.d_name.ptr); - // readdir() includes "." and ".." + // readdir() includes "." and "..", skip them if (isdigit(entry.d_name[0]) == 0) continue; int tid = atoi( basename(entry.d_name.ptr) ); - list = adbg_list_add(list, &tid); - if (list == null) { - adbg_oops(AdbgError.crt); - adbg_list_free(list); + process.thread_list = adbg_list_add(process.thread_list, &tid); + if (process.thread_list == null) { + adbg_list_free(process.thread_list); return null; } } - return list; + return 0; } else { - adbg_oops(AdbgError.unimplemented); - return null; + return adbg_oops(AdbgError.unimplemented); } } -/// Get thread ID from list using index. +/// Get thread from list using index. /// Params: -/// list = Thread list instance. +/// proc = Process instance. /// index = Zero-based index. -/// Returns: Thread ID. Zero means an error occured or end of list. -int adbg_thread_list_get(void *list, size_t index) { - if (list == null) +/// Returns: Thread instance. On error, null. +adbg_thread_t* adbg_thread_list_get(adbg_process_t *proc, size_t index) { + if (proc == null) { + adbg_oops(AdbgError.invalidArgument); + return null; + } + // NOTE: adbg_list_get checks both list pointer and index and sets error + return cast(adbg_thread_t*)adbg_list_get(proc.thread_list, index); +} + +/// Get the thread ID out of this thread instance. +/// Params: thread = Thread instance. +/// Returns: Thread ID. On error, zero. +int adbg_thread_id(adbg_thread_t *thread) { + if (thread == null) { + adbg_oops(AdbgError.invalidArgument); return 0; - int *tid = cast(int*)adbg_list_get(cast(list_t*)list, index); - return tid ? *tid : 0; + } + version (Windows) return thread.id; + else version (linux) return cast(int)thread.handle; + else { + adbg_oops(AdbgError.unimplemented); + return 0; + } } /// Close the thread list instance. @@ -145,3 +195,360 @@ void adbg_thread_list_free(void *list) { return; adbg_list_free(cast(list_t*)list); } + +// +// Context management +// + +// TODO: Support FPU registers (f32, f64, f80 types) +// TODO: Thread struct type, to hold its context, and other info + +enum AdbgRegister { + // x86 + eip = 0, /// (x86) Extended Instruction Pointer + eflags = 1, /// (x86) Extended FLAGS + eax = 2, /// (x86) + ebx = 3, /// (x86) + ecx = 4, /// (x86) + edx = 5, /// (x86) + esp = 6, /// (x86) Extended Stack Pointer (top of stack) + ebp = 7, /// (x86) Extended Base Pointer (start of function) + esi = 8, /// (x86) Extended Source Index + edi = 9, /// (x86) Extended Destination Index + + // x86-64 + rip = 0, /// (x86-64) Re-extended Intruction Pointer + rflags = 1, /// (x86-64) Re-extended FLAGS + rax = 2, /// (x86-64) + rbx = 3, /// (x86-64) + rcx = 4, /// (x86-64) + rdx = 5, /// (x86-64) + rsp = 6, /// (x86-64) Re-extended Stack Pointer (top of stack) + rbp = 7, /// (x86-64) Re-extended Base Pointer (start of function) + rsi = 8, /// (x86-64) Re-extended Source Index + rdi = 9, /// (x86-64) Re-extended Destination Index + r8 = 10, /// (x86-64) + r9 = 11, /// (x86-64) + r10 = 12, /// (x86-64) + r11 = 13, /// (x86-64) + r12 = 14, /// (x86-64) + r13 = 15, /// (x86-64) + r14 = 16, /// (x86-64) + r15 = 17, /// (x86-64) +} + +/// Buffer size for register set. +private enum REG_COUNT = AdbgRegister.max + 1; + +/// Register size +enum AdbgRegisterType : ubyte { + u8, u16, u32, u64, + f32, f64 +} + +/// Register +enum AdbgRegisterFormat { + dec, + hex, + hexPadded, +} + +/// Register name and type. +struct adbg_register_info_t { + const(char) *name; /// Register name + AdbgRegisterType type; /// Register type (size) +} + +/// Register structure, designs a single register for UI ends to understand +struct adbg_register_t { + /// Register name and type. + adbg_register_info_t info; + union { // Data + ulong u64; /// Register data: ulong (u64) + uint u32; /// Register data: uint (u32) + ushort u16; /// Register data: ushort (u16) + ubyte u8; /// Register data: ubyte (u8) + double f64; /// Register data: double (f64) + float f32; /// Register data: float (f32) + } +} + +/// Represents a thread context structure with the register values once a +/// process is paused. +struct adbg_thread_context_t { + /// Register count in registers field. + size_t count; + /// Register population, this may depends by platform. + adbg_register_t[REG_COUNT] items; +} + +// Register sets +private immutable adbg_register_info_t[] regset_x86 = [ + { "eip", AdbgRegisterType.u32 }, + { "eflags", AdbgRegisterType.u32 }, + { "eax", AdbgRegisterType.u32 }, + { "ebx", AdbgRegisterType.u32 }, + { "ecx", AdbgRegisterType.u32 }, + { "edx", AdbgRegisterType.u32 }, + { "esp", AdbgRegisterType.u32 }, + { "ebp", AdbgRegisterType.u32 }, + { "esi", AdbgRegisterType.u32 }, + { "edi", AdbgRegisterType.u32 }, +]; +private immutable adbg_register_info_t[] regset_x86_64 = [ + { "rip", AdbgRegisterType.u64 }, + { "rflags", AdbgRegisterType.u64 }, + { "rax", AdbgRegisterType.u64 }, + { "rbx", AdbgRegisterType.u64 }, + { "rcx", AdbgRegisterType.u64 }, + { "rdx", AdbgRegisterType.u64 }, + { "rsp", AdbgRegisterType.u64 }, + { "rbp", AdbgRegisterType.u64 }, + { "rsi", AdbgRegisterType.u64 }, + { "rdi", AdbgRegisterType.u64 }, + { "r8", AdbgRegisterType.u64 }, + { "r9", AdbgRegisterType.u64 }, + { "r10", AdbgRegisterType.u64 }, + { "r11", AdbgRegisterType.u64 }, + { "r12", AdbgRegisterType.u64 }, + { "r13", AdbgRegisterType.u64 }, + { "r14", AdbgRegisterType.u64 }, + { "r15", AdbgRegisterType.u64 }, +]; + +adbg_register_t* adbg_register_get(adbg_thread_t *thread, size_t i) { + if (thread == null) { + adbg_oops(AdbgError.invalidArgument); + return null; + } + + if (i >= thread.context.count) { + adbg_oops(AdbgError.indexBounds); + return null; + } + + return &thread.context.items[i]; +} + +const(char)* adbg_register_name(adbg_register_t *register) { + if (register == null) { + adbg_oops(AdbgError.invalidArgument); + return null; + } + return register.info.name; +} + +// Configure register set, used internally +private +int adbg_thread_context_config(adbg_thread_context_t *ctx, AdbgMachine mach) { + if (ctx == null) + return adbg_oops(AdbgError.invalidArgument); + + // Select register set + immutable(adbg_register_info_t)[] regs = void; + switch (mach) with (AdbgMachine) { + case i386: regs = regset_x86; break; + case amd64: regs = regset_x86_64; break; + default: return adbg_oops(AdbgError.unimplemented); + } + + version (Trace) trace("regs.length=%d", cast(int)regs.length); + for (size_t i; i < regs.length; ++i) + ctx.items[i].info = regs[i]; + ctx.count = regs.length; + return 0; +} + +// Update the context for thread +int adbg_thread_context_update(adbg_process_t *proc, adbg_thread_t *thread) { + version (Trace) trace("tracee=%p ctx=%p", ctx, tracee); + + if (proc == null || thread == null) + return adbg_oops(AdbgError.invalidArgument); + +version (Win64) { + AdbgMachine mach = adbg_process_get_machine(proc); + switch (mach) { + case AdbgMachine.amd64: + CONTEXT_X64 winctx = void; // CONTEXT + winctx.ContextFlags = CONTEXT_ALL; + if (GetThreadContext(thread.handle, cast(LPCONTEXT)&winctx) == FALSE) { + return adbg_oops(AdbgError.os); + } + thread.context.items[0].u64 = winctx.Rip; + thread.context.items[1].u64 = winctx.EFlags; + thread.context.items[2].u64 = winctx.Rax; + thread.context.items[3].u64 = winctx.Rbx; + thread.context.items[4].u64 = winctx.Rcx; + thread.context.items[5].u64 = winctx.Rdx; + thread.context.items[6].u64 = winctx.Rsp; + thread.context.items[7].u64 = winctx.Rbp; + thread.context.items[8].u64 = winctx.Rsi; + thread.context.items[9].u64 = winctx.Rdi; + thread.context.items[10].u64 = winctx.R8; + thread.context.items[11].u64 = winctx.R9; + thread.context.items[12].u64 = winctx.R10; + thread.context.items[13].u64 = winctx.R11; + thread.context.items[14].u64 = winctx.R12; + thread.context.items[15].u64 = winctx.R13; + thread.context.items[16].u64 = winctx.R14; + thread.context.items[17].u64 = winctx.R15; + break; + case AdbgMachine.i386: // WoW64 process + WOW64_CONTEXT winctxwow64 = void; + winctxwow64.ContextFlags = CONTEXT_ALL; + if (Wow64GetThreadContext(thread.handle, &winctxwow64) == FALSE) { + return adbg_oops(AdbgError.os); + } + thread.context.items[0].u32 = winctxwow64.Eip; + thread.context.items[1].u32 = winctxwow64.EFlags; + thread.context.items[2].u32 = winctxwow64.Eax; + thread.context.items[3].u32 = winctxwow64.Ebx; + thread.context.items[4].u32 = winctxwow64.Ecx; + thread.context.items[5].u32 = winctxwow64.Edx; + thread.context.items[6].u32 = winctxwow64.Esp; + thread.context.items[7].u32 = winctxwow64.Ebp; + thread.context.items[8].u32 = winctxwow64.Esi; + thread.context.items[9].u32 = winctxwow64.Edi; + break; + default: + return adbg_oops(AdbgError.objectInvalidMachine); + } + return 0; +} else version (Win32) { + X86_NT_CONTEXT winctx = void; // CONTEXT + winctx.ContextFlags = CONTEXT_ALL; + if (GetThreadContext(tracee.htid, cast(LPCONTEXT)&winctx) == FALSE) { + return adbg_oops(AdbgError.os); + } + thread.context.items[0].u32 = winctx.Eip; + thread.context.items[1].u32 = winctx.EFlags; + thread.context.items[2].u32 = winctx.Eax; + thread.context.items[3].u32 = winctx.Ebx; + thread.context.items[4].u32 = winctx.Ecx; + thread.context.items[5].u32 = winctx.Edx; + thread.context.items[6].u32 = winctx.Esp; + thread.context.items[7].u32 = winctx.Ebp; + thread.context.items[8].u32 = winctx.Esi; + thread.context.items[9].u32 = winctx.Edi; + return 0; +} else version (linux) { + //TODO: PT_GETFPREGS + // PT_GETWMMXREGS + // PT_GET_THREAD_AREA + // PT_GETCRUNCHREGS + // PT_GETVFPREGS + // PT_GETHBPREGS + user_regs_struct u = void; + if (ptrace(PT_GETREGS, tracee.pid, null, &u) < 0) + return adbg_oops(AdbgError.os); + + version (X86) { + ctx.items[0].u32 = u.eip; + ctx.items[1].u32 = u.eflags; + ctx.items[2].u32 = u.eax; + ctx.items[3].u32 = u.ebx; + ctx.items[4].u32 = u.ecx; + ctx.items[5].u32 = u.edx; + ctx.items[6].u32 = u.esp; + ctx.items[7].u32 = u.ebp; + ctx.items[8].u32 = u.esi; + ctx.items[9].u32 = u.edi; + } else version (X86_64) { + ctx.items[0].u64 = u.rip; + ctx.items[1].u64 = u.eflags; + ctx.items[2].u64 = u.rax; + ctx.items[3].u64 = u.rbx; + ctx.items[4].u64 = u.rcx; + ctx.items[5].u64 = u.rdx; + ctx.items[6].u64 = u.rsp; + ctx.items[7].u64 = u.rbp; + ctx.items[8].u64 = u.rsi; + ctx.items[9].u64 = u.rdi; + ctx.items[10].u64 = u.r8; + ctx.items[11].u64 = u.r9; + ctx.items[12].u64 = u.r10; + ctx.items[13].u64 = u.r11; + ctx.items[14].u64 = u.r12; + ctx.items[15].u64 = u.r13; + ctx.items[16].u64 = u.r14; + ctx.items[17].u64 = u.r15; + } + return 0; +} else { + return adbg_oops(AdbgError.unimplemented); +} +} + +/// Format a register's value into a string buffer. +/// Errors: invalidOption for format. +/// Params: +/// buffer = Reference to text buffer. +/// len = Size of buffer. +/// reg = Register. +/// format = String format. +/// Returns: Number of characters written. +int adbg_register_format(char *buffer, size_t len, adbg_register_t *reg, AdbgRegisterFormat format) { + if (reg == null || buffer == null || len == 0) + return 0; + + // Get value + ulong n = void; + switch (reg.info.type) with (AdbgRegisterType) { + case u8: n = reg.u8; break; + case u16: n = reg.u16; break; + case u32: n = reg.u32; break; + case u64: n = reg.u64; break; + case f32: *cast(double*)n = reg.f32; break; + case f64: *cast(double*)n = reg.f64; break; + default: + adbg_oops(AdbgError.invalidOption); + return 0; + } + + // Get format + const(char) *sformat = void; + switch (format) with (AdbgRegisterFormat) { + case dec: + switch (reg.info.type) with (AdbgRegisterType) { + case u8, u16, u32, u64: + sformat = "%llu"; break; + case f32, f64: + sformat = "%f"; break; + default: + adbg_oops(AdbgError.assertion); + return 0; + } + break; + case hex: + sformat = "%llx"; + break; + case hexPadded: + switch (reg.info.type) with (AdbgRegisterType) { + case u8: sformat = "%02x"; break; + case u16: sformat = "%04x"; break; + case u32, f32: sformat = "%08x"; break; + case u64, f64: sformat = "%016llx"; break; + default: + adbg_oops(AdbgError.assertion); + return 0; + } + break; + default: + adbg_oops(AdbgError.invalidOption); + return 0; + } + + return snprintf(buffer, len, sformat, n); +} +unittest { + adbg_register_t reg = void; + reg.info.type = AdbgRegType.u16; + reg.u16 = 0x1234; + enum BUFSZ = 16; + char[BUFSZ] buffer = void; + int r = adbg_register_format(buffer.ptr, BUFSZ, ®, AdbgRegFormat.hex); + assert(r == 4); + assert(buffer[r] == 0); + assert(buffer[0..r] == "1234"); +} diff --git a/src/adbg/utils/list.d b/src/adbg/utils/list.d index 0ec4181..e334d29 100644 --- a/src/adbg/utils/list.d +++ b/src/adbg/utils/list.d @@ -15,9 +15,14 @@ struct list_t { size_t capacity; /// Capacity in number of items size_t itemsize; /// Size of one item size_t count; /// Current item count - void *buffer; + void *buffer; /// Item buffer } +/// Allocate a new dynamic list. +/// Params: +/// itemsize = Size of one item, in bytes. +/// capacity = Initial capacity size, in number of items. +/// Returns: List instance. On error, null is returned. list_t* adbg_list_new(size_t itemsize, size_t capacity) { size_t isize = itemsize * capacity; // initial size list_t *list = cast(list_t*)malloc(list_t.sizeof + isize); @@ -32,16 +37,28 @@ list_t* adbg_list_new(size_t itemsize, size_t capacity) { return list; } +/// Set the counter to zero. +/// +/// For performance reasons, this function does not clear memory. +/// Params: list = List instance. +void adbg_list_clear(list_t *list) { + if (list == null) + return; + list.count = 0; +} + /// Add an item to the list. /// /// Item data is copied into the list. /// /// If the returned list pointer is null, no content was changed, but an error occured. /// You are free to either close the list or continue with the current, unchanged list. +/// +/// Due to realloc(3), it is important to set the new list pointer. /// Params: /// list = List instance. /// item = Item. -/// Returns: List instance pointer. +/// Returns: List instance pointer. On error, null is returned. list_t* adbg_list_add(list_t *list, void *item) { if (list == null) { adbg_oops(AdbgError.invalidArgument); @@ -51,15 +68,15 @@ list_t* adbg_list_add(list_t *list, void *item) { // Increase capacity if (list.count >= list.capacity) { size_t newcapacity = list.capacity << 1; // double capacity + // NOTE: MSVC will always assign a new memory block list = cast(list_t*)realloc(list, list_t.sizeof + (list.itemsize * newcapacity)); if (list == null) { adbg_oops(AdbgError.crt); return null; } - // Assuming realloc(3) copied data to new region if address changes... + // realloc(3) should have copied data to new block // Only need to readjust buffer pointer - list.capacity = newcapacity; list.buffer = cast(void*)list + list_t.sizeof; } @@ -69,6 +86,11 @@ list_t* adbg_list_add(list_t *list, void *item) { return list; } +/// Get an item at this index. +/// Params: +/// list = List instance. +/// index = Item index. +/// Returns: Item pointer. On error, null is returned. void* adbg_list_get(list_t *list, size_t index) { if (list == null) { adbg_oops(AdbgError.invalidArgument); @@ -81,6 +103,8 @@ void* adbg_list_get(list_t *list, size_t index) { return list.buffer + (list.itemsize * index); } +/// Free the list. +/// Params: list = List instance. void adbg_list_free(list_t *list) { if (list == null) return;