Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

microblaze: Add stack unwinder

Implement intelligent backtracing by searching for stack frame creation,
and emitting only return addresses. Use print_hex_dump() to display the
entire binary kernel stack.

Limitation: MMU kernels are not currently able to trace beyond a system trap
(interrupt, syscall, etc.). It is the intent of this patch to provide
infrastructure that can be extended to add this capability later.

Changes from V1:
* Removed checks in find_frame_creation() that prevented location of the frame
creation instruction in heavily optimized code
* Various formatting/commenting/file location tweaks per review comments
* Dropped Kconfig option to enable STACKTRACE as something logically separate

Signed-off-by: Steven J. Magnani <steve@digidescorp.com>

authored by

Steven J. Magnani and committed by
Michal Simek
ce3266c0 ba9c4f88

+454 -95
+5
arch/microblaze/include/asm/exceptions.h
··· 14 14 #define _ASM_MICROBLAZE_EXCEPTIONS_H 15 15 16 16 #ifdef __KERNEL__ 17 + 18 + #ifndef CONFIG_MMU 19 + #define EX_HANDLER_STACK_SIZ (4*19) 20 + #endif 21 + 17 22 #ifndef __ASSEMBLY__ 18 23 19 24 /* Macros to enable and disable HW exceptions in the MSR */
-1
arch/microblaze/include/asm/system.h
··· 45 45 #define smp_rmb() rmb() 46 46 #define smp_wmb() wmb() 47 47 48 - void show_trace(struct task_struct *task, unsigned long *stack); 49 48 void __bad_xchg(volatile void *ptr, int size); 50 49 51 50 static inline unsigned long __xchg(unsigned long x, volatile void *ptr,
+29
arch/microblaze/include/asm/unwind.h
··· 1 + /* 2 + * Backtrace support for Microblaze 3 + * 4 + * Copyright (C) 2010 Digital Design Corporation 5 + * 6 + * This file is subject to the terms and conditions of the GNU General Public 7 + * License. See the file "COPYING" in the main directory of this archive 8 + * for more details. 9 + */ 10 + 11 + #ifndef __MICROBLAZE_UNWIND_H 12 + #define __MICROBLAZE_UNWIND_H 13 + 14 + struct stack_trace; 15 + 16 + struct trap_handler_info { 17 + unsigned long start_addr; 18 + unsigned long end_addr; 19 + const char *trap_name; 20 + }; 21 + extern struct trap_handler_info microblaze_trap_handlers; 22 + 23 + extern const char _hw_exception_handler; 24 + extern const char ex_handler_unhandled; 25 + 26 + void microblaze_unwind(struct task_struct *task, struct stack_trace *trace); 27 + 28 + #endif /* __MICROBLAZE_UNWIND_H */ 29 +
+1 -1
arch/microblaze/kernel/Makefile
··· 17 17 obj-y += dma.o exceptions.o \ 18 18 hw_exception_handler.o init_task.o intc.o irq.o of_device.o \ 19 19 of_platform.o process.o prom.o prom_parse.o ptrace.o \ 20 - setup.o signal.o sys_microblaze.o timer.o traps.o reset.o 20 + reset.o setup.o signal.o sys_microblaze.o timer.o traps.o unwind.o 21 21 22 22 obj-y += cpu/ 23 23
+28
arch/microblaze/kernel/entry-nommu.S
··· 588 588 #include "syscall_table.S" 589 589 590 590 syscall_table_size=(.-sys_call_table) 591 + 592 + type_SYSCALL: 593 + .ascii "SYSCALL\0" 594 + type_IRQ: 595 + .ascii "IRQ\0" 596 + type_IRQ_PREEMPT: 597 + .ascii "IRQ (PREEMPTED)\0" 598 + type_SYSCALL_PREEMPT: 599 + .ascii " SYSCALL (PREEMPTED)\0" 600 + 601 + /* 602 + * Trap decoding for stack unwinder 603 + * Tuples are (start addr, end addr, string) 604 + * If return address lies on [start addr, end addr], 605 + * unwinder displays 'string' 606 + */ 607 + 608 + .align 4 609 + .global microblaze_trap_handlers 610 + microblaze_trap_handlers: 611 + /* Exact matches come first */ 612 + .word ret_to_user ; .word ret_to_user ; .word type_SYSCALL 613 + .word ret_from_intr; .word ret_from_intr ; .word type_IRQ 614 + /* Fuzzy matches go here */ 615 + .word ret_from_intr; .word no_intr_resched; .word type_IRQ_PREEMPT 616 + .word work_pending ; .word no_work_pending; .word type_SYSCALL_PREEMPT 617 + /* End of table */ 618 + .word 0 ; .word 0 ; .word 0
+27
arch/microblaze/kernel/entry.S
··· 1127 1127 1128 1128 syscall_table_size=(.-sys_call_table) 1129 1129 1130 + type_SYSCALL: 1131 + .ascii "SYSCALL\0" 1132 + type_IRQ: 1133 + .ascii "IRQ\0" 1134 + type_IRQ_PREEMPT: 1135 + .ascii "IRQ (PREEMPTED)\0" 1136 + type_SYSCALL_PREEMPT: 1137 + .ascii " SYSCALL (PREEMPTED)\0" 1138 + 1139 + /* 1140 + * Trap decoding for stack unwinder 1141 + * Tuples are (start addr, end addr, string) 1142 + * If return address lies on [start addr, end addr], 1143 + * unwinder displays 'string' 1144 + */ 1145 + 1146 + .align 4 1147 + .global microblaze_trap_handlers 1148 + microblaze_trap_handlers: 1149 + /* Exact matches come first */ 1150 + .word ret_from_trap; .word ret_from_trap ; .word type_SYSCALL 1151 + .word ret_from_irq ; .word ret_from_irq ; .word type_IRQ 1152 + /* Fuzzy matches go here */ 1153 + .word ret_from_irq ; .word no_intr_resched ; .word type_IRQ_PREEMPT 1154 + .word ret_from_trap; .word TRAP_return ; .word type_SYSCALL_PREEMPT 1155 + /* End of table */ 1156 + .word 0 ; .word 0 ; .word 0
+1 -3
arch/microblaze/kernel/hw_exception_handler.S
··· 78 78 #include <asm/asm-offsets.h> 79 79 80 80 /* Helpful Macros */ 81 - #ifndef CONFIG_MMU 82 - #define EX_HANDLER_STACK_SIZ (4*19) 83 - #endif 84 81 #define NUM_TO_REG(num) r ## num 85 82 86 83 #ifdef CONFIG_MMU ··· 985 988 .end _unaligned_data_exception 986 989 #endif /* CONFIG_MMU */ 987 990 991 + .global ex_handler_unhandled 988 992 ex_handler_unhandled: 989 993 /* FIXME add handle function for unhandled exception - dump register */ 990 994 bri 0
+5 -39
arch/microblaze/kernel/stacktrace.c
··· 14 14 #include <linux/thread_info.h> 15 15 #include <linux/ptrace.h> 16 16 #include <linux/module.h> 17 + #include <asm/unwind.h> 17 18 18 - /* FIXME initial support */ 19 19 void save_stack_trace(struct stack_trace *trace) 20 20 { 21 - unsigned long *sp; 22 - unsigned long addr; 23 - asm("addik %0, r1, 0" : "=r" (sp)); 24 - 25 - while (!kstack_end(sp)) { 26 - addr = *sp++; 27 - if (__kernel_text_address(addr)) { 28 - if (trace->skip > 0) 29 - trace->skip--; 30 - else 31 - trace->entries[trace->nr_entries++] = addr; 32 - 33 - if (trace->nr_entries >= trace->max_entries) 34 - break; 35 - } 36 - } 21 + /* Exclude our helper functions from the trace*/ 22 + trace->skip += 2; 23 + microblaze_unwind(NULL, trace); 37 24 } 38 25 EXPORT_SYMBOL_GPL(save_stack_trace); 39 26 40 27 void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) 41 28 { 42 - unsigned int *sp; 43 - unsigned long addr; 44 - 45 - struct thread_info *ti = task_thread_info(tsk); 46 - 47 - if (tsk == current) 48 - asm("addik %0, r1, 0" : "=r" (sp)); 49 - else 50 - sp = (unsigned int *)ti->cpu_context.r1; 51 - 52 - while (!kstack_end(sp)) { 53 - addr = *sp++; 54 - if (__kernel_text_address(addr)) { 55 - if (trace->skip > 0) 56 - trace->skip--; 57 - else 58 - trace->entries[trace->nr_entries++] = addr; 59 - 60 - if (trace->nr_entries >= trace->max_entries) 61 - break; 62 - } 63 - } 29 + microblaze_unwind(tsk, trace); 64 30 } 65 31 EXPORT_SYMBOL_GPL(save_stack_trace_tsk);
+40 -51
arch/microblaze/kernel/traps.c
··· 16 16 17 17 #include <asm/exceptions.h> 18 18 #include <asm/system.h> 19 + #include <asm/unwind.h> 19 20 20 21 void trap_init(void) 21 22 { 22 23 __enable_hw_exceptions(); 23 24 } 24 25 25 - static unsigned long kstack_depth_to_print = 24; 26 + static unsigned long kstack_depth_to_print; /* 0 == entire stack */ 26 27 27 28 static int __init kstack_setup(char *s) 28 29 { ··· 31 30 } 32 31 __setup("kstack=", kstack_setup); 33 32 34 - void show_trace(struct task_struct *task, unsigned long *stack) 33 + void show_stack(struct task_struct *task, unsigned long *sp) 35 34 { 36 - unsigned long addr; 35 + unsigned long words_to_show; 36 + u32 fp = (u32) sp; 37 37 38 - if (!stack) 39 - stack = (unsigned long *)&stack; 40 - 41 - printk(KERN_NOTICE "Call Trace: "); 42 - #ifdef CONFIG_KALLSYMS 43 - printk(KERN_NOTICE "\n"); 44 - #endif 45 - while (!kstack_end(stack)) { 46 - addr = *stack++; 47 - /* 48 - * If the address is either in the text segment of the 49 - * kernel, or in the region which contains vmalloc'ed 50 - * memory, it *may* be the address of a calling 51 - * routine; if so, print it so that someone tracing 52 - * down the cause of the crash will be able to figure 53 - * out the call path that was taken. 54 - */ 55 - if (kernel_text_address(addr)) 56 - print_ip_sym(addr); 38 + if (fp == 0) { 39 + if (task) { 40 + fp = ((struct thread_info *) 41 + (task->stack))->cpu_context.r1; 42 + } else { 43 + /* Pick up caller of dump_stack() */ 44 + fp = (u32)&sp - 8; 45 + } 57 46 } 58 - printk(KERN_NOTICE "\n"); 47 + 48 + words_to_show = (THREAD_SIZE - (fp & (THREAD_SIZE - 1))) >> 2; 49 + if (kstack_depth_to_print && (words_to_show > kstack_depth_to_print)) 50 + words_to_show = kstack_depth_to_print; 51 + 52 + pr_info("Kernel Stack:\n"); 53 + 54 + /* 55 + * Make the first line an 'odd' size if necessary to get 56 + * remaining lines to start at an address multiple of 0x10 57 + */ 58 + if (fp & 0xF) { 59 + unsigned long line1_words = (0x10 - (fp & 0xF)) >> 2; 60 + if (line1_words < words_to_show) { 61 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 62 + 4, (void *)fp, line1_words << 2, 0); 63 + fp += line1_words << 2; 64 + words_to_show -= line1_words; 65 + } 66 + } 67 + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_ADDRESS, 32, 4, (void *)fp, 68 + words_to_show << 2, 0); 69 + printk(KERN_INFO "\n\n"); 70 + 71 + pr_info("Call Trace:\n"); 72 + microblaze_unwind(task, NULL); 73 + pr_info("\n"); 59 74 60 75 if (!task) 61 76 task = current; 62 77 63 78 debug_show_held_locks(task); 64 - } 65 - 66 - void show_stack(struct task_struct *task, unsigned long *sp) 67 - { 68 - unsigned long *stack; 69 - int i; 70 - 71 - if (sp == NULL) { 72 - if (task) 73 - sp = (unsigned long *) ((struct thread_info *) 74 - (task->stack))->cpu_context.r1; 75 - else 76 - sp = (unsigned long *)&sp; 77 - } 78 - 79 - stack = sp; 80 - 81 - printk(KERN_INFO "\nStack:\n "); 82 - 83 - for (i = 0; i < kstack_depth_to_print; i++) { 84 - if (kstack_end(sp)) 85 - break; 86 - if (i && ((i % 8) == 0)) 87 - printk("\n "); 88 - printk("%08lx ", *sp++); 89 - } 90 - printk("\n"); 91 - show_trace(task, stack); 92 79 } 93 80 94 81 void dump_stack(void)
+318
arch/microblaze/kernel/unwind.c
··· 1 + /* 2 + * Backtrace support for Microblaze 3 + * 4 + * Copyright (C) 2010 Digital Design Corporation 5 + * 6 + * Based on arch/sh/kernel/cpu/sh5/unwind.c code which is: 7 + * Copyright (C) 2004 Paul Mundt 8 + * Copyright (C) 2004 Richard Curnow 9 + * 10 + * This file is subject to the terms and conditions of the GNU General Public 11 + * License. See the file "COPYING" in the main directory of this archive 12 + * for more details. 13 + */ 14 + 15 + /* #define DEBUG 1 */ 16 + #include <linux/kallsyms.h> 17 + #include <linux/kernel.h> 18 + #include <linux/sched.h> 19 + #include <linux/stacktrace.h> 20 + #include <linux/types.h> 21 + #include <linux/errno.h> 22 + #include <linux/module.h> 23 + #include <linux/io.h> 24 + #include <asm/sections.h> 25 + #include <asm/exceptions.h> 26 + #include <asm/unwind.h> 27 + 28 + struct stack_trace; 29 + 30 + /* 31 + * On Microblaze, finding the previous stack frame is a little tricky. 32 + * At this writing (3/2010), Microblaze does not support CONFIG_FRAME_POINTERS, 33 + * and even if it did, gcc (4.1.2) does not store the frame pointer at 34 + * a consistent offset within each frame. To determine frame size, it is 35 + * necessary to search for the assembly instruction that creates or reclaims 36 + * the frame and extract the size from it. 37 + * 38 + * Microblaze stores the stack pointer in r1, and creates a frame via 39 + * 40 + * addik r1, r1, -FRAME_SIZE 41 + * 42 + * The frame is reclaimed via 43 + * 44 + * addik r1, r1, FRAME_SIZE 45 + * 46 + * Frame creation occurs at or near the top of a function. 47 + * Depending on the compiler, reclaim may occur at the end, or before 48 + * a mid-function return. 49 + * 50 + * A stack frame is usually not created in a leaf function. 51 + * 52 + */ 53 + 54 + /** 55 + * get_frame_size - Extract the stack adjustment from an 56 + * "addik r1, r1, adjust" instruction 57 + * @instr : Microblaze instruction 58 + * 59 + * Return - Number of stack bytes the instruction reserves or reclaims 60 + */ 61 + inline long get_frame_size(unsigned long instr) 62 + { 63 + return abs((s16)(instr & 0xFFFF)); 64 + } 65 + 66 + /** 67 + * find_frame_creation - Search backward to find the instruction that creates 68 + * the stack frame (hopefully, for the same function the 69 + * initial PC is in). 70 + * @pc : Program counter at which to begin the search 71 + * 72 + * Return - PC at which stack frame creation occurs 73 + * NULL if this cannot be found, i.e. a leaf function 74 + */ 75 + static unsigned long *find_frame_creation(unsigned long *pc) 76 + { 77 + int i; 78 + 79 + /* NOTE: Distance to search is arbitrary 80 + * 250 works well for most things, 81 + * 750 picks up things like tcp_recvmsg(), 82 + * 1000 needed for fat_fill_super() 83 + */ 84 + for (i = 0; i < 1000; i++, pc--) { 85 + unsigned long instr; 86 + s16 frame_size; 87 + 88 + if (!kernel_text_address((unsigned long) pc)) 89 + return NULL; 90 + 91 + instr = *pc; 92 + 93 + /* addik r1, r1, foo ? */ 94 + if ((instr & 0xFFFF0000) != 0x30210000) 95 + continue; /* No */ 96 + 97 + frame_size = get_frame_size(instr); 98 + if ((frame_size < 8) || (frame_size & 3)) { 99 + pr_debug(" Invalid frame size %d at 0x%p\n", 100 + frame_size, pc); 101 + return NULL; 102 + } 103 + 104 + pr_debug(" Found frame creation at 0x%p, size %d\n", pc, 105 + frame_size); 106 + return pc; 107 + } 108 + 109 + return NULL; 110 + } 111 + 112 + /** 113 + * lookup_prev_stack_frame - Find the stack frame of the previous function. 114 + * @fp : Frame (stack) pointer for current function 115 + * @pc : Program counter within current function 116 + * @leaf_return : r15 value within current function. If the current function 117 + * is a leaf, this is the caller's return address. 118 + * @pprev_fp : On exit, set to frame (stack) pointer for previous function 119 + * @pprev_pc : On exit, set to current function caller's return address 120 + * 121 + * Return - 0 on success, -EINVAL if the previous frame cannot be found 122 + */ 123 + static int lookup_prev_stack_frame(unsigned long fp, unsigned long pc, 124 + unsigned long leaf_return, 125 + unsigned long *pprev_fp, 126 + unsigned long *pprev_pc) 127 + { 128 + unsigned long *prologue = NULL; 129 + 130 + /* _switch_to is a special leaf function */ 131 + if (pc != (unsigned long) &_switch_to) 132 + prologue = find_frame_creation((unsigned long *)pc); 133 + 134 + if (prologue) { 135 + long frame_size = get_frame_size(*prologue); 136 + 137 + *pprev_fp = fp + frame_size; 138 + *pprev_pc = *(unsigned long *)fp; 139 + } else { 140 + if (!leaf_return) 141 + return -EINVAL; 142 + *pprev_pc = leaf_return; 143 + *pprev_fp = fp; 144 + } 145 + 146 + /* NOTE: don't check kernel_text_address here, to allow display 147 + * of userland return address 148 + */ 149 + return (!*pprev_pc || (*pprev_pc & 3)) ? -EINVAL : 0; 150 + } 151 + 152 + static void microblaze_unwind_inner(struct task_struct *task, 153 + unsigned long pc, unsigned long fp, 154 + unsigned long leaf_return, 155 + struct stack_trace *trace); 156 + 157 + /** 158 + * unwind_trap - Unwind through a system trap, that stored previous state 159 + * on the stack. 160 + */ 161 + #ifdef CONFIG_MMU 162 + static inline void unwind_trap(struct task_struct *task, unsigned long pc, 163 + unsigned long fp, struct stack_trace *trace) 164 + { 165 + /* To be implemented */ 166 + } 167 + #else 168 + static inline void unwind_trap(struct task_struct *task, unsigned long pc, 169 + unsigned long fp, struct stack_trace *trace) 170 + { 171 + const struct pt_regs *regs = (const struct pt_regs *) fp; 172 + microblaze_unwind_inner(task, regs->pc, regs->r1, regs->r15, trace); 173 + } 174 + #endif 175 + 176 + /** 177 + * microblaze_unwind_inner - Unwind the stack from the specified point 178 + * @task : Task whose stack we are to unwind (may be NULL) 179 + * @pc : Program counter from which we start unwinding 180 + * @fp : Frame (stack) pointer from which we start unwinding 181 + * @leaf_return : Value of r15 at pc. If the function is a leaf, this is 182 + * the caller's return address. 183 + * @trace : Where to store stack backtrace (PC values). 184 + * NULL == print backtrace to kernel log 185 + */ 186 + void microblaze_unwind_inner(struct task_struct *task, 187 + unsigned long pc, unsigned long fp, 188 + unsigned long leaf_return, 189 + struct stack_trace *trace) 190 + { 191 + int ofs = 0; 192 + 193 + pr_debug(" Unwinding with PC=%p, FP=%p\n", (void *)pc, (void *)fp); 194 + if (!pc || !fp || (pc & 3) || (fp & 3)) { 195 + pr_debug(" Invalid state for unwind, aborting\n"); 196 + return; 197 + } 198 + for (; pc != 0;) { 199 + unsigned long next_fp, next_pc = 0; 200 + unsigned long return_to = pc + 2 * sizeof(unsigned long); 201 + const struct trap_handler_info *handler = 202 + &microblaze_trap_handlers; 203 + 204 + /* Is previous function the HW exception handler? */ 205 + if ((return_to >= (unsigned long)&_hw_exception_handler) 206 + &&(return_to < (unsigned long)&ex_handler_unhandled)) { 207 + /* 208 + * HW exception handler doesn't save all registers, 209 + * so we open-code a special case of unwind_trap() 210 + */ 211 + #ifndef CONFIG_MMU 212 + const struct pt_regs *regs = 213 + (const struct pt_regs *) fp; 214 + #endif 215 + pr_info("HW EXCEPTION\n"); 216 + #ifndef CONFIG_MMU 217 + microblaze_unwind_inner(task, regs->r17 - 4, 218 + fp + EX_HANDLER_STACK_SIZ, 219 + regs->r15, trace); 220 + #endif 221 + return; 222 + } 223 + 224 + /* Is previous function a trap handler? */ 225 + for (; handler->start_addr; ++handler) { 226 + if ((return_to >= handler->start_addr) 227 + && (return_to <= handler->end_addr)) { 228 + if (!trace) 229 + pr_info("%s\n", handler->trap_name); 230 + unwind_trap(task, pc, fp, trace); 231 + return; 232 + } 233 + } 234 + pc -= ofs; 235 + 236 + if (trace) { 237 + #ifdef CONFIG_STACKTRACE 238 + if (trace->skip > 0) 239 + trace->skip--; 240 + else 241 + trace->entries[trace->nr_entries++] = pc; 242 + 243 + if (trace->nr_entries >= trace->max_entries) 244 + break; 245 + #endif 246 + } else { 247 + /* Have we reached userland? */ 248 + if (unlikely(pc == task_pt_regs(task)->pc)) { 249 + pr_info("[<%p>] PID %lu [%s]\n", 250 + (void *) pc, 251 + (unsigned long) task->pid, 252 + task->comm); 253 + break; 254 + } else 255 + print_ip_sym(pc); 256 + } 257 + 258 + /* Stop when we reach anything not part of the kernel */ 259 + if (!kernel_text_address(pc)) 260 + break; 261 + 262 + if (lookup_prev_stack_frame(fp, pc, leaf_return, &next_fp, 263 + &next_pc) == 0) { 264 + ofs = sizeof(unsigned long); 265 + pc = next_pc & ~3; 266 + fp = next_fp; 267 + leaf_return = 0; 268 + } else { 269 + pr_debug(" Failed to find previous stack frame\n"); 270 + break; 271 + } 272 + 273 + pr_debug(" Next PC=%p, next FP=%p\n", 274 + (void *)next_pc, (void *)next_fp); 275 + } 276 + } 277 + 278 + /** 279 + * microblaze_unwind - Stack unwinder for Microblaze (external entry point) 280 + * @task : Task whose stack we are to unwind (NULL == current) 281 + * @trace : Where to store stack backtrace (PC values). 282 + * NULL == print backtrace to kernel log 283 + */ 284 + void microblaze_unwind(struct task_struct *task, struct stack_trace *trace) 285 + { 286 + if (task) { 287 + if (task == current) { 288 + const struct pt_regs *regs = task_pt_regs(task); 289 + microblaze_unwind_inner(task, regs->pc, regs->r1, 290 + regs->r15, trace); 291 + } else { 292 + struct thread_info *thread_info = 293 + (struct thread_info *)(task->stack); 294 + const struct cpu_context *cpu_context = 295 + &thread_info->cpu_context; 296 + 297 + microblaze_unwind_inner(task, 298 + (unsigned long) &_switch_to, 299 + cpu_context->r1, 300 + cpu_context->r15, trace); 301 + } 302 + } else { 303 + unsigned long pc, fp; 304 + 305 + __asm__ __volatile__ ("or %0, r1, r0" : "=r" (fp)); 306 + 307 + __asm__ __volatile__ ( 308 + "brlid %0, 0f;" 309 + "nop;" 310 + "0:" 311 + : "=r" (pc) 312 + ); 313 + 314 + /* Since we are not a leaf function, use leaf_return = 0 */ 315 + microblaze_unwind_inner(current, pc, fp, 0, trace); 316 + } 317 + } 318 +