diff options
author | Qing Zhang <zhangqing@loongson.cn> | 2022-08-06 16:10:02 +0800 |
---|---|---|
committer | Huacai Chen <chenhuacai@loongson.cn> | 2022-08-12 13:10:11 +0800 |
commit | 49232773d8233ed70c4998851bc84e465fc1c788 (patch) | |
tree | eb361dbb849c68b079022e025e755cdb17a1f10c /arch/loongarch | |
parent | dce6098b22d58e5b646b1c67174c53f5a6a05605 (diff) | |
download | lwn-49232773d8233ed70c4998851bc84e465fc1c788.tar.gz lwn-49232773d8233ed70c4998851bc84e465fc1c788.zip |
LoongArch: Add guess unwinder support
Name "guess unwinder" comes from x86, it scans the stack and reports
every kernel text address it finds.
Unwinders can be used by dump_stack() and other stacktrace functions.
Three stages when we do unwind,
1) unwind_start(), the prapare of unwinding, fill unwind_state.
2) unwind_done(), judge whether the unwind process is finished or not.
3) unwind_next_frame(), unwind the next frame.
Add get_stack_info() to get stack info. At present we have irq stack and
task stack. The next_sp is the key info between two types of stacks.
Dividing unwinder helps to add new unwinders in the future.
Signed-off-by: Qing Zhang <zhangqing@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
Diffstat (limited to 'arch/loongarch')
-rw-r--r-- | arch/loongarch/Kconfig.debug | 9 | ||||
-rw-r--r-- | arch/loongarch/include/asm/stacktrace.h | 15 | ||||
-rw-r--r-- | arch/loongarch/include/asm/unwind.h | 36 | ||||
-rw-r--r-- | arch/loongarch/kernel/Makefile | 2 | ||||
-rw-r--r-- | arch/loongarch/kernel/process.c | 61 | ||||
-rw-r--r-- | arch/loongarch/kernel/traps.c | 21 | ||||
-rw-r--r-- | arch/loongarch/kernel/unwind_guess.c | 67 |
7 files changed, 200 insertions, 11 deletions
diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug index e69de29bb2d1..68634d4fa27b 100644 --- a/arch/loongarch/Kconfig.debug +++ b/arch/loongarch/Kconfig.debug @@ -0,0 +1,9 @@ +config UNWINDER_GUESS + bool "Guess unwinder" + help + This option enables the "guess" unwinder for unwinding kernel stack + traces. It scans the stack and reports every kernel text address it + finds. Some of the addresses it reports may be incorrect. + + While this option often produces false positives, it can still be + useful in many cases. diff --git a/arch/loongarch/include/asm/stacktrace.h b/arch/loongarch/include/asm/stacktrace.h index 6b5c2a7aa706..5820a0cabe3a 100644 --- a/arch/loongarch/include/asm/stacktrace.h +++ b/arch/loongarch/include/asm/stacktrace.h @@ -10,6 +10,21 @@ #include <asm/loongarch.h> #include <linux/stringify.h> +enum stack_type { + STACK_TYPE_UNKNOWN, + STACK_TYPE_IRQ, + STACK_TYPE_TASK, +}; + +struct stack_info { + enum stack_type type; + unsigned long begin, end, next_sp; +}; + +bool in_irq_stack(unsigned long stack, struct stack_info *info); +bool in_task_stack(unsigned long stack, struct task_struct *task, struct stack_info *info); +int get_stack_info(unsigned long stack, struct task_struct *task, struct stack_info *info); + #define STR_LONG_L __stringify(LONG_L) #define STR_LONG_S __stringify(LONG_S) #define STR_LONGSIZE __stringify(LONGSIZE) diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h new file mode 100644 index 000000000000..206fcbe24c0a --- /dev/null +++ b/arch/loongarch/include/asm/unwind.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Most of this ideas comes from x86. + * + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#ifndef _ASM_UNWIND_H +#define _ASM_UNWIND_H + +#include <linux/sched.h> + +#include <asm/stacktrace.h> + +struct unwind_state { + struct stack_info stack_info; + struct task_struct *task; + bool first, error; + unsigned long sp, pc; +}; + +void unwind_start(struct unwind_state *state, + struct task_struct *task, struct pt_regs *regs); +bool unwind_next_frame(struct unwind_state *state); +unsigned long unwind_get_return_address(struct unwind_state *state); + +static inline bool unwind_done(struct unwind_state *state) +{ + return state->stack_info.type == STACK_TYPE_UNKNOWN; +} + +static inline bool unwind_error(struct unwind_state *state) +{ + return state->error; +} + +#endif /* _ASM_UNWIND_H */ diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile index 940de9173542..c5fa4adb23b6 100644 --- a/arch/loongarch/kernel/Makefile +++ b/arch/loongarch/kernel/Makefile @@ -22,4 +22,6 @@ obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_NUMA) += numa.o +obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o + CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS) diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c index bfa0dfe8b7d7..839f0e963152 100644 --- a/arch/loongarch/kernel/process.c +++ b/arch/loongarch/kernel/process.c @@ -44,6 +44,7 @@ #include <asm/pgtable.h> #include <asm/processor.h> #include <asm/reg.h> +#include <asm/unwind.h> #include <asm/vdso.h> /* @@ -183,6 +184,66 @@ unsigned long __get_wchan(struct task_struct *task) return 0; } +bool in_irq_stack(unsigned long stack, struct stack_info *info) +{ + unsigned long nextsp; + unsigned long begin = (unsigned long)this_cpu_read(irq_stack); + unsigned long end = begin + IRQ_STACK_START; + + if (stack < begin || stack >= end) + return false; + + nextsp = *(unsigned long *)end; + if (nextsp & (SZREG - 1)) + return false; + + info->begin = begin; + info->end = end; + info->next_sp = nextsp; + info->type = STACK_TYPE_IRQ; + + return true; +} + +bool in_task_stack(unsigned long stack, struct task_struct *task, + struct stack_info *info) +{ + unsigned long begin = (unsigned long)task_stack_page(task); + unsigned long end = begin + THREAD_SIZE - 32; + + if (stack < begin || stack >= end) + return false; + + info->begin = begin; + info->end = end; + info->next_sp = 0; + info->type = STACK_TYPE_TASK; + + return true; +} + +int get_stack_info(unsigned long stack, struct task_struct *task, + struct stack_info *info) +{ + task = task ? : current; + + if (!stack || stack & (SZREG - 1)) + goto unknown; + + if (in_task_stack(stack, task, info)) + return 0; + + if (task != current) + goto unknown; + + if (in_irq_stack(stack, info)) + return 0; + +unknown: + info->type = STACK_TYPE_UNKNOWN; + return -EINVAL; +} + unsigned long stack_top(void) { unsigned long top = TASK_SIZE & PAGE_MASK; diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c index 1bf58c65e2bf..f65fdf90d29e 100644 --- a/arch/loongarch/kernel/traps.c +++ b/arch/loongarch/kernel/traps.c @@ -43,6 +43,7 @@ #include <asm/stacktrace.h> #include <asm/tlb.h> #include <asm/types.h> +#include <asm/unwind.h> #include "access-helper.h" @@ -64,19 +65,17 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs, const char *loglvl, bool user) { unsigned long addr; - unsigned long *sp = (unsigned long *)(regs->regs[3] & ~3); + struct unwind_state state; + struct pt_regs *pregs = (struct pt_regs *)regs; + + if (!task) + task = current; printk("%sCall Trace:", loglvl); -#ifdef CONFIG_KALLSYMS - printk("%s\n", loglvl); -#endif - while (!kstack_end(sp)) { - if (__get_addr(&addr, sp++, user)) { - printk("%s (Bad stack address)", loglvl); - break; - } - if (__kernel_text_address(addr)) - print_ip_sym(loglvl, addr); + for (unwind_start(&state, task, pregs); + !unwind_done(&state); unwind_next_frame(&state)) { + addr = unwind_get_return_address(&state); + print_ip_sym(loglvl, addr); } printk("%s\n", loglvl); } diff --git a/arch/loongarch/kernel/unwind_guess.c b/arch/loongarch/kernel/unwind_guess.c new file mode 100644 index 000000000000..5afa6064d73e --- /dev/null +++ b/arch/loongarch/kernel/unwind_guess.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Technology Corporation Limited + */ +#include <linux/kernel.h> + +#include <asm/unwind.h> + +unsigned long unwind_get_return_address(struct unwind_state *state) +{ + if (unwind_done(state)) + return 0; + else if (state->first) + return state->pc; + + return *(unsigned long *)(state->sp); +} +EXPORT_SYMBOL_GPL(unwind_get_return_address); + +void unwind_start(struct unwind_state *state, struct task_struct *task, + struct pt_regs *regs) +{ + memset(state, 0, sizeof(*state)); + + if (regs) { + state->sp = regs->regs[3]; + state->pc = regs->csr_era; + } + + state->task = task; + state->first = true; + + get_stack_info(state->sp, state->task, &state->stack_info); + + if (!unwind_done(state) && !__kernel_text_address(state->pc)) + unwind_next_frame(state); +} +EXPORT_SYMBOL_GPL(unwind_start); + +bool unwind_next_frame(struct unwind_state *state) +{ + struct stack_info *info = &state->stack_info; + unsigned long addr; + + if (unwind_done(state)) + return false; + + if (state->first) + state->first = false; + + do { + for (state->sp += sizeof(unsigned long); + state->sp < info->end; + state->sp += sizeof(unsigned long)) { + addr = *(unsigned long *)(state->sp); + + if (__kernel_text_address(addr)) + return true; + } + + state->sp = info->next_sp; + + } while (!get_stack_info(state->sp, state->task, info)); + + return false; +} +EXPORT_SYMBOL_GPL(unwind_next_frame); |