135 lines
3.1 KiB
C
135 lines
3.1 KiB
C
/*
|
|
* Stack trace management functions
|
|
*
|
|
* Copyright (C) 2006-2009 Red Hat, Inc., Ingo Molnar <mingo@redhat.com>
|
|
*/
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/export.h>
|
|
#include <linux/uaccess.h>
|
|
#include <asm/stacktrace.h>
|
|
#include <asm/unwind.h>
|
|
|
|
void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
|
|
struct task_struct *task, struct pt_regs *regs)
|
|
{
|
|
struct unwind_state state;
|
|
unsigned long addr;
|
|
|
|
if (regs && !consume_entry(cookie, regs->ip, false))
|
|
return;
|
|
|
|
for (unwind_start(&state, task, regs, NULL); !unwind_done(&state);
|
|
unwind_next_frame(&state)) {
|
|
addr = unwind_get_return_address(&state);
|
|
if (!addr || !consume_entry(cookie, addr, false))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function returns an error if it detects any unreliable features of the
|
|
* stack. Otherwise it guarantees that the stack trace is reliable.
|
|
*
|
|
* If the task is not 'current', the caller *must* ensure the task is inactive.
|
|
*/
|
|
int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
|
|
void *cookie, struct task_struct *task)
|
|
{
|
|
struct unwind_state state;
|
|
struct pt_regs *regs;
|
|
unsigned long addr;
|
|
|
|
for (unwind_start(&state, task, NULL, NULL);
|
|
!unwind_done(&state) && !unwind_error(&state);
|
|
unwind_next_frame(&state)) {
|
|
|
|
regs = unwind_get_entry_regs(&state, NULL);
|
|
if (regs) {
|
|
/* Success path for user tasks */
|
|
if (user_mode(regs))
|
|
return 0;
|
|
|
|
/*
|
|
* Kernel mode registers on the stack indicate an
|
|
* in-kernel interrupt or exception (e.g., preemption
|
|
* or a page fault), which can make frame pointers
|
|
* unreliable.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_FRAME_POINTER))
|
|
return -EINVAL;
|
|
}
|
|
|
|
addr = unwind_get_return_address(&state);
|
|
|
|
/*
|
|
* A NULL or invalid return address probably means there's some
|
|
* generated code which __kernel_text_address() doesn't know
|
|
* about.
|
|
*/
|
|
if (!addr)
|
|
return -EINVAL;
|
|
|
|
if (!consume_entry(cookie, addr, false))
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Check for stack corruption */
|
|
if (unwind_error(&state))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Userspace stacktrace - based on kernel/trace/trace_sysprof.c */
|
|
|
|
struct stack_frame_user {
|
|
const void __user *next_fp;
|
|
unsigned long ret_addr;
|
|
};
|
|
|
|
static int
|
|
copy_stack_frame(const void __user *fp, struct stack_frame_user *frame)
|
|
{
|
|
int ret;
|
|
|
|
if (__range_not_ok(fp, sizeof(*frame), TASK_SIZE))
|
|
return 0;
|
|
|
|
ret = 1;
|
|
pagefault_disable();
|
|
if (__copy_from_user_inatomic(frame, fp, sizeof(*frame)))
|
|
ret = 0;
|
|
pagefault_enable();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void arch_stack_walk_user(stack_trace_consume_fn consume_entry, void *cookie,
|
|
const struct pt_regs *regs)
|
|
{
|
|
const void __user *fp = (const void __user *)regs->bp;
|
|
|
|
if (!consume_entry(cookie, regs->ip, false))
|
|
return;
|
|
|
|
while (1) {
|
|
struct stack_frame_user frame;
|
|
|
|
frame.next_fp = NULL;
|
|
frame.ret_addr = 0;
|
|
if (!copy_stack_frame(fp, &frame))
|
|
break;
|
|
if ((unsigned long)fp < regs->sp)
|
|
break;
|
|
if (!frame.ret_addr)
|
|
break;
|
|
if (!consume_entry(cookie, frame.ret_addr, false))
|
|
break;
|
|
fp = frame.next_fp;
|
|
}
|
|
}
|
|
|