Commit 49aef111 authored by Qing Zhang's avatar Qing Zhang Committed by Huacai Chen
Browse files

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: default avatarQing Zhang <zhangqing@loongson.cn>
Signed-off-by: default avatarHuacai Chen <chenhuacai@loongson.cn>
parent 49232773
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
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
+52 −0
Original line number Diff line number Diff line
@@ -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);
+7 −1
Original line number Diff line number Diff line
@@ -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,
+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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)) {
Loading