mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 01:33:06 +00:00
libdrgn: x86_64: unwind call when pc is 0
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 <inwardvessel@gmail.com>
This commit is contained in:
parent
a397b3a990
commit
412ce956b0
@ -243,6 +243,44 @@ get_registers_from_frame_pointer(struct drgn_program *prog,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwind from a call instruction, assuming that nothing else has been changed
|
||||||
|
// since.
|
||||||
|
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 the return address from the top of the stack.
|
||||||
|
uint64_t ret_addr;
|
||||||
|
err = drgn_program_read_u64(prog, rsp.value, false, &ret_addr);
|
||||||
|
if (err) {
|
||||||
|
if (err->code == DRGN_ERROR_FAULT) {
|
||||||
|
drgn_error_destroy(err);
|
||||||
|
err = &drgn_stop;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most of the registers are unchanged.
|
||||||
|
struct drgn_register_state *tmp = drgn_register_state_dup(regs);
|
||||||
|
if (!tmp)
|
||||||
|
return &drgn_enomem;
|
||||||
|
|
||||||
|
// The PC and rip are the return address we just read.
|
||||||
|
drgn_register_state_set_pc(prog, tmp, ret_addr);
|
||||||
|
drgn_register_state_set_from_u64(prog, tmp, rip, ret_addr);
|
||||||
|
// rsp is after the saved return address.
|
||||||
|
drgn_register_state_set_from_u64(prog, tmp, rsp, rsp.value + 8);
|
||||||
|
*ret = tmp;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static struct drgn_error *
|
static struct drgn_error *
|
||||||
fallback_unwind_x86_64(struct drgn_program *prog,
|
fallback_unwind_x86_64(struct drgn_program *prog,
|
||||||
struct drgn_register_state *regs,
|
struct drgn_register_state *regs,
|
||||||
@ -250,6 +288,13 @@ fallback_unwind_x86_64(struct drgn_program *prog,
|
|||||||
{
|
{
|
||||||
struct drgn_error *err;
|
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 =
|
struct optional_uint64 rbp =
|
||||||
drgn_register_state_get_u64(prog, regs, rbp);
|
drgn_register_state_get_u64(prog, regs, rbp);
|
||||||
if (!rbp.has_value)
|
if (!rbp.has_value)
|
||||||
|
Loading…
Reference in New Issue
Block a user