Skip to content

Commit

Permalink
LoongArch: Add prologue unwinder support
Browse files Browse the repository at this point in the history
It unwind the stack frame based on prologue code analyze.
CONFIG_KALLSYMS is needed, at least the address and length
of each function.

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.

Dividing unwinder helps to add new unwinders in the future, e.g.:
unwinder_frame, unwinder_orc, .etc.

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 4923277 commit 49aef11
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 1 deletion.
20 changes: 20 additions & 0 deletions arch/loongarch/Kconfig.debug
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
choice
prompt "Choose kernel unwinder"
default UNWINDER_PROLOGUE if KALLSYMS
help
This determines which method will be used for unwinding kernel stack
traces for panics, oopses, bugs, warnings, perf, /proc/<pid>/stack,
lockdep, and more.

config UNWINDER_GUESS
bool "Guess unwinder"
help
Expand All @@ -7,3 +15,15 @@ config UNWINDER_GUESS

While this option often produces false positives, it can still be
useful in many cases.

config UNWINDER_PROLOGUE
bool "Prologue unwinder"
depends on KALLSYMS
help
This option enables the "prologue" unwinder for unwinding kernel stack
traces. It unwind the stack frame based on prologue code analyze. Symbol
information is needed, at least the address and length of each function.
Some of the addresses it reports may be incorrect (but better than the
Guess unwinder).

endchoice
52 changes: 52 additions & 0 deletions arch/loongarch/include/asm/inst.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,33 @@ enum reg1i20_op {
lu32id_op = 0x0b,
};

enum reg1i21_op {
beqz_op = 0x10,
bnez_op = 0x11,
};

enum reg2i12_op {
addiw_op = 0x0a,
addid_op = 0x0b,
lu52id_op = 0x0c,
ldb_op = 0xa0,
ldh_op = 0xa1,
ldw_op = 0xa2,
ldd_op = 0xa3,
stb_op = 0xa4,
sth_op = 0xa5,
stw_op = 0xa6,
std_op = 0xa7,
};

enum reg2i16_op {
jirl_op = 0x13,
beq_op = 0x16,
bne_op = 0x17,
blt_op = 0x18,
bge_op = 0x19,
bltu_op = 0x1a,
bgeu_op = 0x1b,
};

struct reg0i26_format {
Expand Down Expand Up @@ -110,6 +131,37 @@ enum loongarch_gpr {
LOONGARCH_GPR_MAX
};

#define is_imm12_negative(val) is_imm_negative(val, 12)

static inline bool is_imm_negative(unsigned long val, unsigned int bit)
{
return val & (1UL << (bit - 1));
}

static inline bool is_branch_ins(union loongarch_instruction *ip)
{
return ip->reg1i21_format.opcode >= beqz_op &&
ip->reg1i21_format.opcode <= bgeu_op;
}

static inline bool is_ra_save_ins(union loongarch_instruction *ip)
{
/* st.d $ra, $sp, offset */
return ip->reg2i12_format.opcode == std_op &&
ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
ip->reg2i12_format.rd == LOONGARCH_GPR_RA &&
!is_imm12_negative(ip->reg2i12_format.immediate);
}

static inline bool is_stack_alloc_ins(union loongarch_instruction *ip)
{
/* addi.d $sp, $sp, -imm */
return ip->reg2i12_format.opcode == addid_op &&
ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
ip->reg2i12_format.rd == LOONGARCH_GPR_SP &&
is_imm12_negative(ip->reg2i12_format.immediate);
}

u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm);
u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm);
u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest);
Expand Down
8 changes: 7 additions & 1 deletion arch/loongarch/include/asm/unwind.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@

#include <asm/stacktrace.h>

enum unwinder_type {
UNWINDER_GUESS,
UNWINDER_PROLOGUE,
};

struct unwind_state {
char type; /* UNWINDER_XXX */
struct stack_info stack_info;
struct task_struct *task;
bool first, error;
unsigned long sp, pc;
unsigned long sp, pc, ra;
};

void unwind_start(struct unwind_state *state,
Expand Down
1 change: 1 addition & 0 deletions arch/loongarch/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ obj-$(CONFIG_SMP) += smp.o
obj-$(CONFIG_NUMA) += numa.o

obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o
obj-$(CONFIG_UNWINDER_PROLOGUE) += unwind_prologue.o

CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS)
3 changes: 3 additions & 0 deletions arch/loongarch/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
if (!task)
task = current;

if (user_mode(regs))
state.type = UNWINDER_GUESS;

printk("%sCall Trace:", loglvl);
for (unwind_start(&state, task, pregs);
!unwind_done(&state); unwind_next_frame(&state)) {
Expand Down
176 changes: 176 additions & 0 deletions arch/loongarch/kernel/unwind_prologue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Technology Corporation Limited
*/
#include <linux/kallsyms.h>

#include <asm/inst.h>
#include <asm/ptrace.h>
#include <asm/unwind.h>

unsigned long unwind_get_return_address(struct unwind_state *state)
{

if (unwind_done(state))
return 0;
else if (state->type)
return state->pc;
else if (state->first)
return state->pc;

return *(unsigned long *)(state->sp);

}
EXPORT_SYMBOL_GPL(unwind_get_return_address);

static bool unwind_by_guess(struct unwind_state *state)
{
struct stack_info *info = &state->stack_info;
unsigned long addr;

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;
}

return false;
}

static bool unwind_by_prologue(struct unwind_state *state)
{
struct stack_info *info = &state->stack_info;
union loongarch_instruction *ip, *ip_end;
unsigned long frame_size = 0, frame_ra = -1;
unsigned long size, offset, pc = state->pc;

if (state->sp >= info->end || state->sp < info->begin)
return false;

if (!kallsyms_lookup_size_offset(pc, &size, &offset))
return false;

ip = (union loongarch_instruction *)(pc - offset);
ip_end = (union loongarch_instruction *)pc;

while (ip < ip_end) {
if (is_stack_alloc_ins(ip)) {
frame_size = (1 << 12) - ip->reg2i12_format.immediate;
ip++;
break;
}
ip++;
}

if (!frame_size) {
if (state->first)
goto first;

return false;
}

while (ip < ip_end) {
if (is_ra_save_ins(ip)) {
frame_ra = ip->reg2i12_format.immediate;
break;
}
if (is_branch_ins(ip))
break;
ip++;
}

if (frame_ra < 0) {
if (state->first) {
state->sp = state->sp + frame_size;
goto first;
}
return false;
}

if (state->first)
state->first = false;

state->pc = *(unsigned long *)(state->sp + frame_ra);
state->sp = state->sp + frame_size;
return !!__kernel_text_address(state->pc);

first:
state->first = false;
if (state->pc == state->ra)
return false;

state->pc = state->ra;

return !!__kernel_text_address(state->ra);
}

void unwind_start(struct unwind_state *state, struct task_struct *task,
struct pt_regs *regs)
{
memset(state, 0, sizeof(*state));

if (regs && __kernel_text_address(regs->csr_era)) {
state->pc = regs->csr_era;
state->sp = regs->regs[3];
state->ra = regs->regs[1];
state->type = UNWINDER_PROLOGUE;
}

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;
struct pt_regs *regs;
unsigned long pc;

if (unwind_done(state))
return false;

do {
switch (state->type) {
case UNWINDER_GUESS:
state->first = false;
if (unwind_by_guess(state))
return true;
break;

case UNWINDER_PROLOGUE:
if (unwind_by_prologue(state))
return true;

if (info->type == STACK_TYPE_IRQ &&
info->end == state->sp) {
regs = (struct pt_regs *)info->next_sp;
pc = regs->csr_era;

if (user_mode(regs) || !__kernel_text_address(pc))
return false;

state->pc = pc;
state->sp = regs->regs[3];
state->ra = regs->regs[1];
state->first = true;
get_stack_info(state->sp, state->task, info);

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 49aef11

Please sign in to comment.