Skip to content

Commit 49aef11

Browse files
zhangqingmychenhuacai
authored andcommitted
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>
1 parent 4923277 commit 49aef11

6 files changed

Lines changed: 259 additions & 1 deletion

File tree

arch/loongarch/Kconfig.debug

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
choice
2+
prompt "Choose kernel unwinder"
3+
default UNWINDER_PROLOGUE if KALLSYMS
4+
help
5+
This determines which method will be used for unwinding kernel stack
6+
traces for panics, oopses, bugs, warnings, perf, /proc/<pid>/stack,
7+
lockdep, and more.
8+
19
config UNWINDER_GUESS
210
bool "Guess unwinder"
311
help
@@ -7,3 +15,15 @@ config UNWINDER_GUESS
715

816
While this option often produces false positives, it can still be
917
useful in many cases.
18+
19+
config UNWINDER_PROLOGUE
20+
bool "Prologue unwinder"
21+
depends on KALLSYMS
22+
help
23+
This option enables the "prologue" unwinder for unwinding kernel stack
24+
traces. It unwind the stack frame based on prologue code analyze. Symbol
25+
information is needed, at least the address and length of each function.
26+
Some of the addresses it reports may be incorrect (but better than the
27+
Guess unwinder).
28+
29+
endchoice

arch/loongarch/include/asm/inst.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,33 @@ enum reg1i20_op {
2323
lu32id_op = 0x0b,
2424
};
2525

26+
enum reg1i21_op {
27+
beqz_op = 0x10,
28+
bnez_op = 0x11,
29+
};
30+
2631
enum reg2i12_op {
32+
addiw_op = 0x0a,
33+
addid_op = 0x0b,
2734
lu52id_op = 0x0c,
35+
ldb_op = 0xa0,
36+
ldh_op = 0xa1,
37+
ldw_op = 0xa2,
38+
ldd_op = 0xa3,
39+
stb_op = 0xa4,
40+
sth_op = 0xa5,
41+
stw_op = 0xa6,
42+
std_op = 0xa7,
2843
};
2944

3045
enum reg2i16_op {
3146
jirl_op = 0x13,
47+
beq_op = 0x16,
48+
bne_op = 0x17,
49+
blt_op = 0x18,
50+
bge_op = 0x19,
51+
bltu_op = 0x1a,
52+
bgeu_op = 0x1b,
3253
};
3354

3455
struct reg0i26_format {
@@ -110,6 +131,37 @@ enum loongarch_gpr {
110131
LOONGARCH_GPR_MAX
111132
};
112133

134+
#define is_imm12_negative(val) is_imm_negative(val, 12)
135+
136+
static inline bool is_imm_negative(unsigned long val, unsigned int bit)
137+
{
138+
return val & (1UL << (bit - 1));
139+
}
140+
141+
static inline bool is_branch_ins(union loongarch_instruction *ip)
142+
{
143+
return ip->reg1i21_format.opcode >= beqz_op &&
144+
ip->reg1i21_format.opcode <= bgeu_op;
145+
}
146+
147+
static inline bool is_ra_save_ins(union loongarch_instruction *ip)
148+
{
149+
/* st.d $ra, $sp, offset */
150+
return ip->reg2i12_format.opcode == std_op &&
151+
ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
152+
ip->reg2i12_format.rd == LOONGARCH_GPR_RA &&
153+
!is_imm12_negative(ip->reg2i12_format.immediate);
154+
}
155+
156+
static inline bool is_stack_alloc_ins(union loongarch_instruction *ip)
157+
{
158+
/* addi.d $sp, $sp, -imm */
159+
return ip->reg2i12_format.opcode == addid_op &&
160+
ip->reg2i12_format.rj == LOONGARCH_GPR_SP &&
161+
ip->reg2i12_format.rd == LOONGARCH_GPR_SP &&
162+
is_imm12_negative(ip->reg2i12_format.immediate);
163+
}
164+
113165
u32 larch_insn_gen_lu32id(enum loongarch_gpr rd, int imm);
114166
u32 larch_insn_gen_lu52id(enum loongarch_gpr rd, enum loongarch_gpr rj, int imm);
115167
u32 larch_insn_gen_jirl(enum loongarch_gpr rd, enum loongarch_gpr rj, unsigned long pc, unsigned long dest);

arch/loongarch/include/asm/unwind.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@
1111

1212
#include <asm/stacktrace.h>
1313

14+
enum unwinder_type {
15+
UNWINDER_GUESS,
16+
UNWINDER_PROLOGUE,
17+
};
18+
1419
struct unwind_state {
20+
char type; /* UNWINDER_XXX */
1521
struct stack_info stack_info;
1622
struct task_struct *task;
1723
bool first, error;
18-
unsigned long sp, pc;
24+
unsigned long sp, pc, ra;
1925
};
2026

2127
void unwind_start(struct unwind_state *state,

arch/loongarch/kernel/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ obj-$(CONFIG_SMP) += smp.o
2323
obj-$(CONFIG_NUMA) += numa.o
2424

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

2728
CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS)

arch/loongarch/kernel/traps.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
7171
if (!task)
7272
task = current;
7373

74+
if (user_mode(regs))
75+
state.type = UNWINDER_GUESS;
76+
7477
printk("%sCall Trace:", loglvl);
7578
for (unwind_start(&state, task, pregs);
7679
!unwind_done(&state); unwind_next_frame(&state)) {
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2022 Loongson Technology Corporation Limited
4+
*/
5+
#include <linux/kallsyms.h>
6+
7+
#include <asm/inst.h>
8+
#include <asm/ptrace.h>
9+
#include <asm/unwind.h>
10+
11+
unsigned long unwind_get_return_address(struct unwind_state *state)
12+
{
13+
14+
if (unwind_done(state))
15+
return 0;
16+
else if (state->type)
17+
return state->pc;
18+
else if (state->first)
19+
return state->pc;
20+
21+
return *(unsigned long *)(state->sp);
22+
23+
}
24+
EXPORT_SYMBOL_GPL(unwind_get_return_address);
25+
26+
static bool unwind_by_guess(struct unwind_state *state)
27+
{
28+
struct stack_info *info = &state->stack_info;
29+
unsigned long addr;
30+
31+
for (state->sp += sizeof(unsigned long);
32+
state->sp < info->end;
33+
state->sp += sizeof(unsigned long)) {
34+
addr = *(unsigned long *)(state->sp);
35+
if (__kernel_text_address(addr))
36+
return true;
37+
}
38+
39+
return false;
40+
}
41+
42+
static bool unwind_by_prologue(struct unwind_state *state)
43+
{
44+
struct stack_info *info = &state->stack_info;
45+
union loongarch_instruction *ip, *ip_end;
46+
unsigned long frame_size = 0, frame_ra = -1;
47+
unsigned long size, offset, pc = state->pc;
48+
49+
if (state->sp >= info->end || state->sp < info->begin)
50+
return false;
51+
52+
if (!kallsyms_lookup_size_offset(pc, &size, &offset))
53+
return false;
54+
55+
ip = (union loongarch_instruction *)(pc - offset);
56+
ip_end = (union loongarch_instruction *)pc;
57+
58+
while (ip < ip_end) {
59+
if (is_stack_alloc_ins(ip)) {
60+
frame_size = (1 << 12) - ip->reg2i12_format.immediate;
61+
ip++;
62+
break;
63+
}
64+
ip++;
65+
}
66+
67+
if (!frame_size) {
68+
if (state->first)
69+
goto first;
70+
71+
return false;
72+
}
73+
74+
while (ip < ip_end) {
75+
if (is_ra_save_ins(ip)) {
76+
frame_ra = ip->reg2i12_format.immediate;
77+
break;
78+
}
79+
if (is_branch_ins(ip))
80+
break;
81+
ip++;
82+
}
83+
84+
if (frame_ra < 0) {
85+
if (state->first) {
86+
state->sp = state->sp + frame_size;
87+
goto first;
88+
}
89+
return false;
90+
}
91+
92+
if (state->first)
93+
state->first = false;
94+
95+
state->pc = *(unsigned long *)(state->sp + frame_ra);
96+
state->sp = state->sp + frame_size;
97+
return !!__kernel_text_address(state->pc);
98+
99+
first:
100+
state->first = false;
101+
if (state->pc == state->ra)
102+
return false;
103+
104+
state->pc = state->ra;
105+
106+
return !!__kernel_text_address(state->ra);
107+
}
108+
109+
void unwind_start(struct unwind_state *state, struct task_struct *task,
110+
struct pt_regs *regs)
111+
{
112+
memset(state, 0, sizeof(*state));
113+
114+
if (regs && __kernel_text_address(regs->csr_era)) {
115+
state->pc = regs->csr_era;
116+
state->sp = regs->regs[3];
117+
state->ra = regs->regs[1];
118+
state->type = UNWINDER_PROLOGUE;
119+
}
120+
121+
state->task = task;
122+
state->first = true;
123+
124+
get_stack_info(state->sp, state->task, &state->stack_info);
125+
126+
if (!unwind_done(state) && !__kernel_text_address(state->pc))
127+
unwind_next_frame(state);
128+
}
129+
EXPORT_SYMBOL_GPL(unwind_start);
130+
131+
bool unwind_next_frame(struct unwind_state *state)
132+
{
133+
struct stack_info *info = &state->stack_info;
134+
struct pt_regs *regs;
135+
unsigned long pc;
136+
137+
if (unwind_done(state))
138+
return false;
139+
140+
do {
141+
switch (state->type) {
142+
case UNWINDER_GUESS:
143+
state->first = false;
144+
if (unwind_by_guess(state))
145+
return true;
146+
break;
147+
148+
case UNWINDER_PROLOGUE:
149+
if (unwind_by_prologue(state))
150+
return true;
151+
152+
if (info->type == STACK_TYPE_IRQ &&
153+
info->end == state->sp) {
154+
regs = (struct pt_regs *)info->next_sp;
155+
pc = regs->csr_era;
156+
157+
if (user_mode(regs) || !__kernel_text_address(pc))
158+
return false;
159+
160+
state->pc = pc;
161+
state->sp = regs->regs[3];
162+
state->ra = regs->regs[1];
163+
state->first = true;
164+
get_stack_info(state->sp, state->task, info);
165+
166+
return true;
167+
}
168+
}
169+
170+
state->sp = info->next_sp;
171+
172+
} while (!get_stack_info(state->sp, state->task, info));
173+
174+
return false;
175+
}
176+
EXPORT_SYMBOL_GPL(unwind_next_frame);

0 commit comments

Comments
 (0)