LoongArch: Add prologue unwinder support
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>
This commit is contained in:
parent
49232773d8
commit
49aef111e2
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
Loading…
Reference in New Issue