[PATCH 15/19] x86/dumpstack: convert show_trace_log_lvl() to the new unwinder
From: Josh Poimboeuf
Date: Thu Jul 21 2016 - 17:24:07 EST
Convert show_trace_log_lvl() to the new unwinder. dump_trace() has been
deprecated.
show_trace_log_lvl() is special compared to other users of the unwinder.
It's the only place where both reliable *and* unreliable addresses are
needed. With frame pointers enabled, most stack walking code doesn't
want to know about unreliable addresses. But in this case, when we're
dumping the stack to the console because something presumably went
wrong, the unreliable addresses are useful:
- They show stale data on the stack which can provide useful clues.
- If something goes wrong with the unwinder, or if frame pointers are
corrupt or missing, all the stack addresses still get shown.
So in order to show all addresses on the stack, and at the same time
figure out which addresses are reliable, we have to do the scanning and
the unwinding in parallel.
The scanning is done with the help of get_stack_info() to traverse the
stacks. The unwinding is done separately by the new unwinder.
In theory we could simplify show_trace_log_lvl() by instead pushing some
of this logic into the unwind code. But then we would need some kind of
"fake" frame logic in the unwinder which would add a lot of complexity
and wouldn't be worth it in order to support only one user.
Another benefit of this approach is that once we have a DWARF unwinder,
we should be able to just plug it in with minimal impact to this code.
Another change here is that callers of show_trace_log_lvl() don't need
to provide the 'bp' argument. The unwinder already finds the relevant
frame pointer by unwinding until it reaches the first frame after the
provided stack pointer.
Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
---
arch/x86/include/asm/stacktrace.h | 10 +--
arch/x86/kernel/dumpstack.c | 180 +++++++++++++++++++-------------------
arch/x86/kernel/dumpstack_32.c | 6 +-
arch/x86/kernel/dumpstack_64.c | 10 +--
4 files changed, 101 insertions(+), 105 deletions(-)
diff --git a/arch/x86/include/asm/stacktrace.h b/arch/x86/include/asm/stacktrace.h
index 647ce3f..c66dece 100644
--- a/arch/x86/include/asm/stacktrace.h
+++ b/arch/x86/include/asm/stacktrace.h
@@ -132,13 +132,11 @@ get_stack_pointer(struct task_struct *task, struct pt_regs *regs)
return (unsigned long *)task->thread.sp;
}
-extern void
-show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
- unsigned long *stack, unsigned long bp, char *log_lvl);
+void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
+ unsigned long *stack, char *log_lvl);
-extern void
-show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,
- unsigned long *sp, unsigned long bp, char *log_lvl);
+void show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,
+ unsigned long *sp, char *log_lvl);
extern unsigned int code_bytes;
diff --git a/arch/x86/kernel/dumpstack.c b/arch/x86/kernel/dumpstack.c
index 6ef8ab5..198dc9e 100644
--- a/arch/x86/kernel/dumpstack.c
+++ b/arch/x86/kernel/dumpstack.c
@@ -17,7 +17,7 @@
#include <linux/sysfs.h>
#include <asm/stacktrace.h>
-
+#include <asm/unwind.h>
int panic_on_unrecovered_nmi;
int panic_on_io_nmi;
@@ -79,107 +79,105 @@ ftrace_graph_ret_addr(struct task_struct *task, int *idx, unsigned long addr)
}
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
-/*
- * x86-64 can have up to three kernel stacks:
- * process stack
- * interrupt stack
- * severe exception (double fault, nmi, stack fault, debug, mce) hardware stack
- */
-
-unsigned long
-print_context_stack(struct task_struct *task,
- unsigned long *stack, unsigned long bp,
- const struct stacktrace_ops *ops, void *data,
- struct stack_info *info, int *graph)
+void show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
+ unsigned long *stack, char *log_lvl)
{
- struct stack_frame *frame = (struct stack_frame *)bp;
+ struct unwind_state state;
+ struct stack_info stack_info = {0};
+ unsigned long stack_mask = 0;
+ int graph_idx = 0;
- /*
- * If we overflowed the stack into a guard page, jump back to the
- * bottom of the usable stack.
- */
- if ((unsigned long)task_stack_page(task) - (unsigned long)stack <
- PAGE_SIZE)
- stack = (unsigned long *)task_stack_page(task);
-
- while (on_stack(info, stack, sizeof(*stack))) {
- unsigned long addr = *stack;
-
- addr = *stack;
- if (__kernel_text_address(addr)) {
- int reliable = 0;
- unsigned long real_addr;
+ printk("%sCall Trace:\n", log_lvl);
- if ((unsigned long) stack == bp + sizeof(long)) {
- reliable = 1;
- frame = frame->next_frame;
- bp = (unsigned long) frame;
- }
+ stack = stack ? : get_stack_pointer(task, regs);
+ if (!task)
+ task = current;
- real_addr = ftrace_graph_ret_addr(task, graph, addr);
- if (addr != real_addr)
- ops->address(data, addr, 0);
- ops->address(data, real_addr, reliable);
- }
- stack++;
- }
- return bp;
-}
-EXPORT_SYMBOL_GPL(print_context_stack);
+ unwind_start(&state, task, regs, stack);
-unsigned long
-print_context_stack_bp(struct task_struct *task,
- unsigned long *stack, unsigned long bp,
- const struct stacktrace_ops *ops, void *data,
- struct stack_info *info, int *graph)
-{
- struct stack_frame *frame = (struct stack_frame *)bp;
- unsigned long *ret_addr = &frame->return_address;
-
- while (on_stack(info, stack, sizeof(*stack) * 2)) {
- unsigned long addr = *ret_addr;
+ /*
+ * Iterate through the stacks, starting with the current stack pointer.
+ * Each stack has a pointer to the next one.
+ *
+ * x86-64 can have several stacks:
+ * - task stack
+ * - interrupt stack
+ * - HW exception stacks (double fault, nmi, debug, mce)
+ *
+ * x86-32 can have up to three stacks:
+ * - task stack
+ * - softirq stack
+ * - hardirq stack
+ */
+ for (; stack; stack = stack_info.next) {
+ const char *str_begin, *str_end;
- if (!__kernel_text_address(addr))
- break;
+ /*
+ * If we overflowed the task stack into a guard page, jump back
+ * to the bottom of the usable stack.
+ */
+ if (task_stack_page(task) - (void *)stack < PAGE_SIZE)
+ stack = task_stack_page(task);
- addr = ftrace_graph_ret_addr(task, graph, addr);
- if (ops->address(data, addr, 1))
+ if (get_stack_info(stack, task, &stack_info, &stack_mask))
break;
- frame = frame->next_frame;
- ret_addr = &frame->return_address;
- }
- return (unsigned long)frame;
-}
-EXPORT_SYMBOL_GPL(print_context_stack_bp);
+ stack_type_str(stack_info.type, &str_begin, &str_end);
+ if (str_begin)
+ printk("%s <%s> ", log_lvl, str_begin);
+
+ /*
+ * Scan the stack, printing any text addresses we find. At the
+ * same time, follow proper stack frames with the unwinder.
+ *
+ * Addresses found during the scan which are not reported by
+ * the unwinder are considered to be additional clues which are
+ * sometimes useful for debugging and are prefixed with '?'.
+ * This also serves as a failsafe option in case the unwinder
+ * goes off the rails.
+ */
+ for (; stack < stack_info.end; stack++) {
+ unsigned long addr = *stack;
+ unsigned long real_addr;
+ unsigned long *ret_addr_p = \
+ unwind_get_return_address_ptr(&state);
-static int print_trace_stack(void *data, const char *name)
-{
- printk("%s <%s> ", (char *)data, name);
- return 0;
-}
+ if (!__kernel_text_address(addr))
+ continue;
-/*
- * Print one address/symbol entries per line.
- */
-static int print_trace_address(void *data, unsigned long addr, int reliable)
-{
- printk_stack_address(addr, reliable, data);
- return 0;
-}
+ if (stack != ret_addr_p) {
+ /* found an "unreliable" address */
+ printk_stack_address(addr, 0, log_lvl);
+ continue;
+ }
-static const struct stacktrace_ops print_trace_ops = {
- .stack = print_trace_stack,
- .address = print_trace_address,
- .walk_stack = print_context_stack,
-};
+ /*
+ * When function graph tracing is enabled, the original
+ * return address on the stack of a traced function is
+ * replaced with the address of an ftrace handler.
+ * In that case we print the ftrace handler address as
+ * an unreliable clue and then print the real function
+ * as a reliable address.
+ */
+ real_addr = ftrace_graph_ret_addr(task, &graph_idx,
+ addr);
+ if (real_addr != addr)
+ printk_stack_address(addr, 0, log_lvl);
+
+ printk_stack_address(real_addr, 1, log_lvl);
+
+ /*
+ * Get the next frame from the unwinder. No need to
+ * check for an error: if anything goes wrong with the
+ * unwinder, the rest of the addresses will just be
+ * printed as unreliable.
+ */
+ unwind_next_frame(&state);
+ }
-void
-show_trace_log_lvl(struct task_struct *task, struct pt_regs *regs,
- unsigned long *stack, unsigned long bp, char *log_lvl)
-{
- printk("%sCall Trace:\n", log_lvl);
- dump_trace(task, regs, stack, bp, &print_trace_ops, log_lvl);
+ if (str_end)
+ printk("%s <%s> ", log_lvl, str_end);
+ }
}
void show_stack(struct task_struct *task, unsigned long *sp)
@@ -195,12 +193,12 @@ void show_stack(struct task_struct *task, unsigned long *sp)
bp = (unsigned long)get_frame_pointer(current, NULL);
}
- show_stack_log_lvl(task, NULL, sp, bp, "");
+ show_stack_log_lvl(task, NULL, sp, "");
}
void show_stack_regs(struct pt_regs *regs)
{
- show_stack_log_lvl(current, regs, NULL, 0, "");
+ show_stack_log_lvl(NULL, regs, NULL, "");
}
static arch_spinlock_t die_lock = __ARCH_SPIN_LOCK_UNLOCKED;
diff --git a/arch/x86/kernel/dumpstack_32.c b/arch/x86/kernel/dumpstack_32.c
index 8f55ddb..6a881cc 100644
--- a/arch/x86/kernel/dumpstack_32.c
+++ b/arch/x86/kernel/dumpstack_32.c
@@ -128,7 +128,7 @@ 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 *sp, char *log_lvl)
{
unsigned long *stack;
int i;
@@ -148,7 +148,7 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,
touch_nmi_watchdog();
}
pr_cont("\n");
- show_trace_log_lvl(task, regs, sp, bp, log_lvl);
+ show_trace_log_lvl(task, regs, sp, log_lvl);
}
@@ -170,7 +170,7 @@ void show_regs(struct pt_regs *regs)
u8 *ip;
pr_emerg("Stack:\n");
- show_stack_log_lvl(NULL, regs, NULL, 0, KERN_EMERG);
+ show_stack_log_lvl(NULL, regs, NULL, KERN_EMERG);
pr_emerg("Code:");
diff --git a/arch/x86/kernel/dumpstack_64.c b/arch/x86/kernel/dumpstack_64.c
index e1a5b6f..6e5ccec 100644
--- a/arch/x86/kernel/dumpstack_64.c
+++ b/arch/x86/kernel/dumpstack_64.c
@@ -15,6 +15,7 @@
#include <linux/nmi.h>
#include <asm/stacktrace.h>
+#include <asm/unwind.h>
static char *exception_stack_names[N_EXCEPTION_STACKS] = {
[ DOUBLEFAULT_STACK-1 ] = "#DF",
@@ -190,9 +191,8 @@ void dump_trace(struct task_struct *task, struct pt_regs *regs,
}
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)
+void show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,
+ unsigned long *sp, char *log_lvl)
{
unsigned long *irq_stack, *irq_stack_end;
unsigned long *stack;
@@ -232,7 +232,7 @@ show_stack_log_lvl(struct task_struct *task, struct pt_regs *regs,
}
pr_cont("\n");
- show_trace_log_lvl(task, regs, sp, bp, log_lvl);
+ show_trace_log_lvl(task, regs, sp, log_lvl);
}
void show_regs(struct pt_regs *regs)
@@ -253,7 +253,7 @@ void show_regs(struct pt_regs *regs)
u8 *ip;
printk(KERN_DEFAULT "Stack:\n");
- show_stack_log_lvl(NULL, regs, NULL, 0, KERN_DEFAULT);
+ show_stack_log_lvl(NULL, regs, NULL, KERN_DEFAULT);
printk(KERN_DEFAULT "Code: ");
--
2.7.4