Skip to content

Commit

Permalink
LoongArch: Add guess unwinder support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Qing Zhang authored and Huacai Chen committed Aug 12, 2022
1 parent dce6098 commit 4923277
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 11 deletions.
9 changes: 9 additions & 0 deletions arch/loongarch/Kconfig.debug
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions arch/loongarch/include/asm/stacktrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
36 changes: 36 additions & 0 deletions arch/loongarch/include/asm/unwind.h
Original file line number Diff line number Diff line change
@@ -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 */
2 changes: 2 additions & 0 deletions arch/loongarch/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
61 changes: 61 additions & 0 deletions arch/loongarch/kernel/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <asm/pgtable.h>
#include <asm/processor.h>
#include <asm/reg.h>
#include <asm/unwind.h>
#include <asm/vdso.h>

/*
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 10 additions & 11 deletions arch/loongarch/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <asm/stacktrace.h>
#include <asm/tlb.h>
#include <asm/types.h>
#include <asm/unwind.h>

#include "access-helper.h"

Expand All @@ -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);
}
Expand Down
67 changes: 67 additions & 0 deletions arch/loongarch/kernel/unwind_guess.c
Original file line number Diff line number Diff line change
@@ -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);

0 comments on commit 4923277

Please sign in to comment.