diff options
| -rw-r--r-- | arch/x86_64/Kconfig | 4 | ||||
| -rw-r--r-- | arch/x86_64/kernel/Makefile | 1 | ||||
| -rw-r--r-- | arch/x86_64/kernel/stacktrace.c | 221 |
3 files changed, 226 insertions, 0 deletions
diff --git a/arch/x86_64/Kconfig b/arch/x86_64/Kconfig index e856804c447f..445f436688f3 100644 --- a/arch/x86_64/Kconfig +++ b/arch/x86_64/Kconfig | |||
| @@ -24,6 +24,10 @@ config X86 | |||
| 24 | bool | 24 | bool |
| 25 | default y | 25 | default y |
| 26 | 26 | ||
| 27 | config STACKTRACE_SUPPORT | ||
| 28 | bool | ||
| 29 | default y | ||
| 30 | |||
| 27 | config SEMAPHORE_SLEEPERS | 31 | config SEMAPHORE_SLEEPERS |
| 28 | bool | 32 | bool |
| 29 | default y | 33 | default y |
diff --git a/arch/x86_64/kernel/Makefile b/arch/x86_64/kernel/Makefile index 819e84ec5b64..b5aaeafc1cd3 100644 --- a/arch/x86_64/kernel/Makefile +++ b/arch/x86_64/kernel/Makefile | |||
| @@ -10,6 +10,7 @@ obj-y := process.o signal.o entry.o traps.o irq.o \ | |||
| 10 | setup64.o bootflag.o e820.o reboot.o quirks.o i8237.o \ | 10 | setup64.o bootflag.o e820.o reboot.o quirks.o i8237.o \ |
| 11 | pci-dma.o pci-nommu.o alternative.o | 11 | pci-dma.o pci-nommu.o alternative.o |
| 12 | 12 | ||
| 13 | obj-$(CONFIG_STACKTRACE) += stacktrace.o | ||
| 13 | obj-$(CONFIG_X86_MCE) += mce.o | 14 | obj-$(CONFIG_X86_MCE) += mce.o |
| 14 | obj-$(CONFIG_X86_MCE_INTEL) += mce_intel.o | 15 | obj-$(CONFIG_X86_MCE_INTEL) += mce_intel.o |
| 15 | obj-$(CONFIG_X86_MCE_AMD) += mce_amd.o | 16 | obj-$(CONFIG_X86_MCE_AMD) += mce_amd.o |
diff --git a/arch/x86_64/kernel/stacktrace.c b/arch/x86_64/kernel/stacktrace.c new file mode 100644 index 000000000000..32cf55eb9af8 --- /dev/null +++ b/arch/x86_64/kernel/stacktrace.c | |||
| @@ -0,0 +1,221 @@ | |||
| 1 | /* | ||
| 2 | * arch/x86_64/kernel/stacktrace.c | ||
| 3 | * | ||
| 4 | * Stack trace management functions | ||
| 5 | * | ||
| 6 | * Copyright (C) 2006 Red Hat, Inc., Ingo Molnar <mingo@redhat.com> | ||
| 7 | */ | ||
| 8 | #include <linux/sched.h> | ||
| 9 | #include <linux/stacktrace.h> | ||
| 10 | |||
| 11 | #include <asm/smp.h> | ||
| 12 | |||
| 13 | static inline int | ||
| 14 | in_range(unsigned long start, unsigned long addr, unsigned long end) | ||
| 15 | { | ||
| 16 | return addr >= start && addr <= end; | ||
| 17 | } | ||
| 18 | |||
| 19 | static unsigned long | ||
| 20 | get_stack_end(struct task_struct *task, unsigned long stack) | ||
| 21 | { | ||
| 22 | unsigned long stack_start, stack_end, flags; | ||
| 23 | int i, cpu; | ||
| 24 | |||
| 25 | /* | ||
| 26 | * The most common case is that we are in the task stack: | ||
| 27 | */ | ||
| 28 | stack_start = (unsigned long)task->thread_info; | ||
| 29 | stack_end = stack_start + THREAD_SIZE; | ||
| 30 | |||
| 31 | if (in_range(stack_start, stack, stack_end)) | ||
| 32 | return stack_end; | ||
| 33 | |||
| 34 | /* | ||
| 35 | * We are in an interrupt if irqstackptr is set: | ||
| 36 | */ | ||
| 37 | raw_local_irq_save(flags); | ||
| 38 | cpu = safe_smp_processor_id(); | ||
| 39 | stack_end = (unsigned long)cpu_pda(cpu)->irqstackptr; | ||
| 40 | |||
| 41 | if (stack_end) { | ||
| 42 | stack_start = stack_end & ~(IRQSTACKSIZE-1); | ||
| 43 | if (in_range(stack_start, stack, stack_end)) | ||
| 44 | goto out_restore; | ||
| 45 | /* | ||
| 46 | * We get here if we are in an IRQ context but we | ||
| 47 | * are also in an exception stack. | ||
| 48 | */ | ||
| 49 | } | ||
| 50 | |||
| 51 | /* | ||
| 52 | * Iterate over all exception stacks, and figure out whether | ||
| 53 | * 'stack' is in one of them: | ||
| 54 | */ | ||
| 55 | for (i = 0; i < N_EXCEPTION_STACKS; i++) { | ||
| 56 | /* | ||
| 57 | * set 'end' to the end of the exception stack. | ||
| 58 | */ | ||
| 59 | stack_end = per_cpu(init_tss, cpu).ist[i]; | ||
| 60 | stack_start = stack_end - EXCEPTION_STKSZ; | ||
| 61 | |||
| 62 | /* | ||
| 63 | * Is 'stack' above this exception frame's end? | ||
| 64 | * If yes then skip to the next frame. | ||
| 65 | */ | ||
| 66 | if (stack >= stack_end) | ||
| 67 | continue; | ||
| 68 | /* | ||
| 69 | * Is 'stack' above this exception frame's start address? | ||
| 70 | * If yes then we found the right frame. | ||
| 71 | */ | ||
| 72 | if (stack >= stack_start) | ||
| 73 | goto out_restore; | ||
| 74 | |||
| 75 | /* | ||
| 76 | * If this is a debug stack, and if it has a larger size than | ||
| 77 | * the usual exception stacks, then 'stack' might still | ||
| 78 | * be within the lower portion of the debug stack: | ||
| 79 | */ | ||
| 80 | #if DEBUG_STKSZ > EXCEPTION_STKSZ | ||
| 81 | if (i == DEBUG_STACK - 1 && stack >= stack_end - DEBUG_STKSZ) { | ||
| 82 | /* | ||
| 83 | * Black magic. A large debug stack is composed of | ||
| 84 | * multiple exception stack entries, which we | ||
| 85 | * iterate through now. Dont look: | ||
| 86 | */ | ||
| 87 | do { | ||
| 88 | stack_end -= EXCEPTION_STKSZ; | ||
| 89 | stack_start -= EXCEPTION_STKSZ; | ||
| 90 | } while (stack < stack_start); | ||
| 91 | |||
| 92 | goto out_restore; | ||
| 93 | } | ||
| 94 | #endif | ||
| 95 | } | ||
| 96 | /* | ||
| 97 | * Ok, 'stack' is not pointing to any of the system stacks. | ||
| 98 | */ | ||
| 99 | stack_end = 0; | ||
| 100 | |||
| 101 | out_restore: | ||
| 102 | raw_local_irq_restore(flags); | ||
| 103 | |||
| 104 | return stack_end; | ||
| 105 | } | ||
| 106 | |||
| 107 | |||
| 108 | /* | ||
| 109 | * Save stack-backtrace addresses into a stack_trace buffer: | ||
| 110 | */ | ||
| 111 | static inline unsigned long | ||
| 112 | save_context_stack(struct stack_trace *trace, unsigned int skip, | ||
| 113 | unsigned long stack, unsigned long stack_end) | ||
| 114 | { | ||
| 115 | unsigned long addr; | ||
| 116 | |||
| 117 | #ifdef CONFIG_FRAME_POINTER | ||
| 118 | unsigned long prev_stack = 0; | ||
| 119 | |||
| 120 | while (in_range(prev_stack, stack, stack_end)) { | ||
| 121 | pr_debug("stack: %p\n", (void *)stack); | ||
| 122 | addr = (unsigned long)(((unsigned long *)stack)[1]); | ||
| 123 | pr_debug("addr: %p\n", (void *)addr); | ||
| 124 | if (!skip) | ||
| 125 | trace->entries[trace->nr_entries++] = addr-1; | ||
| 126 | else | ||
| 127 | skip--; | ||
| 128 | if (trace->nr_entries >= trace->max_entries) | ||
| 129 | break; | ||
| 130 | if (!addr) | ||
| 131 | return 0; | ||
| 132 | /* | ||
| 133 | * Stack frames must go forwards (otherwise a loop could | ||
| 134 | * happen if the stackframe is corrupted), so we move | ||
| 135 | * prev_stack forwards: | ||
| 136 | */ | ||
| 137 | prev_stack = stack; | ||
| 138 | stack = (unsigned long)(((unsigned long *)stack)[0]); | ||
| 139 | } | ||
| 140 | pr_debug("invalid: %p\n", (void *)stack); | ||
| 141 | #else | ||
| 142 | while (stack < stack_end) { | ||
| 143 | addr = ((unsigned long *)stack)[0]; | ||
| 144 | stack += sizeof(long); | ||
| 145 | if (__kernel_text_address(addr)) { | ||
| 146 | if (!skip) | ||
| 147 | trace->entries[trace->nr_entries++] = addr-1; | ||
| 148 | else | ||
| 149 | skip--; | ||
| 150 | if (trace->nr_entries >= trace->max_entries) | ||
| 151 | break; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | #endif | ||
| 155 | return stack; | ||
| 156 | } | ||
| 157 | |||
| 158 | #define MAX_STACKS 10 | ||
| 159 | |||
| 160 | /* | ||
| 161 | * Save stack-backtrace addresses into a stack_trace buffer. | ||
| 162 | * If all_contexts is set, all contexts (hardirq, softirq and process) | ||
| 163 | * are saved. If not set then only the current context is saved. | ||
| 164 | */ | ||
| 165 | void save_stack_trace(struct stack_trace *trace, | ||
| 166 | struct task_struct *task, int all_contexts, | ||
| 167 | unsigned int skip) | ||
| 168 | { | ||
| 169 | unsigned long stack = (unsigned long)&stack; | ||
| 170 | int i, nr_stacks = 0, stacks_done[MAX_STACKS]; | ||
| 171 | |||
| 172 | WARN_ON(trace->nr_entries || !trace->max_entries); | ||
| 173 | |||
| 174 | if (!task) | ||
| 175 | task = current; | ||
| 176 | |||
| 177 | pr_debug("task: %p, ti: %p\n", task, task->thread_info); | ||
| 178 | |||
| 179 | if (!task || task == current) { | ||
| 180 | /* Grab rbp right from our regs: */ | ||
| 181 | asm ("mov %%rbp, %0" : "=r" (stack)); | ||
| 182 | pr_debug("rbp: %p\n", (void *)stack); | ||
| 183 | } else { | ||
| 184 | /* rbp is the last reg pushed by switch_to(): */ | ||
| 185 | stack = task->thread.rsp; | ||
| 186 | pr_debug("other task rsp: %p\n", (void *)stack); | ||
| 187 | stack = (unsigned long)(((unsigned long *)stack)[0]); | ||
| 188 | pr_debug("other task rbp: %p\n", (void *)stack); | ||
| 189 | } | ||
| 190 | |||
| 191 | while (1) { | ||
| 192 | unsigned long stack_end = get_stack_end(task, stack); | ||
| 193 | |||
| 194 | pr_debug("stack: %p\n", (void *)stack); | ||
| 195 | pr_debug("stack end: %p\n", (void *)stack_end); | ||
| 196 | |||
| 197 | /* | ||
| 198 | * Invalid stack addres? | ||
| 199 | */ | ||
| 200 | if (!stack_end) | ||
| 201 | return; | ||
| 202 | /* | ||
| 203 | * Were we in this stack already? (recursion) | ||
| 204 | */ | ||
| 205 | for (i = 0; i < nr_stacks; i++) | ||
| 206 | if (stacks_done[i] == stack_end) | ||
| 207 | return; | ||
| 208 | stacks_done[nr_stacks] = stack_end; | ||
| 209 | |||
| 210 | stack = save_context_stack(trace, skip, stack, stack_end); | ||
| 211 | if (!all_contexts || !stack || | ||
| 212 | trace->nr_entries >= trace->max_entries) | ||
| 213 | return; | ||
| 214 | trace->entries[trace->nr_entries++] = ULONG_MAX; | ||
| 215 | if (trace->nr_entries >= trace->max_entries) | ||
| 216 | return; | ||
| 217 | if (++nr_stacks >= MAX_STACKS) | ||
| 218 | return; | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
