diff --git a/examples/simple.d b/examples/simple.d index 969ea05..736f4f0 100644 --- a/examples/simple.d +++ b/examples/simple.d @@ -1,4 +1,4 @@ -/// Loop on exceptions and continue whenever possible. +/// Minimal example that loops until the first fault is fault. /// /// Authors: dd86k /// Copyright: © dd86k @@ -6,13 +6,12 @@ module examples.simple; import core.stdc.stdio; -import core.stdc.stdlib; -import core.stdc.ctype : isprint; +import core.stdc.stdlib : exit; import adbg; -extern (C): -__gshared: -private: // Shuts up dscanner +extern (C): __gshared: private: + +int putchar(int); adbg_disassembler_t *dis; @@ -22,49 +21,62 @@ void die(int code = 0, const(char) *reason = null) { exit(code); } -int choice(const(char) *msg) { - printf("\n%s: ", msg); -LINPUT: int c = getchar; - if (isprint(c)) return c; - goto LINPUT; -} - void loop_handler(adbg_process_t *proc, int event, void *edata, void *udata) { - if (event != AdbgEvent.exception) + switch (event) { + case AdbgEvent.exception: + adbg_exception_t *ex = adbg_debugger_event_exception(edata); + assert(ex, "exception is null?"); + + // Assume one process, so don't print its PID + printf(`* exception="%s" oscode=`~ADBG_OS_ERROR_FORMAT, + adbg_exception_name(ex), ex.oscode); + + // Print fault address if available + if (ex.faultz) + printf(" address=%#llx", ex.fault_address); + + // If disassembler is available, disassemble one instruction + if (ex.faultz && dis) { + adbg_opcode_t op = void; + if (adbg_dis_process_once(dis, &op, proc, ex.fault_address)) + printf(` nodisasm=%s`, adbg_error_message); + else if (op.operands) + printf(` disasm="%s %s"`, op.mnemonic, op.operands); + else + printf(` disasm="%s"`, op.mnemonic); + } + + putchar('\n'); + + switch (ex.type) with (AdbgException) { + case Breakpoint, Step: + adbg_debugger_continue(proc); + return; + default: // Quit at first fault + *(cast(int*)udata) = 0; + } return; - - adbg_exception_t *ex = cast(adbg_exception_t*)edata; - printf( - "\n----------------------------------------\n"~ - "* EXCEPTION ("~ADBG_OS_ERROR_FORMAT~"): %s\n"~ - "* PID=%u TID=%u\n"~ - "* FAULT=%8llx", - ex.oscode, adbg_exception_name(ex), - proc.pid, proc.tid, - ex.fault_address - ); - - // If disassembler and fault address unavailable, - // skip printing disassembly - if (dis == null || ex.faultz == 0) - return; - - adbg_opcode_t op = void; - if (adbg_dis_process_once(dis, &op, proc, ex.fault_address)) { - printf(" (error:%s)\n", adbg_error_message); + case AdbgEvent.processExit: + int *oscode = adbg_debugger_event_process_exitcode(edata); + assert(oscode, "oscode is null?"); + printf("* exited with code %d\n", *oscode); + *(cast(int*)udata) = 0; return; + default: } - if (op.operands) - printf(" (%s %s)\n", op.mnemonic, op.operands); - else - printf(" (%s)\n", op.mnemonic); } int main(int argc, const(char) **argv) { if (argc < 2) die(1, "Missing path to executable"); - adbg_process_t *process = adbg_debugger_spawn(argv[1], 0); + // if additional arguments, they are for process to debug + const(char) **pargv = argc > 2 ? argv + 2 : null; + + adbg_process_t *process = + adbg_debugger_spawn(argv[1], + AdbgSpawnOpt.argv, pargv, + 0); if (process == null) die; @@ -72,30 +84,10 @@ int main(int argc, const(char) **argv) { if (dis == null) printf("warning: Disassembler unavailable (%s)\n", adbg_error_message()); -LOOP: // Process input - switch (choice("Action [?=Help]")) { - case '?': - puts( - "s - Instruction step.\n"~ - "c - Continue.\n"~ - "q - Quit." - ); - goto LOOP; - case 's': - puts("Stepping..."); - adbg_debugger_stepi(process); - break; - case 'c': - puts("Continuing..."); - adbg_debugger_continue(process); - break; - case 'q': - puts("Quitting..."); - return 0; - default: - goto LOOP; - } - - adbg_debugger_wait(process, &loop_handler, null); - goto LOOP; + int flags = 1; +Lcontinue: + if (adbg_debugger_wait(process, &loop_handler, &flags)) + die; + if (flags) goto Lcontinue; + return 0; } \ No newline at end of file diff --git a/src/adbg/debugger.d b/src/adbg/debugger.d index 411aa20..42d1b0e 100644 --- a/src/adbg/debugger.d +++ b/src/adbg/debugger.d @@ -8,6 +8,13 @@ module adbg.debugger; // TODO: adbg_debugger_spawn: Get/set default child stack size // TODO: High-level disassembly functions (e.g., from exception, etc.) +/* +version (linux) { + version (CRuntime_Glibc) + version = USE_CLONE; +} +*/ + public import adbg.process.base; import adbg.process.exception; import adbg.error; @@ -34,11 +41,9 @@ version (Windows) { import core.sys.posix.libgen : basename; import adbg.include.c.stdio; // snprintf; import adbg.platform : ADBG_CHILD_STACK_SIZE; -} - -version (linux) { - //version (CRuntime_Glibc) - //version = USE_CLONE; + + version (USE_CLONE) + import adbg.include.posix.mann; } extern (C): @@ -163,7 +168,8 @@ version (Windows) { // need to be filled. If the former is null, this acts as a shell, and // Windows will search for the external command, which is unwanted. - // Add argv is specified, we'll have to cram it into args + // Add argv is specified, and first item is set, + // we'll have to cram it into args if (argv && *argv) { // Get minimum total buffer size required int argc; @@ -212,8 +218,10 @@ version (Windows) { memset(&si, 0, si.sizeof); memset(&pi, 0, pi.sizeof); si.cb = STARTUPINFOA.sizeof; - DWORD flags = options & OPT_DEBUG_ALL ? DEBUG_PROCESS : DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS; - flags |= CREATE_DEFAULT_ERROR_MODE; + // CREATE_DEFAULT_ERROR_MODE + // The new process should not inherit the error mode of the caller. + DWORD flags = DEBUG_PROCESS | CREATE_DEFAULT_ERROR_MODE; + if (options & OPT_DEBUG_ALL) flags |= DEBUG_ONLY_THIS_PROCESS; // Create process if (CreateProcessA( @@ -226,8 +234,8 @@ version (Windows) { envp, // lpEnvironment dir, // lpCurrentDirectory &si, &pi) == FALSE) { - adbg_process_free(proc); adbg_oops(AdbgError.os); + adbg_process_free(proc); return null; } proc.hpid = pi.hProcess; @@ -251,13 +259,13 @@ version (Windows) { // Allocate arguments, include space for program and null terminator int argc; - while (argv[argc]) ++argc; + if (argv) while (argv[argc]) ++argc; version(Trace) trace("argc=%d", argc); proc.argv = cast(char**)malloc((argc + 2) * size_t.sizeof); if (proc.argv == null) { version(Trace) trace("mmap=%s", strerror(errno)); - adbg_process_free(proc); adbg_oops(AdbgError.os); + adbg_process_free(proc); return null; } proc.argv[0] = cast(char*)path; @@ -514,8 +522,6 @@ int adbg_debugger_detach(adbg_process_t *proc) { proc.creation = AdbgCreation.unloaded; proc.status = AdbgProcStatus.unloaded; - scope(exit) adbg_process_free(proc); - version (Windows) { if (DebugActiveProcessStop(proc.pid) == FALSE) return adbg_oops(AdbgError.os); @@ -554,7 +560,7 @@ int* adbg_debugger_event_process_exitcode(void *edata) { return null; } adbg_debugger_event_t *event = cast(adbg_debugger_event_t*)edata; - if (event.type != AdbgEvent.exception) { + if (event.type != AdbgEvent.processExit) { adbg_oops(AdbgError.invalidValue); return null; } @@ -580,6 +586,8 @@ int* adbg_debugger_event_process_exitcode(void *edata) { /// Returns: Error code. int adbg_debugger_wait(adbg_process_t *proc, void function(adbg_process_t*, int, void*, void*) ufunc, void *udata) { + version(Trace) trace("proc=%p ufunc=%p udata=%p", proc, ufunc, udata); + if (proc == null || ufunc == null) return adbg_oops(AdbgError.invalidArgument); if (proc.creation == AdbgCreation.unloaded) @@ -589,6 +597,7 @@ int adbg_debugger_wait(adbg_process_t *proc, adbg_debugger_event_t event = void; memset(&tracee, 0, adbg_process_t.sizeof); + tracee.creation = proc.creation; version (Windows) { DEBUG_EVENT de = void; @@ -629,18 +638,13 @@ Lwait: tracee.pid = de.dwProcessId; tracee.tid = de.dwThreadId; - // Fixes access to debugger, thread context functions. - // Especially when attaching, but should be standard with spawned-in processes too. // TODO: Get rid of hack to help multiprocess support // By opening/closing process+thread handles per debugger function that need it: // - Help with leaking handles // - Permissions, since each OS function need different permissions - tracee.hpid = OpenProcess( - PROCESS_ALL_ACCESS, - FALSE, de.dwProcessId); - tracee.htid = OpenThread( - THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME, - FALSE, de.dwThreadId); + // HACK: To have access to debugger API + tracee.hpid = proc.hpid; + tracee.htid = proc.htid; } else version (Posix) { int wstatus = void; @@ -676,14 +680,12 @@ Lwait: } /// Disconnect and terminate the debuggee process. -/// -/// This function frees the process instance on success. -/// Params: tracee = Process. +/// Params: proc = Process. /// Returns: Error code. -int adbg_debugger_terminate(adbg_process_t *tracee) { - if (tracee == null) +int adbg_debugger_terminate(adbg_process_t *proc) { + if (proc == null) return adbg_oops(AdbgError.invalidArgument); - if (tracee.creation == AdbgCreation.unloaded || tracee.pid == 0) + if (proc.creation == AdbgCreation.unloaded || proc.pid == 0) return adbg_oops(AdbgError.debuggerUnattached); version (Windows) { @@ -691,47 +693,53 @@ version (Windows) { // Before using TerminateProcess, // ContinueDebugEvent(pid, tid, DBG_TERMINATE_PROCESS) // was used instead. I forgot where I saw that example. MSDN does not feature it. - if (TerminateProcess(tracee.hpid, DBG_TERMINATE_PROCESS) == FALSE) + if (TerminateProcess(proc.hpid, DBG_TERMINATE_PROCESS) == FALSE) return adbg_oops(AdbgError.os); } else version (Posix) { - if (kill(tracee.pid, SIGKILL) < 0) // PT_KILL is deprecated on Linux + if (kill(proc.pid, SIGKILL) < 0) // PT_KILL is deprecated on Linux return adbg_oops(AdbgError.os); } else static assert(0, "Implement adbg_debugger_terminate"); - adbg_process_free(tracee); + proc.status = AdbgProcStatus.unknown; + proc.creation = AdbgCreation.unloaded; return 0; } /// Make the debuggee process continue from its currently stopped state. -/// Params: tracee = Process. +/// Params: proc = Process. /// Returns: Error code. -int adbg_debugger_continue(adbg_process_t *tracee) { - if (tracee == null) +int adbg_debugger_continue(adbg_process_t *proc) { + version(Trace) trace("proc=%p", proc); + if (proc == null) return adbg_oops(AdbgError.invalidArgument); - if (tracee.creation == AdbgCreation.unloaded) + + version(Trace) trace("pid=%d state=%d", proc.pid, proc.status); + if (proc.creation == AdbgCreation.unloaded) return adbg_oops(AdbgError.debuggerUnattached); - if (tracee.status != AdbgProcStatus.paused) - return 0; - tracee.status = AdbgProcStatus.running; + switch (proc.status) with (AdbgProcStatus) { + case loaded, paused: break; + default: return adbg_oops(AdbgError.debuggerUnpaused); + } version (Windows) { - if (ContinueDebugEvent(tracee.pid, tracee.tid, DBG_CONTINUE) == FALSE) { - tracee.status = AdbgProcStatus.unknown; + if (ContinueDebugEvent(proc.pid, proc.tid, DBG_CONTINUE) == FALSE) { + proc.status = AdbgProcStatus.unknown; return adbg_oops(AdbgError.os); } } else version (linux) { - if (ptrace(PT_CONT, tracee.pid, null, null) < 0) { - tracee.status = AdbgProcStatus.unknown; + if (ptrace(PT_CONT, proc.pid, null, null) < 0) { + proc.status = AdbgProcStatus.unknown; return adbg_oops(AdbgError.os); } } else version (Posix) { - if (ptrace(PT_CONTINUE, tracee.pid, null, 0) < 0) { - tracee.status = AdbgProcStatus.unknown; + if (ptrace(PT_CONTINUE, proc.pid, null, 0) < 0) { + proc.status = AdbgProcStatus.unknown; return adbg_oops(AdbgError.os); } } else static assert(0, "Implement adbg_debugger_continue"); + proc.status = AdbgProcStatus.running; return 0; } @@ -776,12 +784,14 @@ version (Windows) { } return 0; -} else { +} else version (Posix) { if (ptrace(PT_STEP, proc.pid, null, 0) < 0) { proc.status = AdbgProcStatus.unknown; return adbg_oops(AdbgError.os); } return 0; +} else { + return adbg_oops(AdbgError.unimplemented); } } \ No newline at end of file diff --git a/src/adbg/process/base.d b/src/adbg/process/base.d index 4ad761f..741fdd6 100644 --- a/src/adbg/process/base.d +++ b/src/adbg/process/base.d @@ -95,6 +95,7 @@ version (linux) { } void adbg_process_free(adbg_process_t *proc) { + version(Trace) trace("proc=%p", proc); if (proc == null) return; version (Windows) { @@ -102,9 +103,11 @@ void adbg_process_free(adbg_process_t *proc) { CloseHandle(proc.hpid); CloseHandle(proc.htid); } + version (linux) { + if (proc.mhandle) close(proc.mhandle); + } version (Posix) { if (proc.argv) free(proc.argv); - version (linux) if (proc.mhandle) close(proc.mhandle); } adbg_list_free(proc.thread_list); free(proc);