9c00390757
in_exception_stack() does some bad, bad things just so the unwinder can print different values for different areas of the debug exception stack. There's no need to clarify where exactly on the stack it is. Just print "#DB" and be done with it. Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Andy Lutomirski <luto@kernel.org> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Byungchul Park <byungchul.park@lge.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Kees Cook <keescook@chromium.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Nilay Vaish <nilayvaish@gmail.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Thomas Gleixner <tglx@linutronix.de> Link: http://lkml.kernel.org/r/e91cb410169dd576678dd427c35efb716fd0cee1.1473905218.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
290 lines
6.5 KiB
C
290 lines
6.5 KiB
C
/*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
* Copyright (C) 2000, 2001, 2002 Andi Kleen, SuSE Labs
|
|
*/
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/export.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/kexec.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/nmi.h>
|
|
|
|
#include <asm/stacktrace.h>
|
|
|
|
static char *exception_stack_names[N_EXCEPTION_STACKS] = {
|
|
[ DOUBLEFAULT_STACK-1 ] = "#DF",
|
|
[ NMI_STACK-1 ] = "NMI",
|
|
[ DEBUG_STACK-1 ] = "#DB",
|
|
[ MCE_STACK-1 ] = "#MC",
|
|
};
|
|
|
|
static unsigned long exception_stack_sizes[N_EXCEPTION_STACKS] = {
|
|
[0 ... N_EXCEPTION_STACKS - 1] = EXCEPTION_STKSZ,
|
|
[DEBUG_STACK - 1] = DEBUG_STKSZ
|
|
};
|
|
|
|
static unsigned long *in_exception_stack(unsigned long stack, unsigned *usedp,
|
|
char **idp)
|
|
{
|
|
unsigned long begin, end;
|
|
unsigned k;
|
|
|
|
BUILD_BUG_ON(N_EXCEPTION_STACKS != 4);
|
|
|
|
for (k = 0; k < N_EXCEPTION_STACKS; k++) {
|
|
end = raw_cpu_ptr(&orig_ist)->ist[k];
|
|
begin = end - exception_stack_sizes[k];
|
|
|
|
if (stack < begin || stack >= end)
|
|
continue;
|
|
|
|
/*
|
|
* Make sure we only iterate through an exception stack once.
|
|
* If it comes up for the second time then there's something
|
|
* wrong going on - just break and return NULL:
|
|
*/
|
|
if (*usedp & (1U << k))
|
|
break;
|
|
*usedp |= 1U << k;
|
|
|
|
*idp = exception_stack_names[k];
|
|
return (unsigned long *)end;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline int
|
|
in_irq_stack(unsigned long *stack, unsigned long *irq_stack,
|
|
unsigned long *irq_stack_end)
|
|
{
|
|
return (stack >= irq_stack && stack < irq_stack_end);
|
|
}
|
|
|
|
enum stack_type {
|
|
STACK_IS_UNKNOWN,
|
|
STACK_IS_NORMAL,
|
|
STACK_IS_EXCEPTION,
|
|
STACK_IS_IRQ,
|
|
};
|
|
|
|
static enum stack_type
|
|
analyze_stack(struct task_struct *task, unsigned long *stack,
|
|
unsigned long **stack_end, unsigned long *irq_stack,
|
|
unsigned *used, char **id)
|
|
{
|
|
unsigned long addr;
|
|
|
|
addr = ((unsigned long)stack & (~(THREAD_SIZE - 1)));
|
|
if ((unsigned long)task_stack_page(task) == addr)
|
|
return STACK_IS_NORMAL;
|
|
|
|
*stack_end = in_exception_stack((unsigned long)stack, used, id);
|
|
if (*stack_end)
|
|
return STACK_IS_EXCEPTION;
|
|
|
|
if (!irq_stack)
|
|
return STACK_IS_NORMAL;
|
|
|
|
*stack_end = irq_stack;
|
|
irq_stack -= (IRQ_STACK_SIZE / sizeof(long));
|
|
|
|
if (in_irq_stack(stack, irq_stack, *stack_end))
|
|
return STACK_IS_IRQ;
|
|
|
|
return STACK_IS_UNKNOWN;
|
|
}
|
|
|
|
/*
|
|
* x86-64 can have up to three kernel stacks:
|
|
* process stack
|
|
* interrupt stack
|
|
* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack
|
|
*/
|
|
|
|
void dump_trace(struct task_struct *task, struct pt_regs *regs,
|
|
unsigned long *stack, unsigned long bp,
|
|
const struct stacktrace_ops *ops, void *data)
|
|
{
|
|
unsigned long *irq_stack = (unsigned long *)this_cpu_read(irq_stack_ptr);
|
|
unsigned used = 0;
|
|
int graph = 0;
|
|
int done = 0;
|
|
|
|
task = task ? : current;
|
|
stack = stack ? : get_stack_pointer(task, regs);
|
|
bp = bp ? : (unsigned long)get_frame_pointer(task, regs);
|
|
|
|
/*
|
|
* Print function call entries in all stacks, starting at the
|
|
* current stack address. If the stacks consist of nested
|
|
* exceptions
|
|
*/
|
|
while (!done) {
|
|
unsigned long *stack_end;
|
|
enum stack_type stype;
|
|
char *id;
|
|
|
|
stype = analyze_stack(task, stack, &stack_end, irq_stack, &used,
|
|
&id);
|
|
|
|
/* Default finish unless specified to continue */
|
|
done = 1;
|
|
|
|
switch (stype) {
|
|
|
|
/* Break out early if we are on the thread stack */
|
|
case STACK_IS_NORMAL:
|
|
break;
|
|
|
|
case STACK_IS_EXCEPTION:
|
|
|
|
if (ops->stack(data, id) < 0)
|
|
break;
|
|
|
|
bp = ops->walk_stack(task, stack, bp, ops,
|
|
data, stack_end, &graph);
|
|
ops->stack(data, "EOE");
|
|
/*
|
|
* We link to the next stack via the
|
|
* second-to-last pointer (index -2 to end) in the
|
|
* exception stack:
|
|
*/
|
|
stack = (unsigned long *) stack_end[-2];
|
|
done = 0;
|
|
break;
|
|
|
|
case STACK_IS_IRQ:
|
|
|
|
if (ops->stack(data, "IRQ") < 0)
|
|
break;
|
|
bp = ops->walk_stack(task, stack, bp,
|
|
ops, data, stack_end, &graph);
|
|
/*
|
|
* We link to the next stack (which would be
|
|
* the process stack normally) the last
|
|
* pointer (index -1 to end) in the IRQ stack:
|
|
*/
|
|
stack = (unsigned long *) (stack_end[-1]);
|
|
irq_stack = NULL;
|
|
ops->stack(data, "EOI");
|
|
done = 0;
|
|
break;
|
|
|
|
case STACK_IS_UNKNOWN:
|
|
ops->stack(data, "UNK");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This handles the process stack:
|
|
*/
|
|
bp = ops->walk_stack(task, stack, bp, ops, data, NULL, &graph);
|
|
}
|
|
EXPORT_SYMBOL(dump_trace);
|
|
|
|
void
|
|
show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,
|
|
unsigned long *sp, unsigned long bp, char *log_lvl)
|
|
{
|
|
unsigned long *irq_stack_end;
|
|
unsigned long *irq_stack;
|
|
unsigned long *stack;
|
|
int i;
|
|
|
|
irq_stack_end = (unsigned long *)this_cpu_read(irq_stack_ptr);
|
|
irq_stack = irq_stack_end - (IRQ_STACK_SIZE / sizeof(long));
|
|
|
|
sp = sp ? : get_stack_pointer(task, regs);
|
|
|
|
stack = sp;
|
|
for (i = 0; i < kstack_depth_to_print; i++) {
|
|
unsigned long word;
|
|
|
|
if (stack >= irq_stack && stack <= irq_stack_end) {
|
|
if (stack == irq_stack_end) {
|
|
stack = (unsigned long *) (irq_stack_end[-1]);
|
|
pr_cont(" <EOI> ");
|
|
}
|
|
} else {
|
|
if (kstack_end(stack))
|
|
break;
|
|
}
|
|
|
|
if (probe_kernel_address(stack, word))
|
|
break;
|
|
|
|
if ((i % STACKSLOTS_PER_LINE) == 0) {
|
|
if (i != 0)
|
|
pr_cont("\n");
|
|
printk("%s %016lx", log_lvl, word);
|
|
} else
|
|
pr_cont(" %016lx", word);
|
|
|
|
stack++;
|
|
touch_nmi_watchdog();
|
|
}
|
|
|
|
pr_cont("\n");
|
|
show_trace_log_lvl(task, regs, sp, bp, log_lvl);
|
|
}
|
|
|
|
void show_regs(struct pt_regs *regs)
|
|
{
|
|
int i;
|
|
|
|
show_regs_print_info(KERN_DEFAULT);
|
|
__show_regs(regs, 1);
|
|
|
|
/*
|
|
* When in-kernel, we also print out the stack and code at the
|
|
* time of the fault..
|
|
*/
|
|
if (!user_mode(regs)) {
|
|
unsigned int code_prologue = code_bytes * 43 / 64;
|
|
unsigned int code_len = code_bytes;
|
|
unsigned char c;
|
|
u8 *ip;
|
|
|
|
printk(KERN_DEFAULT "Stack:\n");
|
|
show_stack_log_lvl(NULL, regs, NULL, 0, KERN_DEFAULT);
|
|
|
|
printk(KERN_DEFAULT "Code: ");
|
|
|
|
ip = (u8 *)regs->ip - code_prologue;
|
|
if (ip < (u8 *)PAGE_OFFSET || probe_kernel_address(ip, c)) {
|
|
/* try starting at IP */
|
|
ip = (u8 *)regs->ip;
|
|
code_len = code_len - code_prologue + 1;
|
|
}
|
|
for (i = 0; i < code_len; i++, ip++) {
|
|
if (ip < (u8 *)PAGE_OFFSET ||
|
|
probe_kernel_address(ip, c)) {
|
|
pr_cont(" Bad RIP value.");
|
|
break;
|
|
}
|
|
if (ip == (u8 *)regs->ip)
|
|
pr_cont("<%02x> ", c);
|
|
else
|
|
pr_cont("%02x ", c);
|
|
}
|
|
}
|
|
pr_cont("\n");
|
|
}
|
|
|
|
int is_valid_bugaddr(unsigned long ip)
|
|
{
|
|
unsigned short ud2;
|
|
|
|
if (__copy_from_user(&ud2, (const void __user *) ip, sizeof(ud2)))
|
|
return 0;
|
|
|
|
return ud2 == 0x0b0f;
|
|
}
|