|
|
|
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);
|
|
|
|
}
|