2023-11-29 13:07:19

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 0/8] Add objtool and orc support for LoongArch

This version is based on 6.7-rc3, tested with the latest upstream
gcc and binutils (20231127).

v5:
-- Fix the partial backtrace about irq stack
-- Check on_stack() again after get_stack_info()
-- Silence the orc warning about handle_syscall
-- Silence the objtool warnings if CONFIG_CPU_HAS_LBT=y
-- Update the commit message about objdump and readelf info

Tiezhu Yang (8):
objtool/LoongArch: Enable objtool to be built
objtool/LoongArch: Implement instruction decoder
objtool/x86: Separate arch-specific and generic parts
objtool/LoongArch: Enable orc to be built
objtool: Check local label about sibling call
objtool: Check local label in add_dead_ends()
objtool: Check local label in read_unwind_hints()
LoongArch: Add ORC stack unwinder support

arch/loongarch/Kconfig | 2 +
arch/loongarch/Kconfig.debug | 11 +
arch/loongarch/Makefile | 19 +
arch/loongarch/include/asm/Kbuild | 2 +
arch/loongarch/include/asm/bug.h | 1 +
arch/loongarch/include/asm/exception.h | 2 +
arch/loongarch/include/asm/module.h | 7 +
arch/loongarch/include/asm/orc_header.h | 18 +
arch/loongarch/include/asm/orc_lookup.h | 31 ++
arch/loongarch/include/asm/orc_types.h | 58 ++
arch/loongarch/include/asm/stackframe.h | 3 +
arch/loongarch/include/asm/unwind.h | 19 +-
arch/loongarch/include/asm/unwind_hints.h | 28 +
arch/loongarch/kernel/Makefile | 4 +
arch/loongarch/kernel/entry.S | 6 +-
arch/loongarch/kernel/fpu.S | 7 +
arch/loongarch/kernel/genex.S | 6 +-
arch/loongarch/kernel/lbt.S | 5 +
arch/loongarch/kernel/module.c | 22 +-
arch/loongarch/kernel/relocate_kernel.S | 2 +
arch/loongarch/kernel/setup.c | 2 +
arch/loongarch/kernel/stacktrace.c | 1 +
arch/loongarch/kernel/traps.c | 42 +-
arch/loongarch/kernel/unwind_orc.c | 516 ++++++++++++++++++
arch/loongarch/kernel/vmlinux.lds.S | 3 +
arch/loongarch/kvm/switch.S | 7 +-
arch/loongarch/lib/Makefile | 2 +
arch/loongarch/mm/tlb.c | 27 +-
arch/loongarch/mm/tlbex.S | 9 +
arch/loongarch/vdso/Makefile | 1 +
include/linux/compiler.h | 9 +
scripts/Makefile | 7 +-
tools/arch/loongarch/include/asm/inst.h | 161 ++++++
tools/arch/loongarch/include/asm/orc_types.h | 58 ++
tools/include/linux/bitops.h | 11 +
tools/objtool/Makefile | 4 +
tools/objtool/arch/loongarch/Build | 3 +
tools/objtool/arch/loongarch/decode.c | 356 ++++++++++++
.../arch/loongarch/include/arch/cfi_regs.h | 22 +
.../objtool/arch/loongarch/include/arch/elf.h | 30 +
.../arch/loongarch/include/arch/special.h | 33 ++
tools/objtool/arch/loongarch/orc.c | 171 ++++++
tools/objtool/arch/loongarch/special.c | 15 +
tools/objtool/arch/x86/Build | 1 +
tools/objtool/arch/x86/orc.c | 188 +++++++
tools/objtool/check.c | 117 ++--
tools/objtool/include/objtool/elf.h | 1 +
tools/objtool/include/objtool/orc.h | 14 +
tools/objtool/orc_dump.c | 69 +--
tools/objtool/orc_gen.c | 113 +---
50 files changed, 1983 insertions(+), 263 deletions(-)
create mode 100644 arch/loongarch/include/asm/orc_header.h
create mode 100644 arch/loongarch/include/asm/orc_lookup.h
create mode 100644 arch/loongarch/include/asm/orc_types.h
create mode 100644 arch/loongarch/include/asm/unwind_hints.h
create mode 100644 arch/loongarch/kernel/unwind_orc.c
create mode 100644 tools/arch/loongarch/include/asm/inst.h
create mode 100644 tools/arch/loongarch/include/asm/orc_types.h
create mode 100644 tools/objtool/arch/loongarch/Build
create mode 100644 tools/objtool/arch/loongarch/decode.c
create mode 100644 tools/objtool/arch/loongarch/include/arch/cfi_regs.h
create mode 100644 tools/objtool/arch/loongarch/include/arch/elf.h
create mode 100644 tools/objtool/arch/loongarch/include/arch/special.h
create mode 100644 tools/objtool/arch/loongarch/orc.c
create mode 100644 tools/objtool/arch/loongarch/special.c
create mode 100644 tools/objtool/arch/x86/orc.c
create mode 100644 tools/objtool/include/objtool/orc.h

--
2.42.0


2023-11-29 13:07:30

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 3/8] objtool/x86: Separate arch-specific and generic parts

Move init_orc_entry(), write_orc_entry(), reg_name(), orc_type_name()
and print_reg() from generic orc_gen.c and orc_dump.c to arch-specific
orc.c, then introduce a new function orc_print_dump() to print info.

This is preparation for later patch, no functionality change.

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Co-developed-by: Youling Tang <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
Signed-off-by: Tiezhu Yang <[email protected]>
---
tools/objtool/arch/x86/Build | 1 +
tools/objtool/arch/x86/orc.c | 188 ++++++++++++++++++++++++++++
tools/objtool/include/objtool/orc.h | 14 +++
tools/objtool/orc_dump.c | 69 +---------
tools/objtool/orc_gen.c | 113 +----------------
5 files changed, 206 insertions(+), 179 deletions(-)
create mode 100644 tools/objtool/arch/x86/orc.c
create mode 100644 tools/objtool/include/objtool/orc.h

diff --git a/tools/objtool/arch/x86/Build b/tools/objtool/arch/x86/Build
index 9f7869b5c5e0..3dedb2fd8f3a 100644
--- a/tools/objtool/arch/x86/Build
+++ b/tools/objtool/arch/x86/Build
@@ -1,5 +1,6 @@
objtool-y += special.o
objtool-y += decode.o
+objtool-y += orc.o

inat_tables_script = ../arch/x86/tools/gen-insn-attr-x86.awk
inat_tables_maps = ../arch/x86/lib/x86-opcode-map.txt
diff --git a/tools/objtool/arch/x86/orc.c b/tools/objtool/arch/x86/orc.c
new file mode 100644
index 000000000000..b6cd943e87f9
--- /dev/null
+++ b/tools/objtool/arch/x86/orc.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <linux/objtool_types.h>
+#include <asm/orc_types.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];
+
+ memset(orc, 0, sizeof(*orc));
+
+ if (!cfi) {
+ /*
+ * This is usually either unreachable nops/traps (which don't
+ * trigger unreachable instruction warnings), or
+ * STACK_FRAME_NON_STANDARD functions.
+ */
+ orc->type = ORC_TYPE_UNDEFINED;
+ return 0;
+ }
+
+ switch (cfi->type) {
+ case UNWIND_HINT_TYPE_UNDEFINED:
+ orc->type = ORC_TYPE_UNDEFINED;
+ return 0;
+ case UNWIND_HINT_TYPE_END_OF_STACK:
+ orc->type = ORC_TYPE_END_OF_STACK;
+ return 0;
+ case UNWIND_HINT_TYPE_CALL:
+ orc->type = ORC_TYPE_CALL;
+ break;
+ case UNWIND_HINT_TYPE_REGS:
+ orc->type = ORC_TYPE_REGS;
+ break;
+ case UNWIND_HINT_TYPE_REGS_PARTIAL:
+ orc->type = ORC_TYPE_REGS_PARTIAL;
+ break;
+ default:
+ WARN_INSN(insn, "unknown unwind hint type %d", cfi->type);
+ return -1;
+ }
+
+ orc->signal = cfi->signal;
+
+ switch (cfi->cfa.base) {
+ case CFI_SP:
+ orc->sp_reg = ORC_REG_SP;
+ break;
+ case CFI_SP_INDIRECT:
+ orc->sp_reg = ORC_REG_SP_INDIRECT;
+ break;
+ case CFI_BP:
+ orc->sp_reg = ORC_REG_BP;
+ break;
+ case CFI_BP_INDIRECT:
+ orc->sp_reg = ORC_REG_BP_INDIRECT;
+ break;
+ case CFI_R10:
+ orc->sp_reg = ORC_REG_R10;
+ break;
+ case CFI_R13:
+ orc->sp_reg = ORC_REG_R13;
+ break;
+ case CFI_DI:
+ orc->sp_reg = ORC_REG_DI;
+ break;
+ case CFI_DX:
+ orc->sp_reg = ORC_REG_DX;
+ break;
+ default:
+ WARN_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base);
+ return -1;
+ }
+
+ switch (bp->base) {
+ case CFI_UNDEFINED:
+ orc->bp_reg = ORC_REG_UNDEFINED;
+ break;
+ case CFI_CFA:
+ orc->bp_reg = ORC_REG_PREV_SP;
+ break;
+ case CFI_BP:
+ orc->bp_reg = ORC_REG_BP;
+ break;
+ default:
+ WARN_INSN(insn, "unknown BP base reg %d", bp->base);
+ return -1;
+ }
+
+ orc->sp_offset = cfi->cfa.offset;
+ orc->bp_offset = bp->offset;
+
+ return 0;
+}
+
+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,
+ struct orc_entry *o)
+{
+ struct orc_entry *orc;
+
+ /* populate ORC data */
+ orc = (struct orc_entry *)orc_sec->data->d_buf + idx;
+ 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);
+
+ /* populate reloc for ip */
+ if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx,
+ insn_sec, insn_off))
+ return -1;
+
+ return 0;
+}
+
+static const char *reg_name(unsigned int reg)
+{
+ switch (reg) {
+ case ORC_REG_PREV_SP:
+ return "prevsp";
+ case ORC_REG_DX:
+ return "dx";
+ case ORC_REG_DI:
+ return "di";
+ case ORC_REG_BP:
+ return "bp";
+ case ORC_REG_SP:
+ return "sp";
+ case ORC_REG_R10:
+ return "r10";
+ case ORC_REG_R13:
+ return "r13";
+ case ORC_REG_BP_INDIRECT:
+ return "bp(ind)";
+ case ORC_REG_SP_INDIRECT:
+ return "sp(ind)";
+ default:
+ return "?";
+ }
+}
+
+static const char *orc_type_name(unsigned int type)
+{
+ switch (type) {
+ case ORC_TYPE_UNDEFINED:
+ return "(und)";
+ case ORC_TYPE_END_OF_STACK:
+ return "end";
+ case ORC_TYPE_CALL:
+ return "call";
+ case ORC_TYPE_REGS:
+ return "regs";
+ case ORC_TYPE_REGS_PARTIAL:
+ return "regs (partial)";
+ default:
+ return "?";
+ }
+}
+
+static void print_reg(unsigned int reg, int offset)
+{
+ if (reg == ORC_REG_BP_INDIRECT)
+ printf("(bp%+d)", offset);
+ else if (reg == ORC_REG_SP_INDIRECT)
+ printf("(sp)%+d", offset);
+ else if (reg == ORC_REG_UNDEFINED)
+ printf("(und)");
+ else
+ printf("%s%+d", reg_name(reg), offset);
+}
+
+void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i)
+{
+ printf("type:%s", orc_type_name(orc[i].type));
+
+ printf(" sp:");
+ print_reg(orc[i].sp_reg, bswap_if_needed(dummy_elf, orc[i].sp_offset));
+
+ printf(" bp:");
+ print_reg(orc[i].bp_reg, bswap_if_needed(dummy_elf, orc[i].bp_offset));
+
+ printf(" signal:%d\n", orc[i].signal);
+}
diff --git a/tools/objtool/include/objtool/orc.h b/tools/objtool/include/objtool/orc.h
new file mode 100644
index 000000000000..15a32def1071
--- /dev/null
+++ b/tools/objtool/include/objtool/orc.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _OBJTOOL_ORC_H
+#define _OBJTOOL_ORC_H
+
+#include <objtool/check.h>
+
+int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, struct instruction *insn);
+void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i);
+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,
+ struct orc_entry *o);
+
+#endif /* _OBJTOOL_ORC_H */
diff --git a/tools/objtool/orc_dump.c b/tools/objtool/orc_dump.c
index 0e183bb1c720..a62247efb64f 100644
--- a/tools/objtool/orc_dump.c
+++ b/tools/objtool/orc_dump.c
@@ -6,65 +6,10 @@
#include <unistd.h>
#include <asm/orc_types.h>
#include <objtool/objtool.h>
+#include <objtool/orc.h>
#include <objtool/warn.h>
#include <objtool/endianness.h>

-static const char *reg_name(unsigned int reg)
-{
- switch (reg) {
- case ORC_REG_PREV_SP:
- return "prevsp";
- case ORC_REG_DX:
- return "dx";
- case ORC_REG_DI:
- return "di";
- case ORC_REG_BP:
- return "bp";
- case ORC_REG_SP:
- return "sp";
- case ORC_REG_R10:
- return "r10";
- case ORC_REG_R13:
- return "r13";
- case ORC_REG_BP_INDIRECT:
- return "bp(ind)";
- case ORC_REG_SP_INDIRECT:
- return "sp(ind)";
- default:
- return "?";
- }
-}
-
-static const char *orc_type_name(unsigned int type)
-{
- switch (type) {
- case ORC_TYPE_UNDEFINED:
- return "(und)";
- case ORC_TYPE_END_OF_STACK:
- return "end";
- case ORC_TYPE_CALL:
- return "call";
- case ORC_TYPE_REGS:
- return "regs";
- case ORC_TYPE_REGS_PARTIAL:
- return "regs (partial)";
- default:
- return "?";
- }
-}
-
-static void print_reg(unsigned int reg, int offset)
-{
- if (reg == ORC_REG_BP_INDIRECT)
- printf("(bp%+d)", offset);
- else if (reg == ORC_REG_SP_INDIRECT)
- printf("(sp)%+d", offset);
- else if (reg == ORC_REG_UNDEFINED)
- printf("(und)");
- else
- printf("%s%+d", reg_name(reg), offset);
-}
-
int orc_dump(const char *_objname)
{
int fd, nr_entries, i, *orc_ip = NULL, orc_size = 0;
@@ -205,17 +150,7 @@ int orc_dump(const char *_objname)
printf("%llx:", (unsigned long long)(orc_ip_addr + (i * sizeof(int)) + orc_ip[i]));
}

- printf("type:%s", orc_type_name(orc[i].type));
-
- printf(" sp:");
-
- print_reg(orc[i].sp_reg, bswap_if_needed(&dummy_elf, orc[i].sp_offset));
-
- printf(" bp:");
-
- print_reg(orc[i].bp_reg, bswap_if_needed(&dummy_elf, orc[i].bp_offset));
-
- printf(" signal:%d\n", orc[i].signal);
+ orc_print_dump(&dummy_elf, orc, i);
}

elf_end(elf);
diff --git a/tools/objtool/orc_gen.c b/tools/objtool/orc_gen.c
index bae343908867..922e6aac7cea 100644
--- a/tools/objtool/orc_gen.c
+++ b/tools/objtool/orc_gen.c
@@ -10,121 +10,10 @@
#include <asm/orc_types.h>

#include <objtool/check.h>
+#include <objtool/orc.h>
#include <objtool/warn.h>
#include <objtool/endianness.h>

-static int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi,
- struct instruction *insn)
-{
- struct cfi_reg *bp = &cfi->regs[CFI_BP];
-
- memset(orc, 0, sizeof(*orc));
-
- if (!cfi) {
- /*
- * This is usually either unreachable nops/traps (which don't
- * trigger unreachable instruction warnings), or
- * STACK_FRAME_NON_STANDARD functions.
- */
- orc->type = ORC_TYPE_UNDEFINED;
- return 0;
- }
-
- switch (cfi->type) {
- case UNWIND_HINT_TYPE_UNDEFINED:
- orc->type = ORC_TYPE_UNDEFINED;
- return 0;
- case UNWIND_HINT_TYPE_END_OF_STACK:
- orc->type = ORC_TYPE_END_OF_STACK;
- return 0;
- case UNWIND_HINT_TYPE_CALL:
- orc->type = ORC_TYPE_CALL;
- break;
- case UNWIND_HINT_TYPE_REGS:
- orc->type = ORC_TYPE_REGS;
- break;
- case UNWIND_HINT_TYPE_REGS_PARTIAL:
- orc->type = ORC_TYPE_REGS_PARTIAL;
- break;
- default:
- WARN_INSN(insn, "unknown unwind hint type %d", cfi->type);
- return -1;
- }
-
- orc->signal = cfi->signal;
-
- switch (cfi->cfa.base) {
- case CFI_SP:
- orc->sp_reg = ORC_REG_SP;
- break;
- case CFI_SP_INDIRECT:
- orc->sp_reg = ORC_REG_SP_INDIRECT;
- break;
- case CFI_BP:
- orc->sp_reg = ORC_REG_BP;
- break;
- case CFI_BP_INDIRECT:
- orc->sp_reg = ORC_REG_BP_INDIRECT;
- break;
- case CFI_R10:
- orc->sp_reg = ORC_REG_R10;
- break;
- case CFI_R13:
- orc->sp_reg = ORC_REG_R13;
- break;
- case CFI_DI:
- orc->sp_reg = ORC_REG_DI;
- break;
- case CFI_DX:
- orc->sp_reg = ORC_REG_DX;
- break;
- default:
- WARN_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base);
- return -1;
- }
-
- switch (bp->base) {
- case CFI_UNDEFINED:
- orc->bp_reg = ORC_REG_UNDEFINED;
- break;
- case CFI_CFA:
- orc->bp_reg = ORC_REG_PREV_SP;
- break;
- case CFI_BP:
- orc->bp_reg = ORC_REG_BP;
- break;
- default:
- WARN_INSN(insn, "unknown BP base reg %d", bp->base);
- return -1;
- }
-
- orc->sp_offset = cfi->cfa.offset;
- orc->bp_offset = bp->offset;
-
- return 0;
-}
-
-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,
- struct orc_entry *o)
-{
- struct orc_entry *orc;
-
- /* populate ORC data */
- orc = (struct orc_entry *)orc_sec->data->d_buf + idx;
- 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);
-
- /* populate reloc for ip */
- if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx,
- insn_sec, insn_off))
- return -1;
-
- return 0;
-}
-
struct orc_list_entry {
struct list_head list;
struct orc_entry orc;
--
2.42.0

2023-11-29 13:07:34

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 4/8] objtool/LoongArch: Enable orc to be built

Implement arch-specific init_orc_entry(), write_orc_entry(), reg_name(),
orc_type_name(), print_reg() and orc_print_dump(), then set BUILD_ORC as
y to build the orc related files.

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Co-developed-by: Youling Tang <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
Signed-off-by: Tiezhu Yang <[email protected]>
---
tools/arch/loongarch/include/asm/orc_types.h | 58 +++++++
tools/objtool/Makefile | 4 +
tools/objtool/arch/loongarch/Build | 1 +
tools/objtool/arch/loongarch/decode.c | 16 ++
tools/objtool/arch/loongarch/orc.c | 171 +++++++++++++++++++
5 files changed, 250 insertions(+)
create mode 100644 tools/arch/loongarch/include/asm/orc_types.h
create mode 100644 tools/objtool/arch/loongarch/orc.c

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..caf1f71a1057
--- /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>
+
+/*
+ * 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 FP -- which the previous SP
+ * is usually based on -- and PREV_SP and UNDEFINED -- which the previous FP 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_FP 3
+#define ORC_REG_MAX 4
+
+#define ORC_TYPE_UNDEFINED 0
+#define ORC_TYPE_END_OF_STACK 1
+#define ORC_TYPE_CALL 2
+#define ORC_TYPE_REGS 3
+#define ORC_TYPE_REGS_PARTIAL 4
+
+#ifndef __ASSEMBLY__
+/*
+ * 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 FP (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 fp_offset;
+ s16 ra_offset;
+ unsigned int sp_reg:4;
+ unsigned int fp_reg:4;
+ unsigned int ra_reg:4;
+ unsigned int type:3;
+ unsigned int signal:1;
+};
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ORC_TYPES_H */
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 83b100c1e7f6..bf7f7f84ac62 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -57,6 +57,10 @@ ifeq ($(SRCARCH),x86)
BUILD_ORC := y
endif

+ifeq ($(SRCARCH),loongarch)
+ BUILD_ORC := y
+endif
+
export BUILD_ORC
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 ff0b53144d12..aee479d2191c 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -3,6 +3,8 @@
#include <objtool/check.h>
#include <objtool/warn.h>
#include <asm/inst.h>
+#include <asm/orc_types.h>
+#include <linux/objtool_types.h>

#ifndef EM_LOONGARCH
#define EM_LOONGARCH 258
@@ -42,6 +44,20 @@ bool arch_callee_saved_reg(unsigned char reg)

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

diff --git a/tools/objtool/arch/loongarch/orc.c b/tools/objtool/arch/loongarch/orc.c
new file mode 100644
index 000000000000..873536d009d9
--- /dev/null
+++ b/tools/objtool/arch/loongarch/orc.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <linux/objtool_types.h>
+#include <asm/orc_types.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 *fp = &cfi->regs[CFI_FP];
+ struct cfi_reg *ra = &cfi->regs[CFI_RA];
+
+ memset(orc, 0, sizeof(*orc));
+
+ if (!cfi) {
+ /*
+ * This is usually either unreachable nops/traps (which don't
+ * trigger unreachable instruction warnings), or
+ * STACK_FRAME_NON_STANDARD functions.
+ */
+ orc->type = ORC_TYPE_UNDEFINED;
+ return 0;
+ }
+
+ switch (cfi->type) {
+ case UNWIND_HINT_TYPE_UNDEFINED:
+ orc->type = ORC_TYPE_UNDEFINED;
+ return 0;
+ case UNWIND_HINT_TYPE_END_OF_STACK:
+ orc->type = ORC_TYPE_END_OF_STACK;
+ return 0;
+ case UNWIND_HINT_TYPE_CALL:
+ orc->type = ORC_TYPE_CALL;
+ break;
+ case UNWIND_HINT_TYPE_REGS:
+ orc->type = ORC_TYPE_REGS;
+ break;
+ case UNWIND_HINT_TYPE_REGS_PARTIAL:
+ orc->type = ORC_TYPE_REGS_PARTIAL;
+ break;
+ default:
+ WARN_INSN(insn, "unknown unwind hint type %d", cfi->type);
+ return -1;
+ }
+
+ orc->signal = cfi->signal;
+
+ switch (cfi->cfa.base) {
+ case CFI_SP:
+ orc->sp_reg = ORC_REG_SP;
+ break;
+ case CFI_FP:
+ orc->sp_reg = ORC_REG_FP;
+ break;
+ default:
+ WARN_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base);
+ return -1;
+ }
+
+ switch (fp->base) {
+ case CFI_UNDEFINED:
+ orc->fp_reg = ORC_REG_UNDEFINED;
+ orc->fp_offset = 0;
+ break;
+ case CFI_CFA:
+ orc->fp_reg = ORC_REG_PREV_SP;
+ orc->fp_offset = fp->offset;
+ break;
+ case CFI_FP:
+ orc->fp_reg = ORC_REG_FP;
+ break;
+ default:
+ WARN_INSN(insn, "unknown FP base reg %d", fp->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;
+ case CFI_FP:
+ orc->ra_reg = ORC_REG_FP;
+ break;
+ default:
+ WARN_INSN(insn, "unknown RA base reg %d", ra->base);
+ return -1;
+ }
+
+ orc->sp_offset = cfi->cfa.offset;
+
+ return 0;
+}
+
+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,
+ struct orc_entry *o)
+{
+ struct orc_entry *orc;
+
+ /* populate ORC data */
+ orc = (struct orc_entry *)orc_sec->data->d_buf + idx;
+ memcpy(orc, o, sizeof(*orc));
+
+ /* populate reloc for ip */
+ if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx,
+ insn_sec, insn_off))
+ return -1;
+
+ return 0;
+}
+
+static const char *reg_name(unsigned int reg)
+{
+ switch (reg) {
+ case ORC_REG_SP:
+ return "sp";
+ case ORC_REG_FP:
+ 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 print_reg(unsigned int reg, int offset)
+{
+ if (reg == ORC_REG_UNDEFINED)
+ printf(" (und) ");
+ else
+ printf("%s + %3d", reg_name(reg), offset);
+
+}
+
+void orc_print_dump(struct elf *dummy_elf, struct orc_entry *orc, int i)
+{
+ printf("type:%s", orc_type_name(orc[i].type));
+
+ printf(" sp:");
+ print_reg(orc[i].sp_reg, orc[i].sp_offset);
+
+ printf(" fp:");
+ print_reg(orc[i].fp_reg, orc[i].fp_offset);
+
+ printf(" ra:");
+ print_reg(orc[i].ra_reg, orc[i].ra_offset);
+
+ printf(" signal:%d\n", orc[i].signal);
+}
--
2.42.0

2023-11-29 13:07:42

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 7/8] objtool: Check local label in read_unwind_hints()

When update the latest upstream gcc and binutils which enables linker
relaxation by default, it generates more objtool warnings on LoongArch.

We can see that the reloc sym name is local label instead of section
in relocation section '.rela.discard.unwind_hints', in this case, the
reloc sym type is STT_NOTYPE instead of STT_SECTION. Let us check it
to not return -1, then use reloc->sym->offset instead of reloc addend
which is 0 to find the corresponding instruction.

Here are some detailed info:
[fedora@linux 6.7.test]$ gcc --version
gcc (GCC) 14.0.0 20231127 (experimental)
[fedora@linux 6.7.test]$ as --version
GNU assembler (GNU Binutils) 2.41.50.20231127
[fedora@linux 6.7.test]$ readelf -r arch/loongarch/kernel/entry.o | grep -A 3 "rela.discard.unwind_hints"
Relocation section '.rela.discard.unwind_hints' at offset 0x420 contains 7 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000000 001300000063 R_LARCH_32_PCREL 0000000000000000 .Lhere_1 + 0
00000000000c 001400000063 R_LARCH_32_PCREL 00000000000000a8 .Lhere_50 + 0

Signed-off-by: Tiezhu Yang <[email protected]>
---
tools/objtool/check.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 395fce41fb81..039b8ac53da6 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -2240,6 +2240,7 @@ static int read_unwind_hints(struct objtool_file *file)
struct unwind_hint *hint;
struct instruction *insn;
struct reloc *reloc;
+ unsigned long offset;
int i;

sec = find_section_by_name(file->elf, ".discard.unwind_hints");
@@ -2267,7 +2268,16 @@ static int read_unwind_hints(struct objtool_file *file)
return -1;
}

- insn = find_insn(file, reloc->sym->sec, reloc_addend(reloc));
+ if (reloc->sym->type == STT_SECTION) {
+ offset = reloc_addend(reloc);
+ } else if (reloc->sym->local_label) {
+ offset = reloc->sym->offset;
+ } else {
+ WARN("unexpected relocation symbol type in %s", sec->rsec->name);
+ return -1;
+ }
+
+ insn = find_insn(file, reloc->sym->sec, offset);
if (!insn) {
WARN("can't find insn for unwind_hints[%d]", i);
return -1;
--
2.42.0

2023-11-29 13:08:04

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 2/8] objtool/LoongArch: Implement instruction decoder

Only copy the minimal definitions of instruction opcodes and formats
in inst.h from arch/loongarch to tools/arch/loongarch, and also copy
the definition of sign_extend64() to tools/include/linux/bitops.h to
decode the following kinds of instructions:

(1) stack pointer related instructions
addi.d, ld.d, st.d, ldptr.d and stptr.d

(2) branch and jump related instructions
beq, bne, blt, bge, bltu, bgeu, beqz, bnez, bceqz, bcnez, b, bl and jirl

(3) other instructions
break, nop and ertn

See more info about instructions in LoongArch Reference Manual:
https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Co-developed-by: Youling Tang <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
Signed-off-by: Tiezhu Yang <[email protected]>
---
tools/arch/loongarch/include/asm/inst.h | 161 ++++++++++++++
tools/include/linux/bitops.h | 11 +
tools/objtool/arch/loongarch/decode.c | 273 +++++++++++++++++++++++-
3 files changed, 443 insertions(+), 2 deletions(-)
create mode 100644 tools/arch/loongarch/include/asm/inst.h

diff --git a/tools/arch/loongarch/include/asm/inst.h b/tools/arch/loongarch/include/asm/inst.h
new file mode 100644
index 000000000000..c25b5853181d
--- /dev/null
+++ b/tools/arch/loongarch/include/asm/inst.h
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
+ */
+#ifndef _ASM_INST_H
+#define _ASM_INST_H
+
+#include <linux/bitops.h>
+
+#define LOONGARCH_INSN_NOP 0x03400000
+
+enum reg0i15_op {
+ break_op = 0x54,
+};
+
+enum reg0i26_op {
+ b_op = 0x14,
+ bl_op = 0x15,
+};
+
+enum reg1i21_op {
+ beqz_op = 0x10,
+ bnez_op = 0x11,
+ bceqz_op = 0x12, /* bits[9:8] = 0x00 */
+ bcnez_op = 0x12, /* bits[9:8] = 0x01 */
+};
+
+enum reg2_op {
+ ertn_op = 0x1920e,
+};
+
+enum reg2i12_op {
+ addid_op = 0x0b,
+ andi_op = 0x0d,
+ ldd_op = 0xa3,
+ std_op = 0xa7,
+};
+
+enum reg2i14_op {
+ ldptrd_op = 0x26,
+ stptrd_op = 0x27,
+};
+
+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 reg0i15_format {
+ unsigned int immediate : 15;
+ unsigned int opcode : 17;
+};
+
+struct reg0i26_format {
+ unsigned int immediate_h : 10;
+ unsigned int immediate_l : 16;
+ unsigned int opcode : 6;
+};
+
+struct reg1i21_format {
+ unsigned int immediate_h : 5;
+ unsigned int rj : 5;
+ unsigned int immediate_l : 16;
+ unsigned int opcode : 6;
+};
+
+struct reg2_format {
+ unsigned int rd : 5;
+ unsigned int rj : 5;
+ unsigned int opcode : 22;
+};
+
+struct reg2i12_format {
+ unsigned int rd : 5;
+ unsigned int rj : 5;
+ unsigned int immediate : 12;
+ unsigned int opcode : 10;
+};
+
+struct reg2i14_format {
+ unsigned int rd : 5;
+ unsigned int rj : 5;
+ unsigned int immediate : 14;
+ unsigned int opcode : 8;
+};
+
+struct reg2i16_format {
+ unsigned int rd : 5;
+ unsigned int rj : 5;
+ unsigned int immediate : 16;
+ unsigned int opcode : 6;
+};
+
+union loongarch_instruction {
+ unsigned int word;
+ struct reg0i15_format reg0i15_format;
+ struct reg0i26_format reg0i26_format;
+ struct reg1i21_format reg1i21_format;
+ struct reg2_format reg2_format;
+ struct reg2i12_format reg2i12_format;
+ struct reg2i14_format reg2i14_format;
+ struct reg2i16_format reg2i16_format;
+};
+
+#define LOONGARCH_INSN_SIZE sizeof(union loongarch_instruction)
+
+enum loongarch_gpr {
+ LOONGARCH_GPR_ZERO = 0,
+ LOONGARCH_GPR_RA = 1,
+ LOONGARCH_GPR_TP = 2,
+ LOONGARCH_GPR_SP = 3,
+ LOONGARCH_GPR_A0 = 4, /* Reused as V0 for return value */
+ LOONGARCH_GPR_A1, /* Reused as V1 for return value */
+ LOONGARCH_GPR_A2,
+ LOONGARCH_GPR_A3,
+ LOONGARCH_GPR_A4,
+ LOONGARCH_GPR_A5,
+ LOONGARCH_GPR_A6,
+ LOONGARCH_GPR_A7,
+ LOONGARCH_GPR_T0 = 12,
+ LOONGARCH_GPR_T1,
+ LOONGARCH_GPR_T2,
+ LOONGARCH_GPR_T3,
+ LOONGARCH_GPR_T4,
+ LOONGARCH_GPR_T5,
+ LOONGARCH_GPR_T6,
+ LOONGARCH_GPR_T7,
+ LOONGARCH_GPR_T8,
+ LOONGARCH_GPR_FP = 22,
+ LOONGARCH_GPR_S0 = 23,
+ LOONGARCH_GPR_S1,
+ LOONGARCH_GPR_S2,
+ LOONGARCH_GPR_S3,
+ LOONGARCH_GPR_S4,
+ LOONGARCH_GPR_S5,
+ LOONGARCH_GPR_S6,
+ LOONGARCH_GPR_S7,
+ LOONGARCH_GPR_S8,
+ LOONGARCH_GPR_MAX
+};
+
+#define DEF_EMIT_REG2I16_FORMAT(NAME, OP) \
+static inline void emit_##NAME(union loongarch_instruction *insn, \
+ enum loongarch_gpr rj, \
+ enum loongarch_gpr rd, \
+ int offset) \
+{ \
+ insn->reg2i16_format.opcode = OP; \
+ insn->reg2i16_format.immediate = offset; \
+ insn->reg2i16_format.rj = rj; \
+ insn->reg2i16_format.rd = rd; \
+}
+
+DEF_EMIT_REG2I16_FORMAT(jirl, jirl_op)
+
+#endif /* _ASM_INST_H */
diff --git a/tools/include/linux/bitops.h b/tools/include/linux/bitops.h
index f18683b95ea6..7319f6ced108 100644
--- a/tools/include/linux/bitops.h
+++ b/tools/include/linux/bitops.h
@@ -87,4 +87,15 @@ static inline __u32 rol32(__u32 word, unsigned int shift)
return (word << shift) | (word >> ((-shift) & 31));
}

+/**
+ * sign_extend64 - sign extend a 64-bit value using specified bit as sign-bit
+ * @value: value to sign extend
+ * @index: 0 based bit index (0<=index<64) to sign bit
+ */
+static __always_inline __s64 sign_extend64(__u64 value, int index)
+{
+ __u8 shift = 63 - index;
+ return (__s64)(value << shift) >> shift;
+}
+
#endif
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index cc74ba4e0f54..ff0b53144d12 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -1,6 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <objtool/check.h>
+#include <objtool/warn.h>
+#include <asm/inst.h>
+
+#ifndef EM_LOONGARCH
+#define EM_LOONGARCH 258
+#endif

int arch_ftrace_match(char *name)
{
@@ -39,21 +45,284 @@ int arch_decode_hint_reg(u8 sp_reg, int *base)
return 0;
}

+static bool is_loongarch(const struct elf *elf)
+{
+ if (elf->ehdr.e_machine == EM_LOONGARCH)
+ return true;
+
+ WARN("unexpected ELF machine type %d", elf->ehdr.e_machine);
+ return false;
+}
+
+#define ADD_OP(op) \
+ if (!(op = calloc(1, sizeof(*op)))) \
+ return -1; \
+ else for (*ops_list = op, ops_list = &op->next; op; op = NULL)
+
+static bool decode_insn_reg0i26_fomat(union loongarch_instruction inst,
+ struct instruction *insn)
+{
+ switch (inst.reg0i26_format.opcode) {
+ case b_op:
+ insn->type = INSN_JUMP_UNCONDITIONAL;
+ insn->immediate = sign_extend64(inst.reg0i26_format.immediate_h << 16 |
+ inst.reg0i26_format.immediate_l, 25);
+ break;
+ case bl_op:
+ insn->type = INSN_CALL;
+ insn->immediate = sign_extend64(inst.reg0i26_format.immediate_h << 16 |
+ inst.reg0i26_format.immediate_l, 25);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool decode_insn_reg1i21_fomat(union loongarch_instruction inst,
+ struct instruction *insn)
+{
+ switch (inst.reg1i21_format.opcode) {
+ case beqz_op:
+ case bnez_op:
+ case bceqz_op:
+ insn->type = INSN_JUMP_CONDITIONAL;
+ insn->immediate = sign_extend64(inst.reg1i21_format.immediate_h << 16 |
+ inst.reg1i21_format.immediate_l, 20);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool decode_insn_reg2i12_fomat(union loongarch_instruction inst,
+ struct instruction *insn,
+ struct stack_op **ops_list,
+ struct stack_op *op)
+{
+ switch (inst.reg2i12_format.opcode) {
+ case addid_op:
+ if ((inst.reg2i12_format.rd == CFI_SP) || (inst.reg2i12_format.rj == CFI_SP)) {
+ /* addi.d sp,sp,si12 or addi.d fp,sp,si12 */
+ insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
+ ADD_OP(op) {
+ op->src.type = OP_SRC_ADD;
+ op->src.reg = inst.reg2i12_format.rj;
+ op->src.offset = insn->immediate;
+ op->dest.type = OP_DEST_REG;
+ op->dest.reg = inst.reg2i12_format.rd;
+ }
+ }
+ break;
+ case ldd_op:
+ if (inst.reg2i12_format.rj == CFI_SP) {
+ /* ld.d rd,sp,si12 */
+ insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
+ ADD_OP(op) {
+ op->src.type = OP_SRC_REG_INDIRECT;
+ op->src.reg = CFI_SP;
+ op->src.offset = insn->immediate;
+ op->dest.type = OP_DEST_REG;
+ op->dest.reg = inst.reg2i12_format.rd;
+ }
+ }
+ break;
+ case std_op:
+ if (inst.reg2i12_format.rj == CFI_SP) {
+ /* st.d rd,sp,si12 */
+ insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
+ ADD_OP(op) {
+ op->src.type = OP_SRC_REG;
+ op->src.reg = inst.reg2i12_format.rd;
+ op->dest.type = OP_DEST_REG_INDIRECT;
+ op->dest.reg = CFI_SP;
+ op->dest.offset = insn->immediate;
+ }
+ }
+ break;
+ case andi_op:
+ if (inst.reg2i12_format.rd == 0 &&
+ inst.reg2i12_format.rj == 0 &&
+ inst.reg2i12_format.immediate == 0)
+ /* andi r0,r0,0 */
+ insn->type = INSN_NOP;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool decode_insn_reg2i14_fomat(union loongarch_instruction inst,
+ struct instruction *insn,
+ struct stack_op **ops_list,
+ struct stack_op *op)
+{
+ switch (inst.reg2i14_format.opcode) {
+ case ldptrd_op:
+ if (inst.reg2i14_format.rj == CFI_SP) {
+ /* ldptr.d rd,sp,si14 */
+ insn->immediate = sign_extend64(inst.reg2i14_format.immediate, 13);
+ ADD_OP(op) {
+ op->src.type = OP_SRC_REG_INDIRECT;
+ op->src.reg = CFI_SP;
+ op->src.offset = insn->immediate;
+ op->dest.type = OP_DEST_REG;
+ op->dest.reg = inst.reg2i14_format.rd;
+ }
+ }
+ break;
+ case stptrd_op:
+ if (inst.reg2i14_format.rj == CFI_SP) {
+ /* stptr.d ra,sp,0 */
+ if (inst.reg2i14_format.rd == LOONGARCH_GPR_RA &&
+ inst.reg2i14_format.immediate == 0)
+ break;
+
+ /* stptr.d rd,sp,si14 */
+ insn->immediate = sign_extend64(inst.reg2i14_format.immediate, 13);
+ ADD_OP(op) {
+ op->src.type = OP_SRC_REG;
+ op->src.reg = inst.reg2i14_format.rd;
+ op->dest.type = OP_DEST_REG_INDIRECT;
+ op->dest.reg = CFI_SP;
+ op->dest.offset = insn->immediate;
+ }
+ }
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+static bool decode_insn_reg2i16_fomat(union loongarch_instruction inst,
+ struct instruction *insn)
+{
+ switch (inst.reg2i16_format.opcode) {
+ case jirl_op:
+ if (inst.reg2i16_format.rd == 0 &&
+ inst.reg2i16_format.rj == CFI_RA &&
+ inst.reg2i16_format.immediate == 0) {
+ /* jirl r0,ra,0 */
+ insn->type = INSN_RETURN;
+ } else if (inst.reg2i16_format.rd == CFI_RA) {
+ /* jirl ra,rj,offs16 */
+ insn->type = INSN_CALL_DYNAMIC;
+ } else if (inst.reg2i16_format.rd == CFI_A0 &&
+ inst.reg2i16_format.immediate == 0) {
+ /*
+ * jirl a0,t0,0
+ * this is a special case in loongarch_suspend_enter,
+ * just treat it as a call instruction.
+ */
+ insn->type = INSN_CALL_DYNAMIC;
+ } else if (inst.reg2i16_format.rd == 0 &&
+ inst.reg2i16_format.immediate == 0) {
+ /* jirl r0,rj,0 */
+ insn->type = INSN_JUMP_DYNAMIC;
+ } else if (inst.reg2i16_format.rd == 0 &&
+ inst.reg2i16_format.immediate != 0) {
+ /*
+ * jirl r0,t0,12
+ * this is a rare case in JUMP_VIRT_ADDR,
+ * just ignore it due to it is harmless for tracing.
+ */
+ break;
+ } else {
+ /* jirl rd,rj,offs16 */
+ insn->type = INSN_JUMP_UNCONDITIONAL;
+ insn->immediate = sign_extend64(inst.reg2i16_format.immediate, 15);
+ }
+ break;
+ case beq_op:
+ case bne_op:
+ case blt_op:
+ case bge_op:
+ case bltu_op:
+ case bgeu_op:
+ insn->type = INSN_JUMP_CONDITIONAL;
+ insn->immediate = sign_extend64(inst.reg2i16_format.immediate, 15);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
unsigned long offset, unsigned int maxlen,
struct instruction *insn)
{
+ struct stack_op **ops_list = &insn->stack_ops;
+ const struct elf *elf = file->elf;
+ struct stack_op *op = NULL;
+ union loongarch_instruction inst;
+
+ if (!is_loongarch(elf))
+ return -1;
+
+ if (maxlen < LOONGARCH_INSN_SIZE)
+ return 0;
+
+ insn->len = LOONGARCH_INSN_SIZE;
+ insn->type = INSN_OTHER;
+ insn->immediate = 0;
+
+ inst = *(union loongarch_instruction *)(sec->data->d_buf + offset);
+
+ if (decode_insn_reg0i26_fomat(inst, insn))
+ return 0;
+ if (decode_insn_reg1i21_fomat(inst, insn))
+ return 0;
+ if (decode_insn_reg2i12_fomat(inst, insn, ops_list, op))
+ return 0;
+ if (decode_insn_reg2i14_fomat(inst, insn, ops_list, op))
+ return 0;
+ if (decode_insn_reg2i16_fomat(inst, insn))
+ return 0;
+
+ if (inst.word == 0)
+ insn->type = INSN_NOP;
+ else if (inst.reg0i15_format.opcode == break_op) {
+ /* break */
+ insn->type = INSN_BUG;
+ } else if (inst.reg2_format.opcode == ertn_op) {
+ /* ertn */
+ insn->type = INSN_RETURN;
+ }
+
return 0;
}

const char *arch_nop_insn(int len)
{
- return NULL;
+ static u32 nop;
+
+ if (len != LOONGARCH_INSN_SIZE)
+ WARN("invalid NOP size: %d\n", len);
+
+ nop = LOONGARCH_INSN_NOP;
+
+ return (const char *)&nop;
}

const char *arch_ret_insn(int len)
{
- return NULL;
+ static u32 ret;
+
+ if (len != LOONGARCH_INSN_SIZE)
+ WARN("invalid RET size: %d\n", len);
+
+ emit_jirl((union loongarch_instruction *)&ret, LOONGARCH_GPR_RA, LOONGARCH_GPR_ZERO, 0);
+
+ return (const char *)&ret;
}

void arch_initial_func_cfi_state(struct cfi_init_state *state)
--
2.42.0

2023-11-29 13:08:07

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 6/8] objtool: Check local label in add_dead_ends()

When update the latest upstream gcc and binutils which enables linker
relaxation by default, it generates more objtool warnings on LoongArch,
like this:

init/main.o: warning: objtool: unexpected relocation symbol type in .rela.discard.unreachable

We can see that the reloc sym name is local label instead of section
in relocation section '.rela.discard.unreachable', in this case, the
reloc sym type is STT_NOTYPE instead of STT_SECTION. Let us check it
to not return -1, then use reloc->sym->offset instead of reloc addend
which is 0 to find the corresponding instruction. At the same time,
replace the variable "addend" with "offset" to reflect the reality.

Here are some detailed info:
[fedora@linux 6.7.test]$ gcc --version
gcc (GCC) 14.0.0 20231127 (experimental)
[fedora@linux 6.7.test]$ as --version
GNU assembler (GNU Binutils) 2.41.50.20231127
[fedora@linux 6.7.test]$ readelf -r init/main.o | grep -A 2 "rela.discard.unreachable"
Relocation section '.rela.discard.unreachable' at offset 0x4b58 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000000 00de00000063 R_LARCH_32_PCREL 0000000000000220 .L467^B1 + 0

Signed-off-by: Tiezhu Yang <[email protected]>
---
tools/objtool/check.c | 34 +++++++++++++++++++---------------
1 file changed, 19 insertions(+), 15 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 29b3c5495901..395fce41fb81 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -611,7 +611,7 @@ static int add_dead_ends(struct objtool_file *file)
struct section *rsec;
struct reloc *reloc;
struct instruction *insn;
- s64 addend;
+ unsigned long offset;

/*
* Check for manually annotated dead ends.
@@ -622,26 +622,28 @@ static int add_dead_ends(struct objtool_file *file)

for_each_reloc(rsec, reloc) {

- if (reloc->sym->type != STT_SECTION) {
+ if (reloc->sym->type == STT_SECTION) {
+ offset = reloc_addend(reloc);
+ } else if (reloc->sym->local_label) {
+ offset = reloc->sym->offset;
+ } else {
WARN("unexpected relocation symbol type in %s", rsec->name);
return -1;
}

- addend = reloc_addend(reloc);
-
- insn = find_insn(file, reloc->sym->sec, addend);
+ insn = find_insn(file, reloc->sym->sec, offset);
if (insn)
insn = prev_insn_same_sec(file, insn);
- else if (addend == reloc->sym->sec->sh.sh_size) {
+ else if (offset == reloc->sym->sec->sh.sh_size) {
insn = find_last_insn(file, reloc->sym->sec);
if (!insn) {
WARN("can't find unreachable insn at %s+0x%" PRIx64,
- reloc->sym->sec->name, addend);
+ reloc->sym->sec->name, offset);
return -1;
}
} else {
WARN("can't find unreachable insn at %s+0x%" PRIx64,
- reloc->sym->sec->name, addend);
+ reloc->sym->sec->name, offset);
return -1;
}

@@ -661,26 +663,28 @@ static int add_dead_ends(struct objtool_file *file)

for_each_reloc(rsec, reloc) {

- if (reloc->sym->type != STT_SECTION) {
+ if (reloc->sym->type == STT_SECTION) {
+ offset = reloc_addend(reloc);
+ } else if (reloc->sym->local_label) {
+ offset = reloc->sym->offset;
+ } else {
WARN("unexpected relocation symbol type in %s", rsec->name);
return -1;
}

- addend = reloc_addend(reloc);
-
- insn = find_insn(file, reloc->sym->sec, addend);
+ insn = find_insn(file, reloc->sym->sec, offset);
if (insn)
insn = prev_insn_same_sec(file, insn);
- else if (addend == reloc->sym->sec->sh.sh_size) {
+ else if (offset == reloc->sym->sec->sh.sh_size) {
insn = find_last_insn(file, reloc->sym->sec);
if (!insn) {
WARN("can't find reachable insn at %s+0x%" PRIx64,
- reloc->sym->sec->name, addend);
+ reloc->sym->sec->name, offset);
return -1;
}
} else {
WARN("can't find reachable insn at %s+0x%" PRIx64,
- reloc->sym->sec->name, addend);
+ reloc->sym->sec->name, offset);
return -1;
}

--
2.42.0

2023-11-29 13:08:18

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 5/8] objtool: Check local label about sibling call

When update the latest upstream gcc and binutils which enables linker
relaxation by default, it generates more objtool warnings on LoongArch,
like this:

init/version.o: warning: objtool: early_hostname+0x1c: sibling call from callable instruction with modified stack frame

We can see that the branch and jump operation about local label ".L2"
is not sibling call, because a sibling call is a tail-call to another
symbol. In this case, make is_sibling_call() return false, set dest_sec
and dest_off to calculate jump_dest in add_jump_destinations().

As suggested by Peter Zijlstra, add "local_label" member in struct
symbol, then set it as true if symbol type is STT_NOTYPE and symbol
name starts with ".L" string in classify_symbols().

Here are some detailed info:
[fedora@linux 6.7.test]$ gcc --version
gcc (GCC) 14.0.0 20231127 (experimental)
[fedora@linux 6.7.test]$ as --version
GNU assembler (GNU Binutils) 2.41.50.20231127
[fedora@linux 6.7.test]$ objdump -M no-aliases -D init/version.o | grep -A 20 "init.text"
Disassembly of section .init.text:

0000000000000000 <early_hostname>:
0: 1a00000c pcalau12i $t0, 0
4: 02ffc063 addi.d $sp, $sp, -16
8: 00150085 or $a1, $a0, $zero
c: 02810406 addi.w $a2, $zero, 65
10: 02c00184 addi.d $a0, $t0, 0
14: 29c02061 st.d $ra, $sp, 8
18: 54000000 bl 0 # 18 <early_hostname+0x18>
1c: 64001480 bge $a0, $zero, 20 # 30 <.L2>
20: 1a000004 pcalau12i $a0, 0
24: 02810005 addi.w $a1, $zero, 64
28: 02c00084 addi.d $a0, $a0, 0
2c: 54000000 bl 0 # 2c <early_hostname+0x2c>

0000000000000030 <.L2>:
30: 28c02061 ld.d $ra, $sp, 8
34: 00150004 or $a0, $zero, $zero
38: 02c04063 addi.d $sp, $sp, 16
3c: 4c000020 jirl $zero, $ra, 0

By the way, it need to move insn_reloc() before is_sibling_call()
to avoid implicit declaration build error.

Signed-off-by: Tiezhu Yang <[email protected]>
Reviewed-by: Huacai Chen <[email protected]>
---
tools/objtool/check.c | 71 +++++++++++++++++------------
tools/objtool/include/objtool/elf.h | 1 +
2 files changed, 44 insertions(+), 28 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index e94756e09ca9..29b3c5495901 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -20,6 +20,7 @@
#include <linux/hashtable.h>
#include <linux/kernel.h>
#include <linux/static_call_types.h>
+#include <linux/string.h>

struct alternative {
struct alternative *next;
@@ -161,12 +162,38 @@ static bool is_jump_table_jump(struct instruction *insn)
insn_jump_table(alt_group->orig_group->first_insn);
}

-static bool is_sibling_call(struct instruction *insn)
+static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
+{
+ struct reloc *reloc;
+
+ if (insn->no_reloc)
+ return NULL;
+
+ if (!file)
+ return NULL;
+
+ reloc = find_reloc_by_dest_range(file->elf, insn->sec,
+ insn->offset, insn->len);
+ if (!reloc) {
+ insn->no_reloc = 1;
+ return NULL;
+ }
+
+ return reloc;
+}
+
+static bool is_sibling_call(struct objtool_file *file, struct instruction *insn)
{
/*
* Assume only STT_FUNC calls have jump-tables.
*/
if (insn_func(insn)) {
+ struct reloc *reloc = insn_reloc(file, insn);
+
+ /* Disallow sibling calls into STT_NOTYPE if it is local lable */
+ if (reloc && reloc->sym->local_label)
+ return false;
+
/* An indirect jump is either a sibling call or a jump to a table. */
if (insn->type == INSN_JUMP_DYNAMIC)
return !is_jump_table_jump(insn);
@@ -232,7 +259,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
* of the sibling call returns.
*/
func_for_each_insn(file, func, insn) {
- if (is_sibling_call(insn)) {
+ if (is_sibling_call(file, insn)) {
struct instruction *dest = insn->jump_dest;

if (!dest)
@@ -743,7 +770,7 @@ static int create_static_call_sections(struct objtool_file *file)
if (!elf_init_reloc_data_sym(file->elf, sec,
idx * sizeof(*site) + 4,
(idx * 2) + 1, key_sym,
- is_sibling_call(insn) * STATIC_CALL_SITE_TAIL))
+ is_sibling_call(file, insn) * STATIC_CALL_SITE_TAIL))
return -1;

idx++;
@@ -1315,26 +1342,6 @@ __weak bool arch_is_embedded_insn(struct symbol *sym)
return false;
}

-static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
-{
- struct reloc *reloc;
-
- if (insn->no_reloc)
- return NULL;
-
- if (!file)
- return NULL;
-
- reloc = find_reloc_by_dest_range(file->elf, insn->sec,
- insn->offset, insn->len);
- if (!reloc) {
- insn->no_reloc = 1;
- return NULL;
- }
-
- return reloc;
-}
-
static void remove_insn_ops(struct instruction *insn)
{
struct stack_op *op, *next;
@@ -1577,8 +1584,13 @@ static int add_jump_destinations(struct objtool_file *file)
* External sibling call or internal sibling call with
* STT_FUNC reloc.
*/
- add_call_dest(file, insn, reloc->sym, true);
- continue;
+ if (reloc->sym->local_label) {
+ dest_sec = insn->sec;
+ dest_off = arch_jump_destination(insn);
+ } else {
+ add_call_dest(file, insn, reloc->sym, true);
+ continue;
+ }
} else if (reloc->sym->sec->idx) {
dest_sec = reloc->sym->sec;
dest_off = reloc->sym->sym.st_value +
@@ -2522,6 +2534,9 @@ static int classify_symbols(struct objtool_file *file)
struct symbol *func;

for_each_sym(file, func) {
+ if (func->type == STT_NOTYPE && strstarts(func->name, ".L"))
+ func->local_label = true;
+
if (func->bind != STB_GLOBAL)
continue;

@@ -3690,7 +3705,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,

case INSN_JUMP_CONDITIONAL:
case INSN_JUMP_UNCONDITIONAL:
- if (is_sibling_call(insn)) {
+ if (is_sibling_call(file, insn)) {
ret = validate_sibling_call(file, insn, &state);
if (ret)
return ret;
@@ -3711,7 +3726,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,

case INSN_JUMP_DYNAMIC:
case INSN_JUMP_DYNAMIC_CONDITIONAL:
- if (is_sibling_call(insn)) {
+ if (is_sibling_call(file, insn)) {
ret = validate_sibling_call(file, insn, &state);
if (ret)
return ret;
@@ -3875,7 +3890,7 @@ static int validate_unret(struct objtool_file *file, struct instruction *insn)

case INSN_JUMP_UNCONDITIONAL:
case INSN_JUMP_CONDITIONAL:
- if (!is_sibling_call(insn)) {
+ if (!is_sibling_call(file, insn)) {
if (!insn->jump_dest) {
WARN_INSN(insn, "unresolved jump target after linking?!?");
return -1;
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index 9f71e988eca4..2b8a69de4db8 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -67,6 +67,7 @@ struct symbol {
u8 profiling_func : 1;
u8 warned : 1;
u8 embedded_insn : 1;
+ u8 local_label : 1;
struct list_head pv_target;
struct reloc *relocs;
};
--
2.42.0

2023-11-29 13:08:54

by Tiezhu Yang

[permalink] [raw]
Subject: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support

The kernel CONFIG_UNWINDER_ORC option enables the ORC unwinder, which is
similar in concept to a DWARF unwinder. The difference is that the format
of the ORC data is much simpler than DWARF, which in turn allows the ORC
unwinder to be much simpler and faster.

The ORC data consists of unwind tables which are generated by objtool.
After analyzing all the code paths of a .o file, it determines information
about the stack state at each instruction address in the file and outputs
that information to the .orc_unwind and .orc_unwind_ip sections.

The per-object ORC sections are combined at link time and are sorted and
post-processed at boot time. The unwinder uses the resulting data to
correlate instruction addresses with their stack states at run time.

Most of the logic are similar with x86, in order to get ra info before ra
is saved into stack, add ra_reg and ra_offset into orc_entry. At the same
time, modify some arch-specific code to silence the objtool warnings.

Co-developed-by: Jinyang He <[email protected]>
Signed-off-by: Jinyang He <[email protected]>
Co-developed-by: Youling Tang <[email protected]>
Signed-off-by: Youling Tang <[email protected]>
Signed-off-by: Tiezhu Yang <[email protected]>
---
arch/loongarch/Kconfig | 2 +
arch/loongarch/Kconfig.debug | 11 +
arch/loongarch/Makefile | 19 +
arch/loongarch/include/asm/Kbuild | 2 +
arch/loongarch/include/asm/bug.h | 1 +
arch/loongarch/include/asm/exception.h | 2 +
arch/loongarch/include/asm/module.h | 7 +
arch/loongarch/include/asm/orc_header.h | 18 +
arch/loongarch/include/asm/orc_lookup.h | 31 ++
arch/loongarch/include/asm/orc_types.h | 58 +++
arch/loongarch/include/asm/stackframe.h | 3 +
arch/loongarch/include/asm/unwind.h | 19 +-
arch/loongarch/include/asm/unwind_hints.h | 28 ++
arch/loongarch/kernel/Makefile | 4 +
arch/loongarch/kernel/entry.S | 6 +-
arch/loongarch/kernel/fpu.S | 7 +
arch/loongarch/kernel/genex.S | 6 +-
arch/loongarch/kernel/lbt.S | 5 +
arch/loongarch/kernel/module.c | 22 +-
arch/loongarch/kernel/relocate_kernel.S | 2 +
arch/loongarch/kernel/setup.c | 2 +
arch/loongarch/kernel/stacktrace.c | 1 +
arch/loongarch/kernel/traps.c | 42 +-
arch/loongarch/kernel/unwind_orc.c | 516 ++++++++++++++++++++++
arch/loongarch/kernel/vmlinux.lds.S | 3 +
arch/loongarch/kvm/switch.S | 7 +-
arch/loongarch/lib/Makefile | 2 +
arch/loongarch/mm/tlb.c | 27 +-
arch/loongarch/mm/tlbex.S | 9 +
arch/loongarch/vdso/Makefile | 1 +
include/linux/compiler.h | 9 +
scripts/Makefile | 7 +-
32 files changed, 839 insertions(+), 40 deletions(-)
create mode 100644 arch/loongarch/include/asm/orc_header.h
create mode 100644 arch/loongarch/include/asm/orc_lookup.h
create mode 100644 arch/loongarch/include/asm/orc_types.h
create mode 100644 arch/loongarch/include/asm/unwind_hints.h
create mode 100644 arch/loongarch/kernel/unwind_orc.c

diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index ee123820a476..eea57378646d 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -132,6 +132,7 @@ config LOONGARCH
select HAVE_KVM
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
@@ -143,6 +144,7 @@ config LOONGARCH
select HAVE_SAMPLE_FTRACE_DIRECT
select HAVE_SAMPLE_FTRACE_DIRECT_MULTI
select HAVE_SETUP_PER_CPU_AREA if NUMA
+ select HAVE_STACK_VALIDATION if HAVE_OBJTOOL
select HAVE_STACKPROTECTOR
select HAVE_SYSCALL_TRACEPOINTS
select HAVE_TIF_NOHZ
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/Makefile b/arch/loongarch/Makefile
index 204b94b2e6aa..1f0e41f8ab61 100644
--- a/arch/loongarch/Makefile
+++ b/arch/loongarch/Makefile
@@ -25,6 +25,18 @@ endif
32bit-emul = elf32loongarch
64bit-emul = elf64loongarch

+ifdef CONFIG_UNWINDER_ORC
+orc_hash_h := arch/$(SRCARCH)/include/generated/asm/orc_hash.h
+orc_hash_sh := $(srctree)/scripts/orc_hash.sh
+targets += $(orc_hash_h)
+quiet_cmd_orc_hash = GEN $@
+ cmd_orc_hash = mkdir -p $(dir $@); \
+ $(CONFIG_SHELL) $(orc_hash_sh) < $< > $@
+$(orc_hash_h): $(srctree)/arch/loongarch/include/asm/orc_types.h $(orc_hash_sh) FORCE
+ $(call if_changed,orc_hash)
+archprepare: $(orc_hash_h)
+endif
+
ifdef CONFIG_DYNAMIC_FTRACE
KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
CC_FLAGS_FTRACE := -fpatchable-function-entry=2
@@ -81,6 +93,13 @@ KBUILD_AFLAGS_MODULE += -Wa,-mla-global-with-abs
KBUILD_CFLAGS_MODULE += -fplt -Wa,-mla-global-with-abs,-mla-local-with-abs
endif

+KBUILD_AFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
+KBUILD_CFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
+
+ifdef CONFIG_OBJTOOL
+KBUILD_CFLAGS += -fno-optimize-sibling-calls -fno-jump-tables -falign-functions=4 -falign-labels=4
+endif
+
ifeq ($(CONFIG_RELOCATABLE),y)
KBUILD_CFLAGS_KERNEL += -fPIE
LDFLAGS_vmlinux += -static -pie --no-dynamic-linker -z notext
diff --git a/arch/loongarch/include/asm/Kbuild b/arch/loongarch/include/asm/Kbuild
index 93783fa24f6e..a97c0edbb866 100644
--- a/arch/loongarch/include/asm/Kbuild
+++ b/arch/loongarch/include/asm/Kbuild
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
+generated-y += orc_hash.h
+
generic-y += dma-contiguous.h
generic-y += mcs_spinlock.h
generic-y += parport.h
diff --git a/arch/loongarch/include/asm/bug.h b/arch/loongarch/include/asm/bug.h
index d4ca3ba25418..08388876ade4 100644
--- a/arch/loongarch/include/asm/bug.h
+++ b/arch/loongarch/include/asm/bug.h
@@ -44,6 +44,7 @@
do { \
instrumentation_begin(); \
__BUG_FLAGS(BUGFLAG_WARNING|(flags)); \
+ annotate_reachable(); \
instrumentation_end(); \
} while (0)

diff --git a/arch/loongarch/include/asm/exception.h b/arch/loongarch/include/asm/exception.h
index af74a3fdcad1..c6d20736fd92 100644
--- a/arch/loongarch/include/asm/exception.h
+++ b/arch/loongarch/include/asm/exception.h
@@ -6,6 +6,8 @@
#include <asm/ptrace.h>
#include <linux/kprobes.h>

+extern void *exception_table[];
+
void show_registers(struct pt_regs *regs);

asmlinkage void cache_parity_error(void);
diff --git a/arch/loongarch/include/asm/module.h b/arch/loongarch/include/asm/module.h
index 2ecd82bb64e1..f33f3fd32ecc 100644
--- a/arch/loongarch/include/asm/module.h
+++ b/arch/loongarch/include/asm/module.h
@@ -6,6 +6,7 @@
#define _ASM_MODULE_H

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

#define RELA_STACK_DEPTH 16
@@ -21,6 +22,12 @@ struct mod_arch_specific {
struct mod_section plt;
struct mod_section plt_idx;

+#ifdef CONFIG_UNWINDER_ORC
+ unsigned int num_orcs;
+ int *orc_unwind_ip;
+ struct orc_entry *orc_unwind;
+#endif
+
/* For CONFIG_DYNAMIC_FTRACE */
struct plt_entry *ftrace_trampolines;
};
diff --git a/arch/loongarch/include/asm/orc_header.h b/arch/loongarch/include/asm/orc_header.h
new file mode 100644
index 000000000000..f9d509c3fd70
--- /dev/null
+++ b/arch/loongarch/include/asm/orc_header.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _ORC_HEADER_H
+#define _ORC_HEADER_H
+
+#include <linux/types.h>
+#include <linux/compiler.h>
+#include <asm/orc_hash.h>
+
+/*
+ * The header is currently a 20-byte hash of the ORC entry definition; see
+ * scripts/orc_hash.sh.
+ */
+#define ORC_HEADER \
+ __used __section(".orc_header") __aligned(4) \
+ static const u8 orc_header[] = { ORC_HASH }
+
+#endif /* _ORC_HEADER_H */
diff --git a/arch/loongarch/include/asm/orc_lookup.h b/arch/loongarch/include/asm/orc_lookup.h
new file mode 100644
index 000000000000..b02e6357def4
--- /dev/null
+++ b/arch/loongarch/include/asm/orc_lookup.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _ORC_LOOKUP_H
+#define _ORC_LOOKUP_H
+
+/*
+ * This is a lookup table for speeding up access to the .orc_unwind table.
+ * Given an input address offset, the corresponding lookup table entry
+ * specifies a subset of the .orc_unwind table to search.
+ *
+ * Each block represents the end of the previous range and the start of the
+ * next range. An extra block is added to give the last range an end.
+ *
+ * The block size should be a power of 2 to avoid a costly 'div' instruction.
+ *
+ * A block size of 256 was chosen because it roughly doubles unwinder
+ * performance while only adding ~5% to the ORC data footprint.
+ */
+#define LOOKUP_BLOCK_ORDER 8
+#define LOOKUP_BLOCK_SIZE (1 << LOOKUP_BLOCK_ORDER)
+
+#ifndef LINKER_SCRIPT
+
+extern unsigned int orc_lookup[];
+extern unsigned int orc_lookup_end[];
+
+#define LOOKUP_START_IP (unsigned long)_stext
+#define LOOKUP_STOP_IP (unsigned long)_etext
+
+#endif /* LINKER_SCRIPT */
+
+#endif /* _ORC_LOOKUP_H */
diff --git a/arch/loongarch/include/asm/orc_types.h b/arch/loongarch/include/asm/orc_types.h
new file mode 100644
index 000000000000..caf1f71a1057
--- /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>
+
+/*
+ * 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 FP -- which the previous SP
+ * is usually based on -- and PREV_SP and UNDEFINED -- which the previous FP 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_FP 3
+#define ORC_REG_MAX 4
+
+#define ORC_TYPE_UNDEFINED 0
+#define ORC_TYPE_END_OF_STACK 1
+#define ORC_TYPE_CALL 2
+#define ORC_TYPE_REGS 3
+#define ORC_TYPE_REGS_PARTIAL 4
+
+#ifndef __ASSEMBLY__
+/*
+ * 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 FP (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 fp_offset;
+ s16 ra_offset;
+ unsigned int sp_reg:4;
+ unsigned int fp_reg:4;
+ unsigned int ra_reg:4;
+ unsigned int type:3;
+ unsigned int signal:1;
+};
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ORC_TYPES_H */
diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h
index 4fb1e6408b98..45b507a7b06f 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
@@ -162,6 +163,7 @@
li.w t0, CSR_CRMD_WE
csrxchg t0, t0, LOONGARCH_CSR_CRMD
#endif
+ UNWIND_HINT_REGS
.endm

.macro SAVE_ALL docfi=0
@@ -219,6 +221,7 @@

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

diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
index b9dce87afd2e..c7f52d406cce 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, fp, pc, ra;
};

bool default_next_frame(struct unwind_state *state);
@@ -34,6 +35,14 @@ 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);
+void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size);
+#else
+static inline void unwind_init(void) {}
+static inline void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size) {}
+#endif
+
static inline bool unwind_done(struct unwind_state *state)
{
return state->stack_info.type == STACK_TYPE_UNKNOWN;
@@ -61,14 +70,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 +89,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/include/asm/unwind_hints.h b/arch/loongarch/include/asm/unwind_hints.h
new file mode 100644
index 000000000000..82443fed3167
--- /dev/null
+++ b/arch/loongarch/include/asm/unwind_hints.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_LOONGARCH_UNWIND_HINTS_H
+#define _ASM_LOONGARCH_UNWIND_HINTS_H
+
+#include <linux/objtool.h>
+#include <asm/orc_types.h>
+
+#ifdef __ASSEMBLY__
+
+.macro UNWIND_HINT_UNDEFINED
+ UNWIND_HINT type=UNWIND_HINT_TYPE_UNDEFINED
+.endm
+
+.macro UNWIND_HINT_EMPTY
+ UNWIND_HINT sp_reg=ORC_REG_UNDEFINED type=UNWIND_HINT_TYPE_CALL
+.endm
+
+.macro UNWIND_HINT_REGS
+ UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_REGS
+.endm
+
+.macro UNWIND_HINT_FUNC
+ UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_CALL
+.endm
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* _ASM_LOONGARCH_UNWIND_HINTS_H */
diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
index 4fcc168f0732..ac47e11c227e 100644
--- a/arch/loongarch/kernel/Makefile
+++ b/arch/loongarch/kernel/Makefile
@@ -3,6 +3,8 @@
# Makefile for the Linux/LoongArch kernel.
#

+OBJECT_FILES_NON_STANDARD_head.o := y
+
extra-y := vmlinux.lds

obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \
@@ -21,6 +23,7 @@ obj-$(CONFIG_ARCH_STRICT_ALIGN) += unaligned.o

CFLAGS_module.o += $(call cc-option,-Wno-override-init,)
CFLAGS_syscall.o += $(call cc-option,-Wno-override-init,)
+CFLAGS_traps.o += $(call cc-option,-Wno-override-init,)
CFLAGS_perf_event.o += $(call cc-option,-Wno-override-init,)

ifdef CONFIG_FUNCTION_TRACER
@@ -62,6 +65,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/entry.S b/arch/loongarch/kernel/entry.S
index 1ec8e4c4cc2b..2f5c74d26d5f 100644
--- a/arch/loongarch/kernel/entry.S
+++ b/arch/loongarch/kernel/entry.S
@@ -14,11 +14,12 @@
#include <asm/regdef.h>
#include <asm/stackframe.h>
#include <asm/thread_info.h>
+#include <asm/unwind_hints.h>

.text
.cfi_sections .debug_frame
- .align 5
SYM_CODE_START(handle_syscall)
+ UNWIND_HINT_UNDEFINED
csrrd t0, PERCPU_BASE_KS
la.pcrel t1, kernelsp
add.d t1, t1, t0
@@ -57,6 +58,7 @@ SYM_CODE_START(handle_syscall)
cfi_st fp, PT_R22

SAVE_STATIC
+ UNWIND_HINT_REGS

#ifdef CONFIG_KGDB
li.w t1, CSR_CRMD_WE
@@ -75,6 +77,7 @@ SYM_CODE_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
@@ -84,6 +87,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/fpu.S b/arch/loongarch/kernel/fpu.S
index d53ab10f4644..487933899c5d 100644
--- a/arch/loongarch/kernel/fpu.S
+++ b/arch/loongarch/kernel/fpu.S
@@ -15,6 +15,7 @@
#include <asm/fpregdef.h>
#include <asm/loongarch.h>
#include <asm/regdef.h>
+#include <asm/unwind_hints.h>

#define FPU_REG_WIDTH 8
#define LSX_REG_WIDTH 16
@@ -524,3 +525,9 @@ SYM_FUNC_END(_restore_lasx_context)
.L_fpu_fault:
li.w a0, -EFAULT # failure
jr ra
+
+#ifdef CONFIG_CPU_HAS_LBT
+STACK_FRAME_NON_STANDARD _restore_fp
+STACK_FRAME_NON_STANDARD _restore_lsx
+STACK_FRAME_NON_STANDARD _restore_lasx
+#endif
diff --git a/arch/loongarch/kernel/genex.S b/arch/loongarch/kernel/genex.S
index 2bb3aa2dcfcb..6b7ec9f1134d 100644
--- a/arch/loongarch/kernel/genex.S
+++ b/arch/loongarch/kernel/genex.S
@@ -16,7 +16,6 @@
#include <asm/stackframe.h>
#include <asm/thread_info.h>

- .align 5
SYM_FUNC_START(__arch_cpu_idle)
/* start of rollback region */
LONG_L t0, tp, TI_FLAGS
@@ -32,6 +31,7 @@ SYM_FUNC_START(__arch_cpu_idle)
SYM_FUNC_END(__arch_cpu_idle)

SYM_CODE_START(handle_vint)
+ UNWIND_HINT_UNDEFINED
BACKUP_T0T1
SAVE_ALL
la_abs t1, __arch_cpu_idle
@@ -49,6 +49,7 @@ SYM_CODE_START(handle_vint)
SYM_CODE_END(handle_vint)

SYM_CODE_START(except_vec_cex)
+ UNWIND_HINT_UNDEFINED
b cache_parity_error
SYM_CODE_END(except_vec_cex)

@@ -65,8 +66,8 @@ SYM_CODE_END(except_vec_cex)
.endm

.macro BUILD_HANDLER exception handler prep
- .align 5
SYM_CODE_START(handle_\exception)
+ UNWIND_HINT_UNDEFINED
666:
BACKUP_T0T1
SAVE_ALL
@@ -94,6 +95,7 @@ SYM_CODE_END(except_vec_cex)
BUILD_HANDLER reserved reserved none /* others */

SYM_CODE_START(handle_sys)
+ UNWIND_HINT_UNDEFINED
la_abs t0, handle_syscall
jr t0
SYM_CODE_END(handle_sys)
diff --git a/arch/loongarch/kernel/lbt.S b/arch/loongarch/kernel/lbt.S
index 9c75120a26d8..4d6914f84c7e 100644
--- a/arch/loongarch/kernel/lbt.S
+++ b/arch/loongarch/kernel/lbt.S
@@ -11,6 +11,7 @@
#include <asm/asm-offsets.h>
#include <asm/errno.h>
#include <asm/regdef.h>
+#include <asm/unwind_hints.h>

#define SCR_REG_WIDTH 8

@@ -153,3 +154,7 @@ SYM_FUNC_END(_restore_ftop_context)
.L_lbt_fault:
li.w a0, -EFAULT # failure
jr ra
+
+#ifdef CONFIG_CPU_HAS_LBT
+STACK_FRAME_NON_STANDARD _restore_ftop_context
+#endif
diff --git a/arch/loongarch/kernel/module.c b/arch/loongarch/kernel/module.c
index b13b2858fe39..c7d0338d12c1 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/unwind.h>

static int rela_stack_push(s64 stack_value, s64 *rela_stack, size_t *rela_stack_top)
{
@@ -515,15 +516,28 @@ 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 char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
+ const Elf_Shdr *s, *alt = NULL, *orc = NULL, *orc_ip = NULL, *ftrace = NULL;

- for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) {
+ for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
if (!strcmp(".altinstructions", secstrs + s->sh_name))
- apply_alternatives((void *)s->sh_addr, (void *)s->sh_addr + s->sh_size);
+ alt = s;
+ if (!strcmp(".orc_unwind", secstrs + s->sh_name))
+ orc = s;
+ if (!strcmp(".orc_unwind_ip", secstrs + s->sh_name))
+ orc_ip = s;
if (!strcmp(".ftrace_trampoline", secstrs + s->sh_name))
- module_init_ftrace_plt(hdr, s, mod);
+ ftrace = s;
}

+ if (alt)
+ apply_alternatives((void *)alt->sh_addr, (void *)alt->sh_addr + alt->sh_size);
+
+ if (orc && orc_ip)
+ unwind_module_init(mod, (void *)orc_ip->sh_addr, orc_ip->sh_size, (void *)orc->sh_addr, orc->sh_size);
+
+ if (ftrace)
+ module_init_ftrace_plt(hdr, ftrace, mod);
+
return 0;
}
diff --git a/arch/loongarch/kernel/relocate_kernel.S b/arch/loongarch/kernel/relocate_kernel.S
index f49f6b053763..bcc191d278c1 100644
--- a/arch/loongarch/kernel/relocate_kernel.S
+++ b/arch/loongarch/kernel/relocate_kernel.S
@@ -15,6 +15,7 @@
#include <asm/addrspace.h>

SYM_CODE_START(relocate_new_kernel)
+ UNWIND_HINT_UNDEFINED
/*
* a0: EFI boot flag for the new kernel
* a1: Command line pointer for the new kernel
@@ -90,6 +91,7 @@ SYM_CODE_END(relocate_new_kernel)
* then start at the entry point from LOONGARCH_IOCSR_MBUF0.
*/
SYM_CODE_START(kexec_smp_wait)
+ UNWIND_HINT_UNDEFINED
1: li.w t0, 0x100 /* wait for init loop */
2: addi.w t0, t0, -1 /* limit mailbox access */
bnez t0, 2b
diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
index d183a745fb85..ec4459c61db6 100644
--- a/arch/loongarch/kernel/setup.c
+++ b/arch/loongarch/kernel/setup.c
@@ -47,6 +47,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
@@ -605,6 +606,7 @@ static void __init prefill_possible_map(void)
void __init setup_arch(char **cmdline_p)
{
cpu_probe();
+ unwind_init();

init_environ();
efi_init();
diff --git a/arch/loongarch/kernel/stacktrace.c b/arch/loongarch/kernel/stacktrace.c
index 92270f14db94..9848d427cbfa 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->csr_era = thread_saved_ra(task);
}
regs->regs[1] = 0;
+ regs->regs[22] = 0;
}

for (unwind_start(&state, task, regs);
diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
index aebfc3733a76..f9f4eb00c92e 100644
--- a/arch/loongarch/kernel/traps.c
+++ b/arch/loongarch/kernel/traps.c
@@ -53,6 +53,32 @@

#include "access-helper.h"

+void *exception_table[EXCCODE_INT_START] = {
+ [0 ... EXCCODE_INT_START - 1] = handle_reserved,
+
+ [EXCCODE_TLBI] = handle_tlb_load,
+ [EXCCODE_TLBL] = handle_tlb_load,
+ [EXCCODE_TLBS] = handle_tlb_store,
+ [EXCCODE_TLBM] = handle_tlb_modify,
+ [EXCCODE_TLBNR] = handle_tlb_protect,
+ [EXCCODE_TLBNX] = handle_tlb_protect,
+ [EXCCODE_TLBPE] = handle_tlb_protect,
+ [EXCCODE_ADE] = handle_ade,
+ [EXCCODE_ALE] = handle_ale,
+ [EXCCODE_BCE] = handle_bce,
+ [EXCCODE_SYS] = handle_sys,
+ [EXCCODE_BP] = handle_bp,
+ [EXCCODE_INE] = handle_ri,
+ [EXCCODE_IPE] = handle_ri,
+ [EXCCODE_FPDIS] = handle_fpu,
+ [EXCCODE_LSXDIS] = handle_lsx,
+ [EXCCODE_LASXDIS] = handle_lasx,
+ [EXCCODE_FPE] = handle_fpe,
+ [EXCCODE_WATCH] = handle_watch,
+ [EXCCODE_BTDIS] = handle_lbt,
+};
+EXPORT_SYMBOL_GPL(exception_table);
+
static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
const char *loglvl, bool user)
{
@@ -1150,19 +1176,9 @@ void __init trap_init(void)
for (i = EXCCODE_INT_START; i <= EXCCODE_INT_END; i++)
set_handler(i * VECSIZE, handle_vint, VECSIZE);

- set_handler(EXCCODE_ADE * VECSIZE, handle_ade, VECSIZE);
- set_handler(EXCCODE_ALE * VECSIZE, handle_ale, VECSIZE);
- set_handler(EXCCODE_BCE * VECSIZE, handle_bce, VECSIZE);
- set_handler(EXCCODE_SYS * VECSIZE, handle_sys, VECSIZE);
- set_handler(EXCCODE_BP * VECSIZE, handle_bp, VECSIZE);
- set_handler(EXCCODE_INE * VECSIZE, handle_ri, VECSIZE);
- set_handler(EXCCODE_IPE * VECSIZE, handle_ri, VECSIZE);
- set_handler(EXCCODE_FPDIS * VECSIZE, handle_fpu, VECSIZE);
- set_handler(EXCCODE_LSXDIS * VECSIZE, handle_lsx, VECSIZE);
- set_handler(EXCCODE_LASXDIS * VECSIZE, handle_lasx, VECSIZE);
- set_handler(EXCCODE_FPE * VECSIZE, handle_fpe, VECSIZE);
- set_handler(EXCCODE_BTDIS * VECSIZE, handle_lbt, VECSIZE);
- set_handler(EXCCODE_WATCH * VECSIZE, handle_watch, VECSIZE);
+ /* Set exception vector handler */
+ for (i = EXCCODE_ADE; i <= EXCCODE_BTDIS; i++)
+ set_handler(i * VECSIZE, exception_table[i], VECSIZE);

cache_error_setup();

diff --git a/arch/loongarch/kernel/unwind_orc.c b/arch/loongarch/kernel/unwind_orc.c
new file mode 100644
index 000000000000..15f18d1e0dad
--- /dev/null
+++ b/arch/loongarch/kernel/unwind_orc.c
@@ -0,0 +1,516 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/objtool.h>
+#include <linux/module.h>
+#include <linux/sort.h>
+#include <asm/exception.h>
+#include <asm/orc_header.h>
+#include <asm/orc_lookup.h>
+#include <asm/orc_types.h>
+#include <asm/ptrace.h>
+#include <asm/setup.h>
+#include <asm/stacktrace.h>
+#include <asm/tlb.h>
+#include <asm/unwind.h>
+
+ORC_HEADER;
+
+#define orc_warn(fmt, ...) \
+ printk_deferred_once(KERN_WARNING "WARNING: " fmt, ##__VA_ARGS__)
+
+extern int __start_orc_unwind_ip[];
+extern int __stop_orc_unwind_ip[];
+extern struct orc_entry __start_orc_unwind[];
+extern struct orc_entry __stop_orc_unwind[];
+
+static bool orc_init __ro_after_init;
+static unsigned int lookup_num_blocks __ro_after_init;
+
+/* Fake frame pointer entry -- used as a fallback for generated code */
+static struct orc_entry orc_fp_entry = {
+ .sp_reg = ORC_REG_FP,
+ .sp_offset = 16,
+ .fp_reg = ORC_REG_PREV_SP,
+ .fp_offset = -16,
+ .ra_reg = ORC_REG_PREV_SP,
+ .ra_offset = -8,
+ .type = ORC_TYPE_CALL
+};
+
+/*
+ * If we crash with IP==0, the last successfully executed instruction
+ * was probably an indirect function call with a NULL function pointer,
+ * and we don't have unwind information for NULL.
+ * This hardcoded ORC entry for IP==0 allows us to unwind from a NULL function
+ * pointer into its parent and then continue normally from there.
+ */
+static struct orc_entry orc_null_entry = {
+ .sp_reg = ORC_REG_SP,
+ .sp_offset = sizeof(long),
+ .fp_reg = ORC_REG_UNDEFINED,
+ .type = ORC_TYPE_CALL
+};
+
+static inline unsigned long orc_ip(const int *ip)
+{
+ return (unsigned long)ip + *ip;
+}
+
+static struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
+ unsigned int num_entries, unsigned long ip)
+{
+ int *first = ip_table;
+ int *mid = first, *found = first;
+ int *last = ip_table + num_entries - 1;
+
+ if (!num_entries)
+ return NULL;
+
+ /*
+ * Do a binary range search to find the rightmost duplicate of a given
+ * starting address. Some entries are section terminators which are
+ * "weak" entries for ensuring there are no gaps. They should be
+ * ignored when they conflict with a real entry.
+ */
+ while (first <= last) {
+ mid = first + ((last - first) / 2);
+
+ if (orc_ip(mid) <= ip) {
+ found = mid;
+ first = mid + 1;
+ } else
+ last = mid - 1;
+ }
+
+ return u_table + (found - ip_table);
+}
+
+#ifdef CONFIG_MODULES
+static struct orc_entry *orc_module_find(unsigned long ip)
+{
+ struct module *mod;
+
+ mod = __module_address(ip);
+ if (!mod || !mod->arch.orc_unwind || !mod->arch.orc_unwind_ip)
+ return NULL;
+
+ return __orc_find(mod->arch.orc_unwind_ip, mod->arch.orc_unwind, mod->arch.num_orcs, ip);
+}
+#else
+static struct orc_entry *orc_module_find(unsigned long ip)
+{
+ return NULL;
+}
+#endif
+
+#ifdef CONFIG_DYNAMIC_FTRACE
+static struct orc_entry *orc_find(unsigned long ip);
+
+/*
+ * Ftrace dynamic trampolines do not have orc entries of their own.
+ * But they are copies of the ftrace entries that are static and
+ * defined in ftrace_*.S, which do have orc entries.
+ *
+ * If the unwinder comes across a ftrace trampoline, then find the
+ * ftrace function that was used to create it, and use that ftrace
+ * function's orc entry, as the placement of the return code in
+ * the stack will be identical.
+ */
+static struct orc_entry *orc_ftrace_find(unsigned long ip)
+{
+ struct ftrace_ops *ops;
+ unsigned long tramp_addr, offset;
+
+ ops = ftrace_ops_trampoline(ip);
+ if (!ops)
+ return NULL;
+
+ /* Set tramp_addr to the start of the code copied by the trampoline */
+ if (ops->flags & FTRACE_OPS_FL_SAVE_REGS)
+ tramp_addr = (unsigned long)ftrace_regs_caller;
+ else
+ tramp_addr = (unsigned long)ftrace_caller;
+
+ /* Now place tramp_addr to the location within the trampoline ip is at */
+ offset = ip - ops->trampoline;
+ tramp_addr += offset;
+
+ /* Prevent unlikely recursion */
+ if (ip == tramp_addr)
+ return NULL;
+
+ return orc_find(tramp_addr);
+}
+#else
+static struct orc_entry *orc_ftrace_find(unsigned long ip)
+{
+ return NULL;
+}
+#endif
+
+static struct orc_entry *orc_find(unsigned long ip)
+{
+ static struct orc_entry *orc;
+
+ if (ip == 0)
+ return &orc_null_entry;
+
+ /* For non-init vmlinux addresses, use the fast lookup table: */
+ if (ip >= LOOKUP_START_IP && ip < LOOKUP_STOP_IP) {
+ unsigned int idx, start, stop;
+
+ idx = (ip - LOOKUP_START_IP) / LOOKUP_BLOCK_SIZE;
+
+ if (unlikely((idx >= lookup_num_blocks-1))) {
+ orc_warn("WARNING: bad lookup idx: idx=%u num=%u ip=%pB\n",
+ idx, lookup_num_blocks, (void *)ip);
+ return NULL;
+ }
+
+ start = orc_lookup[idx];
+ stop = orc_lookup[idx + 1] + 1;
+
+ if (unlikely((__start_orc_unwind + start >= __stop_orc_unwind) ||
+ (__start_orc_unwind + stop > __stop_orc_unwind))) {
+ orc_warn("WARNING: bad lookup value: idx=%u num=%u start=%u stop=%u ip=%pB\n",
+ idx, lookup_num_blocks, start, stop, (void *)ip);
+ return NULL;
+ }
+
+ return __orc_find(__start_orc_unwind_ip + start,
+ __start_orc_unwind + start, stop - start, ip);
+ }
+
+ /* vmlinux .init slow lookup: */
+ if (is_kernel_inittext(ip))
+ return __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
+ __stop_orc_unwind_ip - __start_orc_unwind_ip, ip);
+
+ /* Module lookup: */
+ orc = orc_module_find(ip);
+ if (orc)
+ return orc;
+
+ return orc_ftrace_find(ip);
+}
+
+#ifdef CONFIG_MODULES
+
+static DEFINE_MUTEX(sort_mutex);
+static int *cur_orc_ip_table = __start_orc_unwind_ip;
+static struct orc_entry *cur_orc_table = __start_orc_unwind;
+
+static void orc_sort_swap(void *_a, void *_b, int size)
+{
+ int delta = _b - _a;
+ int *a = _a, *b = _b, tmp;
+ struct orc_entry *orc_a, *orc_b;
+
+ /* Swap the .orc_unwind_ip entries: */
+ tmp = *a;
+ *a = *b + delta;
+ *b = tmp - delta;
+
+ /* Swap the corresponding .orc_unwind entries: */
+ orc_a = cur_orc_table + (a - cur_orc_ip_table);
+ orc_b = cur_orc_table + (b - cur_orc_ip_table);
+ swap(*orc_a, *orc_b);
+}
+
+static int orc_sort_cmp(const void *_a, const void *_b)
+{
+ const int *a = _a, *b = _b;
+ unsigned long a_val = orc_ip(a);
+ unsigned long b_val = orc_ip(b);
+ struct orc_entry *orc_a;
+
+ if (a_val > b_val)
+ return 1;
+ if (a_val < b_val)
+ return -1;
+
+ /*
+ * The "weak" section terminator entries need to always be first
+ * to ensure the lookup code skips them in favor of real entries.
+ * These terminator entries exist to handle any gaps created by
+ * whitelisted .o files which didn't get objtool generation.
+ */
+ orc_a = cur_orc_table + (a - cur_orc_ip_table);
+
+ return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
+}
+
+void unwind_module_init(struct module *mod, void *_orc_ip, size_t orc_ip_size,
+ void *_orc, size_t orc_size)
+{
+ int *orc_ip = _orc_ip;
+ struct orc_entry *orc = _orc;
+ unsigned int num_entries = orc_ip_size / sizeof(int);
+
+ WARN_ON_ONCE(orc_ip_size % sizeof(int) != 0 ||
+ orc_size % sizeof(*orc) != 0 ||
+ num_entries != orc_size / sizeof(*orc));
+
+ /*
+ * The 'cur_orc_*' globals allow the orc_sort_swap() callback to
+ * associate an .orc_unwind_ip table entry with its corresponding
+ * .orc_unwind entry so they can both be swapped.
+ */
+ mutex_lock(&sort_mutex);
+ cur_orc_ip_table = orc_ip;
+ cur_orc_table = orc;
+ sort(orc_ip, num_entries, sizeof(int), orc_sort_cmp, orc_sort_swap);
+ mutex_unlock(&sort_mutex);
+
+ mod->arch.orc_unwind_ip = orc_ip;
+ mod->arch.orc_unwind = orc;
+ mod->arch.num_orcs = num_entries;
+}
+#endif
+
+void __init unwind_init(void)
+{
+ int i;
+ size_t orc_size = (void *)__stop_orc_unwind - (void *)__start_orc_unwind;
+ size_t orc_ip_size = (void *)__stop_orc_unwind_ip - (void *)__start_orc_unwind_ip;
+ size_t num_entries = orc_ip_size / sizeof(int);
+ struct orc_entry *orc;
+
+ if (!num_entries || orc_ip_size % sizeof(int) != 0 ||
+ orc_size % sizeof(struct orc_entry) != 0 ||
+ num_entries != orc_size / sizeof(struct orc_entry)) {
+ orc_warn("WARNING: Bad or missing .orc_unwind table. Disabling unwinder.\n");
+ return;
+ }
+
+ /*
+ * Note, the orc_unwind and orc_unwind_ip tables were already
+ * sorted at build time via the 'sorttable' tool.
+ * It's ready for binary search straight away, no need to sort it.
+ */
+
+ /* Initialize the fast lookup table: */
+ lookup_num_blocks = orc_lookup_end - orc_lookup;
+ for (i = 0; i < lookup_num_blocks-1; i++) {
+ orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
+ num_entries, LOOKUP_START_IP + (LOOKUP_BLOCK_SIZE * i));
+ if (!orc) {
+ orc_warn("WARNING: Corrupt .orc_unwind table. Disabling unwinder.\n");
+ return;
+ }
+
+ orc_lookup[i] = orc - __start_orc_unwind;
+ }
+
+ /* Initialize the ending block: */
+ orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind, num_entries, LOOKUP_STOP_IP);
+ if (!orc) {
+ orc_warn("WARNING: Corrupt .orc_unwind table. Disabling unwinder.\n");
+ return;
+ }
+ orc_lookup[lookup_num_blocks-1] = orc - __start_orc_unwind;
+
+ orc_init = true;
+}
+
+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);
+ state->type = UNWINDER_ORC;
+ 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;
+
+ if (__module_text_address(ra))
+ return ra;
+
+ if (ra >= eentry && ra < eentry + EXCCODE_INT_END * VECSIZE) {
+ unsigned long func;
+ unsigned long type = (ra - eentry) / VECSIZE;
+ unsigned long offset = (ra - eentry) % VECSIZE;
+
+ switch (type) {
+ case 0 ... EXCCODE_INT_START - 1:
+ func = (unsigned long)exception_table[type];
+ break;
+ case EXCCODE_INT_START ... EXCCODE_INT_END:
+ 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)
+{
+ unsigned long *p, pc;
+ struct pt_regs *regs;
+ struct orc_entry *orc;
+ struct stack_info *info = &state->stack_info;
+
+ 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:
+ if (info->type == STACK_TYPE_IRQ && state->sp == info->end)
+ orc->type = ORC_TYPE_REGS;
+ else
+ state->sp = state->sp + orc->sp_offset;
+ break;
+ case ORC_REG_FP:
+ 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->fp_reg) {
+ case ORC_REG_PREV_SP:
+ p = (unsigned long *)(state->sp + orc->fp_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->fp_reg, (void *)state->pc);
+ goto err;
+ }
+
+ switch (orc->type) {
+ case ORC_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 ORC_TYPE_REGS:
+ if (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 bb2ec86f37a8..eaa7a91162e6 100644
--- a/arch/loongarch/kernel/vmlinux.lds.S
+++ b/arch/loongarch/kernel/vmlinux.lds.S
@@ -2,6 +2,7 @@
#include <linux/sizes.h>
#include <asm/asm-offsets.h>
#include <asm/thread_info.h>
+#include <asm/orc_lookup.h>

#define PAGE_SIZE _PAGE_SIZE
#define RO_EXCEPTION_TABLE_ALIGN 4
@@ -122,6 +123,8 @@ SECTIONS
}
#endif

+ ORC_UNWIND_TABLE
+
.sdata : {
*(.sdata)
}
diff --git a/arch/loongarch/kvm/switch.S b/arch/loongarch/kvm/switch.S
index 0ed9040307b7..9d49c3f6fff5 100644
--- a/arch/loongarch/kvm/switch.S
+++ b/arch/loongarch/kvm/switch.S
@@ -8,7 +8,7 @@
#include <asm/asmmacro.h>
#include <asm/loongarch.h>
#include <asm/regdef.h>
-#include <asm/stackframe.h>
+#include <asm/unwind_hints.h>

#define HGPR_OFFSET(x) (PT_R0 + 8*x)
#define GGPR_OFFSET(x) (KVM_ARCH_GGPR + 8*x)
@@ -112,6 +112,7 @@
.text
.cfi_sections .debug_frame
SYM_CODE_START(kvm_exc_entry)
+ UNWIND_HINT_UNDEFINED
csrwr a2, KVM_TEMP_KS
csrrd a2, KVM_VCPU_KS
addi.d a2, a2, KVM_VCPU_ARCH
@@ -248,3 +249,7 @@ SYM_FUNC_END(kvm_restore_fpu)
.section ".rodata"
SYM_DATA(kvm_exception_size, .quad kvm_exc_entry_end - kvm_exc_entry)
SYM_DATA(kvm_enter_guest_size, .quad kvm_enter_guest_end - kvm_enter_guest)
+
+#ifdef CONFIG_CPU_HAS_LBT
+STACK_FRAME_NON_STANDARD kvm_restore_fpu
+#endif
diff --git a/arch/loongarch/lib/Makefile b/arch/loongarch/lib/Makefile
index a77bf160bfc4..e3023d9a508c 100644
--- a/arch/loongarch/lib/Makefile
+++ b/arch/loongarch/lib/Makefile
@@ -3,6 +3,8 @@
# Makefile for LoongArch-specific library files.
#

+OBJECT_FILES_NON_STANDARD := y
+
lib-y += delay.o memset.o memcpy.o memmove.o \
clear_user.o copy_user.o csum.o dump_tlb.o unaligned.o

diff --git a/arch/loongarch/mm/tlb.c b/arch/loongarch/mm/tlb.c
index 2c0a411f23aa..f01172a8f4e9 100644
--- a/arch/loongarch/mm/tlb.c
+++ b/arch/loongarch/mm/tlb.c
@@ -9,8 +9,9 @@
#include <linux/hugetlb.h>
#include <linux/export.h>

-#include <asm/cpu.h>
#include <asm/bootinfo.h>
+#include <asm/cpu.h>
+#include <asm/exception.h>
#include <asm/mmu_context.h>
#include <asm/pgtable.h>
#include <asm/tlb.h>
@@ -266,24 +267,20 @@ static void setup_tlb_handler(int cpu)
setup_ptwalker();
local_flush_tlb_all();

+ if (cpu_has_ptw) {
+ exception_table[EXCCODE_TLBI] = handle_tlb_load_ptw;
+ exception_table[EXCCODE_TLBL] = handle_tlb_load_ptw;
+ exception_table[EXCCODE_TLBS] = handle_tlb_store_ptw;
+ exception_table[EXCCODE_TLBM] = handle_tlb_modify_ptw;
+ }
+
/* The tlb handlers are generated only once */
if (cpu == 0) {
memcpy((void *)tlbrentry, handle_tlb_refill, 0x80);
local_flush_icache_range(tlbrentry, tlbrentry + 0x80);
- if (!cpu_has_ptw) {
- set_handler(EXCCODE_TLBI * VECSIZE, handle_tlb_load, VECSIZE);
- set_handler(EXCCODE_TLBL * VECSIZE, handle_tlb_load, VECSIZE);
- set_handler(EXCCODE_TLBS * VECSIZE, handle_tlb_store, VECSIZE);
- set_handler(EXCCODE_TLBM * VECSIZE, handle_tlb_modify, VECSIZE);
- } else {
- set_handler(EXCCODE_TLBI * VECSIZE, handle_tlb_load_ptw, VECSIZE);
- set_handler(EXCCODE_TLBL * VECSIZE, handle_tlb_load_ptw, VECSIZE);
- set_handler(EXCCODE_TLBS * VECSIZE, handle_tlb_store_ptw, VECSIZE);
- set_handler(EXCCODE_TLBM * VECSIZE, handle_tlb_modify_ptw, VECSIZE);
- }
- set_handler(EXCCODE_TLBNR * VECSIZE, handle_tlb_protect, VECSIZE);
- set_handler(EXCCODE_TLBNX * VECSIZE, handle_tlb_protect, VECSIZE);
- set_handler(EXCCODE_TLBPE * VECSIZE, handle_tlb_protect, VECSIZE);
+
+ for (int i = EXCCODE_TLBL; i <= EXCCODE_TLBPE; i++)
+ set_handler(i * VECSIZE, exception_table[i], VECSIZE);
}
#ifdef CONFIG_NUMA
else {
diff --git a/arch/loongarch/mm/tlbex.S b/arch/loongarch/mm/tlbex.S
index d5d682f3d29f..a44387b838af 100644
--- a/arch/loongarch/mm/tlbex.S
+++ b/arch/loongarch/mm/tlbex.S
@@ -18,6 +18,7 @@

.macro tlb_do_page_fault, write
SYM_CODE_START(tlb_do_page_fault_\write)
+ UNWIND_HINT_UNDEFINED
SAVE_ALL
csrrd a2, LOONGARCH_CSR_BADV
move a0, sp
@@ -32,6 +33,7 @@
tlb_do_page_fault 1

SYM_CODE_START(handle_tlb_protect)
+ UNWIND_HINT_UNDEFINED
BACKUP_T0T1
SAVE_ALL
move a0, sp
@@ -44,6 +46,7 @@ SYM_CODE_START(handle_tlb_protect)
SYM_CODE_END(handle_tlb_protect)

SYM_CODE_START(handle_tlb_load)
+ UNWIND_HINT_UNDEFINED
csrwr t0, EXCEPTION_KS0
csrwr t1, EXCEPTION_KS1
csrwr ra, EXCEPTION_KS2
@@ -190,6 +193,7 @@ nopage_tlb_load:
SYM_CODE_END(handle_tlb_load)

SYM_CODE_START(handle_tlb_load_ptw)
+ UNWIND_HINT_UNDEFINED
csrwr t0, LOONGARCH_CSR_KS0
csrwr t1, LOONGARCH_CSR_KS1
la_abs t0, tlb_do_page_fault_0
@@ -197,6 +201,7 @@ SYM_CODE_START(handle_tlb_load_ptw)
SYM_CODE_END(handle_tlb_load_ptw)

SYM_CODE_START(handle_tlb_store)
+ UNWIND_HINT_UNDEFINED
csrwr t0, EXCEPTION_KS0
csrwr t1, EXCEPTION_KS1
csrwr ra, EXCEPTION_KS2
@@ -346,6 +351,7 @@ nopage_tlb_store:
SYM_CODE_END(handle_tlb_store)

SYM_CODE_START(handle_tlb_store_ptw)
+ UNWIND_HINT_UNDEFINED
csrwr t0, LOONGARCH_CSR_KS0
csrwr t1, LOONGARCH_CSR_KS1
la_abs t0, tlb_do_page_fault_1
@@ -353,6 +359,7 @@ SYM_CODE_START(handle_tlb_store_ptw)
SYM_CODE_END(handle_tlb_store_ptw)

SYM_CODE_START(handle_tlb_modify)
+ UNWIND_HINT_UNDEFINED
csrwr t0, EXCEPTION_KS0
csrwr t1, EXCEPTION_KS1
csrwr ra, EXCEPTION_KS2
@@ -500,6 +507,7 @@ nopage_tlb_modify:
SYM_CODE_END(handle_tlb_modify)

SYM_CODE_START(handle_tlb_modify_ptw)
+ UNWIND_HINT_UNDEFINED
csrwr t0, LOONGARCH_CSR_KS0
csrwr t1, LOONGARCH_CSR_KS1
la_abs t0, tlb_do_page_fault_1
@@ -507,6 +515,7 @@ SYM_CODE_START(handle_tlb_modify_ptw)
SYM_CODE_END(handle_tlb_modify_ptw)

SYM_CODE_START(handle_tlb_refill)
+ UNWIND_HINT_UNDEFINED
csrwr t0, LOONGARCH_CSR_TLBRSAVE
csrrd t0, LOONGARCH_CSR_PGD
lddir t0, t0, 3
diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile
index c74c9921304f..7a4ad96522ac 100644
--- a/arch/loongarch/vdso/Makefile
+++ b/arch/loongarch/vdso/Makefile
@@ -3,6 +3,7 @@

KASAN_SANITIZE := n
KCOV_INSTRUMENT := n
+OBJECT_FILES_NON_STANDARD := y

# Include the generic Makefile to check the built vdso.
include $(srctree)/lib/vdso/Makefile
diff --git a/include/linux/compiler.h b/include/linux/compiler.h
index bb1339c7057b..39f2d4a05208 100644
--- a/include/linux/compiler.h
+++ b/include/linux/compiler.h
@@ -116,6 +116,14 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
*/
#define __stringify_label(n) #n

+#define __annotate_reachable(c) ({ \
+ asm volatile(__stringify_label(c) ":\n\t" \
+ ".pushsection .discard.reachable\n\t" \
+ ".long " __stringify_label(c) "b - .\n\t" \
+ ".popsection\n\t"); \
+})
+#define annotate_reachable() __annotate_reachable(__COUNTER__)
+
#define __annotate_unreachable(c) ({ \
asm volatile(__stringify_label(c) ":\n\t" \
".pushsection .discard.unreachable\n\t" \
@@ -128,6 +136,7 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
#define __annotate_jump_table __section(".rodata..c_jump_table")

#else /* !CONFIG_OBJTOOL */
+#define annotate_reachable()
#define annotate_unreachable()
#define __annotate_jump_table
#endif /* CONFIG_OBJTOOL */
diff --git a/scripts/Makefile b/scripts/Makefile
index 576cf64be667..e4cca53d2285 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -31,9 +31,12 @@ HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null |

ifdef CONFIG_UNWINDER_ORC
ifeq ($(ARCH),x86_64)
-ARCH := x86
+SRCARCH := x86
endif
-HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/x86/include
+ifeq ($(ARCH),loongarch)
+SRCARCH := loongarch
+endif
+HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/$(SRCARCH)/include
HOSTCFLAGS_sorttable.o += -DUNWINDER_ORC_ENABLED
endif

--
2.42.0

2023-12-02 13:46:14

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support

Hi, Tiezhu,

On Wed, Nov 29, 2023 at 9:07 PM Tiezhu Yang <[email protected]> wrote:
>
> The kernel CONFIG_UNWINDER_ORC option enables the ORC unwinder, which is
> similar in concept to a DWARF unwinder. The difference is that the format
> of the ORC data is much simpler than DWARF, which in turn allows the ORC
> unwinder to be much simpler and faster.
>
> The ORC data consists of unwind tables which are generated by objtool.
> After analyzing all the code paths of a .o file, it determines information
> about the stack state at each instruction address in the file and outputs
> that information to the .orc_unwind and .orc_unwind_ip sections.
>
> The per-object ORC sections are combined at link time and are sorted and
> post-processed at boot time. The unwinder uses the resulting data to
> correlate instruction addresses with their stack states at run time.
>
> Most of the logic are similar with x86, in order to get ra info before ra
> is saved into stack, add ra_reg and ra_offset into orc_entry. At the same
> time, modify some arch-specific code to silence the objtool warnings.
>
> Co-developed-by: Jinyang He <[email protected]>
> Signed-off-by: Jinyang He <[email protected]>
> Co-developed-by: Youling Tang <[email protected]>
> Signed-off-by: Youling Tang <[email protected]>
> Signed-off-by: Tiezhu Yang <[email protected]>
> ---
> arch/loongarch/Kconfig | 2 +
> arch/loongarch/Kconfig.debug | 11 +
> arch/loongarch/Makefile | 19 +
> arch/loongarch/include/asm/Kbuild | 2 +
> arch/loongarch/include/asm/bug.h | 1 +
> arch/loongarch/include/asm/exception.h | 2 +
> arch/loongarch/include/asm/module.h | 7 +
> arch/loongarch/include/asm/orc_header.h | 18 +
> arch/loongarch/include/asm/orc_lookup.h | 31 ++
> arch/loongarch/include/asm/orc_types.h | 58 +++
> arch/loongarch/include/asm/stackframe.h | 3 +
> arch/loongarch/include/asm/unwind.h | 19 +-
> arch/loongarch/include/asm/unwind_hints.h | 28 ++
> arch/loongarch/kernel/Makefile | 4 +
> arch/loongarch/kernel/entry.S | 6 +-
> arch/loongarch/kernel/fpu.S | 7 +
> arch/loongarch/kernel/genex.S | 6 +-
> arch/loongarch/kernel/lbt.S | 5 +
> arch/loongarch/kernel/module.c | 22 +-
> arch/loongarch/kernel/relocate_kernel.S | 2 +
> arch/loongarch/kernel/setup.c | 2 +
> arch/loongarch/kernel/stacktrace.c | 1 +
> arch/loongarch/kernel/traps.c | 42 +-
> arch/loongarch/kernel/unwind_orc.c | 516 ++++++++++++++++++++++
> arch/loongarch/kernel/vmlinux.lds.S | 3 +
> arch/loongarch/kvm/switch.S | 7 +-
> arch/loongarch/lib/Makefile | 2 +
> arch/loongarch/mm/tlb.c | 27 +-
> arch/loongarch/mm/tlbex.S | 9 +
> arch/loongarch/vdso/Makefile | 1 +
> include/linux/compiler.h | 9 +
> scripts/Makefile | 7 +-
> 32 files changed, 839 insertions(+), 40 deletions(-)
> create mode 100644 arch/loongarch/include/asm/orc_header.h
> create mode 100644 arch/loongarch/include/asm/orc_lookup.h
> create mode 100644 arch/loongarch/include/asm/orc_types.h
> create mode 100644 arch/loongarch/include/asm/unwind_hints.h
> create mode 100644 arch/loongarch/kernel/unwind_orc.c
>
> diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
> index ee123820a476..eea57378646d 100644
> --- a/arch/loongarch/Kconfig
> +++ b/arch/loongarch/Kconfig
> @@ -132,6 +132,7 @@ config LOONGARCH
> select HAVE_KVM
> 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
> @@ -143,6 +144,7 @@ config LOONGARCH
> select HAVE_SAMPLE_FTRACE_DIRECT
> select HAVE_SAMPLE_FTRACE_DIRECT_MULTI
> select HAVE_SETUP_PER_CPU_AREA if NUMA
> + select HAVE_STACK_VALIDATION if HAVE_OBJTOOL
> select HAVE_STACKPROTECTOR
> select HAVE_SYSCALL_TRACEPOINTS
> select HAVE_TIF_NOHZ
> 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/Makefile b/arch/loongarch/Makefile
> index 204b94b2e6aa..1f0e41f8ab61 100644
> --- a/arch/loongarch/Makefile
> +++ b/arch/loongarch/Makefile
> @@ -25,6 +25,18 @@ endif
> 32bit-emul = elf32loongarch
> 64bit-emul = elf64loongarch
>
> +ifdef CONFIG_UNWINDER_ORC
> +orc_hash_h := arch/$(SRCARCH)/include/generated/asm/orc_hash.h
> +orc_hash_sh := $(srctree)/scripts/orc_hash.sh
> +targets += $(orc_hash_h)
> +quiet_cmd_orc_hash = GEN $@
> + cmd_orc_hash = mkdir -p $(dir $@); \
> + $(CONFIG_SHELL) $(orc_hash_sh) < $< > $@
> +$(orc_hash_h): $(srctree)/arch/loongarch/include/asm/orc_types.h $(orc_hash_sh) FORCE
> + $(call if_changed,orc_hash)
> +archprepare: $(orc_hash_h)
> +endif
> +
> ifdef CONFIG_DYNAMIC_FTRACE
> KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
> CC_FLAGS_FTRACE := -fpatchable-function-entry=2
> @@ -81,6 +93,13 @@ KBUILD_AFLAGS_MODULE += -Wa,-mla-global-with-abs
> KBUILD_CFLAGS_MODULE += -fplt -Wa,-mla-global-with-abs,-mla-local-with-abs
> endif
>
> +KBUILD_AFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
> +KBUILD_CFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
> +
> +ifdef CONFIG_OBJTOOL
> +KBUILD_CFLAGS += -fno-optimize-sibling-calls -fno-jump-tables -falign-functions=4 -falign-labels=4
> +endif
> +
> ifeq ($(CONFIG_RELOCATABLE),y)
> KBUILD_CFLAGS_KERNEL += -fPIE
> LDFLAGS_vmlinux += -static -pie --no-dynamic-linker -z notext
> diff --git a/arch/loongarch/include/asm/Kbuild b/arch/loongarch/include/asm/Kbuild
> index 93783fa24f6e..a97c0edbb866 100644
> --- a/arch/loongarch/include/asm/Kbuild
> +++ b/arch/loongarch/include/asm/Kbuild
> @@ -1,4 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
> +generated-y += orc_hash.h
> +
> generic-y += dma-contiguous.h
> generic-y += mcs_spinlock.h
> generic-y += parport.h
> diff --git a/arch/loongarch/include/asm/bug.h b/arch/loongarch/include/asm/bug.h
> index d4ca3ba25418..08388876ade4 100644
> --- a/arch/loongarch/include/asm/bug.h
> +++ b/arch/loongarch/include/asm/bug.h
> @@ -44,6 +44,7 @@
> do { \
> instrumentation_begin(); \
> __BUG_FLAGS(BUGFLAG_WARNING|(flags)); \
> + annotate_reachable(); \
> instrumentation_end(); \
> } while (0)
>
> diff --git a/arch/loongarch/include/asm/exception.h b/arch/loongarch/include/asm/exception.h
> index af74a3fdcad1..c6d20736fd92 100644
> --- a/arch/loongarch/include/asm/exception.h
> +++ b/arch/loongarch/include/asm/exception.h
> @@ -6,6 +6,8 @@
> #include <asm/ptrace.h>
> #include <linux/kprobes.h>
>
> +extern void *exception_table[];
> +
> void show_registers(struct pt_regs *regs);
>
> asmlinkage void cache_parity_error(void);
> diff --git a/arch/loongarch/include/asm/module.h b/arch/loongarch/include/asm/module.h
> index 2ecd82bb64e1..f33f3fd32ecc 100644
> --- a/arch/loongarch/include/asm/module.h
> +++ b/arch/loongarch/include/asm/module.h
> @@ -6,6 +6,7 @@
> #define _ASM_MODULE_H
>
> #include <asm/inst.h>
> +#include <asm/orc_types.h>
> #include <asm-generic/module.h>
>
> #define RELA_STACK_DEPTH 16
> @@ -21,6 +22,12 @@ struct mod_arch_specific {
> struct mod_section plt;
> struct mod_section plt_idx;
>
> +#ifdef CONFIG_UNWINDER_ORC
> + unsigned int num_orcs;
> + int *orc_unwind_ip;
> + struct orc_entry *orc_unwind;
> +#endif
> +
> /* For CONFIG_DYNAMIC_FTRACE */
> struct plt_entry *ftrace_trampolines;
> };
> diff --git a/arch/loongarch/include/asm/orc_header.h b/arch/loongarch/include/asm/orc_header.h
> new file mode 100644
> index 000000000000..f9d509c3fd70
> --- /dev/null
> +++ b/arch/loongarch/include/asm/orc_header.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +#ifndef _ORC_HEADER_H
> +#define _ORC_HEADER_H
> +
> +#include <linux/types.h>
> +#include <linux/compiler.h>
> +#include <asm/orc_hash.h>
> +
> +/*
> + * The header is currently a 20-byte hash of the ORC entry definition; see
> + * scripts/orc_hash.sh.
> + */
> +#define ORC_HEADER \
> + __used __section(".orc_header") __aligned(4) \
> + static const u8 orc_header[] = { ORC_HASH }
> +
> +#endif /* _ORC_HEADER_H */
> diff --git a/arch/loongarch/include/asm/orc_lookup.h b/arch/loongarch/include/asm/orc_lookup.h
> new file mode 100644
> index 000000000000..b02e6357def4
> --- /dev/null
> +++ b/arch/loongarch/include/asm/orc_lookup.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +#ifndef _ORC_LOOKUP_H
> +#define _ORC_LOOKUP_H
> +
> +/*
> + * This is a lookup table for speeding up access to the .orc_unwind table.
> + * Given an input address offset, the corresponding lookup table entry
> + * specifies a subset of the .orc_unwind table to search.
> + *
> + * Each block represents the end of the previous range and the start of the
> + * next range. An extra block is added to give the last range an end.
> + *
> + * The block size should be a power of 2 to avoid a costly 'div' instruction.
> + *
> + * A block size of 256 was chosen because it roughly doubles unwinder
> + * performance while only adding ~5% to the ORC data footprint.
> + */
> +#define LOOKUP_BLOCK_ORDER 8
> +#define LOOKUP_BLOCK_SIZE (1 << LOOKUP_BLOCK_ORDER)
> +
> +#ifndef LINKER_SCRIPT
> +
> +extern unsigned int orc_lookup[];
> +extern unsigned int orc_lookup_end[];
> +
> +#define LOOKUP_START_IP (unsigned long)_stext
> +#define LOOKUP_STOP_IP (unsigned long)_etext
> +
> +#endif /* LINKER_SCRIPT */
> +
> +#endif /* _ORC_LOOKUP_H */
> diff --git a/arch/loongarch/include/asm/orc_types.h b/arch/loongarch/include/asm/orc_types.h
> new file mode 100644
> index 000000000000..caf1f71a1057
> --- /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>
> +
> +/*
> + * 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 FP -- which the previous SP
> + * is usually based on -- and PREV_SP and UNDEFINED -- which the previous FP 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_FP 3
> +#define ORC_REG_MAX 4
> +
> +#define ORC_TYPE_UNDEFINED 0
> +#define ORC_TYPE_END_OF_STACK 1
> +#define ORC_TYPE_CALL 2
> +#define ORC_TYPE_REGS 3
> +#define ORC_TYPE_REGS_PARTIAL 4
> +
> +#ifndef __ASSEMBLY__
> +/*
> + * 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 FP (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 fp_offset;
> + s16 ra_offset;
> + unsigned int sp_reg:4;
> + unsigned int fp_reg:4;
> + unsigned int ra_reg:4;
> + unsigned int type:3;
> + unsigned int signal:1;
> +};
> +#endif /* __ASSEMBLY__ */
> +
> +#endif /* _ORC_TYPES_H */
> diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h
> index 4fb1e6408b98..45b507a7b06f 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
> @@ -162,6 +163,7 @@
> li.w t0, CSR_CRMD_WE
> csrxchg t0, t0, LOONGARCH_CSR_CRMD
> #endif
> + UNWIND_HINT_REGS
> .endm
>
> .macro SAVE_ALL docfi=0
> @@ -219,6 +221,7 @@
>
> .macro RESTORE_SP_AND_RET docfi=0
> cfi_ld sp, PT_R3, \docfi
> + UNWIND_HINT_FUNC
> ertn
> .endm
>
> diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
> index b9dce87afd2e..c7f52d406cce 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, fp, pc, ra;
> };
>
> bool default_next_frame(struct unwind_state *state);
> @@ -34,6 +35,14 @@ 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);
> +void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size);
> +#else
> +static inline void unwind_init(void) {}
> +static inline void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size) {}
> +#endif
> +
> static inline bool unwind_done(struct unwind_state *state)
> {
> return state->stack_info.type == STACK_TYPE_UNKNOWN;
> @@ -61,14 +70,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 +89,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/include/asm/unwind_hints.h b/arch/loongarch/include/asm/unwind_hints.h
> new file mode 100644
> index 000000000000..82443fed3167
> --- /dev/null
> +++ b/arch/loongarch/include/asm/unwind_hints.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_LOONGARCH_UNWIND_HINTS_H
> +#define _ASM_LOONGARCH_UNWIND_HINTS_H
> +
> +#include <linux/objtool.h>
> +#include <asm/orc_types.h>
> +
> +#ifdef __ASSEMBLY__
> +
> +.macro UNWIND_HINT_UNDEFINED
> + UNWIND_HINT type=UNWIND_HINT_TYPE_UNDEFINED
> +.endm
> +
> +.macro UNWIND_HINT_EMPTY
> + UNWIND_HINT sp_reg=ORC_REG_UNDEFINED type=UNWIND_HINT_TYPE_CALL
> +.endm
> +
> +.macro UNWIND_HINT_REGS
> + UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_REGS
> +.endm
> +
> +.macro UNWIND_HINT_FUNC
> + UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_CALL
> +.endm
> +
> +#endif /* __ASSEMBLY__ */
> +
> +#endif /* _ASM_LOONGARCH_UNWIND_HINTS_H */
> diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> index 4fcc168f0732..ac47e11c227e 100644
> --- a/arch/loongarch/kernel/Makefile
> +++ b/arch/loongarch/kernel/Makefile
> @@ -3,6 +3,8 @@
> # Makefile for the Linux/LoongArch kernel.
> #
>
> +OBJECT_FILES_NON_STANDARD_head.o := y
> +
> extra-y := vmlinux.lds
>
> obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \
> @@ -21,6 +23,7 @@ obj-$(CONFIG_ARCH_STRICT_ALIGN) += unaligned.o
>
> CFLAGS_module.o += $(call cc-option,-Wno-override-init,)
> CFLAGS_syscall.o += $(call cc-option,-Wno-override-init,)
> +CFLAGS_traps.o += $(call cc-option,-Wno-override-init,)
> CFLAGS_perf_event.o += $(call cc-option,-Wno-override-init,)
>
> ifdef CONFIG_FUNCTION_TRACER
> @@ -62,6 +65,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/entry.S b/arch/loongarch/kernel/entry.S
> index 1ec8e4c4cc2b..2f5c74d26d5f 100644
> --- a/arch/loongarch/kernel/entry.S
> +++ b/arch/loongarch/kernel/entry.S
> @@ -14,11 +14,12 @@
> #include <asm/regdef.h>
> #include <asm/stackframe.h>
> #include <asm/thread_info.h>
> +#include <asm/unwind_hints.h>
>
> .text
> .cfi_sections .debug_frame
> - .align 5
> SYM_CODE_START(handle_syscall)
> + UNWIND_HINT_UNDEFINED
> csrrd t0, PERCPU_BASE_KS
> la.pcrel t1, kernelsp
> add.d t1, t1, t0
> @@ -57,6 +58,7 @@ SYM_CODE_START(handle_syscall)
> cfi_st fp, PT_R22
>
> SAVE_STATIC
> + UNWIND_HINT_REGS
>
> #ifdef CONFIG_KGDB
> li.w t1, CSR_CRMD_WE
> @@ -75,6 +77,7 @@ SYM_CODE_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
> @@ -84,6 +87,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/fpu.S b/arch/loongarch/kernel/fpu.S
> index d53ab10f4644..487933899c5d 100644
> --- a/arch/loongarch/kernel/fpu.S
> +++ b/arch/loongarch/kernel/fpu.S
> @@ -15,6 +15,7 @@
> #include <asm/fpregdef.h>
> #include <asm/loongarch.h>
> #include <asm/regdef.h>
> +#include <asm/unwind_hints.h>
>
> #define FPU_REG_WIDTH 8
> #define LSX_REG_WIDTH 16
> @@ -524,3 +525,9 @@ SYM_FUNC_END(_restore_lasx_context)
> .L_fpu_fault:
> li.w a0, -EFAULT # failure
> jr ra
> +
> +#ifdef CONFIG_CPU_HAS_LBT
> +STACK_FRAME_NON_STANDARD _restore_fp
> +STACK_FRAME_NON_STANDARD _restore_lsx
> +STACK_FRAME_NON_STANDARD _restore_lasx
> +#endif
> diff --git a/arch/loongarch/kernel/genex.S b/arch/loongarch/kernel/genex.S
> index 2bb3aa2dcfcb..6b7ec9f1134d 100644
> --- a/arch/loongarch/kernel/genex.S
> +++ b/arch/loongarch/kernel/genex.S
> @@ -16,7 +16,6 @@
> #include <asm/stackframe.h>
> #include <asm/thread_info.h>
>
> - .align 5
> SYM_FUNC_START(__arch_cpu_idle)
> /* start of rollback region */
> LONG_L t0, tp, TI_FLAGS
> @@ -32,6 +31,7 @@ SYM_FUNC_START(__arch_cpu_idle)
> SYM_FUNC_END(__arch_cpu_idle)
>
> SYM_CODE_START(handle_vint)
> + UNWIND_HINT_UNDEFINED
> BACKUP_T0T1
> SAVE_ALL
> la_abs t1, __arch_cpu_idle
> @@ -49,6 +49,7 @@ SYM_CODE_START(handle_vint)
> SYM_CODE_END(handle_vint)
>
> SYM_CODE_START(except_vec_cex)
> + UNWIND_HINT_UNDEFINED
> b cache_parity_error
> SYM_CODE_END(except_vec_cex)
>
> @@ -65,8 +66,8 @@ SYM_CODE_END(except_vec_cex)
> .endm
>
> .macro BUILD_HANDLER exception handler prep
> - .align 5
> SYM_CODE_START(handle_\exception)
> + UNWIND_HINT_UNDEFINED
> 666:
> BACKUP_T0T1
> SAVE_ALL
> @@ -94,6 +95,7 @@ SYM_CODE_END(except_vec_cex)
> BUILD_HANDLER reserved reserved none /* others */
>
> SYM_CODE_START(handle_sys)
> + UNWIND_HINT_UNDEFINED
> la_abs t0, handle_syscall
> jr t0
> SYM_CODE_END(handle_sys)
> diff --git a/arch/loongarch/kernel/lbt.S b/arch/loongarch/kernel/lbt.S
> index 9c75120a26d8..4d6914f84c7e 100644
> --- a/arch/loongarch/kernel/lbt.S
> +++ b/arch/loongarch/kernel/lbt.S
> @@ -11,6 +11,7 @@
> #include <asm/asm-offsets.h>
> #include <asm/errno.h>
> #include <asm/regdef.h>
> +#include <asm/unwind_hints.h>
>
> #define SCR_REG_WIDTH 8
>
> @@ -153,3 +154,7 @@ SYM_FUNC_END(_restore_ftop_context)
> .L_lbt_fault:
> li.w a0, -EFAULT # failure
> jr ra
> +
> +#ifdef CONFIG_CPU_HAS_LBT
> +STACK_FRAME_NON_STANDARD _restore_ftop_context
> +#endif
This file is only compiled if CONFIG_CPU_HAS_LBT is set, so #ifdef can
be removed.

Huacai

> diff --git a/arch/loongarch/kernel/module.c b/arch/loongarch/kernel/module.c
> index b13b2858fe39..c7d0338d12c1 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/unwind.h>
>
> static int rela_stack_push(s64 stack_value, s64 *rela_stack, size_t *rela_stack_top)
> {
> @@ -515,15 +516,28 @@ 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 char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
> + const Elf_Shdr *s, *alt = NULL, *orc = NULL, *orc_ip = NULL, *ftrace = NULL;
>
> - for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) {
> + for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
> if (!strcmp(".altinstructions", secstrs + s->sh_name))
> - apply_alternatives((void *)s->sh_addr, (void *)s->sh_addr + s->sh_size);
> + alt = s;
> + if (!strcmp(".orc_unwind", secstrs + s->sh_name))
> + orc = s;
> + if (!strcmp(".orc_unwind_ip", secstrs + s->sh_name))
> + orc_ip = s;
> if (!strcmp(".ftrace_trampoline", secstrs + s->sh_name))
> - module_init_ftrace_plt(hdr, s, mod);
> + ftrace = s;
> }
>
> + if (alt)
> + apply_alternatives((void *)alt->sh_addr, (void *)alt->sh_addr + alt->sh_size);
> +
> + if (orc && orc_ip)
> + unwind_module_init(mod, (void *)orc_ip->sh_addr, orc_ip->sh_size, (void *)orc->sh_addr, orc->sh_size);
> +
> + if (ftrace)
> + module_init_ftrace_plt(hdr, ftrace, mod);
> +
> return 0;
> }
> diff --git a/arch/loongarch/kernel/relocate_kernel.S b/arch/loongarch/kernel/relocate_kernel.S
> index f49f6b053763..bcc191d278c1 100644
> --- a/arch/loongarch/kernel/relocate_kernel.S
> +++ b/arch/loongarch/kernel/relocate_kernel.S
> @@ -15,6 +15,7 @@
> #include <asm/addrspace.h>
>
> SYM_CODE_START(relocate_new_kernel)
> + UNWIND_HINT_UNDEFINED
> /*
> * a0: EFI boot flag for the new kernel
> * a1: Command line pointer for the new kernel
> @@ -90,6 +91,7 @@ SYM_CODE_END(relocate_new_kernel)
> * then start at the entry point from LOONGARCH_IOCSR_MBUF0.
> */
> SYM_CODE_START(kexec_smp_wait)
> + UNWIND_HINT_UNDEFINED
> 1: li.w t0, 0x100 /* wait for init loop */
> 2: addi.w t0, t0, -1 /* limit mailbox access */
> bnez t0, 2b
> diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
> index d183a745fb85..ec4459c61db6 100644
> --- a/arch/loongarch/kernel/setup.c
> +++ b/arch/loongarch/kernel/setup.c
> @@ -47,6 +47,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
> @@ -605,6 +606,7 @@ static void __init prefill_possible_map(void)
> void __init setup_arch(char **cmdline_p)
> {
> cpu_probe();
> + unwind_init();
>
> init_environ();
> efi_init();
> diff --git a/arch/loongarch/kernel/stacktrace.c b/arch/loongarch/kernel/stacktrace.c
> index 92270f14db94..9848d427cbfa 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->csr_era = thread_saved_ra(task);
> }
> regs->regs[1] = 0;
> + regs->regs[22] = 0;
> }
>
> for (unwind_start(&state, task, regs);
> diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
> index aebfc3733a76..f9f4eb00c92e 100644
> --- a/arch/loongarch/kernel/traps.c
> +++ b/arch/loongarch/kernel/traps.c
> @@ -53,6 +53,32 @@
>
> #include "access-helper.h"
>
> +void *exception_table[EXCCODE_INT_START] = {
> + [0 ... EXCCODE_INT_START - 1] = handle_reserved,
> +
> + [EXCCODE_TLBI] = handle_tlb_load,
> + [EXCCODE_TLBL] = handle_tlb_load,
> + [EXCCODE_TLBS] = handle_tlb_store,
> + [EXCCODE_TLBM] = handle_tlb_modify,
> + [EXCCODE_TLBNR] = handle_tlb_protect,
> + [EXCCODE_TLBNX] = handle_tlb_protect,
> + [EXCCODE_TLBPE] = handle_tlb_protect,
> + [EXCCODE_ADE] = handle_ade,
> + [EXCCODE_ALE] = handle_ale,
> + [EXCCODE_BCE] = handle_bce,
> + [EXCCODE_SYS] = handle_sys,
> + [EXCCODE_BP] = handle_bp,
> + [EXCCODE_INE] = handle_ri,
> + [EXCCODE_IPE] = handle_ri,
> + [EXCCODE_FPDIS] = handle_fpu,
> + [EXCCODE_LSXDIS] = handle_lsx,
> + [EXCCODE_LASXDIS] = handle_lasx,
> + [EXCCODE_FPE] = handle_fpe,
> + [EXCCODE_WATCH] = handle_watch,
> + [EXCCODE_BTDIS] = handle_lbt,
> +};
> +EXPORT_SYMBOL_GPL(exception_table);
> +
> static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
> const char *loglvl, bool user)
> {
> @@ -1150,19 +1176,9 @@ void __init trap_init(void)
> for (i = EXCCODE_INT_START; i <= EXCCODE_INT_END; i++)
> set_handler(i * VECSIZE, handle_vint, VECSIZE);
>
> - set_handler(EXCCODE_ADE * VECSIZE, handle_ade, VECSIZE);
> - set_handler(EXCCODE_ALE * VECSIZE, handle_ale, VECSIZE);
> - set_handler(EXCCODE_BCE * VECSIZE, handle_bce, VECSIZE);
> - set_handler(EXCCODE_SYS * VECSIZE, handle_sys, VECSIZE);
> - set_handler(EXCCODE_BP * VECSIZE, handle_bp, VECSIZE);
> - set_handler(EXCCODE_INE * VECSIZE, handle_ri, VECSIZE);
> - set_handler(EXCCODE_IPE * VECSIZE, handle_ri, VECSIZE);
> - set_handler(EXCCODE_FPDIS * VECSIZE, handle_fpu, VECSIZE);
> - set_handler(EXCCODE_LSXDIS * VECSIZE, handle_lsx, VECSIZE);
> - set_handler(EXCCODE_LASXDIS * VECSIZE, handle_lasx, VECSIZE);
> - set_handler(EXCCODE_FPE * VECSIZE, handle_fpe, VECSIZE);
> - set_handler(EXCCODE_BTDIS * VECSIZE, handle_lbt, VECSIZE);
> - set_handler(EXCCODE_WATCH * VECSIZE, handle_watch, VECSIZE);
> + /* Set exception vector handler */
> + for (i = EXCCODE_ADE; i <= EXCCODE_BTDIS; i++)
> + set_handler(i * VECSIZE, exception_table[i], VECSIZE);
>
> cache_error_setup();
>
> diff --git a/arch/loongarch/kernel/unwind_orc.c b/arch/loongarch/kernel/unwind_orc.c
> new file mode 100644
> index 000000000000..15f18d1e0dad
> --- /dev/null
> +++ b/arch/loongarch/kernel/unwind_orc.c
> @@ -0,0 +1,516 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/objtool.h>
> +#include <linux/module.h>
> +#include <linux/sort.h>
> +#include <asm/exception.h>
> +#include <asm/orc_header.h>
> +#include <asm/orc_lookup.h>
> +#include <asm/orc_types.h>
> +#include <asm/ptrace.h>
> +#include <asm/setup.h>
> +#include <asm/stacktrace.h>
> +#include <asm/tlb.h>
> +#include <asm/unwind.h>
> +
> +ORC_HEADER;
> +
> +#define orc_warn(fmt, ...) \
> + printk_deferred_once(KERN_WARNING "WARNING: " fmt, ##__VA_ARGS__)
> +
> +extern int __start_orc_unwind_ip[];
> +extern int __stop_orc_unwind_ip[];
> +extern struct orc_entry __start_orc_unwind[];
> +extern struct orc_entry __stop_orc_unwind[];
> +
> +static bool orc_init __ro_after_init;
> +static unsigned int lookup_num_blocks __ro_after_init;
> +
> +/* Fake frame pointer entry -- used as a fallback for generated code */
> +static struct orc_entry orc_fp_entry = {
> + .sp_reg = ORC_REG_FP,
> + .sp_offset = 16,
> + .fp_reg = ORC_REG_PREV_SP,
> + .fp_offset = -16,
> + .ra_reg = ORC_REG_PREV_SP,
> + .ra_offset = -8,
> + .type = ORC_TYPE_CALL
> +};
> +
> +/*
> + * If we crash with IP==0, the last successfully executed instruction
> + * was probably an indirect function call with a NULL function pointer,
> + * and we don't have unwind information for NULL.
> + * This hardcoded ORC entry for IP==0 allows us to unwind from a NULL function
> + * pointer into its parent and then continue normally from there.
> + */
> +static struct orc_entry orc_null_entry = {
> + .sp_reg = ORC_REG_SP,
> + .sp_offset = sizeof(long),
> + .fp_reg = ORC_REG_UNDEFINED,
> + .type = ORC_TYPE_CALL
> +};
> +
> +static inline unsigned long orc_ip(const int *ip)
> +{
> + return (unsigned long)ip + *ip;
> +}
> +
> +static struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
> + unsigned int num_entries, unsigned long ip)
> +{
> + int *first = ip_table;
> + int *mid = first, *found = first;
> + int *last = ip_table + num_entries - 1;
> +
> + if (!num_entries)
> + return NULL;
> +
> + /*
> + * Do a binary range search to find the rightmost duplicate of a given
> + * starting address. Some entries are section terminators which are
> + * "weak" entries for ensuring there are no gaps. They should be
> + * ignored when they conflict with a real entry.
> + */
> + while (first <= last) {
> + mid = first + ((last - first) / 2);
> +
> + if (orc_ip(mid) <= ip) {
> + found = mid;
> + first = mid + 1;
> + } else
> + last = mid - 1;
> + }
> +
> + return u_table + (found - ip_table);
> +}
> +
> +#ifdef CONFIG_MODULES
> +static struct orc_entry *orc_module_find(unsigned long ip)
> +{
> + struct module *mod;
> +
> + mod = __module_address(ip);
> + if (!mod || !mod->arch.orc_unwind || !mod->arch.orc_unwind_ip)
> + return NULL;
> +
> + return __orc_find(mod->arch.orc_unwind_ip, mod->arch.orc_unwind, mod->arch.num_orcs, ip);
> +}
> +#else
> +static struct orc_entry *orc_module_find(unsigned long ip)
> +{
> + return NULL;
> +}
> +#endif
> +
> +#ifdef CONFIG_DYNAMIC_FTRACE
> +static struct orc_entry *orc_find(unsigned long ip);
> +
> +/*
> + * Ftrace dynamic trampolines do not have orc entries of their own.
> + * But they are copies of the ftrace entries that are static and
> + * defined in ftrace_*.S, which do have orc entries.
> + *
> + * If the unwinder comes across a ftrace trampoline, then find the
> + * ftrace function that was used to create it, and use that ftrace
> + * function's orc entry, as the placement of the return code in
> + * the stack will be identical.
> + */
> +static struct orc_entry *orc_ftrace_find(unsigned long ip)
> +{
> + struct ftrace_ops *ops;
> + unsigned long tramp_addr, offset;
> +
> + ops = ftrace_ops_trampoline(ip);
> + if (!ops)
> + return NULL;
> +
> + /* Set tramp_addr to the start of the code copied by the trampoline */
> + if (ops->flags & FTRACE_OPS_FL_SAVE_REGS)
> + tramp_addr = (unsigned long)ftrace_regs_caller;
> + else
> + tramp_addr = (unsigned long)ftrace_caller;
> +
> + /* Now place tramp_addr to the location within the trampoline ip is at */
> + offset = ip - ops->trampoline;
> + tramp_addr += offset;
> +
> + /* Prevent unlikely recursion */
> + if (ip == tramp_addr)
> + return NULL;
> +
> + return orc_find(tramp_addr);
> +}
> +#else
> +static struct orc_entry *orc_ftrace_find(unsigned long ip)
> +{
> + return NULL;
> +}
> +#endif
> +
> +static struct orc_entry *orc_find(unsigned long ip)
> +{
> + static struct orc_entry *orc;
> +
> + if (ip == 0)
> + return &orc_null_entry;
> +
> + /* For non-init vmlinux addresses, use the fast lookup table: */
> + if (ip >= LOOKUP_START_IP && ip < LOOKUP_STOP_IP) {
> + unsigned int idx, start, stop;
> +
> + idx = (ip - LOOKUP_START_IP) / LOOKUP_BLOCK_SIZE;
> +
> + if (unlikely((idx >= lookup_num_blocks-1))) {
> + orc_warn("WARNING: bad lookup idx: idx=%u num=%u ip=%pB\n",
> + idx, lookup_num_blocks, (void *)ip);
> + return NULL;
> + }
> +
> + start = orc_lookup[idx];
> + stop = orc_lookup[idx + 1] + 1;
> +
> + if (unlikely((__start_orc_unwind + start >= __stop_orc_unwind) ||
> + (__start_orc_unwind + stop > __stop_orc_unwind))) {
> + orc_warn("WARNING: bad lookup value: idx=%u num=%u start=%u stop=%u ip=%pB\n",
> + idx, lookup_num_blocks, start, stop, (void *)ip);
> + return NULL;
> + }
> +
> + return __orc_find(__start_orc_unwind_ip + start,
> + __start_orc_unwind + start, stop - start, ip);
> + }
> +
> + /* vmlinux .init slow lookup: */
> + if (is_kernel_inittext(ip))
> + return __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
> + __stop_orc_unwind_ip - __start_orc_unwind_ip, ip);
> +
> + /* Module lookup: */
> + orc = orc_module_find(ip);
> + if (orc)
> + return orc;
> +
> + return orc_ftrace_find(ip);
> +}
> +
> +#ifdef CONFIG_MODULES
> +
> +static DEFINE_MUTEX(sort_mutex);
> +static int *cur_orc_ip_table = __start_orc_unwind_ip;
> +static struct orc_entry *cur_orc_table = __start_orc_unwind;
> +
> +static void orc_sort_swap(void *_a, void *_b, int size)
> +{
> + int delta = _b - _a;
> + int *a = _a, *b = _b, tmp;
> + struct orc_entry *orc_a, *orc_b;
> +
> + /* Swap the .orc_unwind_ip entries: */
> + tmp = *a;
> + *a = *b + delta;
> + *b = tmp - delta;
> +
> + /* Swap the corresponding .orc_unwind entries: */
> + orc_a = cur_orc_table + (a - cur_orc_ip_table);
> + orc_b = cur_orc_table + (b - cur_orc_ip_table);
> + swap(*orc_a, *orc_b);
> +}
> +
> +static int orc_sort_cmp(const void *_a, const void *_b)
> +{
> + const int *a = _a, *b = _b;
> + unsigned long a_val = orc_ip(a);
> + unsigned long b_val = orc_ip(b);
> + struct orc_entry *orc_a;
> +
> + if (a_val > b_val)
> + return 1;
> + if (a_val < b_val)
> + return -1;
> +
> + /*
> + * The "weak" section terminator entries need to always be first
> + * to ensure the lookup code skips them in favor of real entries.
> + * These terminator entries exist to handle any gaps created by
> + * whitelisted .o files which didn't get objtool generation.
> + */
> + orc_a = cur_orc_table + (a - cur_orc_ip_table);
> +
> + return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
> +}
> +
> +void unwind_module_init(struct module *mod, void *_orc_ip, size_t orc_ip_size,
> + void *_orc, size_t orc_size)
> +{
> + int *orc_ip = _orc_ip;
> + struct orc_entry *orc = _orc;
> + unsigned int num_entries = orc_ip_size / sizeof(int);
> +
> + WARN_ON_ONCE(orc_ip_size % sizeof(int) != 0 ||
> + orc_size % sizeof(*orc) != 0 ||
> + num_entries != orc_size / sizeof(*orc));
> +
> + /*
> + * The 'cur_orc_*' globals allow the orc_sort_swap() callback to
> + * associate an .orc_unwind_ip table entry with its corresponding
> + * .orc_unwind entry so they can both be swapped.
> + */
> + mutex_lock(&sort_mutex);
> + cur_orc_ip_table = orc_ip;
> + cur_orc_table = orc;
> + sort(orc_ip, num_entries, sizeof(int), orc_sort_cmp, orc_sort_swap);
> + mutex_unlock(&sort_mutex);
> +
> + mod->arch.orc_unwind_ip = orc_ip;
> + mod->arch.orc_unwind = orc;
> + mod->arch.num_orcs = num_entries;
> +}
> +#endif
> +
> +void __init unwind_init(void)
> +{
> + int i;
> + size_t orc_size = (void *)__stop_orc_unwind - (void *)__start_orc_unwind;
> + size_t orc_ip_size = (void *)__stop_orc_unwind_ip - (void *)__start_orc_unwind_ip;
> + size_t num_entries = orc_ip_size / sizeof(int);
> + struct orc_entry *orc;
> +
> + if (!num_entries || orc_ip_size % sizeof(int) != 0 ||
> + orc_size % sizeof(struct orc_entry) != 0 ||
> + num_entries != orc_size / sizeof(struct orc_entry)) {
> + orc_warn("WARNING: Bad or missing .orc_unwind table. Disabling unwinder.\n");
> + return;
> + }
> +
> + /*
> + * Note, the orc_unwind and orc_unwind_ip tables were already
> + * sorted at build time via the 'sorttable' tool.
> + * It's ready for binary search straight away, no need to sort it.
> + */
> +
> + /* Initialize the fast lookup table: */
> + lookup_num_blocks = orc_lookup_end - orc_lookup;
> + for (i = 0; i < lookup_num_blocks-1; i++) {
> + orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
> + num_entries, LOOKUP_START_IP + (LOOKUP_BLOCK_SIZE * i));
> + if (!orc) {
> + orc_warn("WARNING: Corrupt .orc_unwind table. Disabling unwinder.\n");
> + return;
> + }
> +
> + orc_lookup[i] = orc - __start_orc_unwind;
> + }
> +
> + /* Initialize the ending block: */
> + orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind, num_entries, LOOKUP_STOP_IP);
> + if (!orc) {
> + orc_warn("WARNING: Corrupt .orc_unwind table. Disabling unwinder.\n");
> + return;
> + }
> + orc_lookup[lookup_num_blocks-1] = orc - __start_orc_unwind;
> +
> + orc_init = true;
> +}
> +
> +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);
> + state->type = UNWINDER_ORC;
> + 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;
> +
> + if (__module_text_address(ra))
> + return ra;
> +
> + if (ra >= eentry && ra < eentry + EXCCODE_INT_END * VECSIZE) {
> + unsigned long func;
> + unsigned long type = (ra - eentry) / VECSIZE;
> + unsigned long offset = (ra - eentry) % VECSIZE;
> +
> + switch (type) {
> + case 0 ... EXCCODE_INT_START - 1:
> + func = (unsigned long)exception_table[type];
> + break;
> + case EXCCODE_INT_START ... EXCCODE_INT_END:
> + 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)
> +{
> + unsigned long *p, pc;
> + struct pt_regs *regs;
> + struct orc_entry *orc;
> + struct stack_info *info = &state->stack_info;
> +
> + 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:
> + if (info->type == STACK_TYPE_IRQ && state->sp == info->end)
> + orc->type = ORC_TYPE_REGS;
> + else
> + state->sp = state->sp + orc->sp_offset;
> + break;
> + case ORC_REG_FP:
> + 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->fp_reg) {
> + case ORC_REG_PREV_SP:
> + p = (unsigned long *)(state->sp + orc->fp_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->fp_reg, (void *)state->pc);
> + goto err;
> + }
> +
> + switch (orc->type) {
> + case ORC_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 ORC_TYPE_REGS:
> + if (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 bb2ec86f37a8..eaa7a91162e6 100644
> --- a/arch/loongarch/kernel/vmlinux.lds.S
> +++ b/arch/loongarch/kernel/vmlinux.lds.S
> @@ -2,6 +2,7 @@
> #include <linux/sizes.h>
> #include <asm/asm-offsets.h>
> #include <asm/thread_info.h>
> +#include <asm/orc_lookup.h>
>
> #define PAGE_SIZE _PAGE_SIZE
> #define RO_EXCEPTION_TABLE_ALIGN 4
> @@ -122,6 +123,8 @@ SECTIONS
> }
> #endif
>
> + ORC_UNWIND_TABLE
> +
> .sdata : {
> *(.sdata)
> }
> diff --git a/arch/loongarch/kvm/switch.S b/arch/loongarch/kvm/switch.S
> index 0ed9040307b7..9d49c3f6fff5 100644
> --- a/arch/loongarch/kvm/switch.S
> +++ b/arch/loongarch/kvm/switch.S
> @@ -8,7 +8,7 @@
> #include <asm/asmmacro.h>
> #include <asm/loongarch.h>
> #include <asm/regdef.h>
> -#include <asm/stackframe.h>
> +#include <asm/unwind_hints.h>
>
> #define HGPR_OFFSET(x) (PT_R0 + 8*x)
> #define GGPR_OFFSET(x) (KVM_ARCH_GGPR + 8*x)
> @@ -112,6 +112,7 @@
> .text
> .cfi_sections .debug_frame
> SYM_CODE_START(kvm_exc_entry)
> + UNWIND_HINT_UNDEFINED
> csrwr a2, KVM_TEMP_KS
> csrrd a2, KVM_VCPU_KS
> addi.d a2, a2, KVM_VCPU_ARCH
> @@ -248,3 +249,7 @@ SYM_FUNC_END(kvm_restore_fpu)
> .section ".rodata"
> SYM_DATA(kvm_exception_size, .quad kvm_exc_entry_end - kvm_exc_entry)
> SYM_DATA(kvm_enter_guest_size, .quad kvm_enter_guest_end - kvm_enter_guest)
> +
> +#ifdef CONFIG_CPU_HAS_LBT
> +STACK_FRAME_NON_STANDARD kvm_restore_fpu
> +#endif
> diff --git a/arch/loongarch/lib/Makefile b/arch/loongarch/lib/Makefile
> index a77bf160bfc4..e3023d9a508c 100644
> --- a/arch/loongarch/lib/Makefile
> +++ b/arch/loongarch/lib/Makefile
> @@ -3,6 +3,8 @@
> # Makefile for LoongArch-specific library files.
> #
>
> +OBJECT_FILES_NON_STANDARD := y
> +
> lib-y += delay.o memset.o memcpy.o memmove.o \
> clear_user.o copy_user.o csum.o dump_tlb.o unaligned.o
>
> diff --git a/arch/loongarch/mm/tlb.c b/arch/loongarch/mm/tlb.c
> index 2c0a411f23aa..f01172a8f4e9 100644
> --- a/arch/loongarch/mm/tlb.c
> +++ b/arch/loongarch/mm/tlb.c
> @@ -9,8 +9,9 @@
> #include <linux/hugetlb.h>
> #include <linux/export.h>
>
> -#include <asm/cpu.h>
> #include <asm/bootinfo.h>
> +#include <asm/cpu.h>
> +#include <asm/exception.h>
> #include <asm/mmu_context.h>
> #include <asm/pgtable.h>
> #include <asm/tlb.h>
> @@ -266,24 +267,20 @@ static void setup_tlb_handler(int cpu)
> setup_ptwalker();
> local_flush_tlb_all();
>
> + if (cpu_has_ptw) {
> + exception_table[EXCCODE_TLBI] = handle_tlb_load_ptw;
> + exception_table[EXCCODE_TLBL] = handle_tlb_load_ptw;
> + exception_table[EXCCODE_TLBS] = handle_tlb_store_ptw;
> + exception_table[EXCCODE_TLBM] = handle_tlb_modify_ptw;
> + }
> +
> /* The tlb handlers are generated only once */
> if (cpu == 0) {
> memcpy((void *)tlbrentry, handle_tlb_refill, 0x80);
> local_flush_icache_range(tlbrentry, tlbrentry + 0x80);
> - if (!cpu_has_ptw) {
> - set_handler(EXCCODE_TLBI * VECSIZE, handle_tlb_load, VECSIZE);
> - set_handler(EXCCODE_TLBL * VECSIZE, handle_tlb_load, VECSIZE);
> - set_handler(EXCCODE_TLBS * VECSIZE, handle_tlb_store, VECSIZE);
> - set_handler(EXCCODE_TLBM * VECSIZE, handle_tlb_modify, VECSIZE);
> - } else {
> - set_handler(EXCCODE_TLBI * VECSIZE, handle_tlb_load_ptw, VECSIZE);
> - set_handler(EXCCODE_TLBL * VECSIZE, handle_tlb_load_ptw, VECSIZE);
> - set_handler(EXCCODE_TLBS * VECSIZE, handle_tlb_store_ptw, VECSIZE);
> - set_handler(EXCCODE_TLBM * VECSIZE, handle_tlb_modify_ptw, VECSIZE);
> - }
> - set_handler(EXCCODE_TLBNR * VECSIZE, handle_tlb_protect, VECSIZE);
> - set_handler(EXCCODE_TLBNX * VECSIZE, handle_tlb_protect, VECSIZE);
> - set_handler(EXCCODE_TLBPE * VECSIZE, handle_tlb_protect, VECSIZE);
> +
> + for (int i = EXCCODE_TLBL; i <= EXCCODE_TLBPE; i++)
> + set_handler(i * VECSIZE, exception_table[i], VECSIZE);
> }
> #ifdef CONFIG_NUMA
> else {
> diff --git a/arch/loongarch/mm/tlbex.S b/arch/loongarch/mm/tlbex.S
> index d5d682f3d29f..a44387b838af 100644
> --- a/arch/loongarch/mm/tlbex.S
> +++ b/arch/loongarch/mm/tlbex.S
> @@ -18,6 +18,7 @@
>
> .macro tlb_do_page_fault, write
> SYM_CODE_START(tlb_do_page_fault_\write)
> + UNWIND_HINT_UNDEFINED
> SAVE_ALL
> csrrd a2, LOONGARCH_CSR_BADV
> move a0, sp
> @@ -32,6 +33,7 @@
> tlb_do_page_fault 1
>
> SYM_CODE_START(handle_tlb_protect)
> + UNWIND_HINT_UNDEFINED
> BACKUP_T0T1
> SAVE_ALL
> move a0, sp
> @@ -44,6 +46,7 @@ SYM_CODE_START(handle_tlb_protect)
> SYM_CODE_END(handle_tlb_protect)
>
> SYM_CODE_START(handle_tlb_load)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, EXCEPTION_KS0
> csrwr t1, EXCEPTION_KS1
> csrwr ra, EXCEPTION_KS2
> @@ -190,6 +193,7 @@ nopage_tlb_load:
> SYM_CODE_END(handle_tlb_load)
>
> SYM_CODE_START(handle_tlb_load_ptw)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_KS0
> csrwr t1, LOONGARCH_CSR_KS1
> la_abs t0, tlb_do_page_fault_0
> @@ -197,6 +201,7 @@ SYM_CODE_START(handle_tlb_load_ptw)
> SYM_CODE_END(handle_tlb_load_ptw)
>
> SYM_CODE_START(handle_tlb_store)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, EXCEPTION_KS0
> csrwr t1, EXCEPTION_KS1
> csrwr ra, EXCEPTION_KS2
> @@ -346,6 +351,7 @@ nopage_tlb_store:
> SYM_CODE_END(handle_tlb_store)
>
> SYM_CODE_START(handle_tlb_store_ptw)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_KS0
> csrwr t1, LOONGARCH_CSR_KS1
> la_abs t0, tlb_do_page_fault_1
> @@ -353,6 +359,7 @@ SYM_CODE_START(handle_tlb_store_ptw)
> SYM_CODE_END(handle_tlb_store_ptw)
>
> SYM_CODE_START(handle_tlb_modify)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, EXCEPTION_KS0
> csrwr t1, EXCEPTION_KS1
> csrwr ra, EXCEPTION_KS2
> @@ -500,6 +507,7 @@ nopage_tlb_modify:
> SYM_CODE_END(handle_tlb_modify)
>
> SYM_CODE_START(handle_tlb_modify_ptw)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_KS0
> csrwr t1, LOONGARCH_CSR_KS1
> la_abs t0, tlb_do_page_fault_1
> @@ -507,6 +515,7 @@ SYM_CODE_START(handle_tlb_modify_ptw)
> SYM_CODE_END(handle_tlb_modify_ptw)
>
> SYM_CODE_START(handle_tlb_refill)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_TLBRSAVE
> csrrd t0, LOONGARCH_CSR_PGD
> lddir t0, t0, 3
> diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile
> index c74c9921304f..7a4ad96522ac 100644
> --- a/arch/loongarch/vdso/Makefile
> +++ b/arch/loongarch/vdso/Makefile
> @@ -3,6 +3,7 @@
>
> KASAN_SANITIZE := n
> KCOV_INSTRUMENT := n
> +OBJECT_FILES_NON_STANDARD := y
>
> # Include the generic Makefile to check the built vdso.
> include $(srctree)/lib/vdso/Makefile
> diff --git a/include/linux/compiler.h b/include/linux/compiler.h
> index bb1339c7057b..39f2d4a05208 100644
> --- a/include/linux/compiler.h
> +++ b/include/linux/compiler.h
> @@ -116,6 +116,14 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
> */
> #define __stringify_label(n) #n
>
> +#define __annotate_reachable(c) ({ \
> + asm volatile(__stringify_label(c) ":\n\t" \
> + ".pushsection .discard.reachable\n\t" \
> + ".long " __stringify_label(c) "b - .\n\t" \
> + ".popsection\n\t"); \
> +})
> +#define annotate_reachable() __annotate_reachable(__COUNTER__)
> +
> #define __annotate_unreachable(c) ({ \
> asm volatile(__stringify_label(c) ":\n\t" \
> ".pushsection .discard.unreachable\n\t" \
> @@ -128,6 +136,7 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
> #define __annotate_jump_table __section(".rodata..c_jump_table")
>
> #else /* !CONFIG_OBJTOOL */
> +#define annotate_reachable()
> #define annotate_unreachable()
> #define __annotate_jump_table
> #endif /* CONFIG_OBJTOOL */
> diff --git a/scripts/Makefile b/scripts/Makefile
> index 576cf64be667..e4cca53d2285 100644
> --- a/scripts/Makefile
> +++ b/scripts/Makefile
> @@ -31,9 +31,12 @@ HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null |
>
> ifdef CONFIG_UNWINDER_ORC
> ifeq ($(ARCH),x86_64)
> -ARCH := x86
> +SRCARCH := x86
> endif
> -HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/x86/include
> +ifeq ($(ARCH),loongarch)
> +SRCARCH := loongarch
> +endif
> +HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/$(SRCARCH)/include
> HOSTCFLAGS_sorttable.o += -DUNWINDER_ORC_ENABLED
> endif
>
> --
> 2.42.0
>
>

2023-12-02 13:53:33

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH v5 0/8] Add objtool and orc support for LoongArch

Hi, Tiezhu,

There are still some build warnings.

If FUNCTION_TRACER enabled, we get
AS arch/loongarch/kernel/mcount_dyn.o
arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
ftrace_common+0x0: unreachable instruction
arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
ftrace_caller+0x0: unreachable instruction
arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
ftrace_regs_caller+0x0: unreachable instruction
arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
ftrace_graph_caller+0x0: unreachable instruction
arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
return_to_handler+0x0: unreachable instruction
arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
ftrace_stub_direct_tramp+0x0: unreachable instruction

AS arch/loongarch/kernel/rethook_trampoline.o
arch/loongarch/kernel/rethook_trampoline.o: warning: objtool:
arch_rethook_trampoline+0x0: unreachable instruction

And when I use the latest binutils/gcc for native build, there are
some warnings in some files from the {init, kernel} directory. But
cross-building is fine, I don't know why.

Huacai

On Wed, Nov 29, 2023 at 9:07 PM Tiezhu Yang <[email protected]> wrote:
>
> This version is based on 6.7-rc3, tested with the latest upstream
> gcc and binutils (20231127).
>
> v5:
> -- Fix the partial backtrace about irq stack
> -- Check on_stack() again after get_stack_info()
> -- Silence the orc warning about handle_syscall
> -- Silence the objtool warnings if CONFIG_CPU_HAS_LBT=y
> -- Update the commit message about objdump and readelf info
>
> Tiezhu Yang (8):
> objtool/LoongArch: Enable objtool to be built
> objtool/LoongArch: Implement instruction decoder
> objtool/x86: Separate arch-specific and generic parts
> objtool/LoongArch: Enable orc to be built
> objtool: Check local label about sibling call
> objtool: Check local label in add_dead_ends()
> objtool: Check local label in read_unwind_hints()
> LoongArch: Add ORC stack unwinder support
>
> arch/loongarch/Kconfig | 2 +
> arch/loongarch/Kconfig.debug | 11 +
> arch/loongarch/Makefile | 19 +
> arch/loongarch/include/asm/Kbuild | 2 +
> arch/loongarch/include/asm/bug.h | 1 +
> arch/loongarch/include/asm/exception.h | 2 +
> arch/loongarch/include/asm/module.h | 7 +
> arch/loongarch/include/asm/orc_header.h | 18 +
> arch/loongarch/include/asm/orc_lookup.h | 31 ++
> arch/loongarch/include/asm/orc_types.h | 58 ++
> arch/loongarch/include/asm/stackframe.h | 3 +
> arch/loongarch/include/asm/unwind.h | 19 +-
> arch/loongarch/include/asm/unwind_hints.h | 28 +
> arch/loongarch/kernel/Makefile | 4 +
> arch/loongarch/kernel/entry.S | 6 +-
> arch/loongarch/kernel/fpu.S | 7 +
> arch/loongarch/kernel/genex.S | 6 +-
> arch/loongarch/kernel/lbt.S | 5 +
> arch/loongarch/kernel/module.c | 22 +-
> arch/loongarch/kernel/relocate_kernel.S | 2 +
> arch/loongarch/kernel/setup.c | 2 +
> arch/loongarch/kernel/stacktrace.c | 1 +
> arch/loongarch/kernel/traps.c | 42 +-
> arch/loongarch/kernel/unwind_orc.c | 516 ++++++++++++++++++
> arch/loongarch/kernel/vmlinux.lds.S | 3 +
> arch/loongarch/kvm/switch.S | 7 +-
> arch/loongarch/lib/Makefile | 2 +
> arch/loongarch/mm/tlb.c | 27 +-
> arch/loongarch/mm/tlbex.S | 9 +
> arch/loongarch/vdso/Makefile | 1 +
> include/linux/compiler.h | 9 +
> scripts/Makefile | 7 +-
> tools/arch/loongarch/include/asm/inst.h | 161 ++++++
> tools/arch/loongarch/include/asm/orc_types.h | 58 ++
> tools/include/linux/bitops.h | 11 +
> tools/objtool/Makefile | 4 +
> tools/objtool/arch/loongarch/Build | 3 +
> tools/objtool/arch/loongarch/decode.c | 356 ++++++++++++
> .../arch/loongarch/include/arch/cfi_regs.h | 22 +
> .../objtool/arch/loongarch/include/arch/elf.h | 30 +
> .../arch/loongarch/include/arch/special.h | 33 ++
> tools/objtool/arch/loongarch/orc.c | 171 ++++++
> tools/objtool/arch/loongarch/special.c | 15 +
> tools/objtool/arch/x86/Build | 1 +
> tools/objtool/arch/x86/orc.c | 188 +++++++
> tools/objtool/check.c | 117 ++--
> tools/objtool/include/objtool/elf.h | 1 +
> tools/objtool/include/objtool/orc.h | 14 +
> tools/objtool/orc_dump.c | 69 +--
> tools/objtool/orc_gen.c | 113 +---
> 50 files changed, 1983 insertions(+), 263 deletions(-)
> create mode 100644 arch/loongarch/include/asm/orc_header.h
> create mode 100644 arch/loongarch/include/asm/orc_lookup.h
> create mode 100644 arch/loongarch/include/asm/orc_types.h
> create mode 100644 arch/loongarch/include/asm/unwind_hints.h
> create mode 100644 arch/loongarch/kernel/unwind_orc.c
> create mode 100644 tools/arch/loongarch/include/asm/inst.h
> create mode 100644 tools/arch/loongarch/include/asm/orc_types.h
> create mode 100644 tools/objtool/arch/loongarch/Build
> create mode 100644 tools/objtool/arch/loongarch/decode.c
> create mode 100644 tools/objtool/arch/loongarch/include/arch/cfi_regs.h
> create mode 100644 tools/objtool/arch/loongarch/include/arch/elf.h
> create mode 100644 tools/objtool/arch/loongarch/include/arch/special.h
> create mode 100644 tools/objtool/arch/loongarch/orc.c
> create mode 100644 tools/objtool/arch/loongarch/special.c
> create mode 100644 tools/objtool/arch/x86/orc.c
> create mode 100644 tools/objtool/include/objtool/orc.h
>
> --
> 2.42.0
>
>

2023-12-03 12:43:07

by Huacai Chen

[permalink] [raw]
Subject: Re: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support

On Wed, Nov 29, 2023 at 9:07 PM Tiezhu Yang <[email protected]> wrote:
>
> The kernel CONFIG_UNWINDER_ORC option enables the ORC unwinder, which is
> similar in concept to a DWARF unwinder. The difference is that the format
> of the ORC data is much simpler than DWARF, which in turn allows the ORC
> unwinder to be much simpler and faster.
>
> The ORC data consists of unwind tables which are generated by objtool.
> After analyzing all the code paths of a .o file, it determines information
> about the stack state at each instruction address in the file and outputs
> that information to the .orc_unwind and .orc_unwind_ip sections.
>
> The per-object ORC sections are combined at link time and are sorted and
> post-processed at boot time. The unwinder uses the resulting data to
> correlate instruction addresses with their stack states at run time.
>
> Most of the logic are similar with x86, in order to get ra info before ra
> is saved into stack, add ra_reg and ra_offset into orc_entry. At the same
> time, modify some arch-specific code to silence the objtool warnings.
>
> Co-developed-by: Jinyang He <[email protected]>
> Signed-off-by: Jinyang He <[email protected]>
> Co-developed-by: Youling Tang <[email protected]>
> Signed-off-by: Youling Tang <[email protected]>
> Signed-off-by: Tiezhu Yang <[email protected]>
> ---
> arch/loongarch/Kconfig | 2 +
> arch/loongarch/Kconfig.debug | 11 +
> arch/loongarch/Makefile | 19 +
> arch/loongarch/include/asm/Kbuild | 2 +
> arch/loongarch/include/asm/bug.h | 1 +
> arch/loongarch/include/asm/exception.h | 2 +
> arch/loongarch/include/asm/module.h | 7 +
> arch/loongarch/include/asm/orc_header.h | 18 +
> arch/loongarch/include/asm/orc_lookup.h | 31 ++
> arch/loongarch/include/asm/orc_types.h | 58 +++
> arch/loongarch/include/asm/stackframe.h | 3 +
> arch/loongarch/include/asm/unwind.h | 19 +-
> arch/loongarch/include/asm/unwind_hints.h | 28 ++
> arch/loongarch/kernel/Makefile | 4 +
> arch/loongarch/kernel/entry.S | 6 +-
> arch/loongarch/kernel/fpu.S | 7 +
> arch/loongarch/kernel/genex.S | 6 +-
> arch/loongarch/kernel/lbt.S | 5 +
> arch/loongarch/kernel/module.c | 22 +-
> arch/loongarch/kernel/relocate_kernel.S | 2 +
> arch/loongarch/kernel/setup.c | 2 +
> arch/loongarch/kernel/stacktrace.c | 1 +
> arch/loongarch/kernel/traps.c | 42 +-
> arch/loongarch/kernel/unwind_orc.c | 516 ++++++++++++++++++++++
> arch/loongarch/kernel/vmlinux.lds.S | 3 +
> arch/loongarch/kvm/switch.S | 7 +-
> arch/loongarch/lib/Makefile | 2 +
> arch/loongarch/mm/tlb.c | 27 +-
> arch/loongarch/mm/tlbex.S | 9 +
> arch/loongarch/vdso/Makefile | 1 +
> include/linux/compiler.h | 9 +
> scripts/Makefile | 7 +-
> 32 files changed, 839 insertions(+), 40 deletions(-)
> create mode 100644 arch/loongarch/include/asm/orc_header.h
> create mode 100644 arch/loongarch/include/asm/orc_lookup.h
> create mode 100644 arch/loongarch/include/asm/orc_types.h
> create mode 100644 arch/loongarch/include/asm/unwind_hints.h
> create mode 100644 arch/loongarch/kernel/unwind_orc.c
>
> diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
> index ee123820a476..eea57378646d 100644
> --- a/arch/loongarch/Kconfig
> +++ b/arch/loongarch/Kconfig
> @@ -132,6 +132,7 @@ config LOONGARCH
> select HAVE_KVM
> 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
> @@ -143,6 +144,7 @@ config LOONGARCH
> select HAVE_SAMPLE_FTRACE_DIRECT
> select HAVE_SAMPLE_FTRACE_DIRECT_MULTI
> select HAVE_SETUP_PER_CPU_AREA if NUMA
> + select HAVE_STACK_VALIDATION if HAVE_OBJTOOL
> select HAVE_STACKPROTECTOR
> select HAVE_SYSCALL_TRACEPOINTS
> select HAVE_TIF_NOHZ
> 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/Makefile b/arch/loongarch/Makefile
> index 204b94b2e6aa..1f0e41f8ab61 100644
> --- a/arch/loongarch/Makefile
> +++ b/arch/loongarch/Makefile
> @@ -25,6 +25,18 @@ endif
> 32bit-emul = elf32loongarch
> 64bit-emul = elf64loongarch
>
> +ifdef CONFIG_UNWINDER_ORC
> +orc_hash_h := arch/$(SRCARCH)/include/generated/asm/orc_hash.h
> +orc_hash_sh := $(srctree)/scripts/orc_hash.sh
> +targets += $(orc_hash_h)
> +quiet_cmd_orc_hash = GEN $@
> + cmd_orc_hash = mkdir -p $(dir $@); \
> + $(CONFIG_SHELL) $(orc_hash_sh) < $< > $@
> +$(orc_hash_h): $(srctree)/arch/loongarch/include/asm/orc_types.h $(orc_hash_sh) FORCE
> + $(call if_changed,orc_hash)
> +archprepare: $(orc_hash_h)
> +endif
> +
> ifdef CONFIG_DYNAMIC_FTRACE
> KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
> CC_FLAGS_FTRACE := -fpatchable-function-entry=2
> @@ -81,6 +93,13 @@ KBUILD_AFLAGS_MODULE += -Wa,-mla-global-with-abs
> KBUILD_CFLAGS_MODULE += -fplt -Wa,-mla-global-with-abs,-mla-local-with-abs
> endif
>
> +KBUILD_AFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
> +KBUILD_CFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
> +
> +ifdef CONFIG_OBJTOOL
> +KBUILD_CFLAGS += -fno-optimize-sibling-calls -fno-jump-tables -falign-functions=4 -falign-labels=4
> +endif
> +
> ifeq ($(CONFIG_RELOCATABLE),y)
> KBUILD_CFLAGS_KERNEL += -fPIE
> LDFLAGS_vmlinux += -static -pie --no-dynamic-linker -z notext
> diff --git a/arch/loongarch/include/asm/Kbuild b/arch/loongarch/include/asm/Kbuild
> index 93783fa24f6e..a97c0edbb866 100644
> --- a/arch/loongarch/include/asm/Kbuild
> +++ b/arch/loongarch/include/asm/Kbuild
> @@ -1,4 +1,6 @@
> # SPDX-License-Identifier: GPL-2.0
> +generated-y += orc_hash.h
> +
> generic-y += dma-contiguous.h
> generic-y += mcs_spinlock.h
> generic-y += parport.h
> diff --git a/arch/loongarch/include/asm/bug.h b/arch/loongarch/include/asm/bug.h
> index d4ca3ba25418..08388876ade4 100644
> --- a/arch/loongarch/include/asm/bug.h
> +++ b/arch/loongarch/include/asm/bug.h
> @@ -44,6 +44,7 @@
> do { \
> instrumentation_begin(); \
> __BUG_FLAGS(BUGFLAG_WARNING|(flags)); \
> + annotate_reachable(); \
> instrumentation_end(); \
> } while (0)
>
> diff --git a/arch/loongarch/include/asm/exception.h b/arch/loongarch/include/asm/exception.h
> index af74a3fdcad1..c6d20736fd92 100644
> --- a/arch/loongarch/include/asm/exception.h
> +++ b/arch/loongarch/include/asm/exception.h
> @@ -6,6 +6,8 @@
> #include <asm/ptrace.h>
> #include <linux/kprobes.h>
>
> +extern void *exception_table[];
> +
> void show_registers(struct pt_regs *regs);
>
> asmlinkage void cache_parity_error(void);
> diff --git a/arch/loongarch/include/asm/module.h b/arch/loongarch/include/asm/module.h
> index 2ecd82bb64e1..f33f3fd32ecc 100644
> --- a/arch/loongarch/include/asm/module.h
> +++ b/arch/loongarch/include/asm/module.h
> @@ -6,6 +6,7 @@
> #define _ASM_MODULE_H
>
> #include <asm/inst.h>
> +#include <asm/orc_types.h>
> #include <asm-generic/module.h>
>
> #define RELA_STACK_DEPTH 16
> @@ -21,6 +22,12 @@ struct mod_arch_specific {
> struct mod_section plt;
> struct mod_section plt_idx;
>
> +#ifdef CONFIG_UNWINDER_ORC
> + unsigned int num_orcs;
> + int *orc_unwind_ip;
> + struct orc_entry *orc_unwind;
> +#endif
> +
> /* For CONFIG_DYNAMIC_FTRACE */
> struct plt_entry *ftrace_trampolines;
> };
> diff --git a/arch/loongarch/include/asm/orc_header.h b/arch/loongarch/include/asm/orc_header.h
> new file mode 100644
> index 000000000000..f9d509c3fd70
> --- /dev/null
> +++ b/arch/loongarch/include/asm/orc_header.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +#ifndef _ORC_HEADER_H
> +#define _ORC_HEADER_H
> +
> +#include <linux/types.h>
> +#include <linux/compiler.h>
> +#include <asm/orc_hash.h>
> +
> +/*
> + * The header is currently a 20-byte hash of the ORC entry definition; see
> + * scripts/orc_hash.sh.
> + */
> +#define ORC_HEADER \
> + __used __section(".orc_header") __aligned(4) \
> + static const u8 orc_header[] = { ORC_HASH }
> +
> +#endif /* _ORC_HEADER_H */
> diff --git a/arch/loongarch/include/asm/orc_lookup.h b/arch/loongarch/include/asm/orc_lookup.h
> new file mode 100644
> index 000000000000..b02e6357def4
> --- /dev/null
> +++ b/arch/loongarch/include/asm/orc_lookup.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +#ifndef _ORC_LOOKUP_H
> +#define _ORC_LOOKUP_H
> +
> +/*
> + * This is a lookup table for speeding up access to the .orc_unwind table.
> + * Given an input address offset, the corresponding lookup table entry
> + * specifies a subset of the .orc_unwind table to search.
> + *
> + * Each block represents the end of the previous range and the start of the
> + * next range. An extra block is added to give the last range an end.
> + *
> + * The block size should be a power of 2 to avoid a costly 'div' instruction.
> + *
> + * A block size of 256 was chosen because it roughly doubles unwinder
> + * performance while only adding ~5% to the ORC data footprint.
> + */
> +#define LOOKUP_BLOCK_ORDER 8
> +#define LOOKUP_BLOCK_SIZE (1 << LOOKUP_BLOCK_ORDER)
> +
> +#ifndef LINKER_SCRIPT
> +
> +extern unsigned int orc_lookup[];
> +extern unsigned int orc_lookup_end[];
> +
> +#define LOOKUP_START_IP (unsigned long)_stext
> +#define LOOKUP_STOP_IP (unsigned long)_etext
> +
> +#endif /* LINKER_SCRIPT */
> +
> +#endif /* _ORC_LOOKUP_H */
> diff --git a/arch/loongarch/include/asm/orc_types.h b/arch/loongarch/include/asm/orc_types.h
> new file mode 100644
> index 000000000000..caf1f71a1057
> --- /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>
> +
> +/*
> + * 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 FP -- which the previous SP
> + * is usually based on -- and PREV_SP and UNDEFINED -- which the previous FP 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_FP 3
> +#define ORC_REG_MAX 4
> +
> +#define ORC_TYPE_UNDEFINED 0
> +#define ORC_TYPE_END_OF_STACK 1
> +#define ORC_TYPE_CALL 2
> +#define ORC_TYPE_REGS 3
> +#define ORC_TYPE_REGS_PARTIAL 4
> +
> +#ifndef __ASSEMBLY__
> +/*
> + * 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 FP (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 fp_offset;
> + s16 ra_offset;
> + unsigned int sp_reg:4;
> + unsigned int fp_reg:4;
> + unsigned int ra_reg:4;
> + unsigned int type:3;
> + unsigned int signal:1;
> +};
> +#endif /* __ASSEMBLY__ */
> +
> +#endif /* _ORC_TYPES_H */
> diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h
> index 4fb1e6408b98..45b507a7b06f 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
> @@ -162,6 +163,7 @@
> li.w t0, CSR_CRMD_WE
> csrxchg t0, t0, LOONGARCH_CSR_CRMD
> #endif
> + UNWIND_HINT_REGS
> .endm
>
> .macro SAVE_ALL docfi=0
> @@ -219,6 +221,7 @@
>
> .macro RESTORE_SP_AND_RET docfi=0
> cfi_ld sp, PT_R3, \docfi
> + UNWIND_HINT_FUNC
> ertn
> .endm
>
> diff --git a/arch/loongarch/include/asm/unwind.h b/arch/loongarch/include/asm/unwind.h
> index b9dce87afd2e..c7f52d406cce 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, fp, pc, ra;
> };
>
> bool default_next_frame(struct unwind_state *state);
> @@ -34,6 +35,14 @@ 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);
> +void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size);
> +#else
> +static inline void unwind_init(void) {}
> +static inline void unwind_module_init(struct module *mod, void *orc_ip, size_t orc_ip_size, void *orc, size_t orc_size) {}
> +#endif
> +
> static inline bool unwind_done(struct unwind_state *state)
> {
> return state->stack_info.type == STACK_TYPE_UNKNOWN;
> @@ -61,14 +70,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 +89,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/include/asm/unwind_hints.h b/arch/loongarch/include/asm/unwind_hints.h
> new file mode 100644
> index 000000000000..82443fed3167
> --- /dev/null
> +++ b/arch/loongarch/include/asm/unwind_hints.h
> @@ -0,0 +1,28 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef _ASM_LOONGARCH_UNWIND_HINTS_H
> +#define _ASM_LOONGARCH_UNWIND_HINTS_H
> +
> +#include <linux/objtool.h>
> +#include <asm/orc_types.h>
> +
> +#ifdef __ASSEMBLY__
> +
> +.macro UNWIND_HINT_UNDEFINED
> + UNWIND_HINT type=UNWIND_HINT_TYPE_UNDEFINED
> +.endm
> +
> +.macro UNWIND_HINT_EMPTY
> + UNWIND_HINT sp_reg=ORC_REG_UNDEFINED type=UNWIND_HINT_TYPE_CALL
> +.endm
> +
> +.macro UNWIND_HINT_REGS
> + UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_REGS
> +.endm
> +
> +.macro UNWIND_HINT_FUNC
> + UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_CALL
> +.endm
> +
> +#endif /* __ASSEMBLY__ */
> +
> +#endif /* _ASM_LOONGARCH_UNWIND_HINTS_H */
> diff --git a/arch/loongarch/kernel/Makefile b/arch/loongarch/kernel/Makefile
> index 4fcc168f0732..ac47e11c227e 100644
> --- a/arch/loongarch/kernel/Makefile
> +++ b/arch/loongarch/kernel/Makefile
> @@ -3,6 +3,8 @@
> # Makefile for the Linux/LoongArch kernel.
> #
>
> +OBJECT_FILES_NON_STANDARD_head.o := y
> +
> extra-y := vmlinux.lds
>
> obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \
> @@ -21,6 +23,7 @@ obj-$(CONFIG_ARCH_STRICT_ALIGN) += unaligned.o
>
> CFLAGS_module.o += $(call cc-option,-Wno-override-init,)
> CFLAGS_syscall.o += $(call cc-option,-Wno-override-init,)
> +CFLAGS_traps.o += $(call cc-option,-Wno-override-init,)
> CFLAGS_perf_event.o += $(call cc-option,-Wno-override-init,)
>
> ifdef CONFIG_FUNCTION_TRACER
> @@ -62,6 +65,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/entry.S b/arch/loongarch/kernel/entry.S
> index 1ec8e4c4cc2b..2f5c74d26d5f 100644
> --- a/arch/loongarch/kernel/entry.S
> +++ b/arch/loongarch/kernel/entry.S
> @@ -14,11 +14,12 @@
> #include <asm/regdef.h>
> #include <asm/stackframe.h>
> #include <asm/thread_info.h>
> +#include <asm/unwind_hints.h>
>
> .text
> .cfi_sections .debug_frame
> - .align 5
> SYM_CODE_START(handle_syscall)
> + UNWIND_HINT_UNDEFINED
> csrrd t0, PERCPU_BASE_KS
> la.pcrel t1, kernelsp
> add.d t1, t1, t0
> @@ -57,6 +58,7 @@ SYM_CODE_START(handle_syscall)
> cfi_st fp, PT_R22
>
> SAVE_STATIC
> + UNWIND_HINT_REGS
>
> #ifdef CONFIG_KGDB
> li.w t1, CSR_CRMD_WE
> @@ -75,6 +77,7 @@ SYM_CODE_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
> @@ -84,6 +87,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/fpu.S b/arch/loongarch/kernel/fpu.S
> index d53ab10f4644..487933899c5d 100644
> --- a/arch/loongarch/kernel/fpu.S
> +++ b/arch/loongarch/kernel/fpu.S
> @@ -15,6 +15,7 @@
> #include <asm/fpregdef.h>
> #include <asm/loongarch.h>
> #include <asm/regdef.h>
> +#include <asm/unwind_hints.h>
>
> #define FPU_REG_WIDTH 8
> #define LSX_REG_WIDTH 16
> @@ -524,3 +525,9 @@ SYM_FUNC_END(_restore_lasx_context)
> .L_fpu_fault:
> li.w a0, -EFAULT # failure
> jr ra
> +
> +#ifdef CONFIG_CPU_HAS_LBT
> +STACK_FRAME_NON_STANDARD _restore_fp
> +STACK_FRAME_NON_STANDARD _restore_lsx
> +STACK_FRAME_NON_STANDARD _restore_lasx
> +#endif
> diff --git a/arch/loongarch/kernel/genex.S b/arch/loongarch/kernel/genex.S
> index 2bb3aa2dcfcb..6b7ec9f1134d 100644
> --- a/arch/loongarch/kernel/genex.S
> +++ b/arch/loongarch/kernel/genex.S
> @@ -16,7 +16,6 @@
> #include <asm/stackframe.h>
> #include <asm/thread_info.h>
>
> - .align 5
> SYM_FUNC_START(__arch_cpu_idle)
> /* start of rollback region */
> LONG_L t0, tp, TI_FLAGS
> @@ -32,6 +31,7 @@ SYM_FUNC_START(__arch_cpu_idle)
> SYM_FUNC_END(__arch_cpu_idle)
>
> SYM_CODE_START(handle_vint)
> + UNWIND_HINT_UNDEFINED
> BACKUP_T0T1
> SAVE_ALL
> la_abs t1, __arch_cpu_idle
> @@ -49,6 +49,7 @@ SYM_CODE_START(handle_vint)
> SYM_CODE_END(handle_vint)
>
> SYM_CODE_START(except_vec_cex)
> + UNWIND_HINT_UNDEFINED
> b cache_parity_error
> SYM_CODE_END(except_vec_cex)
>
> @@ -65,8 +66,8 @@ SYM_CODE_END(except_vec_cex)
> .endm
>
> .macro BUILD_HANDLER exception handler prep
> - .align 5
> SYM_CODE_START(handle_\exception)
> + UNWIND_HINT_UNDEFINED
> 666:
> BACKUP_T0T1
> SAVE_ALL
> @@ -94,6 +95,7 @@ SYM_CODE_END(except_vec_cex)
> BUILD_HANDLER reserved reserved none /* others */
>
> SYM_CODE_START(handle_sys)
> + UNWIND_HINT_UNDEFINED
> la_abs t0, handle_syscall
> jr t0
> SYM_CODE_END(handle_sys)
> diff --git a/arch/loongarch/kernel/lbt.S b/arch/loongarch/kernel/lbt.S
> index 9c75120a26d8..4d6914f84c7e 100644
> --- a/arch/loongarch/kernel/lbt.S
> +++ b/arch/loongarch/kernel/lbt.S
> @@ -11,6 +11,7 @@
> #include <asm/asm-offsets.h>
> #include <asm/errno.h>
> #include <asm/regdef.h>
> +#include <asm/unwind_hints.h>
>
> #define SCR_REG_WIDTH 8
>
> @@ -153,3 +154,7 @@ SYM_FUNC_END(_restore_ftop_context)
> .L_lbt_fault:
> li.w a0, -EFAULT # failure
> jr ra
> +
> +#ifdef CONFIG_CPU_HAS_LBT
> +STACK_FRAME_NON_STANDARD _restore_ftop_context
> +#endif
> diff --git a/arch/loongarch/kernel/module.c b/arch/loongarch/kernel/module.c
> index b13b2858fe39..c7d0338d12c1 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/unwind.h>
>
> static int rela_stack_push(s64 stack_value, s64 *rela_stack, size_t *rela_stack_top)
> {
> @@ -515,15 +516,28 @@ 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 char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
> + const Elf_Shdr *s, *alt = NULL, *orc = NULL, *orc_ip = NULL, *ftrace = NULL;
>
> - for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) {
> + for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
> if (!strcmp(".altinstructions", secstrs + s->sh_name))
> - apply_alternatives((void *)s->sh_addr, (void *)s->sh_addr + s->sh_size);
> + alt = s;
> + if (!strcmp(".orc_unwind", secstrs + s->sh_name))
> + orc = s;
> + if (!strcmp(".orc_unwind_ip", secstrs + s->sh_name))
> + orc_ip = s;
> if (!strcmp(".ftrace_trampoline", secstrs + s->sh_name))
> - module_init_ftrace_plt(hdr, s, mod);
> + ftrace = s;
> }
>
> + if (alt)
> + apply_alternatives((void *)alt->sh_addr, (void *)alt->sh_addr + alt->sh_size);
> +
> + if (orc && orc_ip)
> + unwind_module_init(mod, (void *)orc_ip->sh_addr, orc_ip->sh_size, (void *)orc->sh_addr, orc->sh_size);
> +
> + if (ftrace)
> + module_init_ftrace_plt(hdr, ftrace, mod);
> +
> return 0;
> }
> diff --git a/arch/loongarch/kernel/relocate_kernel.S b/arch/loongarch/kernel/relocate_kernel.S
> index f49f6b053763..bcc191d278c1 100644
> --- a/arch/loongarch/kernel/relocate_kernel.S
> +++ b/arch/loongarch/kernel/relocate_kernel.S
> @@ -15,6 +15,7 @@
> #include <asm/addrspace.h>
>
> SYM_CODE_START(relocate_new_kernel)
> + UNWIND_HINT_UNDEFINED
> /*
> * a0: EFI boot flag for the new kernel
> * a1: Command line pointer for the new kernel
> @@ -90,6 +91,7 @@ SYM_CODE_END(relocate_new_kernel)
> * then start at the entry point from LOONGARCH_IOCSR_MBUF0.
> */
> SYM_CODE_START(kexec_smp_wait)
> + UNWIND_HINT_UNDEFINED
> 1: li.w t0, 0x100 /* wait for init loop */
> 2: addi.w t0, t0, -1 /* limit mailbox access */
> bnez t0, 2b
> diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c
> index d183a745fb85..ec4459c61db6 100644
> --- a/arch/loongarch/kernel/setup.c
> +++ b/arch/loongarch/kernel/setup.c
> @@ -47,6 +47,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
> @@ -605,6 +606,7 @@ static void __init prefill_possible_map(void)
> void __init setup_arch(char **cmdline_p)
> {
> cpu_probe();
> + unwind_init();
>
> init_environ();
> efi_init();
> diff --git a/arch/loongarch/kernel/stacktrace.c b/arch/loongarch/kernel/stacktrace.c
> index 92270f14db94..9848d427cbfa 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->csr_era = thread_saved_ra(task);
> }
> regs->regs[1] = 0;
> + regs->regs[22] = 0;
> }
>
> for (unwind_start(&state, task, regs);
> diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
> index aebfc3733a76..f9f4eb00c92e 100644
> --- a/arch/loongarch/kernel/traps.c
> +++ b/arch/loongarch/kernel/traps.c
> @@ -53,6 +53,32 @@
>
> #include "access-helper.h"
>
> +void *exception_table[EXCCODE_INT_START] = {
> + [0 ... EXCCODE_INT_START - 1] = handle_reserved,
> +
> + [EXCCODE_TLBI] = handle_tlb_load,
> + [EXCCODE_TLBL] = handle_tlb_load,
> + [EXCCODE_TLBS] = handle_tlb_store,
> + [EXCCODE_TLBM] = handle_tlb_modify,
> + [EXCCODE_TLBNR] = handle_tlb_protect,
> + [EXCCODE_TLBNX] = handle_tlb_protect,
> + [EXCCODE_TLBPE] = handle_tlb_protect,
> + [EXCCODE_ADE] = handle_ade,
> + [EXCCODE_ALE] = handle_ale,
> + [EXCCODE_BCE] = handle_bce,
> + [EXCCODE_SYS] = handle_sys,
> + [EXCCODE_BP] = handle_bp,
> + [EXCCODE_INE] = handle_ri,
> + [EXCCODE_IPE] = handle_ri,
> + [EXCCODE_FPDIS] = handle_fpu,
> + [EXCCODE_LSXDIS] = handle_lsx,
> + [EXCCODE_LASXDIS] = handle_lasx,
> + [EXCCODE_FPE] = handle_fpe,
> + [EXCCODE_WATCH] = handle_watch,
> + [EXCCODE_BTDIS] = handle_lbt,
> +};
> +EXPORT_SYMBOL_GPL(exception_table);
> +
> static void show_backtrace(struct task_struct *task, const struct pt_regs *regs,
> const char *loglvl, bool user)
> {
> @@ -1150,19 +1176,9 @@ void __init trap_init(void)
> for (i = EXCCODE_INT_START; i <= EXCCODE_INT_END; i++)
> set_handler(i * VECSIZE, handle_vint, VECSIZE);
>
> - set_handler(EXCCODE_ADE * VECSIZE, handle_ade, VECSIZE);
> - set_handler(EXCCODE_ALE * VECSIZE, handle_ale, VECSIZE);
> - set_handler(EXCCODE_BCE * VECSIZE, handle_bce, VECSIZE);
> - set_handler(EXCCODE_SYS * VECSIZE, handle_sys, VECSIZE);
> - set_handler(EXCCODE_BP * VECSIZE, handle_bp, VECSIZE);
> - set_handler(EXCCODE_INE * VECSIZE, handle_ri, VECSIZE);
> - set_handler(EXCCODE_IPE * VECSIZE, handle_ri, VECSIZE);
> - set_handler(EXCCODE_FPDIS * VECSIZE, handle_fpu, VECSIZE);
> - set_handler(EXCCODE_LSXDIS * VECSIZE, handle_lsx, VECSIZE);
> - set_handler(EXCCODE_LASXDIS * VECSIZE, handle_lasx, VECSIZE);
> - set_handler(EXCCODE_FPE * VECSIZE, handle_fpe, VECSIZE);
> - set_handler(EXCCODE_BTDIS * VECSIZE, handle_lbt, VECSIZE);
> - set_handler(EXCCODE_WATCH * VECSIZE, handle_watch, VECSIZE);
> + /* Set exception vector handler */
> + for (i = EXCCODE_ADE; i <= EXCCODE_BTDIS; i++)
> + set_handler(i * VECSIZE, exception_table[i], VECSIZE);
>
> cache_error_setup();
>
> diff --git a/arch/loongarch/kernel/unwind_orc.c b/arch/loongarch/kernel/unwind_orc.c
> new file mode 100644
> index 000000000000..15f18d1e0dad
> --- /dev/null
> +++ b/arch/loongarch/kernel/unwind_orc.c
> @@ -0,0 +1,516 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +#include <linux/objtool.h>
> +#include <linux/module.h>
> +#include <linux/sort.h>
> +#include <asm/exception.h>
> +#include <asm/orc_header.h>
> +#include <asm/orc_lookup.h>
> +#include <asm/orc_types.h>
> +#include <asm/ptrace.h>
> +#include <asm/setup.h>
> +#include <asm/stacktrace.h>
> +#include <asm/tlb.h>
> +#include <asm/unwind.h>
> +
> +ORC_HEADER;
> +
> +#define orc_warn(fmt, ...) \
> + printk_deferred_once(KERN_WARNING "WARNING: " fmt, ##__VA_ARGS__)
> +
> +extern int __start_orc_unwind_ip[];
> +extern int __stop_orc_unwind_ip[];
> +extern struct orc_entry __start_orc_unwind[];
> +extern struct orc_entry __stop_orc_unwind[];
> +
> +static bool orc_init __ro_after_init;
> +static unsigned int lookup_num_blocks __ro_after_init;
> +
> +/* Fake frame pointer entry -- used as a fallback for generated code */
> +static struct orc_entry orc_fp_entry = {
> + .sp_reg = ORC_REG_FP,
> + .sp_offset = 16,
> + .fp_reg = ORC_REG_PREV_SP,
> + .fp_offset = -16,
> + .ra_reg = ORC_REG_PREV_SP,
> + .ra_offset = -8,
> + .type = ORC_TYPE_CALL
> +};
> +
> +/*
> + * If we crash with IP==0, the last successfully executed instruction
> + * was probably an indirect function call with a NULL function pointer,
> + * and we don't have unwind information for NULL.
> + * This hardcoded ORC entry for IP==0 allows us to unwind from a NULL function
> + * pointer into its parent and then continue normally from there.
> + */
> +static struct orc_entry orc_null_entry = {
> + .sp_reg = ORC_REG_SP,
> + .sp_offset = sizeof(long),
> + .fp_reg = ORC_REG_UNDEFINED,
> + .type = ORC_TYPE_CALL
> +};
> +
> +static inline unsigned long orc_ip(const int *ip)
> +{
> + return (unsigned long)ip + *ip;
> +}
> +
> +static struct orc_entry *__orc_find(int *ip_table, struct orc_entry *u_table,
> + unsigned int num_entries, unsigned long ip)
> +{
> + int *first = ip_table;
> + int *mid = first, *found = first;
> + int *last = ip_table + num_entries - 1;
> +
> + if (!num_entries)
> + return NULL;
> +
> + /*
> + * Do a binary range search to find the rightmost duplicate of a given
> + * starting address. Some entries are section terminators which are
> + * "weak" entries for ensuring there are no gaps. They should be
> + * ignored when they conflict with a real entry.
> + */
> + while (first <= last) {
> + mid = first + ((last - first) / 2);
> +
> + if (orc_ip(mid) <= ip) {
> + found = mid;
> + first = mid + 1;
> + } else
> + last = mid - 1;
> + }
> +
> + return u_table + (found - ip_table);
> +}
> +
> +#ifdef CONFIG_MODULES
> +static struct orc_entry *orc_module_find(unsigned long ip)
> +{
> + struct module *mod;
> +
> + mod = __module_address(ip);
> + if (!mod || !mod->arch.orc_unwind || !mod->arch.orc_unwind_ip)
> + return NULL;
> +
> + return __orc_find(mod->arch.orc_unwind_ip, mod->arch.orc_unwind, mod->arch.num_orcs, ip);
> +}
> +#else
> +static struct orc_entry *orc_module_find(unsigned long ip)
> +{
> + return NULL;
> +}
> +#endif
> +
> +#ifdef CONFIG_DYNAMIC_FTRACE
> +static struct orc_entry *orc_find(unsigned long ip);
> +
> +/*
> + * Ftrace dynamic trampolines do not have orc entries of their own.
> + * But they are copies of the ftrace entries that are static and
> + * defined in ftrace_*.S, which do have orc entries.
> + *
> + * If the unwinder comes across a ftrace trampoline, then find the
> + * ftrace function that was used to create it, and use that ftrace
> + * function's orc entry, as the placement of the return code in
> + * the stack will be identical.
> + */
> +static struct orc_entry *orc_ftrace_find(unsigned long ip)
> +{
> + struct ftrace_ops *ops;
> + unsigned long tramp_addr, offset;
> +
> + ops = ftrace_ops_trampoline(ip);
> + if (!ops)
> + return NULL;
> +
> + /* Set tramp_addr to the start of the code copied by the trampoline */
> + if (ops->flags & FTRACE_OPS_FL_SAVE_REGS)
> + tramp_addr = (unsigned long)ftrace_regs_caller;
> + else
> + tramp_addr = (unsigned long)ftrace_caller;
> +
> + /* Now place tramp_addr to the location within the trampoline ip is at */
> + offset = ip - ops->trampoline;
> + tramp_addr += offset;
> +
> + /* Prevent unlikely recursion */
> + if (ip == tramp_addr)
> + return NULL;
> +
> + return orc_find(tramp_addr);
> +}
> +#else
> +static struct orc_entry *orc_ftrace_find(unsigned long ip)
> +{
> + return NULL;
> +}
> +#endif
> +
> +static struct orc_entry *orc_find(unsigned long ip)
> +{
> + static struct orc_entry *orc;
> +
> + if (ip == 0)
> + return &orc_null_entry;
> +
> + /* For non-init vmlinux addresses, use the fast lookup table: */
> + if (ip >= LOOKUP_START_IP && ip < LOOKUP_STOP_IP) {
> + unsigned int idx, start, stop;
> +
> + idx = (ip - LOOKUP_START_IP) / LOOKUP_BLOCK_SIZE;
> +
> + if (unlikely((idx >= lookup_num_blocks-1))) {
> + orc_warn("WARNING: bad lookup idx: idx=%u num=%u ip=%pB\n",
> + idx, lookup_num_blocks, (void *)ip);
> + return NULL;
> + }
> +
> + start = orc_lookup[idx];
> + stop = orc_lookup[idx + 1] + 1;
> +
> + if (unlikely((__start_orc_unwind + start >= __stop_orc_unwind) ||
> + (__start_orc_unwind + stop > __stop_orc_unwind))) {
> + orc_warn("WARNING: bad lookup value: idx=%u num=%u start=%u stop=%u ip=%pB\n",
> + idx, lookup_num_blocks, start, stop, (void *)ip);
> + return NULL;
> + }
> +
> + return __orc_find(__start_orc_unwind_ip + start,
> + __start_orc_unwind + start, stop - start, ip);
> + }
> +
> + /* vmlinux .init slow lookup: */
> + if (is_kernel_inittext(ip))
> + return __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
> + __stop_orc_unwind_ip - __start_orc_unwind_ip, ip);
> +
> + /* Module lookup: */
> + orc = orc_module_find(ip);
> + if (orc)
> + return orc;
> +
> + return orc_ftrace_find(ip);
> +}
> +
> +#ifdef CONFIG_MODULES
> +
> +static DEFINE_MUTEX(sort_mutex);
> +static int *cur_orc_ip_table = __start_orc_unwind_ip;
> +static struct orc_entry *cur_orc_table = __start_orc_unwind;
> +
> +static void orc_sort_swap(void *_a, void *_b, int size)
> +{
> + int delta = _b - _a;
> + int *a = _a, *b = _b, tmp;
> + struct orc_entry *orc_a, *orc_b;
> +
> + /* Swap the .orc_unwind_ip entries: */
> + tmp = *a;
> + *a = *b + delta;
> + *b = tmp - delta;
> +
> + /* Swap the corresponding .orc_unwind entries: */
> + orc_a = cur_orc_table + (a - cur_orc_ip_table);
> + orc_b = cur_orc_table + (b - cur_orc_ip_table);
> + swap(*orc_a, *orc_b);
> +}
> +
> +static int orc_sort_cmp(const void *_a, const void *_b)
> +{
> + const int *a = _a, *b = _b;
> + unsigned long a_val = orc_ip(a);
> + unsigned long b_val = orc_ip(b);
> + struct orc_entry *orc_a;
> +
> + if (a_val > b_val)
> + return 1;
> + if (a_val < b_val)
> + return -1;
> +
> + /*
> + * The "weak" section terminator entries need to always be first
> + * to ensure the lookup code skips them in favor of real entries.
> + * These terminator entries exist to handle any gaps created by
> + * whitelisted .o files which didn't get objtool generation.
> + */
> + orc_a = cur_orc_table + (a - cur_orc_ip_table);
> +
> + return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
> +}
> +
> +void unwind_module_init(struct module *mod, void *_orc_ip, size_t orc_ip_size,
> + void *_orc, size_t orc_size)
> +{
> + int *orc_ip = _orc_ip;
> + struct orc_entry *orc = _orc;
> + unsigned int num_entries = orc_ip_size / sizeof(int);
> +
> + WARN_ON_ONCE(orc_ip_size % sizeof(int) != 0 ||
> + orc_size % sizeof(*orc) != 0 ||
> + num_entries != orc_size / sizeof(*orc));
> +
> + /*
> + * The 'cur_orc_*' globals allow the orc_sort_swap() callback to
> + * associate an .orc_unwind_ip table entry with its corresponding
> + * .orc_unwind entry so they can both be swapped.
> + */
> + mutex_lock(&sort_mutex);
> + cur_orc_ip_table = orc_ip;
> + cur_orc_table = orc;
> + sort(orc_ip, num_entries, sizeof(int), orc_sort_cmp, orc_sort_swap);
> + mutex_unlock(&sort_mutex);
> +
> + mod->arch.orc_unwind_ip = orc_ip;
> + mod->arch.orc_unwind = orc;
> + mod->arch.num_orcs = num_entries;
> +}
> +#endif
> +
> +void __init unwind_init(void)
> +{
> + int i;
> + size_t orc_size = (void *)__stop_orc_unwind - (void *)__start_orc_unwind;
> + size_t orc_ip_size = (void *)__stop_orc_unwind_ip - (void *)__start_orc_unwind_ip;
> + size_t num_entries = orc_ip_size / sizeof(int);
> + struct orc_entry *orc;
> +
> + if (!num_entries || orc_ip_size % sizeof(int) != 0 ||
> + orc_size % sizeof(struct orc_entry) != 0 ||
> + num_entries != orc_size / sizeof(struct orc_entry)) {
> + orc_warn("WARNING: Bad or missing .orc_unwind table. Disabling unwinder.\n");
> + return;
> + }
> +
> + /*
> + * Note, the orc_unwind and orc_unwind_ip tables were already
> + * sorted at build time via the 'sorttable' tool.
> + * It's ready for binary search straight away, no need to sort it.
> + */
> +
> + /* Initialize the fast lookup table: */
> + lookup_num_blocks = orc_lookup_end - orc_lookup;
> + for (i = 0; i < lookup_num_blocks-1; i++) {
> + orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind,
> + num_entries, LOOKUP_START_IP + (LOOKUP_BLOCK_SIZE * i));
> + if (!orc) {
> + orc_warn("WARNING: Corrupt .orc_unwind table. Disabling unwinder.\n");
> + return;
> + }
> +
> + orc_lookup[i] = orc - __start_orc_unwind;
> + }
> +
> + /* Initialize the ending block: */
> + orc = __orc_find(__start_orc_unwind_ip, __start_orc_unwind, num_entries, LOOKUP_STOP_IP);
> + if (!orc) {
> + orc_warn("WARNING: Corrupt .orc_unwind table. Disabling unwinder.\n");
> + return;
> + }
> + orc_lookup[lookup_num_blocks-1] = orc - __start_orc_unwind;
> +
> + orc_init = true;
> +}
> +
> +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);
> + state->type = UNWINDER_ORC;
> + 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;
> +
> + if (__module_text_address(ra))
> + return ra;
> +
> + if (ra >= eentry && ra < eentry + EXCCODE_INT_END * VECSIZE) {
> + unsigned long func;
> + unsigned long type = (ra - eentry) / VECSIZE;
> + unsigned long offset = (ra - eentry) % VECSIZE;
> +
> + switch (type) {
> + case 0 ... EXCCODE_INT_START - 1:
> + func = (unsigned long)exception_table[type];
> + break;
> + case EXCCODE_INT_START ... EXCCODE_INT_END:
> + 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)
> +{
> + unsigned long *p, pc;
> + struct pt_regs *regs;
> + struct orc_entry *orc;
> + struct stack_info *info = &state->stack_info;
> +
> + 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:
> + if (info->type == STACK_TYPE_IRQ && state->sp == info->end)
> + orc->type = ORC_TYPE_REGS;
> + else
> + state->sp = state->sp + orc->sp_offset;
> + break;
> + case ORC_REG_FP:
> + 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->fp_reg) {
> + case ORC_REG_PREV_SP:
> + p = (unsigned long *)(state->sp + orc->fp_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->fp_reg, (void *)state->pc);
> + goto err;
> + }
> +
> + switch (orc->type) {
> + case ORC_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 ORC_TYPE_REGS:
> + if (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 bb2ec86f37a8..eaa7a91162e6 100644
> --- a/arch/loongarch/kernel/vmlinux.lds.S
> +++ b/arch/loongarch/kernel/vmlinux.lds.S
> @@ -2,6 +2,7 @@
> #include <linux/sizes.h>
> #include <asm/asm-offsets.h>
> #include <asm/thread_info.h>
> +#include <asm/orc_lookup.h>
>
> #define PAGE_SIZE _PAGE_SIZE
> #define RO_EXCEPTION_TABLE_ALIGN 4
> @@ -122,6 +123,8 @@ SECTIONS
> }
> #endif
>
> + ORC_UNWIND_TABLE
> +
> .sdata : {
> *(.sdata)
> }
> diff --git a/arch/loongarch/kvm/switch.S b/arch/loongarch/kvm/switch.S
> index 0ed9040307b7..9d49c3f6fff5 100644
> --- a/arch/loongarch/kvm/switch.S
> +++ b/arch/loongarch/kvm/switch.S
> @@ -8,7 +8,7 @@
> #include <asm/asmmacro.h>
> #include <asm/loongarch.h>
> #include <asm/regdef.h>
> -#include <asm/stackframe.h>
> +#include <asm/unwind_hints.h>
>
> #define HGPR_OFFSET(x) (PT_R0 + 8*x)
> #define GGPR_OFFSET(x) (KVM_ARCH_GGPR + 8*x)
> @@ -112,6 +112,7 @@
> .text
> .cfi_sections .debug_frame
> SYM_CODE_START(kvm_exc_entry)
> + UNWIND_HINT_UNDEFINED
> csrwr a2, KVM_TEMP_KS
> csrrd a2, KVM_VCPU_KS
> addi.d a2, a2, KVM_VCPU_ARCH
> @@ -248,3 +249,7 @@ SYM_FUNC_END(kvm_restore_fpu)
> .section ".rodata"
> SYM_DATA(kvm_exception_size, .quad kvm_exc_entry_end - kvm_exc_entry)
> SYM_DATA(kvm_enter_guest_size, .quad kvm_enter_guest_end - kvm_enter_guest)
> +
> +#ifdef CONFIG_CPU_HAS_LBT
> +STACK_FRAME_NON_STANDARD kvm_restore_fpu
> +#endif
> diff --git a/arch/loongarch/lib/Makefile b/arch/loongarch/lib/Makefile
> index a77bf160bfc4..e3023d9a508c 100644
> --- a/arch/loongarch/lib/Makefile
> +++ b/arch/loongarch/lib/Makefile
> @@ -3,6 +3,8 @@
> # Makefile for LoongArch-specific library files.
> #
>
> +OBJECT_FILES_NON_STANDARD := y
> +
> lib-y += delay.o memset.o memcpy.o memmove.o \
> clear_user.o copy_user.o csum.o dump_tlb.o unaligned.o
>
I have a draft live-patch patch here:
https://github.com/chenhuacai/linux/commit/744942cbf456b320f2333638e4bd27d35900284c

But we get such an error:

# selftests: livepatch: test-ftrace.sh

[48/1865]
[14936.262721] livepatch: failed to register ftrace handler for
function 'cmdline_proc_show' (-16)
# TEST: livepatch interaction with ftrace_enabled sysctl ...
[14936.345709] Kernel ade access[#1]:
[14936.349096] CPU: 1 PID: 2571 Comm: (udev-worker) Tainted: G
K 6.7.0-rc3+ #1023 33dbce5afaee02d40ea2806811349d9b0065d5fd
[14936.361203] Hardware name: Loongson
Loongson-3A5000-7A1000-1w-CRB/Loongson-LS3A5000-7A1000-1w-CRB, BIOS
vUDK2018-LoongArch-V2.0.0-prebeta9 10/21/2022
[14936.374515] pc 90000000047277e4 ra 9000000003b5bfcc tp
9000000120b4c000 sp 9000000120b4fcd0
[14936.382816] a0 060240e70314c08f a1 90000000002c01c1 a2
060240e70314c0e3 a3 90000000002c01d4
[14936.391116] a4 90000000002c0194 a5 060240e70314c090 a6
414d495f544f4f42 a7 7974743d656c6f73
[14936.399416] t0 47414d495f544f4f t1 6e696c6d762f3d45 t2
7220782e352d7a75 t3 7665642f3d746f6f
[14936.407717] t4 6f7220336164732f t5 78756e696c657320 t6
6f736e6f6320303d t7 30537974743d656c
[14936.416018] t8 9000000120b4fd00 u0 9000000120b4fe60 s9
0000000000000001 s0 ffff800002d90034
[14936.424318] s1 0000000000000054 s2 90000000002c0180 s3
9000000120b4fdb0 s4 9000000105d243e8
[14936.432619] s5 0000000000000000 s6 9000000120b4fe60 s7
fffffffffffff000 s8 000000007fffc000
[14936.440920] ra: 9000000003b5bfcc seq_puts+0x54/0x78
[14936.446029] ERA: 90000000047277e4 __memcpy_fast+0x58/0xf0
[14936.451567] CRMD: 000000b0 (PLV0 -IE -DA +PG DACF=CC DACM=CC -WE)
[14936.457725] PRMD: 00000004 (PPLV0 +PIE -PWE)
[14936.462057] EUEN: 00000007 (+FPE +SXE +ASXE -BTE)
[14936.466822] ECFG: 00071c1c (LIE=2-4,10-12 VS=7)
[14936.471412] ESTAT: 00480000 [ADEM] (IS= ECode=8 EsubCode=1)
[14936.476951] BADV: 060240e70314c090
[14936.480413] PRID: 0014c010 (Loongson-64bit, Loongson-3A5000)
[14936.486121] Modules linked in: test_klp_livepatch(K) amdgpu
nft_fib_inet nft_fib_ipv4 nft_fib_ipv6 nft_fib nft_reject_inet
nf_reject_ipv4 nf_reject_ipv6 nft_reject nft_ct rfkill nft_cha$
[14936.531409] Process (udev-worker) (pid: 2571,
threadinfo=00000000ee84fdb9, task=00000000e42294a9)
[14936.540231] Stack : 9000000120b4fdd8 0000000000000001
ffff800002d90034 9000000003bde414
[14936.548194] 9000000105d243c0 9000000003b5cea0
0000000000000000 9000000105d243f8
[14936.556156] 0000000000000000 0000000000000000
0000000000000000 9000000120b4fdb0
[14936.564117] 9000000120b4fdd8 9000000120b4fe60
0000000000000001 90000000053e4000
[14936.572079] 0000555587619eb0 9000000104688a00
0000000000000000 9000000101975740
[14936.580040] 0000000000000000 9000000003bd0cbc
0000000000000400 0000000000000000
[14936.588002] 0000000000000400 9000000003b2583c
0000040000000001 0000000000000400
[14936.595963] 0000000000000000 0000000000000000
0000555587619eb0 0000000000000400
[14936.603925] 0000000000000001 9000000104688a00
0000000000000000 0000000000000000
[14936.611887] 0000000000000000 0000400400000000
0000000000000000 c671bfa1c5a3263c
[14936.619848] ...
[14936.622275] Call Trace:
[14936.622278] [<90000000047277e4>] __memcpy_fast+0x58/0xf0
[14936.629978] WARNING: unknown SP base reg 0 at __memcpy_fast+0x58/0xf0
[14936.629982]
[14936.637852] Code: 28c0c0b2 28c0e0b3 02c100a5 <29c0012c> 29c0212d
29c0412e 29c0612f 29c08130 29c0a131
[14936.647553]
[14936.654440] ---[ end trace 0000000000000000 ]---

I think it is probably because we skip mem* functions here.

Huacai

> diff --git a/arch/loongarch/mm/tlb.c b/arch/loongarch/mm/tlb.c
> index 2c0a411f23aa..f01172a8f4e9 100644
> --- a/arch/loongarch/mm/tlb.c
> +++ b/arch/loongarch/mm/tlb.c
> @@ -9,8 +9,9 @@
> #include <linux/hugetlb.h>
> #include <linux/export.h>
>
> -#include <asm/cpu.h>
> #include <asm/bootinfo.h>
> +#include <asm/cpu.h>
> +#include <asm/exception.h>
> #include <asm/mmu_context.h>
> #include <asm/pgtable.h>
> #include <asm/tlb.h>
> @@ -266,24 +267,20 @@ static void setup_tlb_handler(int cpu)
> setup_ptwalker();
> local_flush_tlb_all();
>
> + if (cpu_has_ptw) {
> + exception_table[EXCCODE_TLBI] = handle_tlb_load_ptw;
> + exception_table[EXCCODE_TLBL] = handle_tlb_load_ptw;
> + exception_table[EXCCODE_TLBS] = handle_tlb_store_ptw;
> + exception_table[EXCCODE_TLBM] = handle_tlb_modify_ptw;
> + }
> +
> /* The tlb handlers are generated only once */
> if (cpu == 0) {
> memcpy((void *)tlbrentry, handle_tlb_refill, 0x80);
> local_flush_icache_range(tlbrentry, tlbrentry + 0x80);
> - if (!cpu_has_ptw) {
> - set_handler(EXCCODE_TLBI * VECSIZE, handle_tlb_load, VECSIZE);
> - set_handler(EXCCODE_TLBL * VECSIZE, handle_tlb_load, VECSIZE);
> - set_handler(EXCCODE_TLBS * VECSIZE, handle_tlb_store, VECSIZE);
> - set_handler(EXCCODE_TLBM * VECSIZE, handle_tlb_modify, VECSIZE);
> - } else {
> - set_handler(EXCCODE_TLBI * VECSIZE, handle_tlb_load_ptw, VECSIZE);
> - set_handler(EXCCODE_TLBL * VECSIZE, handle_tlb_load_ptw, VECSIZE);
> - set_handler(EXCCODE_TLBS * VECSIZE, handle_tlb_store_ptw, VECSIZE);
> - set_handler(EXCCODE_TLBM * VECSIZE, handle_tlb_modify_ptw, VECSIZE);
> - }
> - set_handler(EXCCODE_TLBNR * VECSIZE, handle_tlb_protect, VECSIZE);
> - set_handler(EXCCODE_TLBNX * VECSIZE, handle_tlb_protect, VECSIZE);
> - set_handler(EXCCODE_TLBPE * VECSIZE, handle_tlb_protect, VECSIZE);
> +
> + for (int i = EXCCODE_TLBL; i <= EXCCODE_TLBPE; i++)
> + set_handler(i * VECSIZE, exception_table[i], VECSIZE);
> }
> #ifdef CONFIG_NUMA
> else {
> diff --git a/arch/loongarch/mm/tlbex.S b/arch/loongarch/mm/tlbex.S
> index d5d682f3d29f..a44387b838af 100644
> --- a/arch/loongarch/mm/tlbex.S
> +++ b/arch/loongarch/mm/tlbex.S
> @@ -18,6 +18,7 @@
>
> .macro tlb_do_page_fault, write
> SYM_CODE_START(tlb_do_page_fault_\write)
> + UNWIND_HINT_UNDEFINED
> SAVE_ALL
> csrrd a2, LOONGARCH_CSR_BADV
> move a0, sp
> @@ -32,6 +33,7 @@
> tlb_do_page_fault 1
>
> SYM_CODE_START(handle_tlb_protect)
> + UNWIND_HINT_UNDEFINED
> BACKUP_T0T1
> SAVE_ALL
> move a0, sp
> @@ -44,6 +46,7 @@ SYM_CODE_START(handle_tlb_protect)
> SYM_CODE_END(handle_tlb_protect)
>
> SYM_CODE_START(handle_tlb_load)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, EXCEPTION_KS0
> csrwr t1, EXCEPTION_KS1
> csrwr ra, EXCEPTION_KS2
> @@ -190,6 +193,7 @@ nopage_tlb_load:
> SYM_CODE_END(handle_tlb_load)
>
> SYM_CODE_START(handle_tlb_load_ptw)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_KS0
> csrwr t1, LOONGARCH_CSR_KS1
> la_abs t0, tlb_do_page_fault_0
> @@ -197,6 +201,7 @@ SYM_CODE_START(handle_tlb_load_ptw)
> SYM_CODE_END(handle_tlb_load_ptw)
>
> SYM_CODE_START(handle_tlb_store)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, EXCEPTION_KS0
> csrwr t1, EXCEPTION_KS1
> csrwr ra, EXCEPTION_KS2
> @@ -346,6 +351,7 @@ nopage_tlb_store:
> SYM_CODE_END(handle_tlb_store)
>
> SYM_CODE_START(handle_tlb_store_ptw)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_KS0
> csrwr t1, LOONGARCH_CSR_KS1
> la_abs t0, tlb_do_page_fault_1
> @@ -353,6 +359,7 @@ SYM_CODE_START(handle_tlb_store_ptw)
> SYM_CODE_END(handle_tlb_store_ptw)
>
> SYM_CODE_START(handle_tlb_modify)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, EXCEPTION_KS0
> csrwr t1, EXCEPTION_KS1
> csrwr ra, EXCEPTION_KS2
> @@ -500,6 +507,7 @@ nopage_tlb_modify:
> SYM_CODE_END(handle_tlb_modify)
>
> SYM_CODE_START(handle_tlb_modify_ptw)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_KS0
> csrwr t1, LOONGARCH_CSR_KS1
> la_abs t0, tlb_do_page_fault_1
> @@ -507,6 +515,7 @@ SYM_CODE_START(handle_tlb_modify_ptw)
> SYM_CODE_END(handle_tlb_modify_ptw)
>
> SYM_CODE_START(handle_tlb_refill)
> + UNWIND_HINT_UNDEFINED
> csrwr t0, LOONGARCH_CSR_TLBRSAVE
> csrrd t0, LOONGARCH_CSR_PGD
> lddir t0, t0, 3
> diff --git a/arch/loongarch/vdso/Makefile b/arch/loongarch/vdso/Makefile
> index c74c9921304f..7a4ad96522ac 100644
> --- a/arch/loongarch/vdso/Makefile
> +++ b/arch/loongarch/vdso/Makefile
> @@ -3,6 +3,7 @@
>
> KASAN_SANITIZE := n
> KCOV_INSTRUMENT := n
> +OBJECT_FILES_NON_STANDARD := y
>
> # Include the generic Makefile to check the built vdso.
> include $(srctree)/lib/vdso/Makefile
> diff --git a/include/linux/compiler.h b/include/linux/compiler.h
> index bb1339c7057b..39f2d4a05208 100644
> --- a/include/linux/compiler.h
> +++ b/include/linux/compiler.h
> @@ -116,6 +116,14 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
> */
> #define __stringify_label(n) #n
>
> +#define __annotate_reachable(c) ({ \
> + asm volatile(__stringify_label(c) ":\n\t" \
> + ".pushsection .discard.reachable\n\t" \
> + ".long " __stringify_label(c) "b - .\n\t" \
> + ".popsection\n\t"); \
> +})
> +#define annotate_reachable() __annotate_reachable(__COUNTER__)
> +
> #define __annotate_unreachable(c) ({ \
> asm volatile(__stringify_label(c) ":\n\t" \
> ".pushsection .discard.unreachable\n\t" \
> @@ -128,6 +136,7 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
> #define __annotate_jump_table __section(".rodata..c_jump_table")
>
> #else /* !CONFIG_OBJTOOL */
> +#define annotate_reachable()
> #define annotate_unreachable()
> #define __annotate_jump_table
> #endif /* CONFIG_OBJTOOL */
> diff --git a/scripts/Makefile b/scripts/Makefile
> index 576cf64be667..e4cca53d2285 100644
> --- a/scripts/Makefile
> +++ b/scripts/Makefile
> @@ -31,9 +31,12 @@ HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null |
>
> ifdef CONFIG_UNWINDER_ORC
> ifeq ($(ARCH),x86_64)
> -ARCH := x86
> +SRCARCH := x86
> endif
> -HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/x86/include
> +ifeq ($(ARCH),loongarch)
> +SRCARCH := loongarch
> +endif
> +HOSTCFLAGS_sorttable.o += -I$(srctree)/tools/arch/$(SRCARCH)/include
> HOSTCFLAGS_sorttable.o += -DUNWINDER_ORC_ENABLED
> endif
>
> --
> 2.42.0
>
>

2023-12-03 13:20:21

by Xi Ruoyao

[permalink] [raw]
Subject: Re: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support

On Wed, 2023-11-29 at 21:07 +0800, Tiezhu Yang wrote:
> +KBUILD_AFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
> +KBUILD_CFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
> +
> +ifdef CONFIG_OBJTOOL
> +KBUILD_CFLAGS += -fno-optimize-sibling-calls -fno-jump-tables -falign-functions=4 -falign-labels=4
> +endif

I still hate regressing the optimization with a passion.

And -falign-labels=4 implies -falign-functions=4 so at least we can
remove -falign-functions=4.

And in GCC >= 14 the defaults are -falign-labels=16 and -falign-
functions=32. These values are determined by benchmarking on LA464 so
I'd suggest using them (maybe unless CONFIG_CC_OPTIMIZE_FOR_SIZE).

--
Xi Ruoyao <[email protected]>
School of Aerospace Science and Technology, Xidian University

2023-12-07 07:10:57

by Tiezhu Yang

[permalink] [raw]
Subject: Re: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support



On 12/02/2023 09:45 PM, Huacai Chen wrote:
> Hi, Tiezhu,
>
> On Wed, Nov 29, 2023 at 9:07 PM Tiezhu Yang <[email protected]> wrote:
>>
>> The kernel CONFIG_UNWINDER_ORC option enables the ORC unwinder, which is
>> similar in concept to a DWARF unwinder. The difference is that the format
>> of the ORC data is much simpler than DWARF, which in turn allows the ORC
>> unwinder to be much simpler and faster.

...

>> diff --git a/arch/loongarch/kernel/lbt.S b/arch/loongarch/kernel/lbt.S
>> index 9c75120a26d8..4d6914f84c7e 100644
>> --- a/arch/loongarch/kernel/lbt.S
>> +++ b/arch/loongarch/kernel/lbt.S
>> @@ -11,6 +11,7 @@
>> #include <asm/asm-offsets.h>
>> #include <asm/errno.h>
>> #include <asm/regdef.h>
>> +#include <asm/unwind_hints.h>
>>
>> #define SCR_REG_WIDTH 8
>>
>> @@ -153,3 +154,7 @@ SYM_FUNC_END(_restore_ftop_context)
>> .L_lbt_fault:
>> li.w a0, -EFAULT # failure
>> jr ra
>> +
>> +#ifdef CONFIG_CPU_HAS_LBT
>> +STACK_FRAME_NON_STANDARD _restore_ftop_context
>> +#endif
> This file is only compiled if CONFIG_CPU_HAS_LBT is set, so #ifdef can
> be removed.

OK, will remove it in the next version.

Thanks,
Tiezhu

2023-12-07 07:14:35

by Tiezhu Yang

[permalink] [raw]
Subject: Re: [PATCH v5 0/8] Add objtool and orc support for LoongArch



On 12/02/2023 09:52 PM, Huacai Chen wrote:
> Hi, Tiezhu,
>
> There are still some build warnings.
>
> If FUNCTION_TRACER enabled, we get
> AS arch/loongarch/kernel/mcount_dyn.o
> arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
> ftrace_common+0x0: unreachable instruction
> arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
> ftrace_caller+0x0: unreachable instruction
> arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
> ftrace_regs_caller+0x0: unreachable instruction
> arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
> ftrace_graph_caller+0x0: unreachable instruction
> arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
> return_to_handler+0x0: unreachable instruction
> arch/loongarch/kernel/mcount_dyn.o: warning: objtool:
> ftrace_stub_direct_tramp+0x0: unreachable instruction
>
> AS arch/loongarch/kernel/rethook_trampoline.o
> arch/loongarch/kernel/rethook_trampoline.o: warning: objtool:
> arch_rethook_trampoline+0x0: unreachable instruction

I tested with the default config loongson3_defconfig,
the FUNCTION_TRACE is not enabled, let me try to fix it.

> And when I use the latest binutils/gcc for native build, there are
> some warnings in some files from the {init, kernel} directory. But
> cross-building is fine, I don't know why.

I can not reproduce it for native build, it works well,
I will analysis if it can reproduce someday.

Thanks,
Tiezhu

2023-12-07 07:23:34

by Tiezhu Yang

[permalink] [raw]
Subject: Re: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support



On 12/03/2023 08:42 PM, Huacai Chen wrote:
> On Wed, Nov 29, 2023 at 9:07 PM Tiezhu Yang <[email protected]> wrote:
>>
>> The kernel CONFIG_UNWINDER_ORC option enables the ORC unwinder, which is
>> similar in concept to a DWARF unwinder. The difference is that the format
>> of the ORC data is much simpler than DWARF, which in turn allows the ORC
>> unwinder to be much simpler and faster.

...

>> diff --git a/arch/loongarch/lib/Makefile b/arch/loongarch/lib/Makefile
>> index a77bf160bfc4..e3023d9a508c 100644
>> --- a/arch/loongarch/lib/Makefile
>> +++ b/arch/loongarch/lib/Makefile
>> @@ -3,6 +3,8 @@
>> # Makefile for LoongArch-specific library files.
>> #
>>
>> +OBJECT_FILES_NON_STANDARD := y
>> +
>> lib-y += delay.o memset.o memcpy.o memmove.o \
>> clear_user.o copy_user.o csum.o dump_tlb.o unaligned.o
>>
> I have a draft live-patch patch here:
> https://github.com/chenhuacai/linux/commit/744942cbf456b320f2333638e4bd27d35900284c
>
> But we get such an error:
>
> # selftests: livepatch: test-ftrace.sh
>
> [48/1865]
> [14936.262721] livepatch: failed to register ftrace handler for
> function 'cmdline_proc_show' (-16)
> # TEST: livepatch interaction with ftrace_enabled sysctl ...

...

> I think it is probably because we skip mem* functions here.

I did not test livepatch which is a separate new feature,
let me try it, maybe it needs some time because I am very
busy with some other stuffs.

Thanks,
Tiezhu

2023-12-07 07:43:34

by Tiezhu Yang

[permalink] [raw]
Subject: Re: [PATCH v5 8/8] LoongArch: Add ORC stack unwinder support



On 12/03/2023 09:20 PM, Xi Ruoyao wrote:
> On Wed, 2023-11-29 at 21:07 +0800, Tiezhu Yang wrote:
>> +KBUILD_AFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
>> +KBUILD_CFLAGS += $(call cc-option,-mthin-add-sub) $(call cc-option,-Wa$(comma)-mthin-add-sub)
>> +
>> +ifdef CONFIG_OBJTOOL
>> +KBUILD_CFLAGS += -fno-optimize-sibling-calls -fno-jump-tables -falign-functions=4 -falign-labels=4
>> +endif
>
> I still hate regressing the optimization with a passion.

Yes, I agree in general.

>
> And -falign-labels=4 implies -falign-functions=4 so at least we can
> remove -falign-functions=4.

Thanks.

>
> And in GCC >= 14 the defaults are -falign-labels=16 and -falign-
> functions=32. These values are determined by benchmarking on LA464 so
> I'd suggest using them (maybe unless CONFIG_CC_OPTIMIZE_FOR_SIZE).
>

The initial aim to add "-falign-functions=4 -falign-labels=4" is to
avoid generating nop instructions in .o file with -mrelax option
by default, otherwise the orc info can not match the symbol address
in vmlinux.

I want to add -mno-relax option and remove the extra option
"-falign-functions=4 -falign-labels=4", then the offsets in
each .o file and vmlinux are same, and it is not necessary
to do the special handling about local labels, I think
it will make life easier.

Thanks,
Tiezhu