diff options
author | Jan Beulich <jbeulich@novell.com> | 2006-06-26 07:57:32 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-26 13:48:17 -0400 |
commit | b538ed278b80641ee6d7b75497d5e14de1bb1137 (patch) | |
tree | fe508a2f950ec08c832474a253f41b188ceece75 | |
parent | 4552d5dc08b79868829b4be8951b29b07284753f (diff) |
[PATCH] x86_64: reliable stack trace support (x86-64)
These are the x86_64-specific pieces to enable reliable stack traces. The
only restriction with this is that it currently cannot unwind across the
interrupt->normal stack boundary, as that transition is lacking proper
annotation.
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/x86_64/kernel/entry.S | 33 | ||||
-rw-r--r-- | arch/x86_64/kernel/process.c | 2 | ||||
-rw-r--r-- | arch/x86_64/kernel/traps.c | 54 | ||||
-rw-r--r-- | arch/x86_64/kernel/vmlinux.lds.S | 9 | ||||
-rw-r--r-- | include/asm-x86_64/proto.h | 2 | ||||
-rw-r--r-- | include/asm-x86_64/unwind.h | 106 | ||||
-rw-r--r-- | lib/Kconfig.debug | 2 |
7 files changed, 199 insertions, 9 deletions
diff --git a/arch/x86_64/kernel/entry.S b/arch/x86_64/kernel/entry.S index 9999d703b6c4..6c68beec51b3 100644 --- a/arch/x86_64/kernel/entry.S +++ b/arch/x86_64/kernel/entry.S | |||
@@ -1089,3 +1089,36 @@ ENTRY(call_softirq) | |||
1089 | ret | 1089 | ret |
1090 | CFI_ENDPROC | 1090 | CFI_ENDPROC |
1091 | ENDPROC(call_softirq) | 1091 | ENDPROC(call_softirq) |
1092 | |||
1093 | #ifdef CONFIG_STACK_UNWIND | ||
1094 | ENTRY(arch_unwind_init_running) | ||
1095 | CFI_STARTPROC | ||
1096 | movq %r15, R15(%rdi) | ||
1097 | movq %r14, R14(%rdi) | ||
1098 | xchgq %rsi, %rdx | ||
1099 | movq %r13, R13(%rdi) | ||
1100 | movq %r12, R12(%rdi) | ||
1101 | xorl %eax, %eax | ||
1102 | movq %rbp, RBP(%rdi) | ||
1103 | movq %rbx, RBX(%rdi) | ||
1104 | movq (%rsp), %rcx | ||
1105 | movq %rax, R11(%rdi) | ||
1106 | movq %rax, R10(%rdi) | ||
1107 | movq %rax, R9(%rdi) | ||
1108 | movq %rax, R8(%rdi) | ||
1109 | movq %rax, RAX(%rdi) | ||
1110 | movq %rax, RCX(%rdi) | ||
1111 | movq %rax, RDX(%rdi) | ||
1112 | movq %rax, RSI(%rdi) | ||
1113 | movq %rax, RDI(%rdi) | ||
1114 | movq %rax, ORIG_RAX(%rdi) | ||
1115 | movq %rcx, RIP(%rdi) | ||
1116 | leaq 8(%rsp), %rcx | ||
1117 | movq $__KERNEL_CS, CS(%rdi) | ||
1118 | movq %rax, EFLAGS(%rdi) | ||
1119 | movq %rcx, RSP(%rdi) | ||
1120 | movq $__KERNEL_DS, SS(%rdi) | ||
1121 | jmpq *%rdx | ||
1122 | CFI_ENDPROC | ||
1123 | ENDPROC(arch_unwind_init_running) | ||
1124 | #endif | ||
diff --git a/arch/x86_64/kernel/process.c b/arch/x86_64/kernel/process.c index dcb77b462348..d6fa41459c80 100644 --- a/arch/x86_64/kernel/process.c +++ b/arch/x86_64/kernel/process.c | |||
@@ -334,7 +334,7 @@ void show_regs(struct pt_regs *regs) | |||
334 | { | 334 | { |
335 | printk("CPU %d:", smp_processor_id()); | 335 | printk("CPU %d:", smp_processor_id()); |
336 | __show_regs(regs); | 336 | __show_regs(regs); |
337 | show_trace(®s->rsp); | 337 | show_trace(NULL, regs, (void *)(regs + 1)); |
338 | } | 338 | } |
339 | 339 | ||
340 | /* | 340 | /* |
diff --git a/arch/x86_64/kernel/traps.c b/arch/x86_64/kernel/traps.c index 9f8f1eff4a6c..eb1534ff1f5f 100644 --- a/arch/x86_64/kernel/traps.c +++ b/arch/x86_64/kernel/traps.c | |||
@@ -29,6 +29,7 @@ | |||
29 | #include <linux/nmi.h> | 29 | #include <linux/nmi.h> |
30 | #include <linux/kprobes.h> | 30 | #include <linux/kprobes.h> |
31 | #include <linux/kexec.h> | 31 | #include <linux/kexec.h> |
32 | #include <linux/unwind.h> | ||
32 | 33 | ||
33 | #include <asm/system.h> | 34 | #include <asm/system.h> |
34 | #include <asm/uaccess.h> | 35 | #include <asm/uaccess.h> |
@@ -39,7 +40,7 @@ | |||
39 | #include <asm/i387.h> | 40 | #include <asm/i387.h> |
40 | #include <asm/kdebug.h> | 41 | #include <asm/kdebug.h> |
41 | #include <asm/processor.h> | 42 | #include <asm/processor.h> |
42 | 43 | #include <asm/unwind.h> | |
43 | #include <asm/smp.h> | 44 | #include <asm/smp.h> |
44 | #include <asm/pgalloc.h> | 45 | #include <asm/pgalloc.h> |
45 | #include <asm/pda.h> | 46 | #include <asm/pda.h> |
@@ -189,6 +190,23 @@ static unsigned long *in_exception_stack(unsigned cpu, unsigned long stack, | |||
189 | return NULL; | 190 | return NULL; |
190 | } | 191 | } |
191 | 192 | ||
193 | static void show_trace_unwind(struct unwind_frame_info *info, void *context) | ||
194 | { | ||
195 | int i = 11; | ||
196 | |||
197 | while (unwind(info) == 0 && UNW_PC(info)) { | ||
198 | if (i > 50) { | ||
199 | printk("\n "); | ||
200 | i = 7; | ||
201 | } else | ||
202 | i += printk(" "); | ||
203 | i += printk_address(UNW_PC(info)); | ||
204 | if (arch_unw_user_mode(info)) | ||
205 | break; | ||
206 | } | ||
207 | printk("\n"); | ||
208 | } | ||
209 | |||
192 | /* | 210 | /* |
193 | * x86-64 can have upto three kernel stacks: | 211 | * x86-64 can have upto three kernel stacks: |
194 | * process stack | 212 | * process stack |
@@ -196,15 +214,34 @@ static unsigned long *in_exception_stack(unsigned cpu, unsigned long stack, | |||
196 | * severe exception (double fault, nmi, stack fault, debug, mce) hardware stack | 214 | * severe exception (double fault, nmi, stack fault, debug, mce) hardware stack |
197 | */ | 215 | */ |
198 | 216 | ||
199 | void show_trace(unsigned long *stack) | 217 | void show_trace(struct task_struct *tsk, struct pt_regs *regs, unsigned long * stack) |
200 | { | 218 | { |
201 | const unsigned cpu = safe_smp_processor_id(); | 219 | const unsigned cpu = safe_smp_processor_id(); |
202 | unsigned long *irqstack_end = (unsigned long *)cpu_pda(cpu)->irqstackptr; | 220 | unsigned long *irqstack_end = (unsigned long *)cpu_pda(cpu)->irqstackptr; |
203 | int i; | 221 | int i; |
204 | unsigned used = 0; | 222 | unsigned used = 0; |
223 | struct unwind_frame_info info; | ||
205 | 224 | ||
206 | printk("\nCall Trace:"); | 225 | printk("\nCall Trace:"); |
207 | 226 | ||
227 | if (!tsk) | ||
228 | tsk = current; | ||
229 | |||
230 | if (regs) { | ||
231 | if (unwind_init_frame_info(&info, tsk, regs) == 0) { | ||
232 | show_trace_unwind(&info, NULL); | ||
233 | return; | ||
234 | } | ||
235 | } else if (tsk == current) { | ||
236 | if (unwind_init_running(&info, show_trace_unwind, NULL) == 0) | ||
237 | return; | ||
238 | } else { | ||
239 | if (unwind_init_blocked(&info, tsk) == 0) { | ||
240 | show_trace_unwind(&info, NULL); | ||
241 | return; | ||
242 | } | ||
243 | } | ||
244 | |||
208 | #define HANDLE_STACK(cond) \ | 245 | #define HANDLE_STACK(cond) \ |
209 | do while (cond) { \ | 246 | do while (cond) { \ |
210 | unsigned long addr = *stack++; \ | 247 | unsigned long addr = *stack++; \ |
@@ -262,7 +299,7 @@ void show_trace(unsigned long *stack) | |||
262 | printk("\n"); | 299 | printk("\n"); |
263 | } | 300 | } |
264 | 301 | ||
265 | void show_stack(struct task_struct *tsk, unsigned long * rsp) | 302 | static void _show_stack(struct task_struct *tsk, struct pt_regs *regs, unsigned long * rsp) |
266 | { | 303 | { |
267 | unsigned long *stack; | 304 | unsigned long *stack; |
268 | int i; | 305 | int i; |
@@ -296,7 +333,12 @@ void show_stack(struct task_struct *tsk, unsigned long * rsp) | |||
296 | printk("%016lx ", *stack++); | 333 | printk("%016lx ", *stack++); |
297 | touch_nmi_watchdog(); | 334 | touch_nmi_watchdog(); |
298 | } | 335 | } |
299 | show_trace((unsigned long *)rsp); | 336 | show_trace(tsk, regs, rsp); |
337 | } | ||
338 | |||
339 | void show_stack(struct task_struct *tsk, unsigned long * rsp) | ||
340 | { | ||
341 | _show_stack(tsk, NULL, rsp); | ||
300 | } | 342 | } |
301 | 343 | ||
302 | /* | 344 | /* |
@@ -305,7 +347,7 @@ void show_stack(struct task_struct *tsk, unsigned long * rsp) | |||
305 | void dump_stack(void) | 347 | void dump_stack(void) |
306 | { | 348 | { |
307 | unsigned long dummy; | 349 | unsigned long dummy; |
308 | show_trace(&dummy); | 350 | show_trace(NULL, NULL, &dummy); |
309 | } | 351 | } |
310 | 352 | ||
311 | EXPORT_SYMBOL(dump_stack); | 353 | EXPORT_SYMBOL(dump_stack); |
@@ -332,7 +374,7 @@ void show_registers(struct pt_regs *regs) | |||
332 | if (in_kernel) { | 374 | if (in_kernel) { |
333 | 375 | ||
334 | printk("Stack: "); | 376 | printk("Stack: "); |
335 | show_stack(NULL, (unsigned long*)rsp); | 377 | _show_stack(NULL, regs, (unsigned long*)rsp); |
336 | 378 | ||
337 | printk("\nCode: "); | 379 | printk("\nCode: "); |
338 | if (regs->rip < PAGE_OFFSET) | 380 | if (regs->rip < PAGE_OFFSET) |
diff --git a/arch/x86_64/kernel/vmlinux.lds.S b/arch/x86_64/kernel/vmlinux.lds.S index 5968c2415da9..1c6a5f322919 100644 --- a/arch/x86_64/kernel/vmlinux.lds.S +++ b/arch/x86_64/kernel/vmlinux.lds.S | |||
@@ -45,6 +45,15 @@ SECTIONS | |||
45 | 45 | ||
46 | RODATA | 46 | RODATA |
47 | 47 | ||
48 | #ifdef CONFIG_STACK_UNWIND | ||
49 | . = ALIGN(8); | ||
50 | .eh_frame : AT(ADDR(.eh_frame) - LOAD_OFFSET) { | ||
51 | __start_unwind = .; | ||
52 | *(.eh_frame) | ||
53 | __end_unwind = .; | ||
54 | } | ||
55 | #endif | ||
56 | |||
48 | /* Data */ | 57 | /* Data */ |
49 | .data : AT(ADDR(.data) - LOAD_OFFSET) { | 58 | .data : AT(ADDR(.data) - LOAD_OFFSET) { |
50 | *(.data) | 59 | *(.data) |
diff --git a/include/asm-x86_64/proto.h b/include/asm-x86_64/proto.h index 1064533e0959..105476ff8039 100644 --- a/include/asm-x86_64/proto.h +++ b/include/asm-x86_64/proto.h | |||
@@ -75,7 +75,7 @@ extern void main_timer_handler(struct pt_regs *regs); | |||
75 | 75 | ||
76 | extern unsigned long end_pfn_map; | 76 | extern unsigned long end_pfn_map; |
77 | 77 | ||
78 | extern void show_trace(unsigned long * rsp); | 78 | extern void show_trace(struct task_struct *, struct pt_regs *, unsigned long * rsp); |
79 | extern void show_registers(struct pt_regs *regs); | 79 | extern void show_registers(struct pt_regs *regs); |
80 | 80 | ||
81 | extern void exception_table_check(void); | 81 | extern void exception_table_check(void); |
diff --git a/include/asm-x86_64/unwind.h b/include/asm-x86_64/unwind.h new file mode 100644 index 000000000000..4f61de246179 --- /dev/null +++ b/include/asm-x86_64/unwind.h | |||
@@ -0,0 +1,106 @@ | |||
1 | #ifndef _ASM_X86_64_UNWIND_H | ||
2 | #define _ASM_X86_64_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/ptrace.h> | ||
14 | #include <asm/uaccess.h> | ||
15 | #include <asm/vsyscall.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.rip | ||
24 | #define UNW_SP(frame) (frame)->regs.rsp | ||
25 | #ifdef CONFIG_FRAME_POINTER | ||
26 | #define UNW_FP(frame) (frame)->regs.rbp | ||
27 | #define FRAME_RETADDR_OFFSET 8 | ||
28 | #define FRAME_LINK_OFFSET 0 | ||
29 | #define STACK_BOTTOM(tsk) (((tsk)->thread.rsp0 - 1) & ~(THREAD_SIZE - 1)) | ||
30 | #define STACK_TOP(tsk) ((tsk)->thread.rsp0) | ||
31 | #endif | ||
32 | /* Might need to account for the special exception and interrupt handling | ||
33 | stacks here, since normally | ||
34 | EXCEPTION_STACK_ORDER < THREAD_ORDER < IRQSTACK_ORDER, | ||
35 | but the construct is needed only for getting across the stack switch to | ||
36 | the interrupt stack - thus considering the IRQ stack itself is unnecessary, | ||
37 | and the overhead of comparing against all exception handling stacks seems | ||
38 | not desirable. */ | ||
39 | #define STACK_LIMIT(ptr) (((ptr) - 1) & ~(THREAD_SIZE - 1)) | ||
40 | |||
41 | #define UNW_REGISTER_INFO \ | ||
42 | PTREGS_INFO(rax), \ | ||
43 | PTREGS_INFO(rdx), \ | ||
44 | PTREGS_INFO(rcx), \ | ||
45 | PTREGS_INFO(rbx), \ | ||
46 | PTREGS_INFO(rsi), \ | ||
47 | PTREGS_INFO(rdi), \ | ||
48 | PTREGS_INFO(rbp), \ | ||
49 | PTREGS_INFO(rsp), \ | ||
50 | PTREGS_INFO(r8), \ | ||
51 | PTREGS_INFO(r9), \ | ||
52 | PTREGS_INFO(r10), \ | ||
53 | PTREGS_INFO(r11), \ | ||
54 | PTREGS_INFO(r12), \ | ||
55 | PTREGS_INFO(r13), \ | ||
56 | PTREGS_INFO(r14), \ | ||
57 | PTREGS_INFO(r15), \ | ||
58 | PTREGS_INFO(rip) | ||
59 | |||
60 | static inline void arch_unw_init_frame_info(struct unwind_frame_info *info, | ||
61 | /*const*/ struct pt_regs *regs) | ||
62 | { | ||
63 | info->regs = *regs; | ||
64 | } | ||
65 | |||
66 | static inline void arch_unw_init_blocked(struct unwind_frame_info *info) | ||
67 | { | ||
68 | extern const char thread_return[]; | ||
69 | |||
70 | memset(&info->regs, 0, sizeof(info->regs)); | ||
71 | info->regs.rip = (unsigned long)thread_return; | ||
72 | info->regs.cs = __KERNEL_CS; | ||
73 | __get_user(info->regs.rbp, (unsigned long *)info->task->thread.rsp); | ||
74 | info->regs.rsp = info->task->thread.rsp; | ||
75 | info->regs.ss = __KERNEL_DS; | ||
76 | } | ||
77 | |||
78 | extern void arch_unwind_init_running(struct unwind_frame_info *, | ||
79 | void (*callback)(struct unwind_frame_info *, | ||
80 | void *arg), | ||
81 | void *arg); | ||
82 | |||
83 | static inline int arch_unw_user_mode(const struct unwind_frame_info *info) | ||
84 | { | ||
85 | #if 0 /* This can only work when selector register saves/restores | ||
86 | are properly annotated (and tracked in UNW_REGISTER_INFO). */ | ||
87 | return user_mode(&info->regs); | ||
88 | #else | ||
89 | return (long)info->regs.rip >= 0 | ||
90 | || (info->regs.rip >= VSYSCALL_START && info->regs.rip < VSYSCALL_END) | ||
91 | || (long)info->regs.rsp >= 0; | ||
92 | #endif | ||
93 | } | ||
94 | |||
95 | #else | ||
96 | |||
97 | #define UNW_PC(frame) ((void)(frame), 0) | ||
98 | |||
99 | static inline int arch_unw_user_mode(const void *info) | ||
100 | { | ||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | #endif | ||
105 | |||
106 | #endif /* _ASM_X86_64_UNWIND_H */ | ||
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 256b3b805c5c..bbaed84a686b 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 n | 202 | depends on X86_64 |
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. |