diff --git a/src/adbg/debugger/process.d b/src/adbg/debugger/process.d index 7560fcd4..4efdb6d9 100644 --- a/src/adbg/debugger/process.d +++ b/src/adbg/debugger/process.d @@ -95,6 +95,7 @@ enum AdbgCreation : ubyte { spawned, } +//TODO: Deprecate and remove static buffer in process struct enum ADBG_PROCESS_NAME_LENGTH = 256; /// Represents an instance of a process. @@ -170,7 +171,7 @@ struct adbg_options_spawn_t { private int adbg_spawn_optionsv(adbg_options_spawn_t *opts, va_list list) { if (opts == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); memset(opts, 0, adbg_options_spawn_t.sizeof); @@ -232,79 +233,113 @@ int adbg_spawn2(adbg_process_t *tracee, const(char) *path, adbg_options_spawn_t if (tracee == null || path == null || opts == null) return adbg_oops(AdbgError.invalidArgument); - version (Windows) { - int bs = 0x4000; // buffer size, 16 KiB - char *b = cast(char*)malloc(bs); /// flat buffer - if (b == null) - return adbg_oops(AdbgError.crt); - - // Copy execultable path into buffer - ptrdiff_t bi = snprintf(b, bs, "%s ", path); - if (bi < 0) - return adbg_oops(AdbgError.crt); - - // Flatten argv - if (opts.argv) - bi += adbg_util_argv_flatten(b + bi, bs, opts.argv); - - //TODO: Parse envp - - // Create process - STARTUPINFOA si = void; - PROCESS_INFORMATION pi = void; - memset(&si, 0, si.sizeof); // memset faster than _init functions - memset(&pi, 0, pi.sizeof); // memset faster than _init functions - si.cb = STARTUPINFOA.sizeof; - // Not using DEBUG_ONLY_THIS_PROCESS because our posix - // counterpart is using -1 (all children) for waitpid. - if (CreateProcessA( - null, // lpApplicationName - b, // lpCommandLine - null, // lpProcessAttributes - null, // lpThreadAttributes - FALSE, // bInheritHandles - DEBUG_PROCESS, // dwCreationFlags - null, // lpEnvironment - null, // lpCurrentDirectory - &si, &pi) == FALSE) +version (Windows) { + int bs = 0x4000; // buffer size, 16 KiB + char *b = cast(char*)malloc(bs); /// flat buffer + if (b == null) + return adbg_oops(AdbgError.crt); + + // Copy execultable path into buffer + ptrdiff_t bi = snprintf(b, bs, "%s ", path); + if (bi < 0) + return adbg_oops(AdbgError.crt); + + // Flatten argv + if (opts.argv) + bi += adbg_util_argv_flatten(b + bi, bs, opts.argv); + + //TODO: Parse envp + + // Create process + STARTUPINFOA si = void; + PROCESS_INFORMATION pi = void; + memset(&si, 0, si.sizeof); // memset faster than _init functions + memset(&pi, 0, pi.sizeof); // memset faster than _init functions + si.cb = STARTUPINFOA.sizeof; + // Not using DEBUG_ONLY_THIS_PROCESS because our posix + // counterpart is using -1 (all children) for waitpid. + if (CreateProcessA( + null, // lpApplicationName + b, // lpCommandLine + null, // lpProcessAttributes + null, // lpThreadAttributes + FALSE, // bInheritHandles + DEBUG_PROCESS, // dwCreationFlags + null, // lpEnvironment + null, // lpCurrentDirectory + &si, &pi) == FALSE) + return adbg_oops(AdbgError.os); + free(b); //TODO: Verify CreateProcessA copies lpCommandLine/etc. + tracee.hpid = pi.hProcess; + tracee.htid = pi.hThread; + tracee.pid = pi.dwProcessId; + tracee.tid = pi.dwThreadId; + + // Microsoft recommends getting function pointer with + // GetProcAddress("kernel32", "IsWow64Process"), but so far + // all 64-bit versions of Windows have WOW64 (does Embedded too?). + // Nevertheless, required to support 32-bit processes under + // 64-bit builds. + //TODO: IsWow64Process2 support + // with GetProcAddress("kernel32", "IsWow64Process2") + // Introduced in Windows 10, version 1511 + // IsWow64Process: 32-bit proc. under aarch64 returns FALSE + version (Win64) + if (IsWow64Process(tracee.hpid, &tracee.wow64) == FALSE) + return adbg_oops(AdbgError.os); +} else version (Posix) { + // Verify if file exists and we has access to it + stat_t st = void; + if (stat(path, &st) == -1) + return adbg_oops(AdbgError.os); + + const(char)*[16] __argv = void; + const(char)*[1] __envp = void; + + // Proceed normally, execve performs executable checks + version (USE_CLONE) { // clone(2) + //TODO: get default stack size (glibc constant or function) + void *chld_stack = mmap(null, ADBG_CHILD_STACK_SIZE, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, + -1, 0); + if (chld_stack == MAP_FAILED) return adbg_oops(AdbgError.os); - free(b); //TODO: Verify CreateProcessA copies lpCommandLine/etc. - tracee.hpid = pi.hProcess; - tracee.htid = pi.hThread; - tracee.pid = pi.dwProcessId; - tracee.tid = pi.dwThreadId; - - // Microsoft recommends getting function pointer with - // GetProcAddress("kernel32", "IsWow64Process"), but so far - // all 64-bit versions of Windows have WOW64 (does Embedded too?). - // Nevertheless, required to support 32-bit processes under - // 64-bit builds. - //TODO: IsWow64Process2 support - // with GetProcAddress("kernel32", "IsWow64Process2") - // Introduced in Windows 10, version 1511 - // IsWow64Process: 32-bit proc. under aarch64 returns FALSE - version (Win64) - if (IsWow64Process(tracee.hpid, &tracee.wow64) == FALSE) + + // Adjust argv + if (opts.argv) { + size_t i0, i1 = 1; + while (opts.argv[i0] && i1 < 15) + __argv[i1++] = opts.argv[i0++]; + __argv[i1] = null; + } else { + __argv[1] = null; + } + __argv[0] = path; + + // Adjust envp + //TODO: Is this still valid? + if (opts.envp == null) { + opts.envp = cast(const(char)**)&__envp; + opts.envp[0] = null; + } + + // Clone + //TODO: Get default stack size + __adbg_child_t chld = void; + chld.envp = cast(const(char)**)&__envp; + chld.argv = cast(const(char)**)&__argv; + tracee.pid = clone(&adbg_linux_child, + chld_stack + ADBG_CHILD_STACK_SIZE, + CLONE_PTRACE, + &chld); // tid + if (tracee.pid < 0) return adbg_oops(AdbgError.os); - } else version (Posix) { - // Verify if file exists and we has access to it - stat_t st = void; - if (stat(path, &st) == -1) + } else { // fork(2) + tracee.pid = fork(); + if (tracee.pid < 0) return adbg_oops(AdbgError.os); - - const(char)*[16] __argv = void; - const(char)*[1] __envp = void; - - // Proceed normally, execve performs executable checks - version (USE_CLONE) { // clone(2) - //TODO: get default stack size (glibc constant or function) - void *chld_stack = mmap(null, ADBG_CHILD_STACK_SIZE, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, - -1, 0); - if (chld_stack == MAP_FAILED) - return adbg_oops(AdbgError.os); - + if (tracee.pid == 0) { // Child process // Adjust argv if (opts.argv) { size_t i0, i1 = 1; @@ -315,63 +350,29 @@ int adbg_spawn2(adbg_process_t *tracee, const(char) *path, adbg_options_spawn_t __argv[1] = null; } __argv[0] = path; - + // Adjust envp - //TODO: Is this still valid? if (opts.envp == null) { opts.envp = cast(const(char)**)&__envp; opts.envp[0] = null; } - - // Clone - //TODO: Get default stack size - __adbg_child_t chld = void; - chld.envp = cast(const(char)**)&__envp; - chld.argv = cast(const(char)**)&__argv; - tracee.pid = clone(&adbg_linux_child, - chld_stack + ADBG_CHILD_STACK_SIZE, - CLONE_PTRACE, - &chld); // tid - if (tracee.pid < 0) - return adbg_oops(AdbgError.os); - } else { // fork(2) - tracee.pid = fork(); - if (tracee.pid < 0) - return adbg_oops(AdbgError.os); - if (tracee.pid == 0) { // Child process - // Adjust argv - if (opts.argv) { - size_t i0, i1 = 1; - while (opts.argv[i0] && i1 < 15) - __argv[i1++] = opts.argv[i0++]; - __argv[i1] = null; - } else { - __argv[1] = null; - } - __argv[0] = path; - - // Adjust envp - if (opts.envp == null) { - opts.envp = cast(const(char)**)&__envp; - opts.envp[0] = null; - } - - // Trace me - if (ptrace(PT_TRACEME, 0, 0, 0)) - return adbg_error_system; - version (CRuntime_Musl) { - if (raise(SIGTRAP)) - return adbg_error_system; - } - - // Execute - if (execve(path, - cast(const(char)**)__argv, - cast(const(char)**)__envp) == -1) + + // Trace me + if (ptrace(PT_TRACEME, 0, 0, 0)) + return adbg_error_system; + version (CRuntime_Musl) { + if (raise(SIGTRAP)) return adbg_error_system; } - } // USE_CLONE - } + + // Execute + if (execve(path, + cast(const(char)**)__argv, + cast(const(char)**)__envp) == -1) + return adbg_error_system; + } + } // fork(2) +} tracee.status = AdbgStatus.standby; tracee.creation = AdbgCreation.spawned; @@ -435,49 +436,49 @@ L_OPTION: tracee.creation = AdbgCreation.attached; - version (Windows) { - //TODO: Integrate ObRegisterCallbacks? - // https://blog.xpnsec.com/anti-debug-openprocess/ - - // NOTE: Emulate ProcessIdToHandle - // Uses NtOpenProcess with ClientId.UniqueProcess=PID - // Uses PROCESS_ALL_ACCESS, but let's start with the basics - tracee.pid = cast(DWORD)pid; - tracee.hpid = OpenProcess( - PROCESS_VM_OPERATION | - PROCESS_VM_WRITE | - PROCESS_VM_READ | - PROCESS_SUSPEND_RESUME | - PROCESS_QUERY_INFORMATION, - FALSE, - cast(DWORD)pid); - if (tracee.hpid == null) - return adbg_oops(AdbgError.os); - - // Check if process already has an attached debugger - BOOL dbgpresent = void; - if (CheckRemoteDebuggerPresent(tracee.hpid, &dbgpresent) == FALSE) - return adbg_oops(AdbgError.os); - if (dbgpresent) - return adbg_oops(AdbgError.debuggerPresent); - - // Breaks into remote process and initiates break-in - if (DebugActiveProcess(tracee.pid) == FALSE) - return adbg_oops(AdbgError.os); - - // DebugActiveProcess, by default, kills the process on exit. - if (exitkill == false) - DebugSetProcessKillOnExit(FALSE); - } else version (Posix) { - version (Trace) if (stop) trace("Sending break..."); - if (ptrace(stop ? PT_ATTACH : PT_SEIZE, pid, null, null) < 0) - return adbg_oops(AdbgError.os); - - tracee.pid = cast(pid_t)pid; - - if (exitkill && ptrace(PT_SETOPTIONS, pid, null, PT_O_EXITKILL) < 0) - return adbg_oops(AdbgError.os); - } +version (Windows) { + //TODO: Integrate ObRegisterCallbacks? + // https://blog.xpnsec.com/anti-debug-openprocess/ + + // NOTE: Emulate ProcessIdToHandle + // Uses NtOpenProcess with ClientId.UniqueProcess=PID + // Uses PROCESS_ALL_ACCESS, but let's start with the basics + tracee.pid = cast(DWORD)pid; + tracee.hpid = OpenProcess( + PROCESS_VM_OPERATION | + PROCESS_VM_WRITE | + PROCESS_VM_READ | + PROCESS_SUSPEND_RESUME | + PROCESS_QUERY_INFORMATION, + FALSE, + cast(DWORD)pid); + if (tracee.hpid == null) + return adbg_oops(AdbgError.os); + + // Check if process already has an attached debugger + BOOL dbgpresent = void; + if (CheckRemoteDebuggerPresent(tracee.hpid, &dbgpresent) == FALSE) + return adbg_oops(AdbgError.os); + if (dbgpresent) + return adbg_oops(AdbgError.debuggerPresent); + + // Breaks into remote process and initiates break-in + if (DebugActiveProcess(tracee.pid) == FALSE) + return adbg_oops(AdbgError.os); + + // DebugActiveProcess, by default, kills the process on exit. + if (exitkill == false) + DebugSetProcessKillOnExit(FALSE); +} else version (Posix) { + version (Trace) if (stop) trace("Sending break..."); + if (ptrace(stop ? PT_ATTACH : PT_SEIZE, pid, null, null) < 0) + return adbg_oops(AdbgError.os); + + tracee.pid = cast(pid_t)pid; + + if (exitkill && ptrace(PT_SETOPTIONS, pid, null, PT_O_EXITKILL) < 0) + return adbg_oops(AdbgError.os); +} tracee.creation = AdbgCreation.attached; tracee.status = stop ? AdbgStatus.paused : AdbgStatus.running; @@ -487,20 +488,20 @@ L_OPTION: /// Detach debugger from current process. int adbg_detach(adbg_process_t *tracee) { if (tracee == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); if (tracee.creation != AdbgCreation.attached) return adbg_oops(AdbgError.debuggerInvalidAction); tracee.creation = AdbgCreation.unloaded; tracee.status = AdbgStatus.idle; - version (Windows) { - if (DebugActiveProcessStop(tracee.pid) == FALSE) - return adbg_oops(AdbgError.os); - } else version (Posix) { - if (ptrace(PT_DETACH, tracee.pid, null, null) < 0) - return adbg_oops(AdbgError.os); - } +version (Windows) { + if (DebugActiveProcessStop(tracee.pid) == FALSE) + return adbg_oops(AdbgError.os); +} else version (Posix) { + if (ptrace(PT_DETACH, tracee.pid, null, null) < 0) + return adbg_oops(AdbgError.os); +} return 0; } @@ -508,51 +509,52 @@ int adbg_detach(adbg_process_t *tracee) { /// Is this process being debugged? /// Returns: True if a debugger is attached to this process. bool adbg_self_is_debugged() { - version (Windows) { - return IsDebuggerPresent() == TRUE; - } else version (linux) { // https://stackoverflow.com/a/24969863 - import core.stdc.string : strstr; - - // Linux 5.10 example status for cat(1) is 1392 Bytes - enum BUFFERSZ = 4096; - - char *buffer = cast(char*)malloc(BUFFERSZ); - if (buffer == null) - return false; +version (Windows) { + return IsDebuggerPresent() == TRUE; // converts int to bool +} else version (linux) { // https://stackoverflow.com/a/24969863 + import core.stdc.string : strstr; + + // Linux 5.10 example status for cat(1) is 1392 Bytes + enum BUFFERSZ = 4096; + + char *buffer = cast(char*)malloc(BUFFERSZ); + if (buffer == null) + return false; - scope(exit) free(buffer); + scope(exit) free(buffer); - const int status_fd = open("/proc/self/status", O_RDONLY); - if (status_fd == -1) - return false; + const int status_fd = open("/proc/self/status", O_RDONLY); + if (status_fd == -1) + return false; - const ssize_t num_read = read(status_fd, buffer, BUFFERSZ - 1); - close(status_fd); + const ssize_t num_read = read(status_fd, buffer, BUFFERSZ - 1); + close(status_fd); - if (num_read <= 0) - return false; + if (num_read <= 0) + return false; - buffer[num_read] = 0; - const(char)* strptr = strstr(buffer, "TracerPid:"); - if (strptr == null) - return false; - - // Example: "TracerPid:\t0\n" - // "TracerPid:": 10 chars - // ulong.max (18446744073709551615): 20 chars - // spacing is either one tab or a few spaces: 1-8 - // So max search lenght at 40 is a decent guess. - // Search starts at pos 10, at the spacing. - for (size_t i = 10; i < 40; ++i) { - switch (strptr[i]) { - case '0', '\t', ' ': continue; // spacing - case '\n', '\r', 0: return false; // EOL/EOF - default: return true; // non-zero digit - } + buffer[num_read] = 0; + const(char)* strptr = strstr(buffer, "TracerPid:"); + if (strptr == null) + return false; + + // Example: "TracerPid:\t0\n" + // "TracerPid:": 10 chars + // ulong.max (18446744073709551615): 20 chars + // spacing is either one tab or a few spaces: 1-8 + // So max search lenght at 40 is a decent guess. + // Search starts at pos 10, at the spacing. + for (size_t i = 10; i < 40; ++i) { + switch (strptr[i]) { + case '0', '\t', ' ': continue; // spacing + case '\n', '\r', 0: return false; // EOL/EOF + default: return true; // non-zero digit } + } - return false; - } else static assert(0, "adbg_debug_me: Implement me"); + return false; +} else + static assert(0, "adbg_debug_me: Implement me"); } /// Insert a tracee break. @@ -565,6 +567,9 @@ void adbg_self_break() { } else static assert(0, "adbg_debug_me: Implement me"); } +//TODO: Check process debugged +//bool adbg_process_debugged(adbg_process_t *tracee) { + /// Get the debugger's current state. /// Returns: Debugger status. AdbgStatus adbg_status(adbg_process_t *tracee) pure { @@ -572,11 +577,10 @@ AdbgStatus adbg_status(adbg_process_t *tracee) pure { return tracee.status; } -/// Enter the debugging loop. +/// Wait for a debug event. /// -/// Continues execution of the process until a new -/// debug event occurs. When an exception occurs, the exception_t structure is -/// populated with debugging information. +/// Continues execution of the process until a new debug event occurs. When an +/// exception occurs, the exception_t structure is populated with debugging information. /// /// This call is blocking. /// @@ -589,7 +593,7 @@ AdbgStatus adbg_status(adbg_process_t *tracee) pure { /// Returns: Error code. int adbg_wait(adbg_process_t *tracee, void function(adbg_exception_t*) userfunc) { if (tracee == null || userfunc == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); if (tracee.creation == AdbgCreation.unloaded) return adbg_oops(AdbgError.debuggerUnattached); @@ -602,114 +606,114 @@ int adbg_wait(adbg_process_t *tracee, void function(adbg_exception_t*) userfunc) adbg_exception_t exception = void; - version (Windows) { - DEBUG_EVENT de = void; +version (Windows) { + DEBUG_EVENT de = void; L_DEBUG_LOOP: - // Something bad happened - if (WaitForDebugEvent(&de, INFINITE) == FALSE) { - tracee.status = AdbgStatus.unloaded; - tracee.creation = AdbgCreation.unloaded; - return adbg_oops(AdbgError.os); - } - - //TODO: Assign pid from event? - - // Filter events - switch (de.dwDebugEventCode) { - case EXCEPTION_DEBUG_EVENT: break; - /*case CREATE_THREAD_DEBUG_EVENT: - case CREATE_PROCESS_DEBUG_EVENT: - case EXIT_THREAD_DEBUG_EVENT: - //case EXIT_PROCESS_DEBUG_EVENT: - case LOAD_DLL_DEBUG_EVENT: - case UNLOAD_DLL_DEBUG_EVENT: - case OUTPUT_DEBUG_STRING_EVENT: - case RIP_EVENT: - goto default;*/ - case EXIT_PROCESS_DEBUG_EVENT: - goto L_UNLOADED; - default: - ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); - goto L_DEBUG_LOOP; - } - - tracee.status = AdbgStatus.paused; - adbg_exception_translate(&exception, &de, null); - } else version (Posix) { - int wstatus = void; - int stopsig = void; + // Something bad happened + if (WaitForDebugEvent(&de, INFINITE) == FALSE) { + tracee.status = AdbgStatus.unloaded; + tracee.creation = AdbgCreation.unloaded; + return adbg_oops(AdbgError.os); + } + + //TODO: Assign pid from event? + + // Filter events + switch (de.dwDebugEventCode) { + case EXCEPTION_DEBUG_EVENT: break; + /*case CREATE_THREAD_DEBUG_EVENT: + case CREATE_PROCESS_DEBUG_EVENT: + case EXIT_THREAD_DEBUG_EVENT: + //case EXIT_PROCESS_DEBUG_EVENT: + case LOAD_DLL_DEBUG_EVENT: + case UNLOAD_DLL_DEBUG_EVENT: + case OUTPUT_DEBUG_STRING_EVENT: + case RIP_EVENT: + goto default;*/ + case EXIT_PROCESS_DEBUG_EVENT: + goto L_UNLOADED; + default: + ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); + goto L_DEBUG_LOOP; + } + + tracee.status = AdbgStatus.paused; + adbg_exception_translate(&exception, &de, null); +} else version (Posix) { + int wstatus = void; + int stopsig = void; L_DEBUG_LOOP: - tracee.pid = waitpid(-1, &wstatus, 0); - - // Something bad happened - if (tracee.pid < 0) { - tracee.status = AdbgStatus.unloaded; - tracee.creation = AdbgCreation.unloaded; - return adbg_oops(AdbgError.crt); - } - - version (Trace) trace("wstatus=%08x", wstatus); - - // If exited or killed by signal, it's gone. - if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) - goto L_UNLOADED; - - // Skip glibc "continue" signals. - version (CRuntime_Glibc) - if (WIFCONTINUED(wstatus)) - goto L_DEBUG_LOOP; - - //TODO: Check waitpid status for BSDs - // 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 - switch (stopsig) { - case SIGCONT: goto L_DEBUG_LOOP; - // 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. - // 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: + tracee.pid = waitpid(-1, &wstatus, 0); + + // Something bad happened + if (tracee.pid < 0) { + tracee.status = AdbgStatus.unloaded; + tracee.creation = AdbgCreation.unloaded; + return adbg_oops(AdbgError.crt); + } + + version (Trace) trace("wstatus=%08x", wstatus); + + // If exited or killed by signal, it's gone. + if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) + goto L_UNLOADED; + + // Skip glibc "continue" signals. + version (CRuntime_Glibc) + if (WIFCONTINUED(wstatus)) + goto L_DEBUG_LOOP; + + //TODO: Check waitpid status for BSDs + // 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 + switch (stopsig) { + case SIGCONT: goto L_DEBUG_LOOP; + // 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. + // 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; } - - tracee.status = AdbgStatus.paused; - adbg_exception_translate(&exception, &tracee.pid, &stopsig); + exception.fault_address = cast(size_t)sig._sifields._sigfault.si_addr; + break; +// case SIGINT, SIGTERM, SIGABRT: //TODO: Killed? + default: + exception.fault_address = 0; } + tracee.status = AdbgStatus.paused; + adbg_exception_translate(&exception, &tracee.pid, &stopsig); +} + userfunc(&exception); return 0; @@ -722,7 +726,7 @@ L_UNLOADED: int adbg_stop(adbg_process_t *tracee) { if (tracee == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); if (tracee.creation == AdbgCreation.unloaded) return adbg_oops(AdbgError.debuggerUnattached); @@ -733,27 +737,28 @@ int adbg_stop(adbg_process_t *tracee) { int adbg_terminate(adbg_process_t *tracee) { if (tracee == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); if (tracee.creation == AdbgCreation.unloaded) return adbg_oops(AdbgError.debuggerUnattached); tracee.status = AdbgStatus.unloaded; // exited in any case tracee.creation = AdbgCreation.unloaded; - version (Windows) { - if (ContinueDebugEvent(tracee.pid, tracee.tid, DBG_TERMINATE_PROCESS) == FALSE) - return adbg_oops(AdbgError.os); - } else { - if (kill(tracee.pid, SIGKILL) < 0) // PT_KILL is deprecated - return adbg_oops(AdbgError.os); - } +version (Windows) { + if (ContinueDebugEvent(tracee.pid, tracee.tid, DBG_TERMINATE_PROCESS) == FALSE) + return adbg_oops(AdbgError.os); +} else { + if (kill(tracee.pid, SIGKILL) < 0) // PT_KILL is deprecated + return adbg_oops(AdbgError.os); +} + return 0; } int adbg_continue(adbg_process_t *tracee) { if (tracee == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); if (tracee.creation == AdbgCreation.unloaded) return adbg_oops(AdbgError.debuggerUnattached); if (tracee.status != AdbgStatus.paused) @@ -761,17 +766,17 @@ int adbg_continue(adbg_process_t *tracee) { tracee.status = AdbgStatus.running; - version (Windows) { - if (ContinueDebugEvent(tracee.pid, tracee.tid, DBG_CONTINUE) == FALSE) { - tracee.status = AdbgStatus.idle; - return adbg_oops(AdbgError.os); - } - } else { - if (ptrace(PT_CONT, tracee.pid, null, null) < 0) { - tracee.status = AdbgStatus.idle; - return adbg_oops(AdbgError.os); - } +version (Windows) { + if (ContinueDebugEvent(tracee.pid, tracee.tid, DBG_CONTINUE) == FALSE) { + tracee.status = AdbgStatus.idle; + return adbg_oops(AdbgError.os); + } +} else { + if (ptrace(PT_CONT, tracee.pid, null, null) < 0) { + tracee.status = AdbgStatus.idle; + return adbg_oops(AdbgError.os); } +} return 0; } @@ -781,48 +786,48 @@ int adbg_continue(adbg_process_t *tracee) { /// Returns: Error code. int adbg_stepi(adbg_process_t *tracee) { if (tracee == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); if (tracee.creation == AdbgCreation.unloaded) return adbg_oops(AdbgError.debuggerUnattached); +version (Windows) { enum EFLAGS_TF = 0x100; - version (Windows) { - // Enable single-stepping via Trap flag - version (Win64) { - if (tracee.wow64) { - WOW64_CONTEXT winctxwow64 = void; - winctxwow64.ContextFlags = CONTEXT_CONTROL; - Wow64GetThreadContext(tracee.htid, &winctxwow64); - winctxwow64.EFlags |= EFLAGS_TF; - Wow64SetThreadContext(tracee.htid, &winctxwow64); - FlushInstructionCache(tracee.hpid, null, 0); - } else { - CONTEXT winctx = void; - winctx.ContextFlags = CONTEXT_CONTROL; - GetThreadContext(tracee.htid, &winctx); - winctx.EFlags |= EFLAGS_TF; - SetThreadContext(tracee.htid, &winctx); - FlushInstructionCache(tracee.hpid, null, 0); - } + // Enable single-stepping via Trap flag + version (Win64) { + if (tracee.wow64) { + WOW64_CONTEXT winctxwow64 = void; + winctxwow64.ContextFlags = CONTEXT_CONTROL; + Wow64GetThreadContext(tracee.htid, &winctxwow64); + winctxwow64.EFlags |= EFLAGS_TF; + Wow64SetThreadContext(tracee.htid, &winctxwow64); + FlushInstructionCache(tracee.hpid, null, 0); } else { CONTEXT winctx = void; - winctx.ContextFlags = CONTEXT_ALL; + winctx.ContextFlags = CONTEXT_CONTROL; GetThreadContext(tracee.htid, &winctx); winctx.EFlags |= EFLAGS_TF; SetThreadContext(tracee.htid, &winctx); FlushInstructionCache(tracee.hpid, null, 0); } - - return adbg_continue(tracee); } else { - if (ptrace(PT_SINGLESTEP, tracee.pid, null, null) < 0) { - tracee.status = AdbgStatus.idle; - return adbg_oops(AdbgError.os); - } - - return 0; + CONTEXT winctx = void; + winctx.ContextFlags = CONTEXT_ALL; + GetThreadContext(tracee.htid, &winctx); + winctx.EFlags |= EFLAGS_TF; + SetThreadContext(tracee.htid, &winctx); + FlushInstructionCache(tracee.hpid, null, 0); + } + + return adbg_continue(tracee); +} else { + if (ptrace(PT_SINGLESTEP, tracee.pid, null, null) < 0) { + tracee.status = AdbgStatus.idle; + return adbg_oops(AdbgError.os); } + + return 0; +} } /// Get the process' ID; @@ -844,82 +849,83 @@ int adbg_process_get_pid(adbg_process_t *tracee) { /// Returns: String length; Or zero on error. size_t adbg_process_get_name(int pid, char *buffer, size_t bufsize, bool basename) { if (pid <= 0 || buffer == null || bufsize == 0) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); - version (Windows) { - // Get process handle - HANDLE hand = OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, pid); - if (hand == null) { +version (Windows) { + // Get process handle + HANDLE hand = OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, pid); + if (hand == null) { + adbg_oops(AdbgError.os); + return 0; + } + scope(exit) CloseHandle(hand); + + // Get filename or basename + uint bf = cast(uint)bufsize; + uint r = basename ? + GetModuleBaseNameA(hand, null, buffer, bf) : + GetModuleFileNameA(hand, buffer, bf); + buffer[r] = 0; + if (r == 0) + adbg_oops(AdbgError.os); + return r; +} else version (linux) { + //TODO: Could use /proc/pid/exe? (symbolic path) + enum PATHBFSZ = 32; + char[PATHBFSZ] pathbuf = void; // Path buffer + ssize_t r; + + snprintf(pathbuf.ptr, PATHBFSZ, "/proc/%d/cmdline", pid); + int cmdlinefd = open(pathbuf.ptr, O_RDONLY); + if (cmdlinefd > 0) { + r = read(cmdlinefd, buffer, bufsize); + buffer[r] = 0; + close(cmdlinefd); + } + + // Error reading /cmdline, retry with /comm + if (r <= 0) { + snprintf(pathbuf.ptr, PATHBFSZ, "/proc/%d/comm", pid); + int commfd = open(pathbuf.ptr, O_RDONLY); + if (commfd == -1) { adbg_oops(AdbgError.os); return 0; } - scope(exit) CloseHandle(hand); - - // Get filename or basename - uint bf = cast(uint)bufsize; - uint r = basename ? - GetModuleBaseNameA(hand, null, buffer, bf) : - GetModuleFileNameA(hand, buffer, bf); - buffer[r] = 0; - if (r == 0) + scope(exit) close(commfd); + r = read(commfd, buffer, bufsize); + if (r < 0) { adbg_oops(AdbgError.os); - return r; - } else version (linux) { - enum TBUFSZ = 32; - char[TBUFSZ] pathbuf = void; // Path buffer - ssize_t r; - - snprintf(pathbuf.ptr, TBUFSZ, "/proc/%d/cmdline", pid); - int cmdlinefd = open(pathbuf.ptr, O_RDONLY); - if (cmdlinefd > 0) { - r = read(cmdlinefd, buffer, bufsize); - buffer[r] = 0; - close(cmdlinefd); + return 0; } - - // Error reading /cmdline, retry with /comm - if (r <= 0) { - snprintf(pathbuf.ptr, TBUFSZ, "/proc/%d/comm", pid); - int commfd = open(pathbuf.ptr, O_RDONLY); - if (commfd == -1) { - adbg_oops(AdbgError.os); - return 0; - } - scope(exit) close(commfd); - r = read(commfd, buffer, bufsize); - if (r < 0) { - adbg_oops(AdbgError.os); - return 0; - } - buffer[r - 1] = 0; // Delete newline + buffer[r - 1] = 0; // Delete newline + } + + // Basename requested but was given full path + // e.g. /usr/bin/cat to cat + if (basename && buffer[0] == '/') { + ssize_t i = r; + while (--i >= 0) { + if (buffer[i] == '/') break; } + if (i < 0) // Nothing to do + return 0; + size_t s = i + 1; // Start of substring + size_t l = r - s; // Length + for (size_t e; e < l; ++e, ++s) + buffer[e] = buffer[s]; + } + //TODO: Name is not absolute, search in PATH + /* else if (basename == false && buffer[0] != '/') { - // Basename requested but was given full path - // e.g. /usr/bin/cat to cat - if (basename && buffer[0] == '/') { - ssize_t i = r; - while (--i >= 0) { - if (buffer[i] == '/') break; - } - if (i < 0) // Nothing to do - return 0; - size_t s = i + 1; // Start of substring - size_t l = r - s; // Length - for (size_t e; e < l; ++e, ++s) - buffer[e] = buffer[s]; - } - //TODO: Name is not absolute, search in PATH - /* else if (basename == false && buffer[0] != '/') { - - }*/ - - return r; - } else { - adbg_oops(AdbgError.unimplemented); - return 0; - } + }*/ + + return r; +} else { + adbg_oops(AdbgError.unimplemented); + return 0; +} } /// Get the current runtime machine platform. @@ -933,12 +939,107 @@ AdbgMachine adbg_process_get_machine(adbg_process_t *tracee) { if (tracee == null) return mach; + //TODO: There's probably a way to remotely check this version (Win64) version (X86_64) // Windows + x86-64 mach = tracee.wow64 ? AdbgMachine.x86 : AdbgMachine.amd64; return mach; } +/// Get a list of process IDs running. +/// +/// This function allocates memory. The list passed will need to be closed +/// using `free(3)`. To get the name of a process, call `adbg_process_get_name`. +/// +/// Windows: The list is populated by system order using `EnumProcesses`. +/// Linux: The list is populated by process ID using procfs. +/// +/// Params: +/// count = Process list structure instance. +/// ... = Options, terminated by 0. +/// Returns: List of PIDs; Or null on error. +int* adbg_process_list(size_t *count, ...) { + if (count == null) { + adbg_oops(AdbgError.invalidArgument); + return null; + } + + int capacity = 10_000; + + int *plist = void; + +version (Windows) { + if (__dynlib_psapi_load()) + return null; + + // Allocate temp PID buffer + uint hsize = cast(uint)(capacity * HMODULE.sizeof); + DWORD *pidlist = cast(DWORD*)malloc(hsize); + if (pidlist == null) { + adbg_oops(AdbgError.crt); + } + scope(exit) free(pidlist); + + // Enumerate processes + // Note that "needed" is reusable after getting the count + //TODO: Adjust temporary buffer after calling this + DWORD needed = void; + if (EnumProcesses(pidlist, hsize, &needed) == FALSE) { + adbg_oops(AdbgError.os); + return null; + } + DWORD proccount = needed / DWORD.sizeof; + + *count = proccount; + plist = cast(int*)malloc(proccount * int.sizeof); + if (plist == null) { + adbg_oops(AdbgError.crt); + return null; + } + + for (DWORD i; i < proccount; ++i) { + plist[i] = pidlist[i]; + } +} else version (linux) { + + size_t cnt; // minimum amount of entries + DIR *procfd = opendir("/proc"); + for (dirent *procent = void; (procent = readdir(procfd)) != null;) { + // If not directory starting with a digit, skip entry + if (procent.d_type != DT_DIR) + continue; + if (isdigit(procent.d_name[0]) == 0) + continue; + ++cnt; + } + + procfd = cast(int*)malloc(cnt * int.sizeof); + if (procfd == null) { + adbg_oops(AdbgError.crt); + return null; + } + *count = cnt; + + rewinddir(procfd); + size_t i; + for (dirent *procent = void; (procent = readdir(procfd)) != null;) { + // If not directory starting with a digit, skip entry + if (procent.d_type != DT_DIR) + continue; + if (isdigit(procent.d_name[0]) == 0) + continue; + + // Set PID + plist[i++] = atoi(procent.d_name.ptr); + } + closedir(procfd); +} + + return plist; +} + +//TODO: Deprecate process enumeration routines + /// Options for adbg_process_enumerate. enum AdbgProcessEnumerateOption { /// Set the size of the dynamic buffer for the list of processes. @@ -968,10 +1069,6 @@ struct adbg_process_list_t { size_t count; } -//TODO: Redo adbg_process_enumerate -// int* adbg_process_list(size_t *count, ...) -// - List of PIDs instead, use adbg_process_get_name for file path. - // NOTE: For the C vararg to work, list is a parameter instead of a return value. /// Enumerate running processes. /// @@ -987,7 +1084,7 @@ struct adbg_process_list_t { /// Returns: Zero for success; Or error code. int adbg_process_enumerate(adbg_process_list_t *list, ...) { if (list == null) - return adbg_oops(AdbgError.nullArgument); + return adbg_oops(AdbgError.invalidArgument); /// Default fixed buffer size. enum DEFAULT_CAPACITY = 1000;