diff options
| author | Jan Beulich <jbeulich@novell.com> | 2006-06-26 07:57:41 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-26 13:48:17 -0400 |
| commit | 176a2718f408ce92788b29127050b04dfd6e4f68 (patch) | |
| tree | bb637b13098f821551d07d3a13d140a908e7bc04 | |
| parent | dffead4e421e289c8434351400d24fd35723e874 (diff) | |
[PATCH] i386: reliable stack trace support (i386)
These are the i386-specific pieces to enable reliable stack traces. This is
going to be even more useful once CFI annotations get added to he assembly
code, namely to entry.S.
Signed-off-by: Jan Beulich <jbeulich@novell.com>
Signed-off-by: Andi Kleen <ak@suse.de>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
| -rw-r--r-- | arch/i386/kernel/entry.S | 29 | ||||
| -rw-r--r-- | arch/i386/kernel/process.c | 2 | ||||
| -rw-r--r-- | arch/i386/kernel/traps.c | 50 | ||||
| -rw-r--r-- | arch/i386/kernel/vmlinux.lds.S | 9 | ||||
| -rw-r--r-- | include/asm-i386/processor.h | 2 | ||||
| -rw-r--r-- | include/asm-i386/unwind.h | 98 | ||||
| -rw-r--r-- | lib/Kconfig.debug | 2 |
7 files changed, 179 insertions, 13 deletions
diff --git a/arch/i386/kernel/entry.S b/arch/i386/kernel/entry.S index cfc683f153b9..e802f3cac7e3 100644 --- a/arch/i386/kernel/entry.S +++ b/arch/i386/kernel/entry.S | |||
| @@ -663,6 +663,35 @@ ENTRY(spurious_interrupt_bug) | |||
| 663 | pushl $do_spurious_interrupt_bug | 663 | pushl $do_spurious_interrupt_bug |
| 664 | jmp error_code | 664 | jmp error_code |
| 665 | 665 | ||
| 666 | #ifdef CONFIG_STACK_UNWIND | ||
| 667 | ENTRY(arch_unwind_init_running) | ||
| 668 | movl 4(%esp), %edx | ||
| 669 | movl (%esp), %ecx | ||
| 670 | leal 4(%esp), %eax | ||
| 671 | movl %ebx, EBX(%edx) | ||
| 672 | xorl %ebx, %ebx | ||
| 673 | movl %ebx, ECX(%edx) | ||
| 674 | movl %ebx, EDX(%edx) | ||
| 675 | movl %esi, ESI(%edx) | ||
| 676 | movl %edi, EDI(%edx) | ||
| 677 | movl %ebp, EBP(%edx) | ||
| 678 | movl %ebx, EAX(%edx) | ||
| 679 | movl $__USER_DS, DS(%edx) | ||
| 680 | movl $__USER_DS, ES(%edx) | ||
| 681 | movl %ebx, ORIG_EAX(%edx) | ||
| 682 | movl %ecx, EIP(%edx) | ||
| 683 | movl 12(%esp), %ecx | ||
| 684 | movl $__KERNEL_CS, CS(%edx) | ||
| 685 | movl %ebx, EFLAGS(%edx) | ||
| 686 | movl %eax, OLDESP(%edx) | ||
| 687 | movl 8(%esp), %eax | ||
| 688 | movl %ecx, 8(%esp) | ||
| 689 | movl EBX(%edx), %ebx | ||
| 690 | movl $__KERNEL_DS, OLDSS(%edx) | ||
| 691 | jmpl *%eax | ||
| 692 | ENDPROC(arch_unwind_init_running) | ||
| 693 | #endif | ||
| 694 | |||
| 666 | .section .rodata,"a" | 695 | .section .rodata,"a" |
| 667 | #include "syscall_table.S" | 696 | #include "syscall_table.S" |
| 668 | 697 | ||
diff --git a/arch/i386/kernel/process.c b/arch/i386/kernel/process.c index 6259afea46d1..525432e3fef7 100644 --- a/arch/i386/kernel/process.c +++ b/arch/i386/kernel/process.c | |||
| @@ -312,7 +312,7 @@ void show_regs(struct pt_regs * regs) | |||
| 312 | cr3 = read_cr3(); | 312 | cr3 = read_cr3(); |
| 313 | cr4 = read_cr4_safe(); | 313 | cr4 = read_cr4_safe(); |
| 314 | printk("CR0: %08lx CR2: %08lx CR3: %08lx CR4: %08lx\n", cr0, cr2, cr3, cr4); | 314 | printk("CR0: %08lx CR2: %08lx CR3: %08lx CR4: %08lx\n", cr0, cr2, cr3, cr4); |
| 315 | show_trace(NULL, ®s->esp); | 315 | show_trace(NULL, regs, ®s->esp); |
| 316 | } | 316 | } |
| 317 | 317 | ||
| 318 | /* | 318 | /* |
diff --git a/arch/i386/kernel/traps.c b/arch/i386/kernel/traps.c index dcc14477af1f..286584667865 100644 --- a/arch/i386/kernel/traps.c +++ b/arch/i386/kernel/traps.c | |||
| @@ -28,6 +28,7 @@ | |||
| 28 | #include <linux/utsname.h> | 28 | #include <linux/utsname.h> |
| 29 | #include <linux/kprobes.h> | 29 | #include <linux/kprobes.h> |
| 30 | #include <linux/kexec.h> | 30 | #include <linux/kexec.h> |
| 31 | #include <linux/unwind.h> | ||
| 31 | 32 | ||
| 32 | #ifdef CONFIG_EISA | 33 | #ifdef CONFIG_EISA |
| 33 | #include <linux/ioport.h> | 34 | #include <linux/ioport.h> |
| @@ -47,7 +48,7 @@ | |||
| 47 | #include <asm/desc.h> | 48 | #include <asm/desc.h> |
| 48 | #include <asm/i387.h> | 49 | #include <asm/i387.h> |
| 49 | #include <asm/nmi.h> | 50 | #include <asm/nmi.h> |
| 50 | 51 | #include <asm/unwind.h> | |
| 51 | #include <asm/smp.h> | 52 | #include <asm/smp.h> |
| 52 | #include <asm/arch_hooks.h> | 53 | #include <asm/arch_hooks.h> |
| 53 | #include <asm/kdebug.h> | 54 | #include <asm/kdebug.h> |
| @@ -170,14 +171,43 @@ static inline unsigned long print_context_stack(struct thread_info *tinfo, | |||
| 170 | return ebp; | 171 | return ebp; |
| 171 | } | 172 | } |
| 172 | 173 | ||
| 173 | static void show_trace_log_lvl(struct task_struct *task, | 174 | static asmlinkage void show_trace_unwind(struct unwind_frame_info *info, void *log_lvl) |
| 175 | { | ||
| 176 | int printed = 0; /* nr of entries already printed on current line */ | ||
| 177 | |||
| 178 | while (unwind(info) == 0 && UNW_PC(info)) { | ||
| 179 | printed = print_addr_and_symbol(UNW_PC(info), log_lvl, printed); | ||
| 180 | if (arch_unw_user_mode(info)) | ||
| 181 | break; | ||
| 182 | } | ||
| 183 | if (printed) | ||
| 184 | printk("\n"); | ||
| 185 | } | ||
| 186 | |||
| 187 | static void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs, | ||
| 174 | unsigned long *stack, char *log_lvl) | 188 | unsigned long *stack, char *log_lvl) |
| 175 | { | 189 | { |
| 176 | unsigned long ebp; | 190 | unsigned long ebp; |
| 191 | struct unwind_frame_info info; | ||
| 177 | 192 | ||
| 178 | if (!task) | 193 | if (!task) |
| 179 | task = current; | 194 | task = current; |
| 180 | 195 | ||
| 196 | if (regs) { | ||
| 197 | if (unwind_init_frame_info(&info, task, regs) == 0) { | ||
| 198 | show_trace_unwind(&info, log_lvl); | ||
| 199 | return; | ||
| 200 | } | ||
| 201 | } else if (task == current) { | ||
| 202 | if (unwind_init_running(&info, show_trace_unwind, log_lvl) == 0) | ||
| 203 | return; | ||
| 204 | } else { | ||
| 205 | if (unwind_init_blocked(&info, task) == 0) { | ||
| 206 | show_trace_unwind(&info, log_lvl); | ||
| 207 | return; | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 181 | if (task == current) { | 211 | if (task == current) { |
| 182 | /* Grab ebp right from our regs */ | 212 | /* Grab ebp right from our regs */ |
| 183 | asm ("movl %%ebp, %0" : "=r" (ebp) : ); | 213 | asm ("movl %%ebp, %0" : "=r" (ebp) : ); |
| @@ -198,13 +228,13 @@ static void show_trace_log_lvl(struct task_struct *task, | |||
| 198 | } | 228 | } |
| 199 | } | 229 | } |
| 200 | 230 | ||
| 201 | void show_trace(struct task_struct *task, unsigned long * stack) | 231 | void show_trace(struct task_struct *task, struct pt_regs *regs, unsigned long * stack) |
| 202 | { | 232 | { |
| 203 | show_trace_log_lvl(task, stack, ""); | 233 | show_trace_log_lvl(task, regs, stack, ""); |
| 204 | } | 234 | } |
| 205 | 235 | ||
| 206 | static void show_stack_log_lvl(struct task_struct *task, unsigned long *esp, | 236 | static void show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs, |
| 207 | char *log_lvl) | 237 | unsigned long *esp, char *log_lvl) |
| 208 | { | 238 | { |
| 209 | unsigned long *stack; | 239 | unsigned long *stack; |
| 210 | int i; | 240 | int i; |
| @@ -225,13 +255,13 @@ static void show_stack_log_lvl(struct task_struct *task, unsigned long *esp, | |||
| 225 | printk("%08lx ", *stack++); | 255 | printk("%08lx ", *stack++); |
| 226 | } | 256 | } |
| 227 | printk("\n%sCall Trace:\n", log_lvl); | 257 | printk("\n%sCall Trace:\n", log_lvl); |
| 228 | show_trace_log_lvl(task, esp, log_lvl); | 258 | show_trace_log_lvl(task, regs, esp, log_lvl); |
| 229 | } | 259 | } |
| 230 | 260 | ||
| 231 | void show_stack(struct task_struct *task, unsigned long *esp) | 261 | void show_stack(struct task_struct *task, unsigned long *esp) |
| 232 | { | 262 | { |
| 233 | printk(" "); | 263 | printk(" "); |
| 234 | show_stack_log_lvl(task, esp, ""); | 264 | show_stack_log_lvl(task, NULL, esp, ""); |
| 235 | } | 265 | } |
| 236 | 266 | ||
| 237 | /* | 267 | /* |
| @@ -241,7 +271,7 @@ void dump_stack(void) | |||
| 241 | { | 271 | { |
| 242 | unsigned long stack; | 272 | unsigned long stack; |
| 243 | 273 | ||
| 244 | show_trace(current, &stack); | 274 | show_trace(current, NULL, &stack); |
| 245 | } | 275 | } |
| 246 | 276 | ||
| 247 | EXPORT_SYMBOL(dump_stack); | 277 | EXPORT_SYMBOL(dump_stack); |
| @@ -285,7 +315,7 @@ void show_registers(struct pt_regs *regs) | |||
| 285 | u8 __user *eip; | 315 | u8 __user *eip; |
| 286 | 316 | ||
| 287 | printk("\n" KERN_EMERG "Stack: "); | 317 | printk("\n" KERN_EMERG "Stack: "); |
| 288 | show_stack_log_lvl(NULL, (unsigned long *)esp, KERN_EMERG); | 318 | show_stack_log_lvl(NULL, regs, (unsigned long *)esp, KERN_EMERG); |
| 289 | 319 | ||
| 290 | printk(KERN_EMERG "Code: "); | 320 | printk(KERN_EMERG "Code: "); |
| 291 | 321 | ||
diff --git a/arch/i386/kernel/vmlinux.lds.S b/arch/i386/kernel/vmlinux.lds.S index 7512f39c9f25..2d4f1386e2b1 100644 --- a/arch/i386/kernel/vmlinux.lds.S +++ b/arch/i386/kernel/vmlinux.lds.S | |||
| @@ -71,6 +71,15 @@ SECTIONS | |||
| 71 | .data.read_mostly : AT(ADDR(.data.read_mostly) - LOAD_OFFSET) { *(.data.read_mostly) } | 71 | .data.read_mostly : AT(ADDR(.data.read_mostly) - LOAD_OFFSET) { *(.data.read_mostly) } |
| 72 | _edata = .; /* End of data section */ | 72 | _edata = .; /* End of data section */ |
| 73 | 73 | ||
| 74 | #ifdef CONFIG_STACK_UNWIND | ||
| 75 | . = ALIGN(4); | ||
| 76 | .eh_frame : AT(ADDR(.eh_frame) - LOAD_OFFSET) { | ||
| 77 | __start_unwind = .; | ||
| 78 | *(.eh_frame) | ||
| 79 | __end_unwind = .; | ||
| 80 | } | ||
| 81 | #endif | ||
| 82 | |||
| 74 | . = ALIGN(THREAD_SIZE); /* init_task */ | 83 | . = ALIGN(THREAD_SIZE); /* init_task */ |
| 75 | .data.init_task : AT(ADDR(.data.init_task) - LOAD_OFFSET) { | 84 | .data.init_task : AT(ADDR(.data.init_task) - LOAD_OFFSET) { |
| 76 | *(.data.init_task) | 85 | *(.data.init_task) |
diff --git a/include/asm-i386/processor.h b/include/asm-i386/processor.h index b796210c0f5c..55ea992da329 100644 --- a/include/asm-i386/processor.h +++ b/include/asm-i386/processor.h | |||
| @@ -555,7 +555,7 @@ extern void prepare_to_copy(struct task_struct *tsk); | |||
| 555 | extern int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags); | 555 | extern int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags); |
| 556 | 556 | ||
| 557 | extern unsigned long thread_saved_pc(struct task_struct *tsk); | 557 | extern unsigned long thread_saved_pc(struct task_struct *tsk); |
| 558 | void show_trace(struct task_struct *task, unsigned long *stack); | 558 | void show_trace(struct task_struct *task, struct pt_regs *regs, unsigned long *stack); |
| 559 | 559 | ||
| 560 | unsigned long get_wchan(struct task_struct *p); | 560 | unsigned long get_wchan(struct task_struct *p); |
| 561 | 561 | ||
diff --git a/include/asm-i386/unwind.h b/include/asm-i386/unwind.h new file mode 100644 index 000000000000..1c076897ac21 --- /dev/null +++ b/include/asm-i386/unwind.h | |||
| @@ -0,0 +1,98 @@ | |||
| 1 | #ifndef _ASM_I386_UNWIND_H | ||
| 2 | #define _ASM_I386_UNWIND_H | ||
| 3 | |||
| 4 | /* | ||
| 5 | * Copyright (C) 2002-2006 Novell, Inc. | ||
| 6 | * Jan Beulich <jbeulich@novell.com> | ||
| 7 | * This code is released under version 2 of the GNU GPL. | ||
| 8 | */ | ||
| 9 | |||
| 10 | #ifdef CONFIG_STACK_UNWIND | ||
| 11 | |||
| 12 | #include <linux/sched.h> | ||
| 13 | #include <asm/fixmap.h> | ||
| 14 | #include <asm/ptrace.h> | ||
| 15 | #include <asm/uaccess.h> | ||
| 16 | |||
| 17 | struct unwind_frame_info | ||
| 18 | { | ||
| 19 | struct pt_regs regs; | ||
| 20 | struct task_struct *task; | ||
| 21 | }; | ||
| 22 | |||
| 23 | #define UNW_PC(frame) (frame)->regs.eip | ||
| 24 | #define UNW_SP(frame) (frame)->regs.esp | ||
| 25 | #ifdef CONFIG_FRAME_POINTER | ||
| 26 | #define UNW_FP(frame) (frame)->regs.ebp | ||
| 27 | #define FRAME_RETADDR_OFFSET 4 | ||
| 28 | #define FRAME_LINK_OFFSET 0 | ||
| 29 | #define STACK_BOTTOM(tsk) STACK_LIMIT((tsk)->thread.esp0) | ||
| 30 | #define STACK_TOP(tsk) ((tsk)->thread.esp0) | ||
| 31 | #endif | ||
| 32 | #define STACK_LIMIT(ptr) (((ptr) - 1) & ~(THREAD_SIZE - 1)) | ||
| 33 | |||
| 34 | #define UNW_REGISTER_INFO \ | ||
| 35 | PTREGS_INFO(eax), \ | ||
| 36 | PTREGS_INFO(ecx), \ | ||
| 37 | PTREGS_INFO(edx), \ | ||
| 38 | PTREGS_INFO(ebx), \ | ||
| 39 | PTREGS_INFO(esp), \ | ||
| 40 | PTREGS_INFO(ebp), \ | ||
| 41 | PTREGS_INFO(esi), \ | ||
| 42 | PTREGS_INFO(edi), \ | ||
| 43 | PTREGS_INFO(eip) | ||
| 44 | |||
| 45 | static inline void arch_unw_init_frame_info(struct unwind_frame_info *info, | ||
| 46 | /*const*/ struct pt_regs *regs) | ||
| 47 | { | ||
| 48 | if (user_mode_vm(regs)) | ||
| 49 | info->regs = *regs; | ||
| 50 | else { | ||
| 51 | memcpy(&info->regs, regs, offsetof(struct pt_regs, esp)); | ||
| 52 | info->regs.esp = (unsigned long)®s->esp; | ||
| 53 | info->regs.xss = __KERNEL_DS; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | static inline void arch_unw_init_blocked(struct unwind_frame_info *info) | ||
| 58 | { | ||
| 59 | memset(&info->regs, 0, sizeof(info->regs)); | ||
| 60 | info->regs.eip = info->task->thread.eip; | ||
| 61 | info->regs.xcs = __KERNEL_CS; | ||
| 62 | __get_user(info->regs.ebp, (long *)info->task->thread.esp); | ||
| 63 | info->regs.esp = info->task->thread.esp; | ||
| 64 | info->regs.xss = __KERNEL_DS; | ||
| 65 | info->regs.xds = __USER_DS; | ||
| 66 | info->regs.xes = __USER_DS; | ||
| 67 | } | ||
| 68 | |||
| 69 | extern asmlinkage void arch_unwind_init_running(struct unwind_frame_info *, | ||
| 70 | asmlinkage void (*callback)(struct unwind_frame_info *, | ||
| 71 | void *arg), | ||
| 72 | void *arg); | ||
| 73 | |||
| 74 | static inline int arch_unw_user_mode(const struct unwind_frame_info *info) | ||
| 75 | { | ||
| 76 | #if 0 /* This can only work when selector register and EFLAGS saves/restores | ||
| 77 | are properly annotated (and tracked in UNW_REGISTER_INFO). */ | ||
| 78 | return user_mode_vm(&info->regs); | ||
| 79 | #else | ||
| 80 | return info->regs.eip < PAGE_OFFSET | ||
| 81 | || (info->regs.eip >= __fix_to_virt(FIX_VSYSCALL) | ||
| 82 | && info->regs.eip < __fix_to_virt(FIX_VSYSCALL) + PAGE_SIZE) | ||
| 83 | || info->regs.esp < PAGE_OFFSET; | ||
| 84 | #endif | ||
| 85 | } | ||
| 86 | |||
| 87 | #else | ||
| 88 | |||
| 89 | #define UNW_PC(frame) ((void)(frame), 0) | ||
| 90 | |||
| 91 | static inline int arch_unw_user_mode(const void *info) | ||
| 92 | { | ||
| 93 | return 0; | ||
| 94 | } | ||
| 95 | |||
| 96 | #endif | ||
| 97 | |||
| 98 | #endif /* _ASM_I386_UNWIND_H */ | ||
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index bbaed84a686b..8bab0102ac73 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug | |||
| @@ -199,7 +199,7 @@ config UNWIND_INFO | |||
| 199 | config STACK_UNWIND | 199 | config STACK_UNWIND |
| 200 | bool "Stack unwind support" | 200 | bool "Stack unwind support" |
| 201 | depends on UNWIND_INFO | 201 | depends on UNWIND_INFO |
| 202 | depends on X86_64 | 202 | depends on X86 |
| 203 | help | 203 | help |
| 204 | This enables more precise stack traces, omitting all unrelated | 204 | This enables more precise stack traces, omitting all unrelated |
| 205 | occurrences of pointers into kernel code from the dump. | 205 | occurrences of pointers into kernel code from the dump. |
