From c6ad0bbefbeeb4ca17d3deedf7324bb53feb0235 Mon Sep 17 00:00:00 2001 From: dd86k Date: Thu, 31 Oct 2024 16:21:49 -0400 Subject: [PATCH] Introduce stackframes, "error" command --- debugger/shell.d | 99 ++++++++++++++++++++------- src/adbg/debugger.d | 4 +- src/adbg/process/frame.d | 136 +++++++++++++++++++++++++++++++++++++ src/adbg/process/package.d | 1 + src/adbg/process/thread.d | 93 ++++++++++++++++++------- src/adbg/utils/list.d | 5 +- 6 files changed, 283 insertions(+), 55 deletions(-) create mode 100644 src/adbg/process/frame.d diff --git a/debugger/shell.d b/debugger/shell.d index 95f893b..f12afe8 100644 --- a/debugger/shell.d +++ b/debugger/shell.d @@ -241,10 +241,9 @@ struct command2_t { command2_help_section_t[] doc_sections; int function(int, const(char)**) entry; } -// NOTE: Called "commands_list" to avoid conflict with future "command_list" function // TODO: New commands // - b|breakpoint: Breakpoint management -// - sym: Symbol management +// - sym|symbols: Symbol management immutable command2_t[] shell_commands = [ // // Debugger @@ -476,17 +475,24 @@ immutable command2_t[] shell_commands = [ [ "[ITEM]" ], MODULE_SHELL, CATEGORY_SHELL, [ - ], &command_help, }, + { + [ "error" ], + "Show last error in greater details", + [], + MODULE_SHELL, CATEGORY_SHELL, + [ + ], + &command_error, + }, { [ "version" ], "Show Alicedbg version.", [], MODULE_SHELL, CATEGORY_SHELL, [ - ], &command_version, }, @@ -629,27 +635,39 @@ void shell_event_exception(adbg_process_t *proc, void *udata, adbg_exception_t * event_tid = adbg_exception_tid(exception); printf("* Process %d (thread %lld) stopped\n"~ - " Reason : %s ("~ERR_OSFMT~")\n", + "* Reason : %s ("~ERR_OSFMT~")\n", pid, event_tid, adbg_exception_name(exception), adbg_exception_orig_code(exception)); - // No fault address available - if (exception.fault_address == 0) - return; - - printf(" Address : 0x%llx\n", exception.fault_address); - - char[32] machbuf = void; - const(char)* mnemonic = void, operands = void; - if (shell_disassemble(cast(size_t)exception.fault_address, - null, - null, 0, - machbuf.ptr, 32, - &mnemonic, &operands)) - return; + // Fault address available, print it + if (exception.fault_address) { + printf(" Address : 0x%llx\n", exception.fault_address); + char[32] machbuf = void; + const(char)* mnemonic = void, operands = void; + // Disassembling instruction at fault address passed + if (shell_disassemble(cast(size_t)exception.fault_address, + null, + null, 0, + machbuf.ptr, 32, + &mnemonic, &operands) == 0) { + printf("* Machine : %s\n", machbuf.ptr); + printf("* Mnemonic: %s %s\n", mnemonic, operands); + } + } - printf(" Machine : %s\n", machbuf.ptr); - printf(" Mnemonic: %s %s\n", mnemonic, operands); + // Print callstack, if available + adbg_thread_t *thread = adbg_thread_new(event_tid); + if (thread) { + void *frames = adbg_frame_list(process, thread); + if (frames) { + puts("\n* Callstack (WIP):"); + adbg_stackframe_t *frame = void; + for (size_t i; (frame = adbg_frame_list_at(frames, i)) != null; ++i) { + printf("%3zu. %llx\n", i, frame.address); + } + adbg_frame_list_close(frames); + } + } } void shell_event_process_exit(adbg_process_t *proc, void *udata, int code) { printf("* Process %d exited with code %d\n", adbg_process_id(proc), code); @@ -1121,7 +1139,7 @@ int command_thread(int argc, const(char) **argv) { // thread list - get a list of threads if (strcmp(action, "list") == 0) { printf("Threads:"); - for (size_t i; (thread = adbg_thread_list_get(process, i)) != null; ++i) { + for (size_t i; (thread = adbg_thread_list_get(thrlist, i)) != null; ++i) { if (i) putchar(','); printf(" %lld", adbg_thread_id(thread)); } @@ -1139,7 +1157,7 @@ int command_thread(int argc, const(char) **argv) { return ShellError.alicedbg; action = argv[2]; - if (strcmp(action, "registers") == 0 || strcmp(action, "regs") == 0) { + if (*action == 'r' || strcmp(action, "registers") == 0) { int id; adbg_register_t *register = void; while ((register = adbg_register_by_id(thread, id++)) != null) { @@ -1151,10 +1169,28 @@ int command_thread(int argc, const(char) **argv) { -16, hex.ptr, dec.ptr); } - } else - return ShellError.invalidParameter; + return 0; + } else if (*action == 's' || strcmp(action, "stack") == 0) { + if (argc < 4) // stack action + return ShellError.missingArgument; + + action = argv[3]; + if (strcmp(action, "show") == 0) { // show thread id stack + void *frames = adbg_frame_list(process, thread); + if (frames == null) + return ShellError.alicedbg; + + adbg_stackframe_t *frame = void; + for (size_t i; (frame = adbg_frame_list_at(frames, i)) != null; ++i) { + printf("%3zu. %llx\n", i, frame.address); + } + + adbg_frame_list_close(frames); + return 0; + } + } - return 0; + return ShellError.invalidCommand; } int command_cd(int argc, const(char) **argv) { @@ -1174,6 +1210,17 @@ int command_pwd(int argc, const(char) **argv) { return 0; } +int command_error(int argc, const(char) **argv) { + printf( + "Message : %s\n"~ + "Code : %d\n"~ + "Caller : %s:L%d\n", + adbg_error_message(), + adbg_error_code(), + adbg_error_function(), adbg_error_line()); + return 0; +} + int command_version(int argc, const(char) **argv) { static immutable string LINE = `Alicedbg `~ADBG_VERSION~` `~TARGET_PLATFORM~`-`~TARGET_OS~`-`~TARGET_ENV; puts(LINE.ptr); diff --git a/src/adbg/debugger.d b/src/adbg/debugger.d index 0866b8c..ffacc6f 100644 --- a/src/adbg/debugger.d +++ b/src/adbg/debugger.d @@ -238,7 +238,7 @@ version (Windows) { adbg_oops(AdbgError.assertion); return null; } - version(Trace) trace("args='%s'", proc.args); + version(Trace) trace("args='%s'", proc.orig_args); } // TODO: Parse envp @@ -664,7 +664,7 @@ int adbg_debugger_udata(adbg_process_t *proc, void *udata) { /// Params: proc = Process instancied by the debugger. /// Returns: Error code. int adbg_debugger_wait(adbg_process_t *proc) { - version(Trace) trace("proc=%p udata=%p", proc, udata); + version(Trace) trace("proc=%p", proc); if (proc == null) return adbg_oops(AdbgError.invalidArgument); diff --git a/src/adbg/process/frame.d b/src/adbg/process/frame.d new file mode 100644 index 0000000..e00a7c9 --- /dev/null +++ b/src/adbg/process/frame.d @@ -0,0 +1,136 @@ +/// Process frame management. +/// +/// Stack frames, unwinding operations, etc. +/// +/// Authors: dd86k +/// Copyright: © dd86k +/// License: BSD-3-Clause-Clear +module adbg.process.frame; + +import adbg.error; +import adbg.machines; +import adbg.process.base; // for machine info +import adbg.process.thread; // for accessing thread information +import adbg.utils.list; + +// NOTE: Stack frame layouts +// +// # Windows +// +// There are basically two layouts: EBP frames and FPO frames. +// +// With EBP, EBP points to the previous value, EBP+4 points to the return +// address, and EBP+8 points to the first stack argument. +// +// With FPO (POGO?)... To find out. +// +// # Linux +// + +extern (C): + +struct adbg_stackframe_t { + int level; + ulong address; + // TODO: Function information + // TODO: Line information +} + +private +struct __machine_pc_reg { + AdbgMachine machine; + AdbgRegister[] set; +} +// Level 0: Current location, typically Program Counter +// Level 1: Frame Pointer if available +private +static immutable __machine_pc_reg[] stackregs = [ + { AdbgMachine.i386, [ AdbgRegister.x86_eip, AdbgRegister.x86_ebp ] }, + { AdbgMachine.amd64, [ AdbgRegister.amd64_rip, AdbgRegister.amd64_rbp ] }, + { AdbgMachine.arm, [ AdbgRegister.arm_pc, AdbgRegister.arm_fp ] }, + { AdbgMachine.aarch64, [ AdbgRegister.aarch64_pc, AdbgRegister.aarch64_fp ] }, +]; + +void* adbg_frame_list(adbg_process_t *process, adbg_thread_t *thread) { + if (process == null || thread == null) { + adbg_oops(AdbgError.invalidArgument); + return null; + } + + if (adbg_thread_context_update(process, thread)) + return null; + + // Map a set of registers to use at primary stackframe levels + AdbgMachine mach = adbg_process_machine(process); + immutable(AdbgRegister)[] registers; + foreach (ref regs; stackregs) { + if (mach == regs.machine) { + registers = regs.set; + break; + } + } + if (registers.length == 0) { + adbg_oops(AdbgError.unavailable); + return null; + } + + // New frame list + list_t *list = adbg_list_new(adbg_stackframe_t.sizeof, 8); + if (list == null) + return null; + + // Start with the first frame, which is always PC + // If we can't have that, then we cannot even obtain frames at all + adbg_register_t *reg = adbg_register_by_id(thread, registers[0]); + if (reg == null) { + adbg_oops(AdbgError.unavailable); + adbg_list_close(list); + return null; + } + ulong *address = cast(ulong*)adbg_register_value(reg); + if (address == null || *address == 0) { + adbg_list_close(list); + return null; + } + adbg_stackframe_t frame = void; + frame.level = 0; + frame.address = *address; + list = adbg_list_add(list, &frame); + if (list == null) { + adbg_list_close(list); + return null; + } + + // Add additional frames from additional registers + // As best as we can, otherwise just return the list + foreach (AdbgRegister r; registers[1..$]) { + reg = adbg_register_by_id(thread, r); + if (reg == null) + break; + address = cast(ulong*)adbg_register_value(reg); + if (address == null || *address == 0) + break; + + ++frame.level; // frame[0] is level=0, so increment + frame.address = *address; + list = adbg_list_add(list, &frame); + if (list == null) { + adbg_list_close(list); + return null; + } + } + + return list; +} + +size_t adbg_frame_list_count(void *list) { + return adbg_list_count(cast(list_t*)list); +} + +adbg_stackframe_t* adbg_frame_list_at(void *list, size_t index) { + return cast(adbg_stackframe_t*)adbg_list_get(cast(list_t*)list, index); +} + +void adbg_frame_list_close(void *list) { + adbg_list_close(cast(list_t*)list); +} \ No newline at end of file diff --git a/src/adbg/process/package.d b/src/adbg/process/package.d index 0c6e4fb..59013ad 100644 --- a/src/adbg/process/package.d +++ b/src/adbg/process/package.d @@ -10,4 +10,5 @@ public import adbg.process.exception, adbg.process.breakpoint, adbg.process.memory, + adbg.process.frame, adbg.process.thread; \ No newline at end of file diff --git a/src/adbg/process/thread.d b/src/adbg/process/thread.d index 984fc18..bf8831b 100644 --- a/src/adbg/process/thread.d +++ b/src/adbg/process/thread.d @@ -11,7 +11,8 @@ import adbg.machines : AdbgMachine; import adbg.error; import adbg.process.base; import adbg.utils.list; -import core.stdc.stdio : snprintf; +import core.stdc.stdio; // snprintf +import core.stdc.stdlib; // TODO: Consider NOT attaching thread list to process // Affects: adbg_thread_list_update @@ -45,6 +46,47 @@ struct adbg_thread_t { adbg_thread_context_t context; } +/// Make a new single thread instance by its id. +/// Params: id = Thread ID. +/// Returns: New thread instance +adbg_thread_t* adbg_thread_new(long id) { + adbg_thread_t *thread = cast(adbg_thread_t*)malloc(adbg_thread_t.sizeof); + if (thread == null) { + adbg_oops(AdbgError.crt); + return null; + } + thread.id = id; + return thread; +} + +void adbg_thread_close(adbg_thread_t *thread) { + if (thread == null) return; + free(thread); +} + +/// Get the thread ID out of this thread instance. +/// Params: thread = Thread instance. +/// Returns: Thread ID. On error, zero. +long adbg_thread_id(adbg_thread_t *thread) { + version (Trace) trace("thread=%p", thread); +version (Windows) { + if (thread == null) { + adbg_oops(AdbgError.invalidArgument); + return 0; + } + return thread.id; +} else version (Posix) { + if (thread == null) { + adbg_oops(AdbgError.invalidArgument); + return 0; + } + return thread.id; +} else { + adbg_oops(AdbgError.unimplemented); + return 0; +} +} + /// Update the list of threads for the target process. /// Params: process = Process instance. /// Returns: List instance; Otherwise null on error. @@ -212,7 +254,7 @@ adbg_thread_t* adbg_thread_list_get(void *list, size_t index) { /// id = Thread ID /// Returns: Thread instance. On error, null. adbg_thread_t* adbg_thread_list_by_id(void *list, long id) { - version (Trace) trace("process=%p id=%lld", process, id); + version (Trace) trace("list=%p id=%lld", list, id); if (list == null) { adbg_oops(AdbgError.invalidArgument); return null; @@ -227,29 +269,6 @@ adbg_thread_t* adbg_thread_list_by_id(void *list, long id) { return null; } -/// Get the thread ID out of this thread instance. -/// Params: thread = Thread instance. -/// Returns: Thread ID. On error, zero. -long adbg_thread_id(adbg_thread_t *thread) { - version (Trace) trace("thread=%p", thread); -version (Windows) { - if (thread == null) { - adbg_oops(AdbgError.invalidArgument); - return 0; - } - return thread.id; -} else version (Posix) { - if (thread == null) { - adbg_oops(AdbgError.invalidArgument); - return 0; - } - return thread.id; -} else { - adbg_oops(AdbgError.unimplemented); - return 0; -} -} - // // Context management // @@ -366,6 +385,7 @@ enum AdbgRegister { /// Register size enum AdbgRegisterType : ubyte { + none, u8, u16, u32, u64, f32, f64 } @@ -547,6 +567,7 @@ adbg_register_t* adbg_register_by_id(adbg_thread_t *thread, int id) { return &thread.context.items[index]; } +// get register name const(char)* adbg_register_name(adbg_register_t *register) { version (Trace) trace("register=%p", register); if (register == null) { @@ -558,6 +579,28 @@ const(char)* adbg_register_name(adbg_register_t *register) { return register.info.name; } +// get register data type +AdbgRegisterType adbg_register_type(adbg_register_t *register) { + version (Trace) trace("register=%p", register); + if (register == null) { + adbg_oops(AdbgError.invalidArgument); + return AdbgRegisterType.none; + } + assert(register.info); + assert(register.info.type); + return register.info.type; +} + +// get register data value +void* adbg_register_value(adbg_register_t *register) { + version (Trace) trace("register=%p", register); + if (register == null) { + adbg_oops(AdbgError.invalidArgument); + return null; + } + return ®ister.u64; +} + // Configure register set, used internally private int adbg_thread_context_config(adbg_thread_context_t *ctx, AdbgMachine mach) { diff --git a/src/adbg/utils/list.d b/src/adbg/utils/list.d index e2b3034..a85b99f 100644 --- a/src/adbg/utils/list.d +++ b/src/adbg/utils/list.d @@ -310,8 +310,9 @@ extern (D) unittest { /// Free the list. /// Params: list = List instance. -void adbg_list_free(list_t *list) { +void adbg_list_close(list_t *list) { if (list == null) return; free(list); -} \ No newline at end of file +} +alias adbg_list_free = adbg_list_close; \ No newline at end of file