Skip to content

Commit

Permalink
unwind call when pc is 0
Browse files Browse the repository at this point in the history
Signed-off-by: JP Kobryn <[email protected]>
  • Loading branch information
inwardvessel committed Aug 16, 2023
1 parent 2c0e991 commit 5f6c1a7
Showing 1 changed file with 56 additions and 0 deletions.
56 changes: 56 additions & 0 deletions libdrgn/arch_x86_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 5f6c1a7

Please sign in to comment.