/// Command shell and interface to debugger. /// /// Authors: dd86k <dd@dax.moe> /// Copyright: © dd86k <dd@dax.moe> /// License: BSD-3-Clause-Clear module shell; import adbg.error, adbg.debugger, adbg.disassembler, adbg.object; import adbg.include.c.stdio; import adbg.include.c.stdlib; import adbg.include.c.stdarg; import core.stdc.string; import debugger; import common.error; import common.cli : opt_syntax; import common.utils; import term; // Enable new process name, although it is currently broken on Windows //version = UseNewProcessName extern (C): /// Application error enum ShellError { none = 0, invalidParameter = -1, invalidCommand = -2, // or action or sub-command unavailable = -3, loadFailed = -4, pauseRequired = -5, alreadyLoaded = -6, missingOption = -7, missingArgument = -8, unformat = -9, invalidCount = -10, unattached = -11, scanMissingType = -20, scanMissingValue = -21, scanInputOutOfRange = -22, scanNoScan = -23, scanInvalidSubCommand = -24, crt = -1000, alicedbg = -1001, } const(char) *shell_error_string(int code) { switch (code) with (ShellError) { case alicedbg: return adbg_error_msg; case invalidParameter: return "Invalid command parameter."; case invalidCommand: return "Invalid command."; case unavailable: return "Feature unavailable."; case loadFailed: return "Failed to load file."; case pauseRequired: return "Debugger needs to be paused for this action."; case alreadyLoaded: return "File already loaded."; case missingOption: return "Missing option for command."; case missingArgument: return "Missing argument."; case unformat: return "Input is not a number."; case invalidCount: return "Count must be 1 or higher."; case unattached: return "Need to be attached to a process for this feature."; case scanMissingType: return "Missing type parameter."; case scanMissingValue: return "Missing value parameter."; case scanInputOutOfRange: return "Value out of range."; case scanNoScan: return "No prior scan were initiated."; case scanInvalidSubCommand: return "Invalid type or subcommand."; case none: return "No error occured."; default: return "Unknown error."; } } //TODO: Turn into character array (like gdb --args) int shellinit(int argc, const(char)** argv) { int ecode = void; if (loginit(null)) return 1337; // Start process if specified if (argc > 0 && argv) { // Assume argv is null-terminated ecode = shell_proc_spawn(*argv, argv + 1); if (ecode) { printf("Error: %s\n", adbg_error_msg()); return ecode; } } else if (opt_pid) { // Or attach to process if specified ecode = shell_proc_attach(opt_pid); if (ecode) { printf("Error: %s\n", adbg_error_msg()); return ecode; } } coninit(); Lcommand: printf("(adbg) "); // .ptr is temporary because a slice with a length of 0 // also make its pointer null. char* line = conrdln().ptr; if (line == null || line[0] == 4) { // 4 == ^D return 0; } ecode = shell_exec(line); if (ecode) logerror(shell_error_string(ecode)); goto Lcommand; } int shell_exec(const(char) *command) { import adbg.utils.strings : adbg_util_expand; if (command == null) return 0; int argc = void; char** argv = adbg_util_expand(command, &argc); return shell_execv(argc, cast(const(char)**)argv); } int shell_execv(int argc, const(char) **argv) { if (argc <= 0 || argv == null) return 0; const(char) *ucommand = argv[0]; immutable(command2_t) *command = shell_findcommand(ucommand); if (command == null) { return ShellError.invalidCommand; } return command.entry(argc, argv); } private: __gshared: adbg_process_t *process; adbg_disassembler_t *dis; adbg_registers_t *registers; const(char)* last_spawn_exec; const(char)** last_spawn_argv; // NOTE: BetterC stderr bindings on Windows are broken // And don't allow re-opening the streams, so screw it FILE *logfd; int loginit(const(char) *path) { version (Windows) { // 1. HANDLE stdHandle = GetStdHandle(STD_ERROR_HANDLE); // 2. int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT); // 3. FILE* file = _fdopen(fileDescriptor, "w"); // 4. int dup2Result = _dup2(_fileno(file), _fileno(stderr)); // 5. setvbuf(stderr, NULL, _IONBF, 0); if (path == null) path = "CONOUT$"; } else { if (path == null) path = "/dev/stderr"; } logfd = fopen(path, "wb"); if (logfd) { setvbuf(logfd, null, _IONBF, 0); } return logfd == null; } void logerror(const(char) *fmt, ...) { va_list args = void; va_start(args, fmt); logwrite("error", fmt, args); } void logwarn(const(char) *fmt, ...) { va_list args = void; va_start(args, fmt); logwrite("warning", fmt, args); } void loginfo(const(char) *fmt, ...) { va_list args = void; va_start(args, fmt); logwrite(null, fmt, args); } void logwrite(const(char) *level, const(char) *fmt, va_list args) { if (level) { fputs(level, logfd); fputs(": ", logfd); } vfprintf(logfd, fmt, args); putchar('\n'); } immutable string RCFILE = ".adbgrc"; immutable string MODULE_SHELL = "Shell"; immutable string MODULE_DEBUGGER = "Debugger"; immutable string MODULE_DISASSEMBLER = "Disassembler"; immutable string MODULE_OBJECTSERVER = "Object Server"; immutable string CATEGORY_SHELL = "Command-line"; immutable string CATEGORY_PROCESS = "Process management"; immutable string CATEGORY_CONTEXT = "Thread context management"; immutable string CATEGORY_MEMORY = "Memory management"; immutable string CATEGORY_EXCEPTION = "Exception management"; immutable string SECTION_NAME = "NAME"; immutable string SECTION_SYNOPSIS = "SYNOPSIS"; immutable string SECTION_DESCRIPTION = "DESCRIPTION"; immutable string SECTION_NOTES = "NOTES"; immutable string SECTION_EXAMPLES = "EXAMPLES"; struct command2_help_section_t { string name; string[] bodies; } struct command2_help_t { // Debugger, Disassembler, Object Server string module_; // Process management, Memory, etc. string category; // Short description. string description; // command2_help_section_t[] sections; } struct command2_t { string[] names; string description; string[] synopsis; string doc_module; string doc_category; command2_help_section_t[] doc_sections; int function(int, const(char)**) entry; } // NOTE: Called "commands_list" to avoid conflict with future "command_list" function //TODO: Commands // - !: Run host shell commands // - b|breakpoint: Breakpoint management // - t|thread: Thread management (e.g., selection, callstack) // - sym: Symbol management immutable command2_t[] shell_commands = [ // // Debugger // { [ "status" ], "Get process status.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "Print the status of the debuggee process." ] } ], &command_status, }, { [ "spawn" ], "Spawn a new process into debugger.", [ "FILE [ARGS...]" ], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "Spawns a new process from path with the debugger attached." ] } ], &command_spawn, }, { [ "attach" ], "Attach debugger to live process.", [ "PID" ], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "Attaches debugger to an existing process by its Process ID." ] } ], &command_attach, }, { [ "detach" ], "Detach debugger from process.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "If the debugger was attached to a live process, detach "~ "the debugger." ] } ], &command_detach, }, { [ "restart" ], "Restart the debugging process.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "The debugger will be re-attached or the process will be "~ "killed and respawned." ] } ], &command_restart, }, { [ "go" ], "Continue debugging process.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "The debugger will be re-attached or the process will be "~ "killed and respawned." ] } ], &command_go, }, { [ "kill" ], "Terminate process.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "The debugger will be re-attached or the process will be "~ "killed and respawned." ] } ], &command_kill, }, { [ "stepi" ], "Perform an instruction step.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "From a paused state, executes exactly one instruction." ] } ], &command_stepi, }, // // Context // { [ "regs" ], "Lists register values.", [ "[NAME]" ], MODULE_DEBUGGER, CATEGORY_CONTEXT, [ { SECTION_DESCRIPTION, [ "Get list of registers and values from process." ] } ], &command_regs, }, // // Memory // { [ "m", "memory" ], "Dump process memory from address.", [ "ADDRESS [LENGTH=64]" ], MODULE_DEBUGGER, CATEGORY_MEMORY, [ { SECTION_DESCRIPTION, [ "Print memory data from address as hexadecimal." ] } ], &command_memory, }, { [ "maps" ], "List memory mapped items.", [], MODULE_DEBUGGER, CATEGORY_MEMORY, [ { SECTION_DESCRIPTION, [ "Lists loaded modules and their memory regions." ] } ], &command_maps, }, { [ "d", "disassemble" ], "Disassemble instructions at address.", [ "ADDRESS [COUNT=1]" ], MODULE_DEBUGGER, CATEGORY_MEMORY, [ { SECTION_DESCRIPTION, [ "Invoke the disassembler at the address. The debugger "~ "will read process memory, if able, and will repeat "~ "the operation COUNT times. By default, it will only "~ "disassemble one instruction." ] } ], &command_disassemble, }, { [ "scan" ], "Scan for value in memory.", [ "TYPE VALUE", "show", "reset", ], MODULE_DEBUGGER, CATEGORY_MEMORY, [ { SECTION_DESCRIPTION, [ "Scan memory maps for specified value. "~ "No writing capability is available at the moment. "~ "To list last scan results, use 'show' subcommand.", "To clear results, use 'reset' subcommand." ] } ], &command_scan, }, // // Process management // { [ "plist" ], "List running programs.", [], MODULE_DEBUGGER, CATEGORY_PROCESS, [ { SECTION_DESCRIPTION, [ "List active processes." ] } ], &command_plist, }, // // Shell // { [ "help" ], "Show help or a command's help article.", [ "[ITEM]" ], MODULE_SHELL, CATEGORY_SHELL, [ ], &command_help, }, { [ "q", "quit" ], "Quit shell session.", [], MODULE_SHELL, CATEGORY_SHELL, [ { SECTION_DESCRIPTION, [ "Close the shell session along with the debugger and "~ "application if it was spawned using the debugger." ] } ], &command_quit, }, ]; immutable(command2_t)* shell_findcommand(const(char) *ucommand) { debug { // Crash command static immutable(command2_t) ucommand_crash = { [ "crash" ], null, [], null, null, [], &command_crash }; if (strcmp(ucommand, ucommand_crash.names[0].ptr) == 0) return &ucommand_crash; } // NOTE: Can't use foreach for local var escape for (size_t i; i < shell_commands.length; ++i) { immutable(command2_t) *cmd = &shell_commands[i]; for (size_t c; c < cmd.names.length; ++c) { if (strcmp(ucommand, cmd.names[c].ptr) == 0) return cmd; } } return null; } int shell_proc_spawn(const(char) *exec, const(char) **argv) { // Save for restart last_spawn_exec = exec; last_spawn_argv = argv; // Spawn process process = adbg_debugger_spawn(exec, AdbgSpawnOpt.argv, argv, 0); if (process == null) return ShellError.alicedbg; printf("Process '%s' created", exec); if (argv) { printf(" with arguments:"); for (int i; argv[i]; ++i) printf(" '%s'", argv[i]); } putchar('\n'); // Open disassembler for process machine type dis = adbg_dis_open(adbg_process_get_machine(process)); if (dis == null) logwarn("Disassembler not available (%s)", adbg_error_msg()); return 0; } int shell_proc_attach(int pid) { // Save for restart opt_pid = pid; // Attach to process process = adbg_debugger_attach(pid, 0); if (process == null) { return ShellError.alicedbg; } puts("Debugger attached."); // Open disassembler for process machine type dis = adbg_dis_open(adbg_process_get_machine(process)); if (dis) { if (opt_syntax) adbg_dis_options(dis, AdbgDisOpt.syntax, opt_syntax, 0); } else { printf("warning: Disassembler not available (%s)\n", adbg_error_msg()); } return 0; } void shell_event_disassemble(size_t address, int count = 1, bool showAddress = true) { if (dis == null) return; enum MBUFSZ = 64; /// Machine string buffer size ubyte[MAX_INSTR_SIZE] data = void; char[MBUFSZ] machbuf = void; for (int i; i < count; ++i) { if (adbg_memory_read(process, address, data.ptr, MAX_INSTR_SIZE)) { print_adbg_error(); return; } adbg_opcode_t op = void; if (adbg_dis_once(dis, &op, data.ptr, MAX_INSTR_SIZE)) { printf("%8llx (error:%s)\n", cast(ulong)address, adbg_error_msg); return; } // Print address if (showAddress) printf("%8zx ", address); // Print machine bytes into a dedicated buffer size_t bo; for (size_t bi; bi < op.size; ++bi) { bo += snprintf(machbuf.ptr + bo, MBUFSZ - bo, " %02x", op.machine[bi]); //printf(" %02x", op.machine[bi]); } // Print mnemonic & operands printf("%s \t%s", machbuf.ptr, op.mnemonic); if (op.operands) printf(" %s", op.operands); // Terminate line putchar('\n'); address += op.size; } } void shell_event_exception(adbg_process_t *proc, int event, void* evdata) { if (event != AdbgEvent.exception) return; adbg_exception_t *ex = cast(adbg_exception_t*)evdata; printf("* Process %d (thread %d) stopped\n"~ " Reason : %s ("~ADBG_OS_ERROR_FORMAT~")\n", ex.pid, ex.tid, adbg_exception_name(ex), ex.oscode); if (ex.faultz) { printf(" Address : 0x%llx\n", ex.fault_address); if (dis) { printf(" Machine :"); // Print machine bytes ubyte[MAX_INSTR_SIZE] data = void; if (adbg_memory_read(process, ex.faultz, data.ptr, MAX_INSTR_SIZE)) { printf(" read error (%s)\n", adbg_error_msg()); return; // Nothing else to do } adbg_opcode_t op = void; if (adbg_dis_once(dis, &op, data.ptr, MAX_INSTR_SIZE)) { printf(" disassembly error (%s)\n", adbg_error_msg()); return; } // Print machine bytes for (size_t bi; bi < op.size; ++bi) { printf(" %02x", op.machine[bi]); } putchar('\n'); // Print mnemonic printf(" Mnemonic: %s", op.mnemonic); if (op.operands) printf(" %s", op.operands); putchar('\n'); } } } void shell_event_help(immutable(command2_t) *command) { // Print header int p = 34; for (size_t i; i < command.names.length; ++i) { if (i) { printf(", "); --p; } p -= printf("%s", command.names[i].ptr); } with (command) printf("%*s %*s\n\nNAME\n ", p, doc_module.ptr, 34, doc_category.ptr); for (size_t i; i < command.names.length; ++i) { if (i) putchar(','); printf(" %s", command.names[i].ptr); } printf(" - %s\n", command.description.ptr); if (command.synopsis.length) { printf("\n%s\n", SECTION_SYNOPSIS.ptr); foreach (s; command.synopsis) { printf(" %s %s\n", command.names[$-1].ptr, s.ptr); } } enum COL = 72; foreach (section; command.doc_sections) { printf("\n%s\n", section.name.ptr); //TODO: Better cut-offs // [0] spacing? remove // [$-1] non-spacing? put dash for (size_t i; i < section.bodies.length; ++i) { const(char) *b = section.bodies[i].ptr; if (i) putchar('\n'); LPRINT: int o = printf(" %.*s\n", COL, b); if (o < COL) continue; b += COL; goto LPRINT; } } putchar('\n'); } debug int command_crash(int, const(char) **) { void function() fnull; fnull(); return 0; } int command_status(int argc, const(char) **argv) { AdbgProcStatus state = adbg_process_status(process); const(char) *m = void; switch (state) with (AdbgProcStatus) { case unloaded: m = "unloaded"; break; case standby: m = "standby"; break; case running: m = "running"; break; case paused: m = "paused"; break; default: m = "(unknown)"; } puts(m); return 0; } //TODO: List per category // Comparing could be per pointer or enum int command_help(int argc, const(char) **argv) { if (argc > 1) { // Requesting help article for command const(char) *ucommand = argv[1]; immutable(command2_t) *command = shell_findcommand(ucommand); if (command == null) { return ShellError.invalidParameter; } shell_event_help(command); return 0; } enum PADDING = 20; static immutable const(char) *liner = ".........................................."; foreach (cmd; shell_commands) { int p; for (size_t i; i < cmd.names.length; ++i) { if (i) { putchar(','); ++p; } p += printf(" %s", cmd.names[i].ptr); } printf(" %.*s %s\n", PADDING - p, liner, cmd.description.ptr); } return 0; } int command_spawn(int argc, const(char) **argv) { if (argc < 2) return ShellError.missingArgument; // Assume argv is null-terminated return shell_proc_spawn(argv[1], argv + 2); } int command_attach(int argc, const(char) **argv) { if (argc < 2) return ShellError.invalidParameter; return shell_proc_attach(atoi(argv[1])); } int command_detach(int argc, const(char) **argv) { if (adbg_debugger_detach(process)) { return ShellError.alicedbg; } adbg_dis_close(dis); free(registers); return 0; } int command_restart(int argc, const(char) **argv) { int e; switch (process.creation) with (AdbgCreation) { case spawned: // Terminate first, ignore on error (e.g., already gone) adbg_debugger_terminate(process); // Spawn, shell still messages status e = shell_proc_spawn(last_spawn_exec, last_spawn_argv); break; case attached: // Detach first, ignore on error (e.g., already detached) adbg_debugger_detach(process); // Attach, shell still messages status e = shell_proc_attach(opt_pid); break; default: return ShellError.unattached; } return e; } int command_go(int argc, const(char) **argv) { if (adbg_debugger_continue(process)) return ShellError.alicedbg; if (adbg_debugger_wait(process, &shell_event_exception)) return ShellError.alicedbg; // Temporary: Cheap hack for process exit if (adbg_process_status(process) == AdbgProcStatus.unloaded) printf("*\tProcess %d exited\n", process.pid); return 0; } int command_kill(int argc, const(char) **argv) { if (adbg_debugger_terminate(process)) return ShellError.alicedbg; puts("Process killed"); adbg_dis_close(dis); free(registers); return 0; } int command_stepi(int argc, const(char) **argv) { if (adbg_debugger_stepi(process)) return ShellError.alicedbg; if (adbg_debugger_wait(process, &shell_event_exception)) return ShellError.alicedbg; return 0; } int command_regs(int argc, const(char) **argv) { if (process == null) return ShellError.pauseRequired; if (registers == null) { registers = adbg_registers_new(adbg_process_get_machine(process)); if (registers == null) return ShellError.alicedbg; } adbg_registers_fill(registers, process); if (registers.count == 0) { logerror("No registers available"); return ShellError.unavailable; } adbg_register_t *reg = registers.items.ptr; const(char) *rselect = argc >= 2 ? argv[1] : null; bool found; for (size_t i; i < registers.count; ++i, ++reg) { bool show = rselect == null || strcmp(rselect, reg.info.name) == 0; if (show == false) continue; char[20] normal = void, hexdec = void; adbg_register_format(normal.ptr, 20, reg, AdbgRegFormat.dec); adbg_register_format(hexdec.ptr, 20, reg, AdbgRegFormat.hexPadded); printf("%-8s 0x%8s %s\n", reg.info.name, hexdec.ptr, normal.ptr); found = true; } if (rselect && found == false) { logerror("Register not found"); return ShellError.invalidParameter; } return 0; } int command_memory(int argc, const(char) **argv) { if (argc < 2) { return ShellError.missingOption; } long uaddress = void; if (unformat64(&uaddress, argv[1])) return ShellError.unformat; int ulength = 64; if (argc >= 3) { if (unformat(&ulength, argv[2])) return ShellError.unformat; if (ulength <= 0) return 0; } ubyte *data = cast(ubyte*)malloc(ulength); if (data == null) return ShellError.crt; if (adbg_memory_read(process, cast(size_t)uaddress, data, ulength)) return ShellError.alicedbg; enum COLS = 16; /// Columns in bytes enum PADD = 12; /// Address padding // Print column header for (int c; c < PADD; ++c) putchar(' '); putchar(' '); putchar(' '); for (int c; c < COLS; ++c) printf("%2x ", cast(uint)c); putchar('\n'); // Print data rows size_t count = ulength / COLS; for (size_t c; c < count; ++c, uaddress += COLS) { // Print address printf("%.*llx ", PADD, uaddress); // Print data column for (size_t i; i < COLS && c * i < ulength; ++i) printf("%02x ", data[(c * COLS) + i]); // Print ascii column putchar(' '); for (size_t i; i < COLS && c * i < ulength; ++i) putchar(asciichar(data[(c * COLS) + i], '.')); putchar('\n'); } return 0; } //TODO: optional arg: filter module by name (contains string) //TODO: max count to show or option to filter modules out int command_maps(int argc, const(char) **argv) { adbg_memory_map_t *mmaps = void; size_t mcount = void; if (adbg_memory_maps(process, &mmaps, &mcount, 0)) return ShellError.alicedbg; puts("Region Size T Perm File"); for (size_t i; i < mcount; ++i) { adbg_memory_map_t *map = &mmaps[i]; char[4] perms = void; perms[0] = map.access & AdbgMemPerm.read ? 'r' : '-'; perms[1] = map.access & AdbgMemPerm.write ? 'w' : '-'; perms[2] = map.access & AdbgMemPerm.exec ? 'x' : '-'; perms[3] = map.access & AdbgMemPerm.private_ ? 'p' : 's'; char t = void; switch (map.type) { case AdbgPageUse.resident: t = 'R'; break; case AdbgPageUse.fileview: t = 'F'; break; case AdbgPageUse.module_: t = 'M'; break; default: t = '?'; } with (map) printf("%16zx %10zd %c %.4s %s\n", cast(size_t)base, size, t, perms.ptr, name.ptr); } free(mmaps); return 0; } //TODO: start,+length start,end syntax int command_disassemble(int argc, const(char) **argv) { if (process == null) return ShellError.unattached; if (dis == null) return ShellError.unavailable; if (argc < 2) return ShellError.missingOption; long uaddress = void; if (unformat64(&uaddress, argv[1])) return ShellError.unformat; int ucount = 1; if (argc >= 3) { if (unformat(&ucount, argv[2])) return ShellError.unformat; if (ucount <= 0) return 0; } if (ucount < 1) { return ShellError.invalidCount; } shell_event_disassemble(cast(size_t)uaddress, ucount); return 0; } size_t last_scan_size; ulong last_scan_data; adbg_scan_t *last_scan; void shell_event_list_scan_results() { // super lazy hack long mask = void; switch (last_scan_size) { case 8: mask = 0; break; case 7: mask = 0xff_ffff_ffff_ffff; break; case 6: mask = 0xffff_ffff_ffff; break; case 5: mask = 0xff_ffff_ffff; break; case 4: mask = 0xffff_ffff; break; case 3: mask = 0xff_ffff; break; case 2: mask = 0xffff; break; case 1: mask = 0xff; break; default: puts("fatal: mask fail"); return; } // 0000. ffffffffffffffff 18446744073709551615 puts("No. Address Previous Current"); adbg_scan_result_t *result = last_scan.results; uint count = cast(uint)last_scan.result_count + 1; // temp cast until better z printf for (uint i = 1; i < count; ++i, ++result) { printf("%4u. %-16llx %*llu ", i, result.address, -20, result.value_u64 & mask); ulong udata = void; if (adbg_memory_read(process, cast(size_t)result.address, &udata, cast(uint)last_scan_size)) puts("???"); else printf("%llu\n", udata & mask); } } int command_scan(int argc, const(char) **argv) { if (argc < 2) return ShellError.scanMissingType; const(char) *usub = argv[1]; if (strcmp(usub, "show") == 0) { if (last_scan == null) return ShellError.scanNoScan; shell_event_list_scan_results; return 0; } else if (strcmp(usub, "reset") == 0) { if (last_scan == null) return 0; adbg_memory_scan_close(last_scan); last_scan = null; return 0; } if (argc < 3) return ShellError.scanMissingValue; const(char) *uin = argv[2]; union u { long data64; int data; } u user = void; if (strcmp(usub, "byte") == 0) { last_scan_size = ubyte.sizeof; if (unformat(&user.data, uin)) return ShellError.unformat; if (user.data > ubyte.max) return ShellError.scanInputOutOfRange; } else if (strcmp(usub, "short") == 0) { last_scan_size = short.sizeof; if (unformat(&user.data, uin)) return ShellError.unformat; if (user.data > short.max) return ShellError.scanInputOutOfRange; } else if (strcmp(usub, "int") == 0) { last_scan_size = int.sizeof; if (unformat(&user.data, uin)) return ShellError.unformat; } else if (strcmp(usub, "long") == 0) { last_scan_size = long.sizeof; if (unformat64(&user.data64, uin)) return ShellError.unformat; } else return ShellError.scanInvalidSubCommand; if (last_scan) adbg_memory_scan_close(last_scan); last_scan_data = user.data64; if ((last_scan = adbg_memory_scan(process, &user, last_scan_size, AdbgScanOpt.capacity, 100, 0)) == null) return ShellError.alicedbg; printf("Scan completed with %u results.\n", cast(uint)last_scan.result_count); return 0; } int command_rescan(int argc, const(char) **argv) { if (argc < 2) return ShellError.scanMissingType; if (argc < 3) return ShellError.scanMissingValue; const(char) *usub = argv[1]; const(char) *uin = argv[2]; union u { long data64; int data; } u user = void; if (strcmp(usub, "byte") == 0) { last_scan_size = ubyte.sizeof; if (unformat(&user.data, uin)) return ShellError.unformat; if (user.data > ubyte.max) return ShellError.scanInputOutOfRange; } else if (strcmp(usub, "short") == 0) { last_scan_size = short.sizeof; if (unformat(&user.data, uin)) return ShellError.unformat; if (user.data > short.max) return ShellError.scanInputOutOfRange; } else if (strcmp(usub, "int") == 0) { last_scan_size = int.sizeof; if (unformat(&user.data, uin)) return ShellError.unformat; } else if (strcmp(usub, "long") == 0) { last_scan_size = long.sizeof; if (unformat64(&user.data64, uin)) return ShellError.unformat; } else return ShellError.scanInvalidSubCommand; if (adbg_memory_rescan(last_scan, &user, last_scan_size)) return ShellError.alicedbg; last_scan_data = user.data64; printf("Scan completed with %u results.\n", cast(uint)last_scan.result_count); return 0; } int command_plist(int argc, const(char) **argv) { // Disabled until adbg_process_get_name works on Windows version (UseNewProcessName) { size_t count = void; int *plist = adbg_process_list(&count, 0); if (plist == null) return ShellError.alicedbg; enum BUFFERSIZE = 2048; char[BUFFERSIZE] buffer = void; version (Trace) trace("count=%zd", count); puts("PID Name"); foreach (int pid; plist[0..count]) { printf("%10d ", pid); if (adbg_process_get_name(pid, buffer.ptr, BUFFERSIZE, true)) { puts(buffer.ptr); continue; } if (adbg_process_get_name(pid, buffer.ptr, BUFFERSIZE, false)) { puts(buffer.ptr); continue; } version (Trace) trace("error: %s", adbg_error_msg()); putchar('\n'); } free(plist); } else { adbg_process_list_t list = void; if (adbg_process_enumerate(&list, 0)) { return ShellError.alicedbg; } puts("PID Name"); foreach (adbg_process_t proc; list.processes[0..list.count]) { printf("%10d %s\n", proc.pid, proc.name.ptr); } } return 0; } int command_quit(int argc, const(char) **argv) { //TODO: Quit confirmation if debuggee is alive // could do with optional "forced yes" type of optional exit(0); return 0; }