diff --git a/libdrgn/arch_x86_64.c b/libdrgn/arch_x86_64.c index e03cb31c7..2aef53e1e 100644 --- a/libdrgn/arch_x86_64.c +++ b/libdrgn/arch_x86_64.c @@ -243,12 +243,68 @@ get_registers_from_frame_pointer(struct drgn_program *prog, return NULL; } +/** + * Escapes the effect a call operation has on registers, changing registers + * so that RIP points to the caller frame post-call instruction and RSP is + * increased to point to the top of the caller frame. + * This can be used in cases where, for example, a NULL function pointer + * was called and as a result the RIP (program counter) value of zero prevents + * any unwinding. + */ +static struct drgn_error * +unwind_call(struct drgn_program *prog, + struct drgn_register_state *regs, + struct drgn_register_state **ret) +{ + struct drgn_error *err; + + struct optional_uint64 rsp = + drgn_register_state_get_u64(prog, regs, rsp); + if (!rsp.has_value) + return &drgn_stop; + + /* + * Read RSP which contains the return address to be used + * as the new RIP value. + */ + uint64_t postcall_rip; + err = drgn_program_read_u64(prog, rsp.value, false, &postcall_rip); + if (err) { + if (err->code == DRGN_ERROR_FAULT) { + drgn_error_destroy(err); + err = &drgn_stop; + } + return err; + } + + struct drgn_register_state *tmp = drgn_register_state_dup(regs); + if (!tmp) + return &drgn_enomem; + + /* Set PC & RIP to be the new RIP value previously read. */ + drgn_register_state_set_pc(prog, tmp, postcall_rip); + drgn_register_state_set_from_u64(prog, tmp, rip, postcall_rip); + /* Adjust RSP to the top of the frame below. */ + drgn_register_state_set_from_u64(prog, tmp, rsp, rsp.value + 8); + *ret = tmp; + return NULL; +} + static struct drgn_error * fallback_unwind_x86_64(struct drgn_program *prog, struct drgn_register_state *regs, struct drgn_register_state **ret) { struct drgn_error *err; + struct optional_uint64 pc; + + /* + * Check if a NULL function pointer was called + * and adjust registers so unwinding is possible. + */ + pc = drgn_register_state_get_pc(regs); + if (pc.has_value && pc.value == 0) + return unwind_call(prog, regs, ret); struct optional_uint64 rbp = drgn_register_state_get_u64(prog, regs, rbp);