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);