2022-07-28 14:54:42

by Qing Zhang

[permalink] [raw]
Subject: [PATCH 1/3] LoongArch: Add guess unwinder support

Name "guess unwinder" comes from x86, It scans the stack and reports
every kernel text address it finds.

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.

Make the dump_stack process go through unwind process.
Add get_stack_info() to get stack info. At present we have irq stack and
task stack. Maybe add another type in future. The next_sp means the key
info between this type stack and next type stack.

Dividing unwinder helps to add new unwinders in the future.

Signed-off-by: Qing Zhang <[email protected]>

diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug
index e69de29bb2d1..68634d4fa27b 100644
--- a/arch/loongarch/Kconfig.debug
+++ b/arch/loongarch/Kconfig.debug
@@ -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.
diff --git a/arch/loongarch/include/asm/stacktrace.h b/arch/loongarch/include/asm/stacktrace.h
index 26483e396ad1..33077010356d 100644
--- a/arch/loongarch/include/asm/stacktrace.h
+++ b/arch/loongarch/include/asm/stacktrace.h
@@ -10,6 +10,23 @@
#include <asm/loongarch.h>
#include <linux/stringify.h>

+enum stack_type {
+ STACK_TYPE_UNKNOWN,
+ STACK_TYPE_TASK,
+ STACK_TYPE_IRQ,
+};
+
+struct stack_info {
+ enum stack_type type;
+ unsigned long begin, end, next_sp;
+};
+
+bool in_task_stack(unsigned long stack, struct task_struct *task,
+ struct stack_info *info);
+bool in_irq_stack(unsigned long stack, 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)
diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
new file mode 100644
index 000000000000..243330b39d0d
--- /dev/null
+++ b/arch/loongarch/include/asm/unwind.h
@@ -0,0 +1,37 @@
+/* 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;
+ unsigned long sp, pc;
+ bool first;
+ bool error;
+};
+
+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 */
diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
index 940de9173542..c5fa4adb23b6 100644
--- a/arch/loongarch/kernel/Makefile
+++ b/arch/loongarch/kernel/Makefile
@@ -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)
diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c
index bfa0dfe8b7d7..709b7a1664f8 100644
--- a/arch/loongarch/kernel/process.c
+++ b/arch/loongarch/kernel/process.c
@@ -44,6 +44,7 @@
#include <asm/pgtable.h>
#include <asm/processor.h>
#include <asm/reg.h>
+#include <asm/unwind.h>
#include <asm/vdso.h>

/*
@@ -183,6 +184,66 @@ unsigned long __get_wchan(struct task_struct *task)
return 0;
}

+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->type = STACK_TYPE_TASK;
+ info->begin = begin;
+ info->end = end;
+ info->next_sp = 0;
+
+ return true;
+}
+
+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->type = STACK_TYPE_IRQ;
+ info->begin = begin;
+ info->end = end;
+ info->next_sp = nextsp;
+
+ 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;
diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
index 1bf58c65e2bf..ef2c3aeb1dab 100644
--- a/arch/loongarch/kernel/traps.c
+++ b/arch/loongarch/kernel/traps.c
@@ -43,6 +43,7 @@
#include <asm/stacktrace.h>
#include <asm/tlb.h>
#include <asm/types.h>
+#include <asm/unwind.h>

#include "access-helper.h"

@@ -64,19 +65,18 @@ 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;
+
+ unwind_start(&state, task, pregs);

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_done(&state); unwind_next_frame(&state)) {
+ addr = unwind_get_return_address(&state);
+ print_ip_sym(loglvl, addr);
}
printk("%s\n", loglvl);
}
diff --git a/arch/loongarch/kernel/unwind_guess.c b/arch/loongarch/kernel/unwind_guess.c
new file mode 100644
index 000000000000..7eeb3e1a989d
--- /dev/null
+++ b/arch/loongarch/kernel/unwind_guess.c
@@ -0,0 +1,65 @@
+// 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);
+
+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);
+
+void unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs)
+{
+ memset(state, 0, sizeof(*state));
+
+ state->task = task;
+
+ state->sp = regs->regs[3];
+ state->pc = regs->csr_era;
+ 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);
--
2.20.1


2022-07-28 14:56:18

by Qing Zhang

[permalink] [raw]
Subject: [PATCH 2/3] 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, eg:
unwind_frame, unwind_orc .etc

Signed-off-by: Qing Zhang <[email protected]>

diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug
index 68634d4fa27b..57cdbe0cfd98 100644
--- a/arch/loongarch/Kconfig.debug
+++ b/arch/loongarch/Kconfig.debug
@@ -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,14 @@ 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.
+
+endchoice
diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h
index 575d1bb66ffb..bd684cff4008 100644
--- a/arch/loongarch/include/asm/inst.h
+++ b/arch/loongarch/include/asm/inst.h
@@ -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,29 @@ enum loongarch_gpr {
LOONGARCH_GPR_MAX
};

+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 &&
+ ip->reg2i12_format.immediate & (1 << 11);
+}
+
+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;
+}
+
+static inline bool is_branch_insn(union loongarch_instruction insn)
+{
+ return insn.reg1i21_format.opcode >= beqz_op &&
+ insn.reg1i21_format.opcode <= bgeu_op;
+}
+
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);
diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
index 243330b39d0d..09394e536ea9 100644
--- a/arch/loongarch/include/asm/unwind.h
+++ b/arch/loongarch/include/asm/unwind.h
@@ -14,6 +14,10 @@
struct unwind_state {
struct stack_info stack_info;
struct task_struct *task;
+#if defined(CONFIG_UNWINDER_PROLOGUE)
+ unsigned long ra;
+ bool enable;
+#endif
unsigned long sp, pc;
bool first;
bool error;
diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
index c5fa4adb23b6..918600e7b30f 100644
--- a/arch/loongarch/kernel/Makefile
+++ b/arch/loongarch/kernel/Makefile
@@ -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)
diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
index ef2c3aeb1dab..3e904fa12d48 100644
--- a/arch/loongarch/kernel/traps.c
+++ b/arch/loongarch/kernel/traps.c
@@ -73,6 +73,11 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,

unwind_start(&state, task, pregs);

+#ifdef CONFIG_UNWINDER_PROLOGUE
+ if (user_mode(regs))
+ state.enable = false;
+#endif
+
printk("%sCall Trace:", loglvl);
for (; !unwind_done(&state); unwind_next_frame(&state)) {
addr = unwind_get_return_address(&state);
diff --git a/arch/loongarch/kernel/unwind_prologue.c b/arch/loongarch/kernel/unwind_prologue.c
new file mode 100644
index 000000000000..6539c9e98364
--- /dev/null
+++ b/arch/loongarch/kernel/unwind_prologue.c
@@ -0,0 +1,174 @@
+// 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;
+
+ if (state->enable)
+ 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_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_insn(*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);
+}
+
+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;
+}
+
+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 {
+ if (state->enable) {
+ 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;
+ }
+ } else {
+ if (state->first)
+ state->first = false;
+
+ if (unwind_by_guess(state))
+ return true;
+ }
+
+ state->sp = info->next_sp;
+
+ } while (!get_stack_info(state->sp, state->task, info));
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(unwind_next_frame);
+
+void unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs)
+{
+ memset(state, 0, sizeof(*state));
+
+ if (__kernel_text_address(regs->csr_era))
+ state->enable = true;
+
+ state->task = task;
+ state->pc = regs->csr_era;
+ state->sp = regs->regs[3];
+ state->ra = regs->regs[1];
+ 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);
--
2.20.1

2022-07-29 02:24:39

by Youling Tang

[permalink] [raw]
Subject: Re: [PATCH 2/3] LoongArch: Add prologue unwinder support



On 07/28/2022 10:05 PM, Qing Zhang wrote:
> 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, eg:
> unwind_frame, unwind_orc .etc
>
> Signed-off-by: Qing Zhang <[email protected]>
>
> diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug
> index 68634d4fa27b..57cdbe0cfd98 100644
> --- a/arch/loongarch/Kconfig.debug
> +++ b/arch/loongarch/Kconfig.debug
> @@ -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,14 @@ 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.
> +
> +endchoice
> diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h
> index 575d1bb66ffb..bd684cff4008 100644
> --- a/arch/loongarch/include/asm/inst.h
> +++ b/arch/loongarch/include/asm/inst.h
> @@ -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,29 @@ enum loongarch_gpr {
> LOONGARCH_GPR_MAX
> };
>
> +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 &&
> + ip->reg2i12_format.immediate & (1 << 11);
> +}
> +
> +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;
> +}
> +
> +static inline bool is_branch_insn(union loongarch_instruction insn)
> +{
> + return insn.reg1i21_format.opcode >= beqz_op &&
> + insn.reg1i21_format.opcode <= bgeu_op;
> +}
> +
> 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);
> diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
> index 243330b39d0d..09394e536ea9 100644
> --- a/arch/loongarch/include/asm/unwind.h
> +++ b/arch/loongarch/include/asm/unwind.h
> @@ -14,6 +14,10 @@
> struct unwind_state {
> struct stack_info stack_info;
> struct task_struct *task;
> +#if defined(CONFIG_UNWINDER_PROLOGUE)
> + unsigned long ra;
> + bool enable;
> +#endif
> unsigned long sp, pc;
> bool first;
> bool error;
> diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> index c5fa4adb23b6..918600e7b30f 100644
> --- a/arch/loongarch/kernel/Makefile
> +++ b/arch/loongarch/kernel/Makefile
> @@ -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)
> diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
> index ef2c3aeb1dab..3e904fa12d48 100644
> --- a/arch/loongarch/kernel/traps.c
> +++ b/arch/loongarch/kernel/traps.c
> @@ -73,6 +73,11 @@ static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
>
> unwind_start(&state, task, pregs);
>
> +#ifdef CONFIG_UNWINDER_PROLOGUE
> + if (user_mode(regs))
> + state.enable = false;
> +#endif
> +
> printk("%sCall Trace:", loglvl);
> for (; !unwind_done(&state); unwind_next_frame(&state)) {
> addr = unwind_get_return_address(&state);
> diff --git a/arch/loongarch/kernel/unwind_prologue.c b/arch/loongarch/kernel/unwind_prologue.c
> new file mode 100644
> index 000000000000..6539c9e98364
> --- /dev/null
> +++ b/arch/loongarch/kernel/unwind_prologue.c
> @@ -0,0 +1,174 @@
> +// 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;
This judgment can be removed, because unwind_done() has been judged
before entering this function, and unwind_get_return_address will not
be called if it is true.

> +
> + if (state->enable)
> + 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_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;
Because the immediate member in struct reg2i12_format is defined as an
unsigned type, the value obtained by frame_ra here can only be a
positive number.

> + break;
> + }
> + if (is_branch_insn(*ip))
> + break;
> + ip++;
> + }
> +
> + if (frame_ra < 0) {
In addition to judging whether the initial value of frame_ra is
negative, we also want to judge whether the previously assigned
frame_ra is negative.

Save the ra value to the stack in the prologue, offset must be a
positive number, so we can add another judgment to is_ra_save_ins, the
code is as follows:
+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 &&
+ !(ip->reg2i12_format.immediate & (1 << 11));
+}
> + 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);
> +}
> +
> +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;
> +}
> +
> +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;
Can be removed as above.

Thanks,
Youling
> +
> + do {
> + if (state->enable) {
> + 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;
> + }
> + } else {
> + if (state->first)
> + state->first = false;
> +
> + if (unwind_by_guess(state))
> + return true;
> + }
> +
> + state->sp = info->next_sp;
> +
> + } while (!get_stack_info(state->sp, state->task, info));
> +
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(unwind_next_frame);
> +
> +void unwind_start(struct unwind_state *state, struct task_struct *task,
> + struct pt_regs *regs)
> +{
> + memset(state, 0, sizeof(*state));
> +
> + if (__kernel_text_address(regs->csr_era))
> + state->enable = true;
> +
> + state->task = task;
> + state->pc = regs->csr_era;
> + state->sp = regs->regs[3];
> + state->ra = regs->regs[1];
> + 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);
>

2022-07-29 02:29:31

by Youling Tang

[permalink] [raw]
Subject: Re: [PATCH 1/3] LoongArch: Add guess unwinder support

Hi, Qing

On 07/28/2022 10:05 PM, Qing Zhang wrote:
> Name "guess unwinder" comes from x86, It scans the stack and reports
> every kernel text address it finds.
>
> 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.
>
> Make the dump_stack process go through unwind process.
> Add get_stack_info() to get stack info. At present we have irq stack and
> task stack. Maybe add another type in future. The next_sp means the key
> info between this type stack and next type stack.
>
> Dividing unwinder helps to add new unwinders in the future.
>
> Signed-off-by: Qing Zhang <[email protected]>
>
> diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug
> index e69de29bb2d1..68634d4fa27b 100644
> --- a/arch/loongarch/Kconfig.debug
> +++ b/arch/loongarch/Kconfig.debug
> @@ -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.
> diff --git a/arch/loongarch/include/asm/stacktrace.h b/arch/loongarch/include/asm/stacktrace.h
> index 26483e396ad1..33077010356d 100644
> --- a/arch/loongarch/include/asm/stacktrace.h
> +++ b/arch/loongarch/include/asm/stacktrace.h
> @@ -10,6 +10,23 @@
> #include <asm/loongarch.h>
> #include <linux/stringify.h>
>
> +enum stack_type {
> + STACK_TYPE_UNKNOWN,
> + STACK_TYPE_TASK,
> + STACK_TYPE_IRQ,
> +};
> +
> +struct stack_info {
> + enum stack_type type;
> + unsigned long begin, end, next_sp;
> +};
> +
> +bool in_task_stack(unsigned long stack, struct task_struct *task,
> + struct stack_info *info);
> +bool in_irq_stack(unsigned long stack, 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)
> diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
> new file mode 100644
> index 000000000000..243330b39d0d
> --- /dev/null
> +++ b/arch/loongarch/include/asm/unwind.h
> @@ -0,0 +1,37 @@
> +/* 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;
> + unsigned long sp, pc;
> + bool first;
> + bool error;
> +};
> +
> +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 */
> diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> index 940de9173542..c5fa4adb23b6 100644
> --- a/arch/loongarch/kernel/Makefile
> +++ b/arch/loongarch/kernel/Makefile
> @@ -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)
> diff --git a/arch/loongarch/kernel/process.c b/arch/loongarch/kernel/process.c
> index bfa0dfe8b7d7..709b7a1664f8 100644
> --- a/arch/loongarch/kernel/process.c
> +++ b/arch/loongarch/kernel/process.c
> @@ -44,6 +44,7 @@
> #include <asm/pgtable.h>
> #include <asm/processor.h>
> #include <asm/reg.h>
> +#include <asm/unwind.h>
> #include <asm/vdso.h>
>
> /*
> @@ -183,6 +184,66 @@ unsigned long __get_wchan(struct task_struct *task)
> return 0;
> }
>
> +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->type = STACK_TYPE_TASK;
> + info->begin = begin;
> + info->end = end;
> + info->next_sp = 0;
> +
> + return true;
> +}
> +
> +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->type = STACK_TYPE_IRQ;
> + info->begin = begin;
> + info->end = end;
> + info->next_sp = nextsp;
> +
> + 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;
> diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
> index 1bf58c65e2bf..ef2c3aeb1dab 100644
> --- a/arch/loongarch/kernel/traps.c
> +++ b/arch/loongarch/kernel/traps.c
> @@ -43,6 +43,7 @@
> #include <asm/stacktrace.h>
> #include <asm/tlb.h>
> #include <asm/types.h>
> +#include <asm/unwind.h>
>
> #include "access-helper.h"
>
> @@ -64,19 +65,18 @@ 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;
> +
> + unwind_start(&state, task, pregs);
>
> 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_done(&state); unwind_next_frame(&state)) {
> + addr = unwind_get_return_address(&state);
> + print_ip_sym(loglvl, addr);
> }
> printk("%s\n", loglvl);
> }
> diff --git a/arch/loongarch/kernel/unwind_guess.c b/arch/loongarch/kernel/unwind_guess.c
> new file mode 100644
> index 000000000000..7eeb3e1a989d
> --- /dev/null
> +++ b/arch/loongarch/kernel/unwind_guess.c
> @@ -0,0 +1,65 @@
> +// 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;
This judgment can be removed, because unwind_done() has been judged
before entering this function, and unwind_get_return_address will not
be called if it is true.

> + else if (state->first)
> + return state->pc;
> +
> + return *(unsigned long *)(state->sp);
> +}
> +EXPORT_SYMBOL_GPL(unwind_get_return_address);
> +
> +bool unwind_next_frame(struct unwind_state *state)
> +{
> + struct stack_info *info = &state->stack_info;
> + unsigned long addr;
> +
> + if (unwind_done(state))
> + return false;
> +
Can be removed as above.

Thanks,
Youling
> + 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);
> +
> +void unwind_start(struct unwind_state *state, struct task_struct *task,
> + struct pt_regs *regs)
> +{
> + memset(state, 0, sizeof(*state));
> +
> + state->task = task;
> +
> + state->sp = regs->regs[3];
> + state->pc = regs->csr_era;
> + 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);
>

2022-07-29 02:33:27

by Jinyang He

[permalink] [raw]
Subject: Re: [PATCH 2/3] LoongArch: Add prologue unwinder support

Hi, Qing,


On 07/28/2022 10:05 PM, Qing Zhang wrote:
> 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, eg:
> unwind_frame, unwind_orc .etc
>
> Signed-off-by: Qing Zhang <[email protected]>
>
>
> +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 &&
> + ip->reg2i12_format.immediate & (1 << 11);
Checking the sign bit can be used in other place.
> +}
> +
> +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;
> +}
> +
> +static inline bool is_branch_insn(union loongarch_instruction insn)
Does it by using pointer parameter as above functions do.
> +{
> + return insn.reg1i21_format.opcode >= beqz_op &&
> + insn.reg1i21_format.opcode <= bgeu_op;
> +}
> +
> 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);
> diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
> index 243330b39d0d..09394e536ea9 100644
> --- a/arch/loongarch/include/asm/unwind.h
> +++ b/arch/loongarch/include/asm/unwind.h
> @@ -14,6 +14,10 @@
> struct unwind_state {
> struct stack_info stack_info;
> struct task_struct *task;
> +#if defined(CONFIG_UNWINDER_PROLOGUE)
> + unsigned long ra;
> + bool enable;
Annotating here is appreciating. Enable is the way of prologue analysis
while !enable is the way of guess.
> +#endif
> unsigned long sp, pc;
> bool first;
> bool error;
[...]
> +
> +unsigned long unwind_get_return_address(struct unwind_state *state)
> +{
> +
> + if (unwind_done(state))
> + return 0;
> +
> + if (state->enable)
> + return state->pc;
> + else if (state->first)
> + return state->pc;
Combine conditions.
> +
> + return *(unsigned long *)(state->sp);
> +
> +}
> +EXPORT_SYMBOL_GPL(unwind_get_return_address);
> +
> +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;
Due to there will be other place convert unsigned to signed, we have
a chance that create a inline function in inst.h. Do it as same as
checking the sign bit.

> + ip++;
> + break;
> + }
> + ip++;
> + }
> +
[...]
> +
> + do {
> + if (state->enable) {
> + 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;
> + }
> + } else {
> + if (state->first)
> + state->first = false;
> +
> + if (unwind_by_guess(state))
> + return true;
> + }
I'd prefer separate the block of 'if...else...' into two inline
functions, that makes codes clear.

Thanks,
Jinyang

2022-07-29 02:51:16

by Jinyang He

[permalink] [raw]
Subject: Re: [PATCH 1/3] LoongArch: Add guess unwinder support

Hi, Youling,

[...]
>> +unsigned long unwind_get_return_address(struct unwind_state *state)
>> +{
>> + if (unwind_done(state))
>> + return 0;
> This judgment can be removed, because unwind_done() has been judged
> before entering this function, and unwind_get_return_address will not
> be called if it is true.
These unwinder functions are exported by "EXPORT_SYMBOL_GPL".
What's more, new ways to use them will be added in the future possible.
Assuming has judged unwind_done is not reliable.

Thanks,
Jinyang

2022-07-29 02:59:56

by Youling Tang

[permalink] [raw]
Subject: Re: [PATCH 1/3] LoongArch: Add guess unwinder support

Hi, Jinyang

On 07/29/2022 10:28 AM, Jinyang He wrote:
> Hi, Youling,
>
> [...]
>>> +unsigned long unwind_get_return_address(struct unwind_state *state)
>>> +{
>>> + if (unwind_done(state))
>>> + return 0;
>> This judgment can be removed, because unwind_done() has been judged
>> before entering this function, and unwind_get_return_address will not
>> be called if it is true.
> These unwinder functions are exported by "EXPORT_SYMBOL_GPL".
> What's more, new ways to use them will be added in the future possible.
> Assuming has judged unwind_done is not reliable.
In this case, most of the code will be checked twice by unwind_done(),
which feels a bit redundant.

Thanks,
Youling

2022-07-29 06:06:39

by Qing Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/3] LoongArch: Add prologue unwinder support



On 2022/7/29 上午10:14, Jinyang He wrote:
> Hi, Qing,
>
>
> On 07/28/2022 10:05 PM, Qing Zhang wrote:
>> 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, eg:
>> unwind_frame, unwind_orc .etc
>>
>> Signed-off-by: Qing Zhang <[email protected]>
>>
>> +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 &&
>> +        ip->reg2i12_format.immediate & (1 << 11);
> Checking the sign bit can be used in other place.
>> +}
>> +
>> +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;
>> +}
>> +
>> +static inline bool is_branch_insn(union loongarch_instruction insn)
> Does it by using pointer parameter as above functions do.
>> +{
>> +    return insn.reg1i21_format.opcode >= beqz_op &&
>> +            insn.reg1i21_format.opcode <= bgeu_op;
>> +}
>> +
>>   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);
>> diff --git a/arch/loongarch/include/asm/unwind.h
>> b/arch/loongarch/include/asm/unwind.h
>> index 243330b39d0d..09394e536ea9 100644
>> --- a/arch/loongarch/include/asm/unwind.h
>> +++ b/arch/loongarch/include/asm/unwind.h
>> @@ -14,6 +14,10 @@
>>   struct unwind_state {
>>       struct stack_info stack_info;
>>       struct task_struct *task;
>> +#if defined(CONFIG_UNWINDER_PROLOGUE)
>> +    unsigned long ra;
>> +    bool enable;
> Annotating here is appreciating. Enable is the way of prologue analysis
> while !enable is the way of guess.
>> +#endif
>>       unsigned long sp, pc;
>>       bool first;
>>       bool error;
> [...]
>> +
>> +unsigned long unwind_get_return_address(struct unwind_state *state)
>> +{
>> +
>> +    if (unwind_done(state))
>> +        return 0;
>> +
>> +    if (state->enable)
>> +        return state->pc;
>> +    else if (state->first)
>> +        return state->pc;
> Combine conditions.
>> +
>> +    return *(unsigned long *)(state->sp);
>> +
>> +}
>> +EXPORT_SYMBOL_GPL(unwind_get_return_address);
>> +
>> +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;
> Due to there will be other place convert unsigned to signed, we have
> a chance that create a inline function in inst.h. Do it as same as
> checking the sign bit.Hi,
Jinyang

I will fix all in v2.
eg:
#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_stack_alloc_ins(union loongarch_instruction *ip)
{
...
ip->reg2i12_format.rd == LOONGARCH_GPR_SP &&
is_imm12_negative(ip->reg2i12_format.immediate);
}

static inline bool is_ra_save_ins(union loongarch_instruction *ip)
{
...
!is_imm12_negative(ip->reg2i12_format.immediate);
}

Thanks,
Qing
>
>> +            ip++;
>> +            break;
>> +        }
>> +        ip++;
>> +    }
>> +
> [...]
>> +
>> +    do {
>> +        if (state->enable) {
>> +            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;
>> +            }
>> +        } else {
>> +            if (state->first)
>> +                state->first = false;
>> +
>> +            if (unwind_by_guess(state))
>> +                return true;
>> +        }
> I'd prefer separate the block of 'if...else...' into two inline
> functions, that makes codes clear.
>
> Thanks,
> Jinyang

2022-07-29 06:13:45

by Qing Zhang

[permalink] [raw]
Subject: Re: [PATCH 2/3] LoongArch: Add prologue unwinder support



On 2022/7/29 上午10:03, Youling Tang wrote:
>
>
> On 07/28/2022 10:05 PM, Qing Zhang wrote:
>> 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, eg:
>> unwind_frame, unwind_orc .etc
>>
>> Signed-off-by: Qing Zhang <[email protected]>
>>
>> +
>> +    while (ip < ip_end) {
>> +        if (is_ra_save_ins(ip)) {
>> +            frame_ra = ip->reg2i12_format.immediate;
> Because the immediate member in struct reg2i12_format is defined as an
> unsigned type, the value obtained by frame_ra here can only be a
> positive number.
>
>> +            break;
>> +        }
>> +        if (is_branch_insn(*ip))
>> +            break;
>> +        ip++;
>> +    }
>> +
>> +    if (frame_ra < 0) {
> In addition to judging whether the initial value of frame_ra is
> negative, we also want to judge whether the previously assigned
> frame_ra is negative.
>
> Save the ra value to the stack in the prologue, offset must be a
> positive number, so we can add another judgment to is_ra_save_ins, the
> code is as follows:
> +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 &&
> +        !(ip->reg2i12_format.immediate & (1 << 11));
> +}

Hi,
youling

you are right and I will send v2.

Thanks,
Qing