2023-06-20 08:19:57

by Youling Tang

[permalink] [raw]
Subject: [RFC PATCH v1 15/23] objtool: Add ORC support for LoongArch

Add ORC support for the LoongArch in objtool, mainly including ORC unwind
table generation and dumping, in preparation for subsequent ORC unwinder
support.

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
---
arch/loongarch/Kconfig | 2 +
arch/loongarch/Makefile | 4 +
arch/loongarch/include/asm/orc_types.h | 58 +++++++
arch/loongarch/include/asm/unwind_hints.h | 110 +++++++++++++
scripts/Makefile | 5 +-
tools/arch/loongarch/include/asm/orc_types.h | 58 +++++++
.../arch/loongarch/include/asm/unwind_hints.h | 86 ++++++++++
tools/objtool/Makefile | 2 +
tools/objtool/arch/loongarch/Build | 1 +
tools/objtool/arch/loongarch/decode.c | 17 +-
tools/objtool/arch/loongarch/orc.c | 155 ++++++++++++++++++
tools/objtool/include/objtool/objtool.h | 3 +
tools/objtool/orc_gen.c | 5 +
tools/objtool/sync-check.sh | 2 +
14 files changed, 506 insertions(+), 2 deletions(-)
create mode 100644 arch/loongarch/include/asm/orc_types.h
create mode 100644 arch/loongarch/include/asm/unwind_hints.h
create mode 100644 tools/arch/loongarch/include/asm/orc_types.h
create mode 100644 tools/objtool/arch/loongarch/orc.c

diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 7fd51257e0ed..dd8143ea33c4 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -111,6 +111,7 @@ config LOONGARCH
select HAVE_KRETPROBES
select HAVE_MOD_ARCH_SPECIFIC
select HAVE_NMI
+ select HAVE_OBJTOOL if AS_HAS_EXPLICIT_RELOCS
select HAVE_PCI
select HAVE_PERF_EVENTS
select HAVE_PERF_REGS
@@ -119,6 +120,7 @@ config LOONGARCH
select HAVE_RSEQ
select HAVE_SETUP_PER_CPU_AREA if NUMA
select HAVE_STACKPROTECTOR
+ select HAVE_STACK_VALIDATION if HAVE_OBJTOOL
select HAVE_SYSCALL_TRACEPOINTS
select HAVE_TIF_NOHZ
select HAVE_VIRT_CPU_ACCOUNTING_GEN if !SMP
diff --git a/arch/loongarch/Makefile b/arch/loongarch/Makefile
index f71edf574101..848b595332c2 100644
--- a/arch/loongarch/Makefile
+++ b/arch/loongarch/Makefile
@@ -25,6 +25,10 @@ endif
32bit-emul = elf32loongarch
64bit-emul = elf64loongarch

+ifeq ($(CONFIG_OBJTOOL),y)
+KBUILD_CFLAGS += -fno-jump-tables
+endif
+
ifdef CONFIG_DYNAMIC_FTRACE
KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
CC_FLAGS_FTRACE := -fpatchable-function-entry=2
diff --git a/arch/loongarch/include/asm/orc_types.h b/arch/loongarch/include/asm/orc_types.h
new file mode 100644
index 000000000000..97976927bda5
--- /dev/null
+++ b/arch/loongarch/include/asm/orc_types.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _ORC_TYPES_H
+#define _ORC_TYPES_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+
+/*
+ * The ORC_REG_* registers are base registers which are used to find other
+ * registers on the stack.
+ *
+ * ORC_REG_PREV_SP, also known as DWARF Call Frame Address (CFA), is the
+ * address of the previous frame: the caller's SP before it called the current
+ * function.
+ *
+ * ORC_REG_UNDEFINED means the corresponding register's value didn't change in
+ * the current frame.
+ *
+ * The most commonly used base registers are SP and BP -- which the previous SP
+ * is usually based on -- and PREV_SP and UNDEFINED -- which the previous BP is
+ * usually based on.
+ *
+ * The rest of the base registers are needed for special cases like entry code
+ * and GCC realigned stacks.
+ */
+#define ORC_REG_UNDEFINED 0
+#define ORC_REG_PREV_SP 1
+#define ORC_REG_SP 2
+#define ORC_REG_BP 3
+#define ORC_REG_MAX 4
+
+#ifndef __ASSEMBLY__
+#include <asm/byteorder.h>
+
+/*
+ * This struct is more or less a vastly simplified version of the DWARF Call
+ * Frame Information standard. It contains only the necessary parts of DWARF
+ * CFI, simplified for ease of access by the in-kernel unwinder. It tells the
+ * unwinder how to find the previous SP and BP (and sometimes entry regs) on
+ * the stack for a given code address. Each instance of the struct corresponds
+ * to one or more code locations.
+ */
+struct orc_entry {
+ s16 sp_offset;
+ s16 bp_offset;
+ s16 ra_offset;
+ unsigned int sp_reg:4;
+ unsigned int bp_reg:4;
+ unsigned int ra_reg:4;
+ unsigned int type:2;
+ unsigned signal:1;
+ unsigned int end:1;
+} __packed;
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ORC_TYPES_H */
diff --git a/arch/loongarch/include/asm/unwind_hints.h b/arch/loongarch/include/asm/unwind_hints.h
new file mode 100644
index 000000000000..de18b08de1fc
--- /dev/null
+++ b/arch/loongarch/include/asm/unwind_hints.h
@@ -0,0 +1,110 @@
+#ifndef _ASM_LOONGARCH_UNWIND_HINTS_H
+#define _ASM_LOONGARCH_UNWIND_HINTS_H
+
+#ifndef __ASSEMBLY__
+
+#include <linux/types.h>
+
+/*
+ * This struct is used by asm and inline asm code to manually annotate the
+ * location of registers on the stack.
+ */
+struct unwind_hint {
+ u32 ip;
+ s16 sp_offset;
+ u8 sp_reg;
+ u8 type;
+ u8 signal;
+ u8 end;
+};
+#endif
+
+#include <linux/objtool.h>
+
+#include "orc_types.h"
+
+#ifdef CONFIG_OBJTOOL
+
+#ifndef __ASSEMBLY__
+
+#define UNWIND_HINT(sp_reg, sp_offset, type, signal, end) \
+ "987: \n\t" \
+ ".pushsection .discard.unwind_hints\n\t" \
+ /* struct unwind_hint */ \
+ ".long 987b - .\n\t" \
+ ".short " __stringify(sp_offset) "\n\t" \
+ ".byte " __stringify(sp_reg) "\n\t" \
+ ".byte " __stringify(type) "\n\t" \
+ ".byte " __stringify(signal) "\n\t" \
+ ".byte " __stringify(end) "\n\t" \
+ ".balign 4 \n\t" \
+ ".popsection\n\t"
+
+#else /* __ASSEMBLY__ */
+
+/*
+ * In asm, there are two kinds of code: normal C-type callable functions and
+ * the rest. The normal callable functions can be called by other code, and
+ * don't do anything unusual with the stack. Such normal callable functions
+ * are annotated with the ENTRY/ENDPROC macros. Most asm code falls in this
+ * category. In this case, no special debugging annotations are needed because
+ * objtool can automatically generate the ORC data for the ORC unwinder to read
+ * at runtime.
+ *
+ * Anything which doesn't fall into the above category, such as syscall and
+ * interrupt handlers, tends to not be called directly by other functions, and
+ * often does unusual non-C-function-type things with the stack pointer. Such
+ * code needs to be annotated such that objtool can understand it. The
+ * following CFI hint macros are for this type of code.
+ *
+ * These macros provide hints to objtool about the state of the stack at each
+ * instruction. Objtool starts from the hints and follows the code flow,
+ * making automatic CFI adjustments when it sees pushes and pops, filling out
+ * the debuginfo as necessary. It will also warn if it sees any
+ * inconsistencies.
+ */
+.macro UNWIND_HINT type:req sp_reg=0 sp_offset=0 signal=0 end=0
+.Lunwind_hint_ip_\@:
+ .pushsection .discard.unwind_hints
+ /* struct unwind_hint */
+ .long .Lunwind_hint_ip_\@ - .
+ .short \sp_offset
+ .byte \sp_reg
+ .byte \type
+ .byte \signal
+ .byte \end
+ .balign 4
+ .popsection
+.endm
+
+#endif /* __ASSEMBLY__ */
+
+#else /* !CONFIG_OBJTOOL */
+
+#ifndef __ASSEMBLY__
+#define UNWIND_HINT(sp_reg, sp_offset, type, signal, end) \
+ "\n\t"
+#else
+.macro UNWIND_HINT type:req sp_reg=0 sp_offset=0 signal=0 end=0
+.endm
+#endif
+
+#endif /* CONFIG_OBJTOOL */
+
+#ifdef __ASSEMBLY__
+
+.macro UNWIND_HINT_EMPTY
+ UNWIND_HINT sp_reg=ORC_REG_UNDEFINED type=UNWIND_HINT_TYPE_CALL end=1
+.endm
+
+.macro UNWIND_HINT_REGS base=ORC_REG_SP offset=0
+ UNWIND_HINT sp_reg=\base sp_offset=\offset type=UNWIND_HINT_TYPE_REGS
+.endm
+
+.macro UNWIND_HINT_FUNC sp_offset=0
+ UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=\sp_offset type=UNWIND_HINT_TYPE_CALL
+.endm
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ASM_LOONGARCH_UNWIND_HINTS_H */
diff --git a/scripts/Makefile b/scripts/Makefile
index 32b6ba722728..d62d85f86658 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -29,7 +29,10 @@ ifdef CONFIG_UNWINDER_ORC
ifeq ($(ARCH),x86_64)
ARCH := x86
endif
-HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/x86/include
+ifeq ($(ARCH),loongarch)
+ARCH := loongarch
+endif
+HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/$(ARCH)/include
HOSTCFLAGS_sorttable.o += -DUNWINDER_ORC_ENABLED
endif

diff --git a/tools/arch/loongarch/include/asm/orc_types.h b/tools/arch/loongarch/include/asm/orc_types.h
new file mode 100644
index 000000000000..97976927bda5
--- /dev/null
+++ b/tools/arch/loongarch/include/asm/orc_types.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _ORC_TYPES_H
+#define _ORC_TYPES_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+
+/*
+ * The ORC_REG_* registers are base registers which are used to find other
+ * registers on the stack.
+ *
+ * ORC_REG_PREV_SP, also known as DWARF Call Frame Address (CFA), is the
+ * address of the previous frame: the caller's SP before it called the current
+ * function.
+ *
+ * ORC_REG_UNDEFINED means the corresponding register's value didn't change in
+ * the current frame.
+ *
+ * The most commonly used base registers are SP and BP -- which the previous SP
+ * is usually based on -- and PREV_SP and UNDEFINED -- which the previous BP is
+ * usually based on.
+ *
+ * The rest of the base registers are needed for special cases like entry code
+ * and GCC realigned stacks.
+ */
+#define ORC_REG_UNDEFINED 0
+#define ORC_REG_PREV_SP 1
+#define ORC_REG_SP 2
+#define ORC_REG_BP 3
+#define ORC_REG_MAX 4
+
+#ifndef __ASSEMBLY__
+#include <asm/byteorder.h>
+
+/*
+ * This struct is more or less a vastly simplified version of the DWARF Call
+ * Frame Information standard. It contains only the necessary parts of DWARF
+ * CFI, simplified for ease of access by the in-kernel unwinder. It tells the
+ * unwinder how to find the previous SP and BP (and sometimes entry regs) on
+ * the stack for a given code address. Each instance of the struct corresponds
+ * to one or more code locations.
+ */
+struct orc_entry {
+ s16 sp_offset;
+ s16 bp_offset;
+ s16 ra_offset;
+ unsigned int sp_reg:4;
+ unsigned int bp_reg:4;
+ unsigned int ra_reg:4;
+ unsigned int type:2;
+ unsigned signal:1;
+ unsigned int end:1;
+} __packed;
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ORC_TYPES_H */
diff --git a/tools/arch/loongarch/include/asm/unwind_hints.h b/tools/arch/loongarch/include/asm/unwind_hints.h
index ac48ee34bf7b..de18b08de1fc 100644
--- a/tools/arch/loongarch/include/asm/unwind_hints.h
+++ b/tools/arch/loongarch/include/asm/unwind_hints.h
@@ -21,4 +21,90 @@ struct unwind_hint {

#include <linux/objtool.h>

+#include "orc_types.h"
+
+#ifdef CONFIG_OBJTOOL
+
+#ifndef __ASSEMBLY__
+
+#define UNWIND_HINT(sp_reg, sp_offset, type, signal, end) \
+ "987: \n\t" \
+ ".pushsection .discard.unwind_hints\n\t" \
+ /* struct unwind_hint */ \
+ ".long 987b - .\n\t" \
+ ".short " __stringify(sp_offset) "\n\t" \
+ ".byte " __stringify(sp_reg) "\n\t" \
+ ".byte " __stringify(type) "\n\t" \
+ ".byte " __stringify(signal) "\n\t" \
+ ".byte " __stringify(end) "\n\t" \
+ ".balign 4 \n\t" \
+ ".popsection\n\t"
+
+#else /* __ASSEMBLY__ */
+
+/*
+ * In asm, there are two kinds of code: normal C-type callable functions and
+ * the rest. The normal callable functions can be called by other code, and
+ * don't do anything unusual with the stack. Such normal callable functions
+ * are annotated with the ENTRY/ENDPROC macros. Most asm code falls in this
+ * category. In this case, no special debugging annotations are needed because
+ * objtool can automatically generate the ORC data for the ORC unwinder to read
+ * at runtime.
+ *
+ * Anything which doesn't fall into the above category, such as syscall and
+ * interrupt handlers, tends to not be called directly by other functions, and
+ * often does unusual non-C-function-type things with the stack pointer. Such
+ * code needs to be annotated such that objtool can understand it. The
+ * following CFI hint macros are for this type of code.
+ *
+ * These macros provide hints to objtool about the state of the stack at each
+ * instruction. Objtool starts from the hints and follows the code flow,
+ * making automatic CFI adjustments when it sees pushes and pops, filling out
+ * the debuginfo as necessary. It will also warn if it sees any
+ * inconsistencies.
+ */
+.macro UNWIND_HINT type:req sp_reg=0 sp_offset=0 signal=0 end=0
+.Lunwind_hint_ip_\@:
+ .pushsection .discard.unwind_hints
+ /* struct unwind_hint */
+ .long .Lunwind_hint_ip_\@ - .
+ .short \sp_offset
+ .byte \sp_reg
+ .byte \type
+ .byte \signal
+ .byte \end
+ .balign 4
+ .popsection
+.endm
+
+#endif /* __ASSEMBLY__ */
+
+#else /* !CONFIG_OBJTOOL */
+
+#ifndef __ASSEMBLY__
+#define UNWIND_HINT(sp_reg, sp_offset, type, signal, end) \
+ "\n\t"
+#else
+.macro UNWIND_HINT type:req sp_reg=0 sp_offset=0 signal=0 end=0
+.endm
+#endif
+
+#endif /* CONFIG_OBJTOOL */
+
+#ifdef __ASSEMBLY__
+
+.macro UNWIND_HINT_EMPTY
+ UNWIND_HINT sp_reg=ORC_REG_UNDEFINED type=UNWIND_HINT_TYPE_CALL end=1
+.endm
+
+.macro UNWIND_HINT_REGS base=ORC_REG_SP offset=0
+ UNWIND_HINT sp_reg=\base sp_offset=\offset type=UNWIND_HINT_TYPE_REGS
+.endm
+
+.macro UNWIND_HINT_FUNC sp_offset=0
+ UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=\sp_offset type=UNWIND_HINT_TYPE_CALL
+.endm
+
+#endif /* __ASSEMBLY__ */
+
#endif /* _ASM_LOONGARCH_UNWIND_HINTS_H */
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 034fbd9f3f13..d641423aa70b 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -59,9 +59,11 @@ ifeq ($(SRCARCH),x86)
endif

ifeq ($(SRCARCH),loongarch)
+ BUILD_ORC := y
STATIC_CHECK := y
endif

+
export BUILD_ORC STATIC_CHECK
export srctree OUTPUT CFLAGS SRCARCH AWK
include $(srctree)/tools/build/Makefile.include
diff --git a/tools/objtool/arch/loongarch/Build b/tools/objtool/arch/loongarch/Build
index d24d5636a5b8..1d4b784b6887 100644
--- a/tools/objtool/arch/loongarch/Build
+++ b/tools/objtool/arch/loongarch/Build
@@ -1,2 +1,3 @@
objtool-y += decode.o
objtool-y += special.o
+objtool-y += orc.o
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index fc24efd6dba2..90adbbeab8d4 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -5,6 +5,7 @@

#include <linux/bitops.h>
#include <asm/inst.h>
+#include <asm/orc_types.h>

#include <objtool/check.h>
#include <objtool/elf.h>
@@ -49,7 +50,21 @@ bool arch_callee_saved_reg(unsigned char reg)

int arch_decode_hint_reg(u8 sp_reg, int *base)
{
- exit(-1);
+ switch (sp_reg) {
+ case ORC_REG_UNDEFINED:
+ *base = CFI_UNDEFINED;
+ break;
+ case ORC_REG_SP:
+ *base = CFI_SP;
+ break;
+ case ORC_REG_BP:
+ *base = CFI_FP;
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
}

const char *arch_nop_insn(int len)
diff --git a/tools/objtool/arch/loongarch/orc.c b/tools/objtool/arch/loongarch/orc.c
new file mode 100644
index 000000000000..a1adb1e6755b
--- /dev/null
+++ b/tools/objtool/arch/loongarch/orc.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <stdlib.h>
+
+#include <linux/objtool.h>
+
+#include <objtool/check.h>
+#include <objtool/orc.h>
+#include <objtool/warn.h>
+#include <objtool/endianness.h>
+
+int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi,
+ struct instruction *insn)
+{
+ struct cfi_reg *bp = &cfi->regs[CFI_BP];
+ struct cfi_reg *ra = &cfi->regs[CFI_RA];
+
+ memset(orc, 0, sizeof(*orc));
+
+ if (!cfi) {
+ orc->end = 0;
+ orc->sp_reg = ORC_REG_UNDEFINED;
+ return 0;
+ }
+
+ orc->end = cfi->end;
+
+ if (cfi->cfa.base == CFI_UNDEFINED) {
+ orc->sp_reg = ORC_REG_UNDEFINED;
+ return 0;
+ }
+
+ switch (cfi->cfa.base) {
+ case CFI_SP:
+ orc->sp_reg = ORC_REG_SP;
+ break;
+ case CFI_BP:
+ orc->sp_reg = ORC_REG_BP;
+ break;
+ default:
+ WARN_FUNC("unknown CFA base reg %d",
+ insn->sec, insn->offset, cfi->cfa.base);
+ return -1;
+ }
+
+ switch (bp->base) {
+ case CFI_UNDEFINED:
+ orc->bp_reg = ORC_REG_UNDEFINED;
+ orc->bp_offset = 0;
+ break;
+ case CFI_CFA:
+ orc->bp_reg = ORC_REG_PREV_SP;
+ orc->bp_offset = bp->offset;
+ break;
+ default:
+ WARN_FUNC("unknown BP base reg %d",
+ insn->sec, insn->offset, bp->base);
+ return -1;
+ }
+
+ switch (ra->base) {
+ case CFI_UNDEFINED:
+ orc->ra_reg = ORC_REG_UNDEFINED;
+ orc->ra_offset = 0;
+ break;
+ case CFI_CFA:
+ orc->ra_reg = ORC_REG_PREV_SP;
+ orc->ra_offset = ra->offset;
+ break;
+ default:
+ WARN_FUNC("unknown BP base reg %d",
+ insn->sec, insn->offset, bp->base);
+ return -1;
+ }
+
+ orc->sp_offset = cfi->cfa.offset;
+ orc->type = cfi->type;
+
+ return 0;
+}
+
+static const char *reg_name(unsigned int reg)
+{
+ switch (reg) {
+ case ORC_REG_SP:
+ return "sp";
+ case ORC_REG_BP:
+ return "fp";
+ case ORC_REG_PREV_SP:
+ return "prevsp";
+ default:
+ return "?";
+ }
+}
+
+static const char *orc_type_name(unsigned int type)
+{
+ switch (type) {
+ case UNWIND_HINT_TYPE_CALL:
+ return "call";
+ case UNWIND_HINT_TYPE_REGS:
+ return "regs";
+ case UNWIND_HINT_TYPE_REGS_PARTIAL:
+ return "regs (partial)";
+ default:
+ return "?";
+ }
+}
+
+static void orc_print_reg(unsigned int reg, int offset)
+{
+ if (reg == ORC_REG_UNDEFINED)
+ printf(" (und) ");
+ else
+ printf("%s + %3d", reg_name(reg), offset);
+
+}
+
+static void orc_print_sp(void)
+{
+ printf(" sp:");
+}
+
+static void orc_print_fp(void)
+{
+ printf(" fp:");
+}
+
+static void orc_print_ra(void)
+{
+ printf(" ra:");
+}
+
+void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i)
+{
+ orc_print_sp();
+
+ orc_print_reg(orc[i].sp_reg, bswap_if_needed(dummy_elf, orc[i].sp_offset));
+
+ orc_print_fp();
+
+ orc_print_reg(orc[i].bp_reg, bswap_if_needed(dummy_elf, orc[i].bp_offset));
+
+ orc_print_ra();
+
+ orc_print_reg(orc[i].ra_reg, bswap_if_needed(dummy_elf, orc[i].ra_offset));
+
+ printf(" type:%s signal:%d end:%d\n",
+ orc_type_name(orc[i].type), orc[i].signal, orc[i].end);
+}
+
+void __weak arch_write_orc(struct elf *elf, struct orc_entry *orc)
+{
+ orc->ra_offset = bswap_if_needed(elf, orc->ra_offset);
+}
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index 94a33ee7b363..046b4bd816c1 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -12,6 +12,8 @@

#include <objtool/elf.h>

+#include <asm/orc_types.h>
+
#define __weak __attribute__((weak))

struct pv_state {
@@ -46,5 +48,6 @@ void objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
int check(struct objtool_file *file);
int orc_dump(const char *objname);
int orc_create(struct objtool_file *file);
+void arch_write_orc(struct elf *elf, struct orc_entry *orc);

#endif /* _OBJTOOL_H */
diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c
index 08c5a27252a2..bff46c7a7987 100644
--- a/tools/objtool/orc_gen.c
+++ b/tools/objtool/orc_gen.c
@@ -14,6 +14,10 @@
#include <objtool/warn.h>
#include <objtool/endianness.h>

+void __weak arch_write_orc(struct elf *elf, struct orc_entry *orc)
+{
+}
+
static int write_orc_entry(struct elf *elf, struct section *orc_sec,
struct section *ip_sec, unsigned int idx,
struct section *insn_sec, unsigned long insn_off,
@@ -26,6 +30,7 @@ static int write_orc_entry(struct elf *elf, struct section *orc_sec,
memcpy(orc, o, sizeof(*orc));
orc->sp_offset = bswap_if_needed(elf, orc->sp_offset);
orc->bp_offset = bswap_if_needed(elf, orc->bp_offset);
+ arch_write_orc(elf, orc);

/* populate reloc for ip */
if (elf_add_reloc_to_insn(elf, ip_sec, idx * sizeof(int), R_PCREL,
diff --git a/tools/objtool/sync-check.sh b/tools/objtool/sync-check.sh
index cfee9a034fa7..69c68a4ca5c4 100755
--- a/tools/objtool/sync-check.sh
+++ b/tools/objtool/sync-check.sh
@@ -31,6 +31,8 @@ fi
if [ "$SRCARCH" = "loongarch" ]; then
FILES="$FILES
arch/loongarch/include/asm/asm.h
+arch/loongarch/include/asm/unwind_hints.h
+arch/loongarch/include/asm/orc_types.h
"
fi

--
2.39.2



2023-06-20 08:23:05

by Youling Tang

[permalink] [raw]
Subject: [RFC PATCH v1 19/23] LoongArch: Annotate unwind_hint

Some assembly symbols contain code that might be executed with an
unspecified stack state (e.g. invalid stack pointer,no stackframe, ...).

Annotate those symbol with UNWIND_HINT_EMPTY to let objtool be aware of
them.

When taking an exception/interrupt, add UNWIND_HINT_REGS to indicate
from which point the pt_regs is on stack.

Whether returning to userland or creating a new task, sp is
pointing to a pt_regs frame, add UNWIND_HINT_REGS after that.

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
---
arch/loongarch/include/asm/stackframe.h | 3 +++
arch/loongarch/kernel/entry.S | 2 ++
arch/loongarch/kernel/head.S | 1 +
arch/loongarch/kernel/relocate_kernel.S | 5 +++++
4 files changed, 11 insertions(+)

diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h
index 7df80e6ae9d2..ab16f2d10257 100644
--- a/arch/loongarch/include/asm/stackframe.h
+++ b/arch/loongarch/include/asm/stackframe.h
@@ -13,6 +13,7 @@
#include <asm/asm-offsets.h>
#include <asm/loongarch.h>
#include <asm/thread_info.h>
+#include <asm/unwind_hints.h>

/* Make the addition of cfi info a little easier. */
.macro cfi_rel_offset reg offset=0 docfi=0
@@ -158,6 +159,7 @@
cfi_st u0, PT_R21, \docfi
csrrd u0, PERCPU_BASE_KS
9:
+ UNWIND_HINT_REGS
.endm

.macro SAVE_ALL docfi=0
@@ -215,6 +217,7 @@

.macro RESTORE_SP_AND_RET docfi=0
cfi_ld sp, PT_R3, \docfi
+ UNWIND_HINT_FUNC
ertn
.endm

diff --git a/arch/loongarch/kernel/entry.S b/arch/loongarch/kernel/entry.S
index d737e3cf42d3..458d4e8e126f 100644
--- a/arch/loongarch/kernel/entry.S
+++ b/arch/loongarch/kernel/entry.S
@@ -70,6 +70,7 @@ SYM_FUNC_END(handle_syscall)
_ASM_NOKPROBE(handle_syscall)

SYM_CODE_START(ret_from_fork)
+ UNWIND_HINT_REGS
bl schedule_tail # a0 = struct task_struct *prev
move a0, sp
bl syscall_exit_to_user_mode
@@ -79,6 +80,7 @@ SYM_CODE_START(ret_from_fork)
SYM_CODE_END(ret_from_fork)

SYM_CODE_START(ret_from_kernel_thread)
+ UNWIND_HINT_REGS
bl schedule_tail # a0 = struct task_struct *prev
move a0, s1
jirl ra, s0, 0
diff --git a/arch/loongarch/kernel/head.S b/arch/loongarch/kernel/head.S
index aa64b179744f..73e5ec0006bb 100644
--- a/arch/loongarch/kernel/head.S
+++ b/arch/loongarch/kernel/head.S
@@ -43,6 +43,7 @@ SYM_DATA(kernel_offset, .long kernel_offset - _text);
.align 12

SYM_CODE_START(kernel_entry) # kernel entry point
+ UNWIND_HINT_EMPTY

/* Config direct window and set PG */
li.d t0, CSR_DMW0_INIT # UC, PLV0, 0x8000 xxxx xxxx xxxx
diff --git a/arch/loongarch/kernel/relocate_kernel.S b/arch/loongarch/kernel/relocate_kernel.S
index d13252553a7c..d0123fa2b511 100644
--- a/arch/loongarch/kernel/relocate_kernel.S
+++ b/arch/loongarch/kernel/relocate_kernel.S
@@ -13,8 +13,11 @@
#include <asm/loongarch.h>
#include <asm/stackframe.h>
#include <asm/addrspace.h>
+#include <asm/unwind_hints.h>

SYM_CODE_START(relocate_new_kernel)
+ UNWIND_HINT_EMPTY
+
/*
* a0: EFI boot flag for the new kernel
* a1: Command line pointer for the new kernel
@@ -91,6 +94,8 @@ SYM_CODE_END(relocate_new_kernel)
* then start at the entry point from LOONGARCH_IOCSR_MBUF0.
*/
SYM_CODE_START(kexec_smp_wait)
+ UNWIND_HINT_EMPTY
+
1: li.w t0, 0x100 /* wait for init loop */
2: addi.w t0, t0, -1 /* limit mailbox access */
bnez t0, 2b
--
2.39.2


2023-06-20 08:30:20

by Youling Tang

[permalink] [raw]
Subject: [RFC PATCH v1 16/23] LoongArch: Add ORC unwinder support

With a blatant copy of some x86, we introduce the another unwinder named
"orc unwinder" to unwind stack with custom data created by objtool.

Before vmlinux created, we check all metadata, find the stack operation,
note stack state and create orc data. Objtool insert two sections into
vmlinux. '.orc_unwind_ip' and '.orc_unwind'. (For modules, insert
'.rela.orc_unwind_ip' to relocate '.orc_unwind_ip'.) Each insn has only
one stack state in .orc_unwind and orc_unwind_ip hint its pc address.
Through unwinding orc data, we can get stack info both kernel and module.

Do not like x86, we add ra_reg and ra_offset into orc_entry so that we
can get ra info before ra saved into stack. At present, the unwind ability
has not been fully reflected, because it has not been annotated in the
assembly code.

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
---
arch/loongarch/Kconfig.debug | 11 +
arch/loongarch/include/asm/module.h | 7 +
arch/loongarch/include/asm/unwind.h | 17 +-
arch/loongarch/kernel/Makefile | 1 +
arch/loongarch/kernel/module.c | 11 +-
arch/loongarch/kernel/setup.c | 2 +
arch/loongarch/kernel/stacktrace.c | 1 +
arch/loongarch/kernel/unwind_orc.c | 301 ++++++++++++++++++++++++++++
arch/loongarch/kernel/vmlinux.lds.S | 3 +
9 files changed, 351 insertions(+), 3 deletions(-)
create mode 100644 arch/loongarch/kernel/unwind_orc.c

diff --git a/arch/loongarch/Kconfig.debug b/arch/loongarch/Kconfig.debug
index 8d36aab53008..98d60630c3d4 100644
--- a/arch/loongarch/Kconfig.debug
+++ b/arch/loongarch/Kconfig.debug
@@ -26,4 +26,15 @@ config UNWINDER_PROLOGUE
Some of the addresses it reports may be incorrect (but better than the
Guess unwinder).

+config UNWINDER_ORC
+ bool "ORC unwinder"
+ select OBJTOOL
+ help
+ This option enables the ORC (Oops Rewind Capability) unwinder for
+ unwinding kernel stack traces. It uses a custom data format which is
+ a simplified version of the DWARF Call Frame Information standard.
+
+ Enabling this option will increase the kernel's runtime memory usage
+ by roughly 2-4MB, depending on your kernel config.
+
endchoice
diff --git a/arch/loongarch/include/asm/module.h b/arch/loongarch/include/asm/module.h
index 12a0f1e66916..0f654108d1ee 100644
--- a/arch/loongarch/include/asm/module.h
+++ b/arch/loongarch/include/asm/module.h
@@ -7,6 +7,7 @@

#include <asm/inst.h>
#include <asm-generic/module.h>
+#include <asm/orc_types.h>

#define RELA_STACK_DEPTH 16

@@ -23,6 +24,12 @@ struct mod_arch_specific {

/* For CONFIG_DYNAMIC_FTRACE */
struct plt_entry *ftrace_trampolines;
+
+#ifdef CONFIG_UNWINDER_ORC
+ unsigned int num_orcs;
+ int *orc_unwind_ip;
+ struct orc_entry *orc_unwind;
+#endif
};

struct got_entry {
diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
index b9dce87afd2e..da92211c8595 100644
--- a/arch/loongarch/include/asm/unwind.h
+++ b/arch/loongarch/include/asm/unwind.h
@@ -16,6 +16,7 @@
enum unwinder_type {
UNWINDER_GUESS,
UNWINDER_PROLOGUE,
+ UNWINDER_ORC,
};

struct unwind_state {
@@ -24,7 +25,7 @@ struct unwind_state {
struct task_struct *task;
bool first, error, reset;
int graph_idx;
- unsigned long sp, pc, ra;
+ unsigned long sp, pc, ra, fp;
};

bool default_next_frame(struct unwind_state *state);
@@ -34,6 +35,12 @@ void unwind_start(struct unwind_state *state,
bool unwind_next_frame(struct unwind_state *state);
unsigned long unwind_get_return_address(struct unwind_state *state);

+#ifdef CONFIG_UNWINDER_ORC
+void unwind_init(void);
+#else
+static inline void unwind_init(void) {}
+#endif
+
static inline bool unwind_done(struct unwind_state *state)
{
return state->stack_info.type == STACK_TYPE_UNKNOWN;
@@ -61,14 +68,17 @@ static __always_inline void __unwind_start(struct unwind_state *state,
state->sp = regs->regs[3];
state->pc = regs->csr_era;
state->ra = regs->regs[1];
+ state->fp = regs->regs[22];
} else if (task && task != current) {
state->sp = thread_saved_fp(task);
state->pc = thread_saved_ra(task);
state->ra = 0;
+ state->fp = 0;
} else {
state->sp = (unsigned long)__builtin_frame_address(0);
state->pc = (unsigned long)__builtin_return_address(0);
state->ra = 0;
+ state->fp = 0;
}
state->task = task;
get_stack_info(state->sp, state->task, &state->stack_info);
@@ -77,6 +87,9 @@ static __always_inline void __unwind_start(struct unwind_state *state,

static __always_inline unsigned long __unwind_get_return_address(struct unwind_state *state)
{
- return unwind_done(state) ? 0 : state->pc;
+ if (unwind_done(state))
+ return 0;
+
+ return __kernel_text_address(state->pc) ? state->pc : 0;
}
#endif /* _ASM_UNWIND_H */
diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
index 78d4e3384305..fcbfa0f38b53 100644
--- a/arch/loongarch/kernel/Makefile
+++ b/arch/loongarch/kernel/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_CRASH_DUMP) += crash_dump.o

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

obj-$(CONFIG_PERF_EVENTS) += perf_event.o perf_regs.o
obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
diff --git a/arch/loongarch/kernel/module.c b/arch/loongarch/kernel/module.c
index b8b86088b2dd..44f31b1958b1 100644
--- a/arch/loongarch/kernel/module.c
+++ b/arch/loongarch/kernel/module.c
@@ -20,6 +20,7 @@
#include <linux/kernel.h>
#include <asm/alternative.h>
#include <asm/inst.h>
+#include <asm-generic/orc_lookup.h>

static int rela_stack_push(s64 stack_value, s64 *rela_stack, size_t *rela_stack_top)
{
@@ -495,7 +496,7 @@ static void module_init_ftrace_plt(const Elf_Ehdr *hdr,
int module_finalize(const Elf_Ehdr *hdr,
const Elf_Shdr *sechdrs, struct module *mod)
{
- const Elf_Shdr *s, *se;
+ const Elf_Shdr *s, *se, *orc = NULL, *orc_ip = NULL;
const char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;

for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) {
@@ -503,7 +504,15 @@ int module_finalize(const Elf_Ehdr *hdr,
apply_alternatives((void *)s->sh_addr, (void *)s->sh_addr + s->sh_size);
if (!strcmp(".ftrace_trampoline", secstrs + s->sh_name))
module_init_ftrace_plt(hdr, s, mod);
+ if (!strcmp(".orc_unwind", secstrs + s->sh_name))
+ orc = s;
+ if (!strcmp(".orc_unwind_ip", secstrs + s->sh_name))
+ orc_ip = s;
}

+ if (orc && orc_ip)
+ orc_lookup_module_init(mod, (void *)orc_ip->sh_addr, orc_ip->sh_size,
+ (void *)orc->sh_addr, orc->sh_size);
+
return 0;
}
diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
index bae84ccf6d36..698024922e09 100644
--- a/arch/loongarch/kernel/setup.c
+++ b/arch/loongarch/kernel/setup.c
@@ -48,6 +48,7 @@
#include <asm/sections.h>
#include <asm/setup.h>
#include <asm/time.h>
+#include <asm/unwind.h>

#define SMBIOS_BIOSSIZE_OFFSET 0x09
#define SMBIOS_BIOSEXTERN_OFFSET 0x13
@@ -568,6 +569,7 @@ static void __init prefill_possible_map(void)

void __init setup_arch(char **cmdline_p)
{
+ unwind_init();
cpu_probe();

init_environ();
diff --git a/arch/loongarch/kernel/stacktrace.c b/arch/loongarch/kernel/stacktrace.c
index 3a690f96f00c..3872925375c4 100644
--- a/arch/loongarch/kernel/stacktrace.c
+++ b/arch/loongarch/kernel/stacktrace.c
@@ -29,6 +29,7 @@ void arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
}

regs->regs[1] = 0;
+ regs->regs[22] = 0;
for (unwind_start(&state, task, regs);
!unwind_done(&state); unwind_next_frame(&state)) {
addr = unwind_get_return_address(&state);
diff --git a/arch/loongarch/kernel/unwind_orc.c b/arch/loongarch/kernel/unwind_orc.c
new file mode 100644
index 000000000000..0cdf4a56a9ba
--- /dev/null
+++ b/arch/loongarch/kernel/unwind_orc.c
@@ -0,0 +1,301 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/objtool.h>
+#include <linux/module.h>
+#include <linux/sort.h>
+#include <asm/ptrace.h>
+#include <asm/setup.h>
+#include <asm/stacktrace.h>
+#include <asm/unwind.h>
+#include <asm/orc_types.h>
+#include <asm-generic/orc_lookup.h>
+
+extern asmlinkage void handle_ade(void);
+extern asmlinkage void handle_ale(void);
+extern asmlinkage void handle_sys(void);
+extern asmlinkage void handle_bp(void);
+extern asmlinkage void handle_ri(void);
+extern asmlinkage void handle_fpu(void);
+extern asmlinkage void handle_fpe(void);
+extern asmlinkage void handle_lbt(void);
+extern asmlinkage void handle_lsx(void);
+extern asmlinkage void handle_lasx(void);
+extern asmlinkage void handle_reserved(void);
+extern asmlinkage void handle_watch(void);
+extern asmlinkage void handle_vint(void);
+extern asmlinkage void handle_tlb_load(void);
+extern asmlinkage void handle_tlb_store(void);
+extern asmlinkage void handle_tlb_modify(void);
+extern asmlinkage void handle_tlb_protect(void);
+
+/* Fake frame pointer entry -- used as a fallback for generated code */
+static struct orc_entry orc_fp_entry = {
+ .type = UNWIND_HINT_TYPE_CALL,
+ .sp_reg = ORC_REG_BP,
+ .sp_offset = 16,
+ .bp_reg = ORC_REG_PREV_SP,
+ .bp_offset = -16,
+ .ra_reg = ORC_REG_PREV_SP,
+ .ra_offset = -8,
+ .end = 0,
+};
+
+void __init unwind_init(void)
+{
+ orc_lookup_init();
+}
+
+static inline bool on_stack(struct stack_info *info, unsigned long addr, size_t len)
+{
+ unsigned long begin = info->begin;
+ unsigned long end = info->end;
+
+ return (info->type != STACK_TYPE_UNKNOWN &&
+ addr >= begin && addr < end &&
+ addr + len > begin && addr + len <= end);
+}
+
+static bool stack_access_ok(struct unwind_state *state, unsigned long addr,
+ size_t len)
+{
+ struct stack_info *info = &state->stack_info;
+
+ if (on_stack(info, addr, len))
+ return true;
+
+ return !get_stack_info(addr, state->task, info) &&
+ on_stack(info, addr, len);
+}
+
+unsigned long unwind_get_return_address(struct unwind_state *state)
+{
+ return __unwind_get_return_address(state);
+}
+EXPORT_SYMBOL_GPL(unwind_get_return_address);
+
+void unwind_start(struct unwind_state *state, struct task_struct *task,
+ struct pt_regs *regs)
+{
+ __unwind_start(state, task, regs);
+ if (!unwind_done(state) && !__kernel_text_address(state->pc))
+ unwind_next_frame(state);
+}
+EXPORT_SYMBOL_GPL(unwind_start);
+
+
+static bool is_entry_func(unsigned long addr)
+{
+ extern u32 kernel_entry;
+ extern u32 kernel_entry_end;
+
+ return addr >= (unsigned long)&kernel_entry &&
+ addr < (unsigned long)&kernel_entry_end;
+}
+
+static inline unsigned long bt_address(unsigned long ra)
+{
+ extern unsigned long eentry;
+
+ if (__kernel_text_address(ra))
+ return ra;
+
+ /* We are in preempt_disable() here */
+ if (__module_text_address(ra))
+ return ra;
+
+ if (ra >= eentry && ra < eentry + EXCCODE_INT_END * VECSIZE) {
+ unsigned long type = (ra - eentry) / VECSIZE;
+ unsigned long offset = (ra - eentry) % VECSIZE;
+ unsigned long func;
+
+ switch (type) {
+ case EXCCODE_TLBL:
+ case EXCCODE_TLBI:
+ func = (unsigned long)handle_tlb_load;
+ break;
+ case EXCCODE_TLBS:
+ func = (unsigned long)handle_tlb_store;
+ break;
+ case EXCCODE_TLBM:
+ func = (unsigned long)handle_tlb_modify;
+ break;
+ case EXCCODE_TLBNR:
+ case EXCCODE_TLBNX:
+ case EXCCODE_TLBPE:
+ func = (unsigned long)handle_tlb_protect;
+ break;
+ case EXCCODE_ADE:
+ func = (unsigned long)handle_ade;
+ break;
+ case EXCCODE_ALE:
+ func = (unsigned long)handle_ale;
+ break;
+ case EXCCODE_SYS:
+ func = (unsigned long)handle_sys;
+ break;
+ case EXCCODE_BP:
+ func = (unsigned long)handle_bp;
+ break;
+ case EXCCODE_INE:
+ case EXCCODE_IPE:
+ func = (unsigned long)handle_ri;
+ break;
+ case EXCCODE_FPDIS:
+ func = (unsigned long)handle_fpu;
+ break;
+ case EXCCODE_LSXDIS:
+ func = (unsigned long)handle_lsx;
+ break;
+ case EXCCODE_LASXDIS:
+ func = (unsigned long)handle_lasx;
+ break;
+ case EXCCODE_FPE:
+ func = (unsigned long)handle_fpe;
+ break;
+ case EXCCODE_BTDIS:
+ func = (unsigned long)handle_lbt;
+ break;
+ case EXCCODE_WATCH:
+ func = (unsigned long)handle_watch;
+ break;
+ case EXCCODE_INT_START ... EXCCODE_INT_END - 1:
+ func = (unsigned long)handle_vint;
+ break;
+ default:
+
+ func = (unsigned long)handle_reserved;
+ break;
+ }
+
+ return func + offset;
+ }
+
+ return ra;
+}
+
+bool unwind_next_frame(struct unwind_state *state)
+{
+ struct stack_info *info = &state->stack_info;
+ struct orc_entry *orc;
+ struct pt_regs *regs;
+ unsigned long *p, pc;
+
+ if (unwind_done(state))
+ return false;
+
+ /* Don't let modules unload while we're reading their ORC data. */
+ preempt_disable();
+
+ if (is_entry_func(state->pc))
+ goto end;
+
+ orc = orc_find(state->pc);
+ if (!orc) {
+ orc = &orc_fp_entry;
+ state->error = true;
+ }
+
+ switch (orc->sp_reg) {
+ case ORC_REG_SP:
+ state->sp = state->sp + orc->sp_offset;
+ break;
+ case ORC_REG_BP:
+ state->sp = state->fp;
+ break;
+ default:
+ orc_warn("unknown SP base reg %d at %pB\n",
+ orc->sp_reg, (void *)state->pc);
+ goto err;
+ }
+
+ switch (orc->bp_reg) {
+ case ORC_REG_PREV_SP:
+ p = (unsigned long *)(state->sp + orc->bp_offset);
+ if (!stack_access_ok(state, (unsigned long)p, sizeof(unsigned long)))
+ goto err;
+
+ state->fp = *p;
+ break;
+ case ORC_REG_UNDEFINED:
+ /* Nothing. */
+ break;
+ default:
+ orc_warn("unknown FP base reg %d at %pB\n",
+ orc->bp_reg, (void *)state->pc);
+ goto err;
+ }
+
+ switch (orc->type) {
+ case UNWIND_HINT_TYPE_CALL:
+ if (orc->ra_reg == ORC_REG_PREV_SP) {
+ p = (unsigned long *)(state->sp + orc->ra_offset);
+ if (!stack_access_ok(state, (unsigned long)p, sizeof(unsigned long)))
+ goto err;
+
+ pc = unwind_graph_addr(state, *p, state->sp);
+ pc -= LOONGARCH_INSN_SIZE;
+ } else if (orc->ra_reg == ORC_REG_UNDEFINED) {
+ if (!state->ra || state->ra == state->pc)
+ goto err;
+
+ pc = unwind_graph_addr(state, state->ra, state->sp);
+ pc -= LOONGARCH_INSN_SIZE;
+ state->ra = 0;
+ } else {
+ orc_warn("unknown ra base reg %d at %pB\n",
+ orc->ra_reg, (void *)state->pc);
+ goto err;
+ }
+ break;
+ case UNWIND_HINT_TYPE_REGS:
+ if (state->stack_info.type == STACK_TYPE_IRQ && state->sp == info->end)
+ regs = (struct pt_regs *)info->next_sp;
+ else
+ regs = (struct pt_regs *)state->sp;
+
+ if (!stack_access_ok(state, (unsigned long)regs, sizeof(*regs)))
+ goto err;
+
+ if ((info->end == (unsigned long)regs + sizeof(*regs)) &&
+ !regs->regs[3] && !regs->regs[1])
+ goto end;
+
+ if (user_mode(regs))
+ goto end;
+
+ pc = regs->csr_era;
+ if (!__kernel_text_address(pc))
+ goto err;
+
+ state->sp = regs->regs[3];
+ state->ra = regs->regs[1];
+ state->fp = regs->regs[22];
+ get_stack_info(state->sp, state->task, info);
+
+ break;
+ default:
+ orc_warn("unknown .orc_unwind entry type %d at %pB\n",
+ orc->type, (void *)state->pc);
+ goto err;
+ }
+
+ state->pc = bt_address(pc);
+ if (!state->pc) {
+ pr_err("cannot find unwind pc at %pK\n", (void *)pc);
+ goto err;
+ }
+
+ if (!__kernel_text_address(state->pc))
+ goto err;
+
+ preempt_enable();
+ return true;
+
+err:
+ state->error = true;
+
+end:
+ preempt_enable();
+ state->stack_info.type = STACK_TYPE_UNKNOWN;
+ return false;
+}
+EXPORT_SYMBOL_GPL(unwind_next_frame);
diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S
index 0c7b041be9d8..ca19b83a2c8b 100644
--- a/arch/loongarch/kernel/vmlinux.lds.S
+++ b/arch/loongarch/kernel/vmlinux.lds.S
@@ -13,6 +13,7 @@
#define BSS_FIRST_SECTIONS *(.bss..swapper_pg_dir)

#include <asm-generic/vmlinux.lds.h>
+#include <asm-generic/orc_lookup.h>
#include "image-vars.h"

/*
@@ -74,6 +75,8 @@ SECTIONS
}
#endif

+ ORC_UNWIND_TABLE
+
.got : ALIGN(16) { *(.got) }
.plt : ALIGN(16) { *(.plt) }
.got.plt : ALIGN(16) { *(.got.plt) }
--
2.39.2