[PATCH 10/19] x86/dumpstack: add get_stack_info() interface

From: Josh Poimboeuf
Date: Thu Jul 21 2016 - 17:25:26 EST


valid_stack_ptr() is buggy: it assumes that all stacks are of size
THREAD_SIZE, which is not true for exception stacks. So the
walk_stack() callbacks will need to know the location of the beginning
of the stack as well as the end.

Another issue is that in general the various features of a stack (type,
size, next stack pointer, description string) are scattered around in
various places throughout the stack dump code.

Encapsulate all that information in a single place with a new stack_info
struct and a get_stack_info() interface.

Signed-off-by: Josh Poimboeuf <jpoimboe@xxxxxxxxxx>
---
arch/x86/events/core.c | 2 +-
arch/x86/include/asm/stacktrace.h | 41 +++++++++-
arch/x86/kernel/dumpstack.c | 42 ++++++-----
arch/x86/kernel/dumpstack_32.c | 100 ++++++++++++++++++------
arch/x86/kernel/dumpstack_64.c | 155 ++++++++++++++++++++------------------
arch/x86/kernel/stacktrace.c | 2 +-
6 files changed, 218 insertions(+), 124 deletions(-)

diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c
index fad9788..f388f57 100644
--- a/arch/x86/events/core.c
+++ b/arch/x86/events/core.c
@@ -2248,7 +2248,7 @@ void arch_perf_update_userpage(struct perf_event *event,
* callchain support
*/

-static int backtrace_stack(void *data, char *name)
+static int backtrace_stack(void *data, const char *name)
{
return 0;
}
diff --git a/arch/x86/include/asm/stacktrace.h b/arch/x86/include/asm/stacktrace.h
index 5d3d258..647ce3f 100644
--- a/arch/x86/include/asm/stacktrace.h
+++ b/arch/x86/include/asm/stacktrace.h
@@ -9,6 +9,39 @@
#include <linux/uaccess.h>
#include <linux/ptrace.h>

+enum stack_type {
+ STACK_TYPE_UNKNOWN,
+ STACK_TYPE_TASK,
+ STACK_TYPE_IRQ,
+ STACK_TYPE_SOFTIRQ,
+ STACK_TYPE_EXCEPTION,
+ STACK_TYPE_EXCEPTION_LAST = STACK_TYPE_EXCEPTION + N_EXCEPTION_STACKS-1,
+};
+
+struct stack_info {
+ enum stack_type type;
+ unsigned long *begin, *end, *next;
+};
+
+bool in_task_stack(unsigned long *stack, struct task_struct *task,
+ struct stack_info *info, unsigned long *visit_mask);
+
+int get_stack_info(unsigned long *stack, struct task_struct *task,
+ struct stack_info *info, unsigned long *visit_mask);
+
+void stack_type_str(enum stack_type type, const char **begin,
+ const char **end);
+
+static inline bool on_stack(struct stack_info *info, void *addr, size_t len)
+{
+ void *begin = info->begin;
+ void *end = info->end;
+
+ return (info->type != STACK_TYPE_UNKNOWN &&
+ addr >= begin && addr < end &&
+ addr + len > begin && addr + len <= end);
+}
+
extern int kstack_depth_to_print;

struct thread_info;
@@ -32,27 +65,27 @@ typedef unsigned long (*walk_stack_t)(struct task_struct *task,
unsigned long bp,
const struct stacktrace_ops *ops,
void *data,
- unsigned long *end,
+ struct stack_info *info,
int *graph);

extern unsigned long
print_context_stack(struct task_struct *task,
unsigned long *stack, unsigned long bp,
const struct stacktrace_ops *ops, void *data,
- unsigned long *end, int *graph);
+ struct stack_info *info, int *graph);

extern unsigned long
print_context_stack_bp(struct task_struct *task,
unsigned long *stack, unsigned long bp,
const struct stacktrace_ops *ops, void *data,
- unsigned long *end, int *graph);
+ struct stack_info *info, int *graph);

/* Generic stack tracer with callbacks */

struct stacktrace_ops {
int (*address)(void *data, unsigned long address, int reliable);
/* On negative return stop dumping */
- int (*stack)(void *data, char *name);
+ int (*stack)(void *data, const char *name);
walk_stack_t walk_stack;
};

diff --git a/arch/x86/kernel/dumpstack.c b/arch/x86/kernel/dumpstack.c
index 0a8694b..6ef8ab5 100644
--- a/arch/x86/kernel/dumpstack.c
+++ b/arch/x86/kernel/dumpstack.c
@@ -25,6 +25,25 @@ unsigned int code_bytes = 64;
int kstack_depth_to_print = 3 * STACKSLOTS_PER_LINE;
static int die_counter;

+bool in_task_stack(unsigned long *stack, struct task_struct *task,
+ struct stack_info *info, unsigned long *visit_mask)
+{
+ unsigned long addr = (unsigned long)stack & ~(THREAD_SIZE - 1);
+
+ if ((unsigned long)task_stack_page(task) != addr)
+ return false;
+
+ if (visit_mask && test_and_set_bit(STACK_TYPE_TASK, visit_mask))
+ return false;
+
+ info->type = STACK_TYPE_TASK;
+ info->begin = task_stack_page(task);
+ info->end = task_stack_page(task) + THREAD_SIZE;
+ info->next = NULL;
+
+ return true;
+}
+
static void printk_stack_address(unsigned long address, int reliable,
char *log_lvl)
{
@@ -67,24 +86,11 @@ ftrace_graph_ret_addr(struct task_struct *task, int *idx, unsigned long addr)
* severe exception (double fault, nmi, stack fault, debug, mce) hardware stack
*/

-static inline int valid_stack_ptr(struct task_struct *task,
- void *p, unsigned int size, void *end)
-{
- void *t = task_stack_page(task);
- if (end) {
- if (p < end && p >= (end-THREAD_SIZE))
- return 1;
- else
- return 0;
- }
- return p >= t && p < t + THREAD_SIZE - size;
-}
-
unsigned long
print_context_stack(struct task_struct *task,
unsigned long *stack, unsigned long bp,
const struct stacktrace_ops *ops, void *data,
- unsigned long *end, int *graph)
+ struct stack_info *info, int *graph)
{
struct stack_frame *frame = (struct stack_frame *)bp;

@@ -96,7 +102,7 @@ print_context_stack(struct task_struct *task,
PAGE_SIZE)
stack = (unsigned long *)task_stack_page(task);

- while (valid_stack_ptr(task, stack, sizeof(*stack), end)) {
+ while (on_stack(info, stack, sizeof(*stack))) {
unsigned long addr = *stack;

addr = *stack;
@@ -125,12 +131,12 @@ unsigned long
print_context_stack_bp(struct task_struct *task,
unsigned long *stack, unsigned long bp,
const struct stacktrace_ops *ops, void *data,
- unsigned long *end, int *graph)
+ struct stack_info *info, int *graph)
{
struct stack_frame *frame = (struct stack_frame *)bp;
unsigned long *ret_addr = &frame->return_address;

- while (valid_stack_ptr(task, ret_addr, sizeof(*ret_addr), end)) {
+ while (on_stack(info, stack, sizeof(*stack) * 2)) {
unsigned long addr = *ret_addr;

if (!__kernel_text_address(addr))
@@ -147,7 +153,7 @@ print_context_stack_bp(struct task_struct *task,
}
EXPORT_SYMBOL_GPL(print_context_stack_bp);

-static int print_trace_stack(void *data, char *name)
+static int print_trace_stack(void *data, const char *name)
{
printk("%s <%s> ", (char *)data, name);
return 0;
diff --git a/arch/x86/kernel/dumpstack_32.c b/arch/x86/kernel/dumpstack_32.c
index b07d5c9..8f55ddb 100644
--- a/arch/x86/kernel/dumpstack_32.c
+++ b/arch/x86/kernel/dumpstack_32.c
@@ -16,61 +16,111 @@

#include <asm/stacktrace.h>

-static void *is_irq_stack(void *p, void *irq)
+void stack_type_str(enum stack_type type, const char **begin, const char **end)
{
- if (p < irq || p >= (irq + THREAD_SIZE))
- return NULL;
- return irq + THREAD_SIZE;
+ switch (type) {
+ case STACK_TYPE_IRQ:
+ case STACK_TYPE_SOFTIRQ:
+ *begin = "IRQ";
+ *end = "EOI";
+ break;
+ default:
+ *begin = NULL;
+ *end = NULL;
+ }
}

+static bool in_hardirq_stack(unsigned long *stack, struct stack_info *info,
+ unsigned long *visit_mask)
+{
+ unsigned long *begin = (unsigned long *)this_cpu_read(hardirq_stack);
+ unsigned long *end = begin + (THREAD_SIZE / sizeof(long));
+
+ if (stack < begin || stack >= end)
+ return false;
+
+ if (visit_mask && test_and_set_bit(STACK_TYPE_IRQ, visit_mask))
+ return false;
+
+ info->type = STACK_TYPE_IRQ;
+ info->begin = begin;
+ info->end = end;
+ info->next = (unsigned long *)*begin;

-static void *is_hardirq_stack(unsigned long *stack)
+ return true;
+}
+
+static bool in_softirq_stack(unsigned long *stack, struct stack_info *info,
+ unsigned long *visit_mask)
{
- void *irq = this_cpu_read(hardirq_stack);
+ unsigned long *begin = (unsigned long *)this_cpu_read(softirq_stack);
+ unsigned long *end = begin + (THREAD_SIZE / sizeof(long));
+
+ if (stack < begin || stack >= end)
+ return false;
+
+ if (visit_mask && test_and_set_bit(STACK_TYPE_SOFTIRQ, visit_mask))
+ return false;
+
+ info->type = STACK_TYPE_SOFTIRQ;
+ info->begin = begin;
+ info->end = end;
+ info->next = (unsigned long *)*begin;

- return is_irq_stack(stack, irq);
+ return true;
}

-static void *is_softirq_stack(unsigned long *stack);
+int get_stack_info(unsigned long *stack, struct task_struct *task,
+ struct stack_info *info, unsigned long *visit_mask)
{
- void *irq = this_cpu_read(softirq_stack);
+ if (!task)
+ task = current;

- return is_irq_stack(stack, irq);
+ if (task == current) {
+ if (in_hardirq_stack(stack, info, visit_mask))
+ return 0;
+
+ if (in_softirq_stack(stack, info, visit_mask))
+ return 0;
+ }
+
+ if (in_task_stack(stack, task, info, visit_mask))
+ return 0;
+
+ info->type = STACK_TYPE_UNKNOWN;
+ return -EINVAL;
}

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 visit_mask = 0;
int graph = 0;
- u32 *prev_esp;

task = task ? : current;
stack = stack ? : get_stack_pointer(task, regs);
bp = bp ? : (unsigned long)get_frame_pointer(task, regs);

for (;;) {
- void *end_stack;
+ const char *begin_str, *end_str;
+ struct stack_info info;

- end_stack = is_hardirq_stack(stack);
- if (!end_stack)
- end_stack = is_softirq_stack(stack);
+ if (get_stack_info(stack, task, &info, &visit_mask))
+ break;

- bp = ops->walk_stack(task, stack, bp, ops, data,
- end_stack, &graph);
+ stack_type_str(info.type, &begin_str, &end_str);

- /* Stop if not on irq stack */
- if (!end_stack)
+ if (begin_str && ops->stack(data, begin_str) < 0)
break;

- /* The previous esp is saved on the bottom of the stack */
- prev_esp = (u32 *)(end_stack - THREAD_SIZE);
- stack = (unsigned long *)*prev_esp;
- if (!stack)
- break;
+ bp = ops->walk_stack(task, stack, bp, ops, data, &info, &graph);

- if (ops->stack(data, "IRQ") < 0)
+ if (end_str && ops->stack(data, end_str) < 0)
break;
+
+ stack = info.next;
+
touch_nmi_watchdog();
}
}
diff --git a/arch/x86/kernel/dumpstack_64.c b/arch/x86/kernel/dumpstack_64.c
index 0641d75..e1a5b6f 100644
--- a/arch/x86/kernel/dumpstack_64.c
+++ b/arch/x86/kernel/dumpstack_64.c
@@ -28,8 +28,27 @@ static unsigned long exception_stack_sizes[N_EXCEPTION_STACKS] = {
[DEBUG_STACK - 1] = DEBUG_STKSZ
};

-static unsigned long *in_exception_stack(unsigned long *s, char **name,
- unsigned long *visit_mask)
+void stack_type_str(enum stack_type type, const char **begin, const char **end)
+{
+ BUILD_BUG_ON(N_EXCEPTION_STACKS != 4);
+
+ switch (type) {
+ case STACK_TYPE_IRQ:
+ *begin = "IRQ";
+ *end = "EOI";
+ break;
+ case STACK_TYPE_EXCEPTION ... STACK_TYPE_EXCEPTION_LAST:
+ *begin = exception_stack_names[type - STACK_TYPE_EXCEPTION];
+ *end = "EOE";
+ break;
+ default:
+ *begin = NULL;
+ *end = NULL;
+ }
+}
+
+static bool in_exception_stack(unsigned long *s, struct stack_info *info,
+ unsigned long *visit_mask)
{
unsigned long stack = (unsigned long)s;
unsigned long begin, end;
@@ -44,55 +63,62 @@ static unsigned long *in_exception_stack(unsigned long *s, char **name,
if (stack < begin || stack >= end)
continue;

- if (test_and_set_bit(k, visit_mask))
+ if (visit_mask &&
+ test_and_set_bit(STACK_TYPE_EXCEPTION + k, visit_mask))
return false;

- *name = exception_stack_names[k];
- return (unsigned long *)end;
+ info->type = STACK_TYPE_EXCEPTION + k;
+ info->begin = (unsigned long *)begin;
+ info->end = (unsigned long *)end;
+ info->next = (unsigned long *)info->end[-2];
+
+ return true;
}

- return NULL;
+ return false;
}

-static inline int
-in_irq_stack(unsigned long *stack, unsigned long *irq_stack,
- unsigned long *irq_stack_end)
+static bool in_irq_stack(unsigned long *stack, struct stack_info *info,
+ unsigned long *visit_mask)
{
- return (stack >= irq_stack && stack < irq_stack_end);
-}
+ unsigned long *end = (unsigned long *)this_cpu_read(irq_stack_ptr);
+ unsigned long *begin = end - (IRQ_USABLE_STACK_SIZE / sizeof(long));

-enum stack_type {
- STACK_IS_UNKNOWN,
- STACK_IS_NORMAL,
- STACK_IS_EXCEPTION,
- STACK_IS_IRQ,
-};
+ if (stack < begin || stack >= end)
+ return false;

-static enum stack_type
-analyze_stack(struct task_struct *task, unsigned long *stack,
- unsigned long **stack_end, unsigned long *irq_stack,
- unsigned long *visit_mask, char **name)
-{
- unsigned long addr;
+ if (visit_mask && test_and_set_bit(STACK_TYPE_IRQ, visit_mask))
+ return false;

- addr = ((unsigned long)stack & (~(THREAD_SIZE - 1)));
- if ((unsigned long)task_stack_page(task) == addr)
- return STACK_IS_NORMAL;
+ info->type = STACK_TYPE_IRQ;
+ info->begin = begin;
+ info->end = end;
+ info->next = (unsigned long *)end[-1];

- *stack_end = in_exception_stack(stack, name, visit_mask);
- if (*stack_end)
- return STACK_IS_EXCEPTION;
+ return true;
+}

- if (!irq_stack)
- return STACK_IS_NORMAL;
+int get_stack_info(unsigned long *stack, struct task_struct *task,
+ struct stack_info *info, unsigned long *visit_mask)
+{
+ if (!task)
+ task = current;
+
+ if (in_task_stack(stack, task, info, visit_mask))
+ return 0;

- *stack_end = irq_stack;
- irq_stack -= (IRQ_USABLE_STACK_SIZE / sizeof(long));
+ if (task != current)
+ goto unknown;
+
+ if (in_exception_stack(stack, info, visit_mask))
+ return 0;

- if (in_irq_stack(stack, irq_stack, *stack_end))
- return STACK_IS_IRQ;
+ if (in_irq_stack(stack, info, visit_mask))
+ return 0;

- return STACK_IS_UNKNOWN;
+unknown:
+ info->type = STACK_TYPE_UNKNOWN;
+ return -EINVAL;
}

/*
@@ -106,8 +132,8 @@ 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 long visit_mask = 0;
+ struct stack_info info;
int graph = 0;
int done = 0;

@@ -121,57 +147,37 @@ void dump_trace(struct task_struct *task, struct pt_regs *regs,
* exceptions
*/
while (!done) {
- unsigned long *stack_end;
- enum stack_type stype;
- char *name;
+ const char *begin_str, *end_str;

- stype = analyze_stack(task, stack, &stack_end, irq_stack,
- &visit_mask, &name);
+ get_stack_info(stack, task, &info, &visit_mask);

/* Default finish unless specified to continue */
done = 1;

- switch (stype) {
+ switch (info.type) {

/* Break out early if we are on the thread stack */
- case STACK_IS_NORMAL:
+ case STACK_TYPE_TASK:
break;

- case STACK_IS_EXCEPTION:
+ case STACK_TYPE_IRQ:
+ case STACK_TYPE_EXCEPTION ... STACK_TYPE_EXCEPTION_LAST:
+
+ stack_type_str(info.type, &begin_str, &end_str);

- if (ops->stack(data, name) < 0)
+ if (ops->stack(data, begin_str) < 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;
+ data, &info, &graph);

- case STACK_IS_IRQ:
+ ops->stack(data, end_str);

- 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");
+ stack = info.next;
done = 0;
break;

- case STACK_IS_UNKNOWN:
+ default:
ops->stack(data, "UNK");
break;
}
@@ -180,7 +186,7 @@ void dump_trace(struct task_struct *task, struct pt_regs *regs,
/*
* This handles the process stack:
*/
- bp = ops->walk_stack(task, stack, bp, ops, data, NULL, &graph);
+ bp = ops->walk_stack(task, stack, bp, ops, data, &info, &graph);
}
EXPORT_SYMBOL(dump_trace);

@@ -188,13 +194,12 @@ 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 *irq_stack, *irq_stack_end;
unsigned long *stack;
int i;

- irq_stack_end = (unsigned long *)this_cpu_read(irq_stack_ptr);
- irq_stack = irq_stack_end - IRQ_USABLE_STACK_SIZE;
+ irq_stack_end = (unsigned long *)this_cpu_read(irq_stack_ptr);
+ irq_stack = irq_stack_end - IRQ_USABLE_STACK_SIZE;

sp = sp ? : get_stack_pointer(task, regs);

diff --git a/arch/x86/kernel/stacktrace.c b/arch/x86/kernel/stacktrace.c
index 4738f5e..785aef1 100644
--- a/arch/x86/kernel/stacktrace.c
+++ b/arch/x86/kernel/stacktrace.c
@@ -9,7 +9,7 @@
#include <linux/uaccess.h>
#include <asm/stacktrace.h>

-static int save_stack_stack(void *data, char *name)
+static int save_stack_stack(void *data, const char *name)
{
return 0;
}
--
2.7.4