xref: /openbmc/linux/arch/riscv/kernel/stacktrace.c (revision d6b6592ac6d11eab91e6758d224eac35f4122aca)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
25d8544e2SPalmer Dabbelt /*
35d8544e2SPalmer Dabbelt  * Copyright (C) 2008 ARM Limited
45d8544e2SPalmer Dabbelt  * Copyright (C) 2014 Regents of the University of California
55d8544e2SPalmer Dabbelt  */
65d8544e2SPalmer Dabbelt 
75d8544e2SPalmer Dabbelt #include <linux/export.h>
85d8544e2SPalmer Dabbelt #include <linux/kallsyms.h>
95d8544e2SPalmer Dabbelt #include <linux/sched.h>
105d8544e2SPalmer Dabbelt #include <linux/sched/debug.h>
115d8544e2SPalmer Dabbelt #include <linux/sched/task_stack.h>
125d8544e2SPalmer Dabbelt #include <linux/stacktrace.h>
13b785ec12SAlan Kao #include <linux/ftrace.h>
145d8544e2SPalmer Dabbelt 
1599c168fcSKefeng Wang #include <asm/stacktrace.h>
1699c168fcSKefeng Wang 
175d8544e2SPalmer Dabbelt #ifdef CONFIG_FRAME_POINTER
185d8544e2SPalmer Dabbelt 
197ecdadf7SGuo Ren extern asmlinkage void ret_from_exception(void);
207ecdadf7SGuo Ren 
fp_is_valid(unsigned long fp,unsigned long sp)21c273cae0SMatthew Bystrin static inline int fp_is_valid(unsigned long fp, unsigned long sp)
22c273cae0SMatthew Bystrin {
23c273cae0SMatthew Bystrin 	unsigned long low, high;
24c273cae0SMatthew Bystrin 
25c273cae0SMatthew Bystrin 	low = sp + sizeof(struct stackframe);
26c273cae0SMatthew Bystrin 	high = ALIGN(sp, THREAD_SIZE);
27c273cae0SMatthew Bystrin 
28c273cae0SMatthew Bystrin 	return !(fp < low || fp > high || fp & 0x07);
29c273cae0SMatthew Bystrin }
30c273cae0SMatthew Bystrin 
walk_stackframe(struct task_struct * task,struct pt_regs * regs,bool (* fn)(void *,unsigned long),void * arg)31dbeb90b0SMao Han void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
325cb0080fSKefeng Wang 			     bool (*fn)(void *, unsigned long), void *arg)
335d8544e2SPalmer Dabbelt {
345d8544e2SPalmer Dabbelt 	unsigned long fp, sp, pc;
35*55f6da70SPuranjay Mohan 	int graph_idx = 0;
366a00ef44SChangbin Du 	int level = 0;
375d8544e2SPalmer Dabbelt 
385d8544e2SPalmer Dabbelt 	if (regs) {
396ab77af4SChristoph Hellwig 		fp = frame_pointer(regs);
406ab77af4SChristoph Hellwig 		sp = user_stack_pointer(regs);
416ab77af4SChristoph Hellwig 		pc = instruction_pointer(regs);
4278d9d800SJisheng Zhang 	} else if (task == NULL || task == current) {
436a00ef44SChangbin Du 		fp = (unsigned long)__builtin_frame_address(0);
44fdecfea0SKees Cook 		sp = current_stack_pointer;
456a00ef44SChangbin Du 		pc = (unsigned long)walk_stackframe;
46cb80242cSLiu Shixin 		level = -1;
475d8544e2SPalmer Dabbelt 	} else {
485d8544e2SPalmer Dabbelt 		/* task blocked in __switch_to */
495d8544e2SPalmer Dabbelt 		fp = task->thread.s[0];
505d8544e2SPalmer Dabbelt 		sp = task->thread.sp;
515d8544e2SPalmer Dabbelt 		pc = task->thread.ra;
525d8544e2SPalmer Dabbelt 	}
535d8544e2SPalmer Dabbelt 
545d8544e2SPalmer Dabbelt 	for (;;) {
555d8544e2SPalmer Dabbelt 		struct stackframe *frame;
565d8544e2SPalmer Dabbelt 
57cb80242cSLiu Shixin 		if (unlikely(!__kernel_text_address(pc) || (level++ >= 0 && !fn(arg, pc))))
585d8544e2SPalmer Dabbelt 			break;
595d8544e2SPalmer Dabbelt 
60c273cae0SMatthew Bystrin 		if (unlikely(!fp_is_valid(fp, sp)))
615d8544e2SPalmer Dabbelt 			break;
62c273cae0SMatthew Bystrin 
635d8544e2SPalmer Dabbelt 		/* Unwind stack frame */
645d8544e2SPalmer Dabbelt 		frame = (struct stackframe *)fp - 1;
655d8544e2SPalmer Dabbelt 		sp = fp;
66c273cae0SMatthew Bystrin 		if (regs && (regs->epc == pc) && fp_is_valid(frame->ra, sp)) {
67c273cae0SMatthew Bystrin 			/* We hit function where ra is not saved on the stack */
68f766f77aSChen Huang 			fp = frame->ra;
69f766f77aSChen Huang 			pc = regs->ra;
70f766f77aSChen Huang 		} else {
715d8544e2SPalmer Dabbelt 			fp = frame->fp;
72*55f6da70SPuranjay Mohan 			pc = ftrace_graph_ret_addr(current, &graph_idx, frame->ra,
735c3022e4SGuo Ren 						   &frame->ra);
747ecdadf7SGuo Ren 			if (pc == (unsigned long)ret_from_exception) {
757ecdadf7SGuo Ren 				if (unlikely(!__kernel_text_address(pc) || !fn(arg, pc)))
767ecdadf7SGuo Ren 					break;
777ecdadf7SGuo Ren 
787ecdadf7SGuo Ren 				pc = ((struct pt_regs *)sp)->epc;
797ecdadf7SGuo Ren 				fp = ((struct pt_regs *)sp)->s0;
807ecdadf7SGuo Ren 			}
815d8544e2SPalmer Dabbelt 		}
82f766f77aSChen Huang 
83f766f77aSChen Huang 	}
845d8544e2SPalmer Dabbelt }
855d8544e2SPalmer Dabbelt 
865d8544e2SPalmer Dabbelt #else /* !CONFIG_FRAME_POINTER */
875d8544e2SPalmer Dabbelt 
walk_stackframe(struct task_struct * task,struct pt_regs * regs,bool (* fn)(void *,unsigned long),void * arg)880502bee3SKefeng Wang void notrace walk_stackframe(struct task_struct *task,
899dd97064SKefeng Wang 	struct pt_regs *regs, bool (*fn)(void *, unsigned long), void *arg)
905d8544e2SPalmer Dabbelt {
915d8544e2SPalmer Dabbelt 	unsigned long sp, pc;
925d8544e2SPalmer Dabbelt 	unsigned long *ksp;
935d8544e2SPalmer Dabbelt 
945d8544e2SPalmer Dabbelt 	if (regs) {
956ab77af4SChristoph Hellwig 		sp = user_stack_pointer(regs);
966ab77af4SChristoph Hellwig 		pc = instruction_pointer(regs);
975d8544e2SPalmer Dabbelt 	} else if (task == NULL || task == current) {
98fdecfea0SKees Cook 		sp = current_stack_pointer;
995d8544e2SPalmer Dabbelt 		pc = (unsigned long)walk_stackframe;
1005d8544e2SPalmer Dabbelt 	} else {
1015d8544e2SPalmer Dabbelt 		/* task blocked in __switch_to */
1025d8544e2SPalmer Dabbelt 		sp = task->thread.sp;
1035d8544e2SPalmer Dabbelt 		pc = task->thread.ra;
1045d8544e2SPalmer Dabbelt 	}
1055d8544e2SPalmer Dabbelt 
1065d8544e2SPalmer Dabbelt 	if (unlikely(sp & 0x7))
1075d8544e2SPalmer Dabbelt 		return;
1085d8544e2SPalmer Dabbelt 
1095d8544e2SPalmer Dabbelt 	ksp = (unsigned long *)sp;
1105d8544e2SPalmer Dabbelt 	while (!kstack_end(ksp)) {
1115cb0080fSKefeng Wang 		if (__kernel_text_address(pc) && unlikely(!fn(arg, pc)))
1125d8544e2SPalmer Dabbelt 			break;
11376950340SAlexandre Ghiti 		pc = READ_ONCE_NOCHECK(*ksp++) - 0x4;
1145d8544e2SPalmer Dabbelt 	}
1155d8544e2SPalmer Dabbelt }
1165d8544e2SPalmer Dabbelt 
1175d8544e2SPalmer Dabbelt #endif /* CONFIG_FRAME_POINTER */
1185d8544e2SPalmer Dabbelt 
print_trace_address(void * arg,unsigned long pc)1195cb0080fSKefeng Wang static bool print_trace_address(void *arg, unsigned long pc)
1205d8544e2SPalmer Dabbelt {
1210b3d4365SDmitry Safonov 	const char *loglvl = arg;
1220b3d4365SDmitry Safonov 
1230b3d4365SDmitry Safonov 	print_ip_sym(loglvl, pc);
1245cb0080fSKefeng Wang 	return true;
1255d8544e2SPalmer Dabbelt }
1265d8544e2SPalmer Dabbelt 
dump_backtrace(struct pt_regs * regs,struct task_struct * task,const char * loglvl)127eac2f305SChen Huang noinline void dump_backtrace(struct pt_regs *regs, struct task_struct *task,
128091b9450SKefeng Wang 		    const char *loglvl)
129091b9450SKefeng Wang {
130091b9450SKefeng Wang 	walk_stackframe(task, regs, print_trace_address, (void *)loglvl);
131091b9450SKefeng Wang }
132091b9450SKefeng Wang 
show_stack(struct task_struct * task,unsigned long * sp,const char * loglvl)1339cb8f069SDmitry Safonov void show_stack(struct task_struct *task, unsigned long *sp, const char *loglvl)
1340b3d4365SDmitry Safonov {
135eac2f305SChen Huang 	pr_cont("%sCall Trace:\n", loglvl);
136091b9450SKefeng Wang 	dump_backtrace(NULL, task, loglvl);
1370b3d4365SDmitry Safonov }
1380b3d4365SDmitry Safonov 
save_wchan(void * arg,unsigned long pc)1399dd97064SKefeng Wang static bool save_wchan(void *arg, unsigned long pc)
1405d8544e2SPalmer Dabbelt {
1415d8544e2SPalmer Dabbelt 	if (!in_sched_functions(pc)) {
1425d8544e2SPalmer Dabbelt 		unsigned long *p = arg;
1435d8544e2SPalmer Dabbelt 		*p = pc;
1445d8544e2SPalmer Dabbelt 		return false;
1455d8544e2SPalmer Dabbelt 	}
1465cb0080fSKefeng Wang 	return true;
1475cb0080fSKefeng Wang }
1485d8544e2SPalmer Dabbelt 
__get_wchan(struct task_struct * task)14942a20f86SKees Cook unsigned long __get_wchan(struct task_struct *task)
1505d8544e2SPalmer Dabbelt {
1515d8544e2SPalmer Dabbelt 	unsigned long pc = 0;
1525d8544e2SPalmer Dabbelt 
15376f5dfacSJisheng Zhang 	if (!try_get_task_stack(task))
15476f5dfacSJisheng Zhang 		return 0;
1555d8544e2SPalmer Dabbelt 	walk_stackframe(task, NULL, save_wchan, &pc);
15676f5dfacSJisheng Zhang 	put_task_stack(task);
1575d8544e2SPalmer Dabbelt 	return pc;
1585d8544e2SPalmer Dabbelt }
1595d8544e2SPalmer Dabbelt 
arch_stack_walk(stack_trace_consume_fn consume_entry,void * cookie,struct task_struct * task,struct pt_regs * regs)16074eb70ceSAndy Chiu noinline noinstr void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
1615cb0080fSKefeng Wang 		     struct task_struct *task, struct pt_regs *regs)
1625d8544e2SPalmer Dabbelt {
1635cb0080fSKefeng Wang 	walk_stackframe(task, regs, consume_entry, cookie);
1645d8544e2SPalmer Dabbelt }
165