Skip to content

Commit

Permalink
simple: Turn example as non-interactive, logger kind
Browse files Browse the repository at this point in the history
  • Loading branch information
dd86k committed Sep 19, 2024
1 parent 2e5d5dc commit 57c8f75
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 111 deletions.
122 changes: 57 additions & 65 deletions examples/simple.d
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/// Loop on exceptions and continue whenever possible.
/// Minimal example that loops until the first fault is fault.
///
/// Authors: dd86k <dd@dax.moe>
/// Copyright: © dd86k <dd@dax.moe>
/// License: BSD-3-Clause-Clear
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;

Expand All @@ -22,80 +21,73 @@ 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;

dis = adbg_dis_open(adbg_process_machine(process));
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;
}
100 changes: 55 additions & 45 deletions src/adbg/debugger.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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):
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -676,62 +680,66 @@ 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) {
// NOTE: ContinueDebugEvent
// 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;
}

Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit 57c8f75

Please sign in to comment.