Skip to content

Commit

Permalink
Improve wait macros and add "exitProcess" event type
Browse files Browse the repository at this point in the history
- Add support for exitProcess event type on Windows and Posix platforms
- Update wait.h macros for Linux and FreeBSD
- Simplify wait signal filtering on Posix platforms
- Add functions to get event data
  • Loading branch information
dd86k committed Sep 18, 2024
1 parent 73f0c45 commit 08e7179
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 188 deletions.
12 changes: 9 additions & 3 deletions debugger/shell.d
Original file line number Diff line number Diff line change
Expand Up @@ -603,11 +603,12 @@ void shell_event_disassemble(size_t address, int count = 1, bool showAddress = t
void shell_event_exception(adbg_process_t *proc, int event, void *edata, void *udata) {
switch (event) with (AdbgEvent) {
case exception:
adbg_exception_t *ex = cast(adbg_exception_t*)edata;
adbg_exception_t *ex = adbg_debugger_event_exception(edata);
assert(ex);

printf("* Process %d (thread %d) stopped\n"~
" Reason : %s ("~ADBG_OS_ERROR_FORMAT~")\n",
ex.pid, ex.tid,
proc.pid, proc.tid,
adbg_exception_name(ex), ex.oscode);

// No fault address available
Expand Down Expand Up @@ -647,13 +648,18 @@ void shell_event_exception(adbg_process_t *proc, int event, void *edata, void *u
printf(" %s", op.operands);
putchar('\n');
return;
case processExit:
int *exitcode = adbg_debugger_event_process_exitcode(edata);
assert(exitcode);
printf("* Process exited with code %d\n", *exitcode);
return;
default:
}
}

void shell_event_help(immutable(command2_t) *command) {
// Print header
int p = 34;
int p = 34; // horizontal alignment padding
for (size_t i; i < command.names.length; ++i) {
if (i) {
printf(", ");
Expand Down
4 changes: 2 additions & 2 deletions examples/simple.d
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void loop_handler(adbg_process_t *proc, int event, void *edata, void *udata) {
"* PID=%u TID=%u\n"~
"* FAULT=%8llx",
ex.oscode, adbg_exception_name(ex),
ex.pid, ex.tid,
proc.pid, proc.tid,
ex.fault_address
);

Expand Down Expand Up @@ -96,6 +96,6 @@ LOOP: // Process input
goto LOOP;
}

adbg_debugger_wait(process, &loop_handler);
adbg_debugger_wait(process, &loop_handler, null);
goto LOOP;
}
204 changes: 103 additions & 101 deletions src/adbg/debugger.d
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
/// License: BSD-3-Clause-Clear
module adbg.debugger;

// TODO: adbg_debugger_spawn: Get default child stack size
// TODO: adbg_debugger_spawn: Get/set default child stack size
// TODO: High-level disassembly functions (e.g., from exception, etc.)

public import adbg.process.base;
import adbg.process.exception;
Expand Down Expand Up @@ -45,6 +46,7 @@ extern (C):
/// Debugging events
enum AdbgEvent {
exception,
processExit,
}

version (Posix)
Expand Down Expand Up @@ -524,48 +526,93 @@ version (Windows) {
return 0;
}

private struct adbg_debugger_event_t {
AdbgEvent type;
union {
adbg_exception_t exception;
int exitcode;
}
}

adbg_exception_t* adbg_debugger_event_exception(void *edata) {
if (edata == null) {
adbg_oops(AdbgError.invalidArgument);
return null;
}
adbg_debugger_event_t *event = cast(adbg_debugger_event_t*)edata;
if (event.type != AdbgEvent.exception) {
adbg_oops(AdbgError.invalidValue);
return null;
}

return &event.exception;
}

int* adbg_debugger_event_process_exitcode(void *edata) {
if (edata == null) {
adbg_oops(AdbgError.invalidArgument);
return null;
}
adbg_debugger_event_t *event = cast(adbg_debugger_event_t*)edata;
if (event.type != AdbgEvent.exception) {
adbg_oops(AdbgError.invalidValue);
return null;
}

return &event.exitcode;
}

/// Continue execution of the process until a new debug event occurs.
///
/// This call is blocking.
///
/// Windows: Uses WaitForDebugEvent.
/// Posix: Uses ptrace(2) and waitpid(2), filters SIGCONT out.
/// It is highly recommended to use the callback's process instance for
/// debugging services, and to not call this function within the callback.
///
/// After the callback, this function returns.
///
/// Windows: Uses WaitForDebugEvent.
/// Posix: Uses waitpid(2) and ptrace(2), filters SIGCONT out.
/// Params:
/// tracee = Tracee instance.
/// userfunc = User function callback on event.
/// proc = Tracee instance.
/// ufunc = User function callback on event.
/// udata = User data passed to callback. Can be used to identify requests, for example.
/// Returns: Error code.
int adbg_debugger_wait(adbg_process_t *tracee,
void function(adbg_process_t *proc, int type, void *data, void *user) userfunc,
void *udata) {
if (tracee == null || userfunc == null)
int adbg_debugger_wait(adbg_process_t *proc,
void function(adbg_process_t*, int, void*, void*) ufunc, void *udata) {
if (proc == null || ufunc == null)
return adbg_oops(AdbgError.invalidArgument);
if (tracee.creation == AdbgCreation.unloaded)
if (proc.creation == AdbgCreation.unloaded)
return adbg_oops(AdbgError.debuggerUnattached);

//TODO: Urgent: The process instance should ideally not be modified
// Changing process information (e.g., PID) can in turn
// be bad news for other API functions (e.g., breakpoints).
//
// Children processes should be allocated and attached to exception
// (via adbg_exception_get_process?).
adbg_process_t tracee = void;
adbg_debugger_event_t event = void;

adbg_exception_t exception = void;
memset(&tracee, 0, adbg_process_t.sizeof);

version (Windows) {
DEBUG_EVENT de = void;
Lwait:
// Something bad happened
if (WaitForDebugEvent(&de, INFINITE) == FALSE) {
tracee.status = AdbgProcStatus.unloaded;
tracee.creation = AdbgCreation.unloaded;
return adbg_oops(AdbgError.os);
}

version(Trace) trace("EventCode=%#x", de.dwDebugEventCode);

// Filter events
switch (de.dwDebugEventCode) {
case EXCEPTION_DEBUG_EVENT: break;
case EXCEPTION_DEBUG_EVENT:
event.type = AdbgEvent.exception;
tracee.status = AdbgProcStatus.paused;
adbg_exception_translate(&event.exception, &de, null);
break;
case EXIT_PROCESS_DEBUG_EVENT:
event.type = AdbgEvent.processExit;
tracee.status = AdbgProcStatus.unknown;
event.exitcode = de.ExitProcess.dwExitCode;
break;
/*case CREATE_THREAD_DEBUG_EVENT:
case CREATE_PROCESS_DEBUG_EVENT:
case EXIT_THREAD_DEBUG_EVENT:
Expand All @@ -574,109 +621,63 @@ Lwait:
case OUTPUT_DEBUG_STRING_EVENT:
case RIP_EVENT:
goto default;*/
case EXIT_PROCESS_DEBUG_EVENT:
goto Lexited;
default:
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
goto 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, replace with refreshing thread list
// or just opening/closing thread handle when needed
tracee.tid = de.dwThreadId;
// 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);

tracee.status = AdbgProcStatus.paused;
adbg_exception_translate(&exception, &de, null);
} else version (Posix) {
int wstatus = void;
int stopsig = void;
Lwait:
tracee.pid = waitpid(-1, &wstatus, 0);

version (Trace) trace("wstatus=%#x", wstatus);

// Something bad happened
if (tracee.pid < 0) {
tracee.status = AdbgProcStatus.unloaded;
tracee.creation = AdbgCreation.unloaded;
// Something terrible happened
if (tracee.pid < 0)
return adbg_oops(AdbgError.crt);
}

// If exited or killed by signal, it's gone.
if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus))
goto Lexited;

// Skip glibc "continue" signals.
version (CRuntime_Glibc)
if (WIFCONTINUED(wstatus))
version(Trace) trace("wstatus=%#x", wstatus);

if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) { // Process exited or killed
event.type = AdbgEvent.processExit;
event.exitcode = WTERMSIG(wstatus);
} else if (WIFSTOPPED(wstatus)) { // Process stopped by signal
event.type = AdbgEvent.exception;
tracee.status = AdbgProcStatus.paused;
int sig = WSTOPSIG(wstatus);
adbg_exception_translate(&exception, &tracee.pid, &sig);
/*
} else if (WIFCONTINUED(wstatus)) { // Process continues, ignore these
goto Lwait;
*/
} else {
version (Trace) if (!WIFCONTINUED(wstatus)) trace("Unknown status code");
goto Lwait;

// Bits Description (Linux)
// 6:0 Signo that caused child to exit
// 0x7f if child stopped/continued
// or zero if child exited without signal
// 7 Core dumped
// 15:8 exit value (or returned main value)
// or signal that cause child to stop/continue
stopsig = WEXITSTATUS(wstatus);

// Get fault address
version (linux) switch (stopsig) {
case SIGCONT: goto Lwait;
// NOTE: si_addr is NOT populated under ptrace for SIGTRAP
// - linux does not fill si_addr on a SIGTRAP from a ptrace event
// - see sigaction(2)
// - linux *only* fills user_regs_struct for "user area"
// - see arch/x86/include/asm/user_64.h
// - "ptrace does not yet supply these. Someday...."
// - So yeah, debug registers and "fault_address" not filled
// - No access to ucontext_t from ptrace either
// - using EIP/RIP is NOT a good idea
// - IP ALWAYS point to NEXT instruction
// - First SIGTRAP does NOT contain int3
// - Windows does, though, and points to it
// - gdbserver and lldb never attempt to do such thing anyway
// - RIP-1 (x86) could *maybe* point to int3 or similar.
// - User area might have DR3, it does have "fault_address"
// NOTE: Newer D compilers fixed siginfo_t as a whole
// for version (linux). Noticed on DMD 2.103.1.
// Old glibc: ._sifields._sigfault.si_addr
// Old musl: .__si_fields.__sigfault.si_addr
// New: ._sifields._sigfault.si_addr & .si_addr()
// NOTE: .si_addr() emits linker errors on Musl platforms.
case SIGILL, SIGSEGV, SIGFPE, SIGBUS:
siginfo_t sig = void;
if (ptrace(PT_GETSIGINFO, tracee.pid, null, &sig) < 0) {
exception.fault_address = 0;
break;
}
exception.fault_address = cast(size_t)sig._sifields._sigfault.si_addr;
break;
// case SIGINT, SIGTERM, SIGABRT: //TODO: Killed?
default:
exception.fault_address = 0;
}
else exception.fault_address = 0;

tracee.status = AdbgProcStatus.paused;
adbg_exception_translate(&exception, &tracee.pid, &stopsig);
}

userfunc(tracee, AdbgEvent.exception, &exception, udata);
return 0;
} else static assert(0, "Implement adbg_debugger_wait");

Lexited:
tracee.status = AdbgProcStatus.unloaded;
tracee.creation = AdbgCreation.unloaded;
ufunc(&tracee, event.type, &event, udata);
return 0;
}

/// Disconnect and terminate the debuggee process.
///
/// This function frees the process instance on success.
/// Params: tracee = Process.
/// Returns: Error code.
int adbg_debugger_terminate(adbg_process_t *tracee) {
Expand All @@ -692,10 +693,11 @@ version (Windows) {
// was used instead. I forgot where I saw that example. MSDN does not feature it.
if (TerminateProcess(tracee.hpid, DBG_TERMINATE_PROCESS) == FALSE)
return adbg_oops(AdbgError.os);
} else {
} else version (Posix) {
if (kill(tracee.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);
return 0;
}
Expand Down Expand Up @@ -723,12 +725,12 @@ version (Windows) {
tracee.status = AdbgProcStatus.unknown;
return adbg_oops(AdbgError.os);
}
} else {
} else version (Posix) {
if (ptrace(PT_CONTINUE, tracee.pid, null, 0) < 0) {
tracee.status = AdbgProcStatus.unknown;
return adbg_oops(AdbgError.os);
}
}
} else static assert(0, "Implement adbg_debugger_continue");

return 0;
}
Expand Down
Loading

0 comments on commit 08e7179

Please sign in to comment.