Skip to content

Commit

Permalink
unwind call when pc is 0
Browse files Browse the repository at this point in the history
While using drgn for debugging a linux vmcore, a stack trace was found
to contain a single frame of "0: 0x0". It seemed peculiar since the
corresponding panic dump in kmsg showed a stack trace of multiple
frames with resolved symbols. Digging into the drgn stack unwinding
functions, it was found that there was no way of handling a program
counter (RIP on x86) of zero. The main unwinding function would return
an error leading to an attempt to call a fallback unwind function.
Even though the fallback routine was attempted, it did not help since
the fallback tries to make use of frame pointers. In the case where
the kernel is compiled without frame pointers, the fallback function
would also error out due to an attempt to read from an unmapped
register.

A program counter of zero can be assumed to be a call to a null
function pointer. Using this as a basis, when in the absence of frame
pointers, the call to a null address can be unwound in a special way.
Registers can be slightly adjusted so that unwinding is possible.
First, the program counter needs to point somewhere it can be unwound.
At the time of the null call, RSP will contain the return address so
if we take that RSP and assign it to RIP, we effectively put the
program counter after the null call. Next, the top of the stack frame
needs to change to the top of the calling frame. This is done by
incrementing RSP. At this point, unwinding can continue and any frames
below the null call will be included in the trace. This special
unwinding operation is done in the new function "unwind_call()". To
solve the problem of being unable to handle a zero pc when frame
pointers are not present, the fallback unwind function is updated to
check for a zero pc and call the new function if needed.

Signed-off-by: JP Kobryn <[email protected]>
  • Loading branch information
inwardvessel committed Aug 22, 2023
1 parent 6eced2f commit 8590170
Showing 1 changed file with 55 additions and 0 deletions.
55 changes: 55 additions & 0 deletions libdrgn/arch_x86_64.c
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +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;

/*
* If the program counter is 0, it's likely that a NULL function pointer
* was called. Assume that the only thing we need to unwind is a single
* call instruction.
*/
struct optional_uint64 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);
if (!rbp.has_value)
Expand Down

0 comments on commit 8590170

Please sign in to comment.