Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | #include <linux/export.h>
#include <linux/sched.h>
#include <linux/stacktrace.h>
#include <asm/stacktrace.h>
#if defined(CONFIG_FRAME_POINTER)
#ifdef CONFIG_KALLSYMS
#include <linux/kallsyms.h>
#include <linux/module.h>
static unsigned long tbi_boing_addr;
static unsigned long tbi_boing_size;
static void tbi_boing_init(void)
{
/* We need to know where TBIBoingVec is and it's size */
unsigned long size;
unsigned long offset;
char modname[MODULE_NAME_LEN];
char name[KSYM_NAME_LEN];
tbi_boing_addr = kallsyms_lookup_name("___TBIBoingVec");
if (!tbi_boing_addr)
tbi_boing_addr = 1;
else if (!lookup_symbol_attrs(tbi_boing_addr, &size,
&offset, modname, name))
tbi_boing_size = size;
}
#endif
#define ALIGN_DOWN(addr, size) ((addr)&(~((size)-1)))
/*
* Unwind the current stack frame and store the new register values in the
* structure passed as argument. Unwinding is equivalent to a function return,
* hence the new PC value rather than LR should be used for backtrace.
*/
int notrace unwind_frame(struct stackframe *frame)
{
struct metag_frame *fp = (struct metag_frame *)frame->fp;
unsigned long lr;
unsigned long fpnew;
if (frame->fp & 0x7)
return -EINVAL;
fpnew = fp->fp;
lr = fp->lr - 4;
#ifdef CONFIG_KALLSYMS
/* If we've reached TBIBoingVec then we're at an interrupt
* entry point or a syscall entry point. The frame pointer
* points to a pt_regs which can be used to continue tracing on
* the other side of the boing.
*/
if (!tbi_boing_addr)
tbi_boing_init();
if (tbi_boing_size && lr >= tbi_boing_addr &&
lr < tbi_boing_addr + tbi_boing_size) {
struct pt_regs *regs = (struct pt_regs *)fpnew;
if (user_mode(regs))
return -EINVAL;
fpnew = regs->ctx.AX[1].U0;
lr = regs->ctx.DX[4].U1;
}
#endif
/* stack grows up, so frame pointers must decrease */
if (fpnew < (ALIGN_DOWN((unsigned long)fp, THREAD_SIZE) +
sizeof(struct thread_info)) || fpnew >= (unsigned long)fp)
return -EINVAL;
/* restore the registers from the stack frame */
frame->fp = fpnew;
frame->pc = lr;
return 0;
}
#else
int notrace unwind_frame(struct stackframe *frame)
{
struct metag_frame *sp = (struct metag_frame *)frame->sp;
if (frame->sp & 0x7)
return -EINVAL;
while (!kstack_end(sp)) {
unsigned long addr = sp->lr - 4;
sp--;
if (__kernel_text_address(addr)) {
frame->sp = (unsigned long)sp;
frame->pc = addr;
return 0;
}
}
return -EINVAL;
}
#endif
void notrace walk_stackframe(struct stackframe *frame,
int (*fn)(struct stackframe *, void *), void *data)
{
while (1) {
int ret;
if (fn(frame, data))
break;
ret = unwind_frame(frame);
if (ret < 0)
break;
}
}
EXPORT_SYMBOL(walk_stackframe);
#ifdef CONFIG_STACKTRACE
struct stack_trace_data {
struct stack_trace *trace;
unsigned int no_sched_functions;
unsigned int skip;
};
static int save_trace(struct stackframe *frame, void *d)
{
struct stack_trace_data *data = d;
struct stack_trace *trace = data->trace;
unsigned long addr = frame->pc;
if (data->no_sched_functions && in_sched_functions(addr))
return 0;
if (data->skip) {
data->skip--;
return 0;
}
trace->entries[trace->nr_entries++] = addr;
return trace->nr_entries >= trace->max_entries;
}
void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace)
{
struct stack_trace_data data;
struct stackframe frame;
data.trace = trace;
data.skip = trace->skip;
if (tsk != current) {
#ifdef CONFIG_SMP
/*
* What guarantees do we have here that 'tsk' is not
* running on another CPU? For now, ignore it as we
* can't guarantee we won't explode.
*/
if (trace->nr_entries < trace->max_entries)
trace->entries[trace->nr_entries++] = ULONG_MAX;
return;
#else
data.no_sched_functions = 1;
frame.fp = thread_saved_fp(tsk);
frame.sp = thread_saved_sp(tsk);
frame.lr = 0; /* recovered from the stack */
frame.pc = thread_saved_pc(tsk);
#endif
} else {
register unsigned long current_sp asm ("A0StP");
data.no_sched_functions = 0;
frame.fp = (unsigned long)__builtin_frame_address(0);
frame.sp = current_sp;
frame.lr = (unsigned long)__builtin_return_address(0);
frame.pc = (unsigned long)save_stack_trace_tsk;
}
walk_stackframe(&frame, save_trace, &data);
if (trace->nr_entries < trace->max_entries)
trace->entries[trace->nr_entries++] = ULONG_MAX;
}
void save_stack_trace(struct stack_trace *trace)
{
save_stack_trace_tsk(current, trace);
}
EXPORT_SYMBOL_GPL(save_stack_trace);
#endif
|