[PATCH] parisc: Fix and improve kernel stack unwinding

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patchset fixes and improves stack unwinding a lot:
1. Show backward stack traces with up to 30 callsites
2. Do not depend on CONFIG_KALLSYMS to generate backtraces. This is done
   by adding proper unwind entries for some of the used assembler
   functions.
3. Speed up backtrace generation

Make sure you have this patch to GNU as installed:
https://sourceware.org/ml/binutils/2018-07/msg00474.html
Without this patch, unwind info in the kernel is often wrong for various
functions.

Signed-off-by: Helge Deller <deller@xxxxxx>

diff --git a/arch/parisc/include/asm/assembly.h b/arch/parisc/include/asm/assembly.h
index 60e6f07b7e32..3185b255f757 100644
--- a/arch/parisc/include/asm/assembly.h
+++ b/arch/parisc/include/asm/assembly.h
@@ -36,6 +36,7 @@
 #define RP_OFFSET	16
 #define FRAME_SIZE	128
 #define CALLEE_REG_FRAME_SIZE	144
+#define ASM_ULONG_SIZE	8
 #define ASM_ULONG_INSN	.dword
 #else	/* CONFIG_64BIT */
 #define LDREG	ldw
@@ -50,6 +51,7 @@
 #define RP_OFFSET	20
 #define FRAME_SIZE	64
 #define CALLEE_REG_FRAME_SIZE	128
+#define ASM_ULONG_SIZE	4
 #define ASM_ULONG_INSN	.word
 #endif
 
diff --git a/arch/parisc/include/asm/unwind.h b/arch/parisc/include/asm/unwind.h
index c73a3ee20226..f133b7efbebb 100644
--- a/arch/parisc/include/asm/unwind.h
+++ b/arch/parisc/include/asm/unwind.h
@@ -4,6 +4,9 @@
 
 #include <linux/list.h>
 
+/* Max number of levels to backtrace */
+#define MAX_UNWIND_ENTRIES	30
+
 /* From ABI specifications */
 struct unwind_table_entry {
 	unsigned int region_start;
diff --git a/arch/parisc/kernel/entry.S b/arch/parisc/kernel/entry.S
index e95207c0565e..4ad0e78a0cc6 100644
--- a/arch/parisc/kernel/entry.S
+++ b/arch/parisc/kernel/entry.S
@@ -764,7 +764,6 @@ END(fault_vector_11)
 #endif
 	/* Fault vector is separately protected and *must* be on its own page */
 	.align		PAGE_SIZE
-ENTRY(end_fault_vector)
 
 	.import		handle_interruption,code
 	.import		do_cpu_irq_mask,code
@@ -776,7 +775,9 @@ ENTRY(end_fault_vector)
 	 */
 
 ENTRY_CFI(ret_from_kernel_thread)
-
+	.proc
+	.callinfo FRAME=0
+	.entry
 	/* Call schedule_tail first though */
 	BL	schedule_tail, %r2
 	nop
@@ -791,6 +792,8 @@ ENTRY_CFI(ret_from_kernel_thread)
 	copy	%r31, %r2
 	b	finish_child_return
 	nop
+	.exit
+	.procend
 ENDPROC_CFI(ret_from_kernel_thread)
 
 
@@ -815,8 +818,12 @@ ENTRY_CFI(_switch_to)
 	LDREG	TASK_THREAD_INFO(%r25), %r25
 	bv	%r0(%r2)
 	mtctl   %r25,%cr30
+ENDPROC_CFI(_switch_to)
 
-_switch_to_ret:
+ENTRY_CFI(_switch_to_ret)
+	.proc
+	.callinfo FRAME=0
+	.entry
 	mtctl	%r0, %cr0		/* Needed for single stepping */
 	callee_rest
 	callee_rest_float
@@ -824,7 +831,9 @@ _switch_to_ret:
 	LDREG	-RP_OFFSET(%r30), %r2
 	bv	%r0(%r2)
 	copy	%r26, %r28
-ENDPROC_CFI(_switch_to)
+	.exit
+	.procend
+ENDPROC_CFI(_switch_to_ret)
 
 	/*
 	 * Common rfi return path for interruptions, kernel execve, and
@@ -885,12 +894,19 @@ ENTRY_CFI(syscall_exit_rfi)
 	STREG   %r19,PT_SR5(%r16)
 	STREG   %r19,PT_SR6(%r16)
 	STREG   %r19,PT_SR7(%r16)
+ENDPROC_CFI(syscall_exit_rfi)
 
-intr_return:
+ENTRY_CFI(intr_return)
+	.proc
+	.callinfo FRAME=0
+	.entry
 	/* check for reschedule */
 	mfctl   %cr30,%r1
 	LDREG   TI_FLAGS(%r1),%r19	/* sched.h: TIF_NEED_RESCHED */
 	bb,<,n	%r19,31-TIF_NEED_RESCHED,intr_do_resched /* forward */
+	.exit
+	.procend
+ENDPROC_CFI(intr_return)
 
 	.import do_notify_resume,code
 intr_check_sig:
@@ -1046,7 +1062,6 @@ intr_extint:
 
 	b	do_cpu_irq_mask
 	ldo	R%intr_return(%r2), %r2	/* return to intr_return, not here */
-ENDPROC_CFI(syscall_exit_rfi)
 
 
 	/* Generic interruptions (illegal insn, unaligned, page fault, etc) */
@@ -1790,6 +1805,9 @@ ENTRY_CFI(sys_rt_sigreturn_wrapper)
 ENDPROC_CFI(sys_rt_sigreturn_wrapper)
 
 ENTRY_CFI(syscall_exit)
+	.proc
+	.callinfo FRAME=0
+	.entry
 	/* NOTE: Not all syscalls exit this way.  rt_sigreturn will exit
 	 * via syscall_exit_rfi if the signal was received while the process
 	 * was running.
@@ -1988,6 +2006,8 @@ syscall_do_resched:
 #else
 	nop
 #endif
+	.exit
+	.procend
 ENDPROC_CFI(syscall_exit)
 
 
@@ -2082,34 +2102,36 @@ ENDPROC_CFI(return_to_handler)
 
 #endif	/* CONFIG_FUNCTION_TRACER */
 
-#ifdef CONFIG_IRQSTACKS
 /* void call_on_stack(unsigned long param1, void *func,
 		      unsigned long new_stack) */
 ENTRY_CFI(call_on_stack)
+	.proc
+	.callinfo FRAME=2*FRAME_SIZE,CALLS,SAVE_RP,SAVE_SP
+	.entry
+#ifdef CONFIG_IRQSTACKS
 	copy	%sp, %r1
 
 	/* Regarding the HPPA calling conventions for function pointers,
 	   we assume the PIC register is not changed across call.  For
 	   CONFIG_64BIT, the argument pointer is left to point at the
 	   argument region allocated for the call to call_on_stack. */
+
+	/* Switch to new stack.  We allocate two frames.  */
+	ldo	2*FRAME_SIZE(%arg2), %sp
 # ifdef CONFIG_64BIT
-	/* Switch to new stack.  We allocate two 128 byte frames.  */
-	ldo	256(%arg2), %sp
 	/* Save previous stack pointer and return pointer in frame marker */
-	STREG	%rp, -144(%sp)
+	STREG	%rp, -FRAME_SIZE-RP_OFFSET(%sp)
 	/* Calls always use function descriptor */
 	LDREG	16(%arg1), %arg1
 	bve,l	(%arg1), %rp
-	STREG	%r1, -136(%sp)
-	LDREG	-144(%sp), %rp
+	STREG	%r1, -FRAME_SIZE-ASM_ULONG_SIZE(%sp)
+	LDREG	-FRAME_SIZE-RP_OFFSET(%sp), %rp
 	bve	(%rp)
-	LDREG	-136(%sp), %sp
+	LDREG	-FRAME_SIZE-ASM_ULONG_SIZE(%sp), %sp
 # else
-	/* Switch to new stack.  We allocate two 64 byte frames.  */
-	ldo	128(%arg2), %sp
 	/* Save previous stack pointer and return pointer in frame marker */
-	STREG	%r1, -68(%sp)
-	STREG	%rp, -84(%sp)
+	STREG	%r1, -FRAME_SIZE-ASM_ULONG_SIZE(%sp)
+	STREG	%rp, -FRAME_SIZE-RP_OFFSET(%sp)
 	/* Calls use function descriptor if PLABEL bit is set */
 	bb,>=,n	%arg1, 30, 1f
 	depwi	0,31,2, %arg1
@@ -2117,12 +2139,16 @@ ENTRY_CFI(call_on_stack)
 1:
 	be,l	0(%sr4,%arg1), %sr0, %r31
 	copy	%r31, %rp
-	LDREG	-84(%sp), %rp
+	LDREG	-FRAME_SIZE-RP_OFFSET(%sp), %rp
 	bv	(%rp)
-	LDREG	-68(%sp), %sp
+	LDREG	-FRAME_SIZE-ASM_ULONG_SIZE(%sp), %sp
 # endif /* CONFIG_64BIT */
-ENDPROC_CFI(call_on_stack)
+#else
+	.word PARISC_BUG_BREAK_INSN
 #endif /* CONFIG_IRQSTACKS */
+	.exit
+	.procend
+ENDPROC_CFI(call_on_stack)
 
 ENTRY_CFI(get_register)
 	/*
diff --git a/arch/parisc/kernel/process.c b/arch/parisc/kernel/process.c
index b931745815e0..eb39e7e380d7 100644
--- a/arch/parisc/kernel/process.c
+++ b/arch/parisc/kernel/process.c
@@ -302,7 +302,7 @@ get_wchan(struct task_struct *p)
 		ip = info.ip;
 		if (!in_sched_functions(ip))
 			return ip;
-	} while (count++ < 16);
+	} while (count++ < MAX_UNWIND_ENTRIES);
 	return 0;
 }
 
diff --git a/arch/parisc/kernel/traps.c b/arch/parisc/kernel/traps.c
index 4309ad31a874..318815212518 100644
--- a/arch/parisc/kernel/traps.c
+++ b/arch/parisc/kernel/traps.c
@@ -172,7 +172,7 @@ static void do_show_stack(struct unwind_frame_info *info)
 	int i = 1;
 
 	printk(KERN_CRIT "Backtrace:\n");
-	while (i <= 16) {
+	while (i <= MAX_UNWIND_ENTRIES) {
 		if (unwind_once(info) < 0 || info->ip == 0)
 			break;
 
diff --git a/arch/parisc/kernel/unwind.c b/arch/parisc/kernel/unwind.c
index 2ef83d78eec4..8856302f2ce9 100644
--- a/arch/parisc/kernel/unwind.c
+++ b/arch/parisc/kernel/unwind.c
@@ -13,7 +13,6 @@
 #include <linux/init.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
-#include <linux/kallsyms.h>
 #include <linux/sort.h>
 
 #include <linux/uaccess.h>
@@ -117,7 +116,8 @@ unwind_table_init(struct unwind_table *table, const char *name,
 	for (; start <= end; start++) {
 		if (start < end && 
 		    start->region_end > (start+1)->region_start) {
-			printk("WARNING: Out of order unwind entry! %p and %p\n", start, start+1);
+			pr_warn("Out of order unwind entry! %px and %px\n",
+				start, start+1);
 		}
 
 		start->region_start += base_addr;
@@ -203,23 +203,54 @@ int __init unwind_init(void)
 	return 0;
 }
 
-#ifdef CONFIG_64BIT
-#define get_func_addr(fptr) fptr[2]
-#else
-#define get_func_addr(fptr) fptr[0]
-#endif
-
 static int unwind_special(struct unwind_frame_info *info, unsigned long pc, int frame_size)
 {
-	extern void handle_interruption(int, struct pt_regs *);
-	static unsigned long *hi = (unsigned long *)&handle_interruption;
-
-	if (pc == get_func_addr(hi)) {
+	/*
+	 * We have to use void * instead of a function pointer, because
+	 * function pointers aren't a pointer to the function on 64-bit.
+	 * Make them const so the compiler knows they live in .text
+	 */
+	extern void * const handle_interruption;
+	extern void * const ret_from_kernel_thread;
+	extern void * const syscall_exit;
+	extern void * const intr_return;
+	extern void * const _switch_to_ret;
+	extern void * const call_on_stack;
+
+	if (pc == (unsigned long) &handle_interruption) {
 		struct pt_regs *regs = (struct pt_regs *)(info->sp - frame_size - PT_SZ_ALGN);
 		dbg("Unwinding through handle_interruption()\n");
 		info->prev_sp = regs->gr[30];
 		info->prev_ip = regs->iaoq[0];
+		return 1;
+	}
 
+	if (pc == (unsigned long) &ret_from_kernel_thread ||
+	    pc == (unsigned long) &syscall_exit) {
+		info->prev_sp = info->prev_ip = 0;
+		return 1;
+	}
+
+	if (pc == (unsigned long) &intr_return) {
+		struct pt_regs *regs;
+
+		dbg("Found intr_return()\n");
+		regs = (struct pt_regs *)(info->sp - PT_SZ_ALGN);
+		info->prev_sp = regs->gr[30];
+		info->prev_ip = regs->iaoq[0];
+		info->rp = regs->gr[2];
+		return 1;
+	}
+
+	if (pc == (unsigned long) &_switch_to_ret) {
+		info->prev_sp = info->sp - CALLEE_SAVE_FRAME_SIZE;
+		info->prev_ip = *(unsigned long *)(info->prev_sp - RP_OFFSET);
+		return 1;
+	}
+
+	if (pc == (unsigned long) &call_on_stack) {
+		info->prev_sp = *(unsigned long *)(info->sp - FRAME_SIZE - ASM_ULONG_SIZE);
+		info->prev_ip = *(unsigned long *)(info->sp - FRAME_SIZE - RP_OFFSET);
 		return 1;
 	}
 
@@ -238,34 +269,8 @@ static void unwind_frame_regs(struct unwind_frame_info *info)
 	if (e == NULL) {
 		unsigned long sp;
 
-		dbg("Cannot find unwind entry for 0x%lx; forced unwinding\n", info->ip);
-
-#ifdef CONFIG_KALLSYMS
-		/* Handle some frequent special cases.... */
-		{
-			char symname[KSYM_NAME_LEN];
-			char *modname;
-
-			kallsyms_lookup(info->ip, NULL, NULL, &modname,
-				symname);
-
-			dbg("info->ip = 0x%lx, name = %s\n", info->ip, symname);
-
-			if (strcmp(symname, "_switch_to_ret") == 0) {
-				info->prev_sp = info->sp - CALLEE_SAVE_FRAME_SIZE;
-				info->prev_ip = *(unsigned long *)(info->prev_sp - RP_OFFSET);
-				dbg("_switch_to_ret @ %lx - setting "
-				    "prev_sp=%lx prev_ip=%lx\n", 
-				    info->ip, info->prev_sp, 
-				    info->prev_ip);
-				return;
-			} else if (strcmp(symname, "ret_from_kernel_thread") == 0 ||
-				   strcmp(symname, "syscall_exit") == 0) {
-				info->prev_ip = info->prev_sp = 0;
-				return;
-			}
-		}
-#endif
+		dbg("Cannot find unwind entry for %pS; forced unwinding\n",
+			(void *) info->ip);
 
 		/* Since we are doing the unwinding blind, we don't know if
 		   we are adjusting the stack correctly or extracting the rp
--
To unsubscribe from this list: send the line "unsubscribe linux-parisc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux SoC]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux