static struct mi_registers get_process_registers(struct mi_process proc) { struct user_regs_struct regs = { 0 }; struct iovec iov = { ®s, sizeof(regs) }; struct mi_registers result = { 0 }; ptrace(PTRACE_GETREGSET, proc.pid, NT_PRSTATUS, &iov); result.rax = regs.rax; result.rip = regs.rip; result.rbp = regs.rbp; result.rbx = regs.rbx; result.rcx = regs.rcx; result.rdx = regs.rdx; result.rsi = regs.rsi; result.rdi = regs.rdi; result.rsp = regs.rsp; result._sys = regs; return(result); } static void set_process_registers(struct mi_process proc, struct mi_registers regs) { regs._sys.rax = regs.rax; regs._sys.rip = regs.rip; regs._sys.rbp = regs.rbp; regs._sys.rbx = regs.rbx; regs._sys.rcx = regs.rcx; regs._sys.rdx = regs.rdx; regs._sys.rsi = regs.rsi; regs._sys.rdi = regs.rdi; regs._sys.rsp = regs.rsp; struct iovec iov = { ®s._sys, sizeof(regs._sys) }; ptrace(PTRACE_SETREGSET, proc.pid, NT_PRSTATUS, &iov); } static int is_call_instruction(long word) { // It's a call if: // first byte == 0xe8 // first byte == 0xff AND "reg field" (bit 3-5 in next byte) == 2 or 3 // REX.W + 0xff/3 ????? TODO TODO TODO u8 first_byte = word & 0xFF; if (first_byte == 0xE8) { return 1; } else if (first_byte == 0xFF) { u8 second_byte = word & 0xFF00UL; u8 reg = second_byte & 0x38; // 111000 (to extract bits 3-5) if (reg == 2 || reg == 3) { return 1; } } return 0; } static long place_breakpoint_at(struct mi_process proc, u64 address) { long saved_instruction = ptrace(PTRACE_PEEKDATA, proc.pid, address, NULL); long int3_byte = 0x000000000000CC; long int3_word = (saved_instruction & 0xFFFFFFFFFFFFFF00) | int3_byte; /* write 0xcc */ ptrace(PTRACE_POKEDATA, proc.pid, address, int3_word); return(saved_instruction); } static void run_until_breakpoint_and_restore(struct mi_process proc, u64 address, long saved_instruction) { /* wait till child hits the interrupt */ ptrace(PTRACE_CONT, proc.pid, 0, 0); waitpid(proc.pid, 0, 0); /* restore original instrucion */ ptrace(PTRACE_POKEDATA, proc.pid, address, saved_instruction); /* step back to original instruction */ struct mi_registers regs = get_process_registers(proc); regs.rip -= 1; set_process_registers(proc, regs); } static u64 get_address_of_subroutine(struct mi_process proc, char *sr) { for (int i = 0; i < proc.debug.func_count; ++i) { struct mi_function *func = proc.debug.functions + i; if (0 == strcmp(func->name, sr)) { return(func->low_pc); } } return(0); } static struct mi_sourcepoint * pc_to_sourcepoint(struct mi_process proc, u64 pc, int *comp_unit) { // NOTE: find first point BIGGER that pc, return the sourcepoint just before that // TODO: binary search for (int c = 0; c < proc.debug.cu_count; ++c) { struct mi_compunit unit = proc.debug.compilation_units[c]; if (unit.low_pc <= pc && pc < unit.high_pc) { for (int i = 0; i < unit.source.sp_count; ++i) { struct mi_sourcepoint *point = unit.source.sp_table + i; if (point->pc > pc) { *comp_unit = c; return(unit.source.sp_table + i - 1); } } break; } } return(0); } static struct mi_buffer read_file_mmap(char *path) { struct mi_buffer result = { 0 }; struct stat s; int fd = open(path, O_RDONLY); if (fd == -1) { printf("File not found: %s\n", path); DIE("open() failed\n"); } fstat(fd, &s); result.data = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0); result.size = s.st_size; printf("mmap()-ed %ld bytes (%s)\n", result.size, path); close(fd); return(result); } static void print_sourcepoint(struct mi_process proc, int comp_unit, struct mi_sourcepoint *sp) { if (!sp) { printf("Unknown location\n"); return; } struct mi_compunit unit = proc.debug.compilation_units[comp_unit]; // NOTE: sourcepoint file indices are 1-based if (unit.source.source_files[sp->file - 1].file.data == 0) { char *file_filename = unit.source.source_files[sp->file - 1].filename; char *file_dirname = unit.source.source_files[sp->file - 1].dir; char *comp_dir = unit.source.comp_dir; char full_path[512] = { 0 }; snprintf(full_path, 511, "%s/%s/%s", comp_dir, file_dirname, file_filename); struct mi_buffer file = read_file_mmap(full_path); unit.source.source_files[sp->file - 1].file = file; } struct mi_buffer file = unit.source.source_files[sp->file - 1].file; char *source = (char *) file.data; u64 size = file.size; int current_line = 1; for (u32 i = 0; i < size; ++i) { if (current_line == sp->line) { int len = 0; int start = 0; int leading = 1; for (u32 j = i; j < size; ++j) { if (leading && (source[j] == '\t' || source[j] == ' ')) { ++start; } else { leading = 0; } if (source[j] == '\n') { break; } ++len; } printf("%.*s\n", len - start, source + i + start); return; } if (source[i] == '\n') { current_line++; } } } static struct mi_process process_create(char *path) { struct mi_process result = { 0 }; /* disable ASLR */ syscall(SYS_personality, ADDR_NO_RANDOMIZE); pid_t pid = fork(); if (pid == -1) { DIE("fork error\n"); } if (pid == 0) { int rt = ptrace(PTRACE_TRACEME, 0, 0, 0); assert(rt == 0); char *args[] = { path, NULL }; rt = execvp(path, args); if (rt == -1) { DIE("exevp error\n"); } } struct mi_buffer file = read_file_mmap(path); result.elf = file.data; result.elf_size = file.size; result.pid = pid; return(result); } static struct mi_function * get_function_around_pc(struct mi_process proc, u64 pc) { struct mi_debuginfo debug = proc.debug; for (int c = 0; c < debug.cu_count; ++c) { struct mi_compunit *unit = debug.compilation_units + c; if (unit->low_pc <= pc && pc < unit->high_pc) { for (int f = 0; f < unit->functions_count; ++f) { struct mi_function *func = debug.functions + unit->functions_from + f; if (func->low_pc <= pc && pc < func->high_pc) { return(func); } } break; } } return(0); } static struct mi_variable * get_variable(struct mi_process proc, char *name, int length, u64 pc) { struct mi_debuginfo debug = proc.debug; for (int c = 0; c < debug.cu_count; ++c) { struct mi_compunit *unit = debug.compilation_units + c; if (unit->low_pc <= pc && pc < unit->high_pc) { for (int f = 0; f < unit->functions_count; ++f) { struct mi_function *func = debug.functions + unit->functions_from + f; if (func->low_pc <= pc && pc < func->high_pc) { for (int v = 0; v < func->variables_count; ++v) { struct mi_variable *variable = debug.variables + func->variables_from + v; if (0 == strncmp(name, variable->name, length)) { return(variable); } } return(0); } } break; } } return(0); } static u64 get_cfa_at_pc(struct mi_process proc, u64 pc) { struct dwarf_fde fde = eh_frame_find_fde(proc, pc); if (!fde.length) { printf("Could not find FDE for pc %#lx\n", pc); return(0); } struct mi_registers regs = get_process_registers(proc); struct dwarf_regset regset = eh_frame_init_registers(regs, fde.cie); regset = eh_frame_find_pc(fde, regset, pc); return(regset.cfa); }