248 lines
5.1 KiB
C
248 lines
5.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Stack trace management functions
|
|
*
|
|
* Copyright (C) 2018 snyh <xiabin@deepin.com>
|
|
*/
|
|
#include <linux/sched.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/perf_event.h>
|
|
#include <linux/kallsyms.h>
|
|
|
|
#include <asm/stacktrace.h>
|
|
|
|
/*
|
|
* sw_64 PCS assigns the frame pointer to r15.
|
|
*
|
|
* A simple function prologue looks like this:
|
|
* ldi sp,-xx(sp)
|
|
* stl ra,0(sp)
|
|
* stl fp,8(sp)
|
|
* mov sp,fp
|
|
*
|
|
* A simple function epilogue looks like this:
|
|
* mov fp,sp
|
|
* ldl ra,0(sp)
|
|
* ldl fp,8(sp)
|
|
* ldi sp,+xx(sp)
|
|
*/
|
|
|
|
#ifdef CONFIG_FRAME_POINTER
|
|
|
|
int unwind_frame(struct task_struct *tsk, struct stackframe *frame)
|
|
{
|
|
unsigned long fp = frame->fp;
|
|
|
|
if (fp & 0x7)
|
|
return -EINVAL;
|
|
|
|
if (!tsk)
|
|
tsk = current;
|
|
|
|
if (!on_accessible_stack(tsk, fp, NULL))
|
|
return -EINVAL;
|
|
|
|
frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
|
|
frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8));
|
|
|
|
/*
|
|
* Frames created upon entry from user have NULL FP and PC values, so
|
|
* don't bother reporting these. Frames created by __noreturn functions
|
|
* might have a valid FP even if PC is bogus, so only terminate where
|
|
* both are NULL.
|
|
*/
|
|
if (!frame->fp && !frame->pc)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(unwind_frame);
|
|
|
|
void walk_stackframe(struct task_struct *tsk, struct pt_regs *regs,
|
|
int (*fn)(unsigned long, void *), void *data)
|
|
{
|
|
unsigned long pc, fp;
|
|
|
|
struct stackframe frame;
|
|
|
|
if (regs) {
|
|
unsigned long offset;
|
|
|
|
pc = regs->pc;
|
|
fp = regs->regs[15];
|
|
if (kallsyms_lookup_size_offset(pc, NULL, &offset)
|
|
&& offset < 16) {
|
|
/* call stack has not been setup
|
|
* store pc first then loop from ra
|
|
*/
|
|
if (fn(pc, data))
|
|
return;
|
|
pc = regs->regs[26];
|
|
}
|
|
} else if (tsk == current || tsk == NULL) {
|
|
fp = (unsigned long)__builtin_frame_address(0);
|
|
pc = (unsigned long)walk_stackframe;
|
|
} else {
|
|
fp = tsk->thread.s[6];
|
|
pc = tsk->thread.ra;
|
|
}
|
|
|
|
if (!__kernel_text_address(pc) || fn(pc, data))
|
|
return;
|
|
|
|
frame.pc = pc;
|
|
frame.fp = fp;
|
|
while (1) {
|
|
int ret;
|
|
|
|
ret = unwind_frame(tsk, &frame);
|
|
if (ret < 0)
|
|
break;
|
|
|
|
if (fn(frame.pc, data))
|
|
break;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(walk_stackframe);
|
|
|
|
#else /* !CONFIG_FRAME_POINTER */
|
|
void walk_stackframe(struct task_struct *tsk, struct pt_regs *regs,
|
|
int (*fn)(unsigned long, void *), void *data)
|
|
{
|
|
unsigned long *ksp;
|
|
unsigned long sp, pc;
|
|
|
|
if (regs) {
|
|
sp = (unsigned long)(regs+1);
|
|
pc = regs->pc;
|
|
} else if (tsk == current || tsk == NULL) {
|
|
register unsigned long current_sp __asm__ ("$30");
|
|
sp = current_sp;
|
|
pc = (unsigned long)walk_stackframe;
|
|
} else {
|
|
sp = tsk->thread.sp;
|
|
pc = tsk->thread.ra;
|
|
}
|
|
|
|
ksp = (unsigned long *)sp;
|
|
|
|
while (!kstack_end(ksp)) {
|
|
if (__kernel_text_address(pc) && fn(pc, data))
|
|
break;
|
|
pc = *ksp++;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(walk_stackframe);
|
|
|
|
#endif/* CONFIG_FRAME_POINTER */
|
|
|
|
static int print_address_trace(unsigned long pc, void *data)
|
|
{
|
|
print_ip_sym((const char *)data, pc);
|
|
return 0;
|
|
}
|
|
|
|
void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl)
|
|
{
|
|
pr_info("Trace:\n");
|
|
walk_stackframe(task, NULL, print_address_trace, (void *)loglvl);
|
|
}
|
|
|
|
#ifdef CONFIG_STACKTRACE
|
|
/*
|
|
* Save stack-backtrace addresses into a stack_trace buffer.
|
|
*/
|
|
struct stack_trace_data {
|
|
struct stack_trace *trace;
|
|
unsigned int nosched;
|
|
};
|
|
|
|
int save_trace(unsigned long pc, void *d)
|
|
{
|
|
struct stack_trace_data *data = d;
|
|
struct stack_trace *trace = data->trace;
|
|
|
|
if (data->nosched && in_sched_functions(pc))
|
|
return 0;
|
|
if (trace->skip > 0) {
|
|
trace->skip--;
|
|
return 0;
|
|
}
|
|
|
|
trace->entries[trace->nr_entries++] = pc;
|
|
return (trace->nr_entries >= trace->max_entries);
|
|
}
|
|
|
|
void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace)
|
|
{
|
|
struct stack_trace_data data;
|
|
|
|
data.trace = trace;
|
|
data.nosched = 0;
|
|
|
|
walk_stackframe(current, regs, save_trace, &data);
|
|
|
|
if (trace->nr_entries < trace->max_entries)
|
|
trace->entries[trace->nr_entries++] = ULONG_MAX;
|
|
}
|
|
|
|
static void __save_stack_trace(struct task_struct *tsk,
|
|
struct stack_trace *trace, unsigned int nosched)
|
|
{
|
|
struct stack_trace_data data;
|
|
|
|
data.trace = trace;
|
|
data.nosched = nosched;
|
|
|
|
walk_stackframe(tsk, NULL, save_trace, &data);
|
|
|
|
if (trace->nr_entries < trace->max_entries)
|
|
trace->entries[trace->nr_entries++] = ULONG_MAX;
|
|
}
|
|
|
|
void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
|
|
{
|
|
__save_stack_trace(tsk, trace, 1);
|
|
}
|
|
EXPORT_SYMBOL_GPL(save_stack_trace_tsk);
|
|
|
|
void save_stack_trace(struct stack_trace *trace)
|
|
{
|
|
__save_stack_trace(current, trace, 0);
|
|
}
|
|
EXPORT_SYMBOL_GPL(save_stack_trace);
|
|
#endif
|
|
|
|
static int save_pc(unsigned long pc, void *data)
|
|
{
|
|
unsigned long *p = data;
|
|
*p = 0;
|
|
|
|
if (!in_sched_functions(pc))
|
|
*p = pc;
|
|
|
|
return *p;
|
|
}
|
|
|
|
unsigned long __get_wchan(struct task_struct *tsk)
|
|
{
|
|
unsigned long pc;
|
|
|
|
if (!tsk || tsk == current || task_is_running(tsk))
|
|
return 0;
|
|
walk_stackframe(tsk, NULL, save_pc, &pc);
|
|
|
|
return pc;
|
|
}
|
|
|
|
#ifdef CONFIG_HAVE_RELIABLE_STACKTRACE
|
|
int save_stack_trace_tsk_reliable(struct task_struct *tsk,
|
|
struct stack_trace *trace)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|