LoongArch kernels can provide ORC unwind tables. Without consuming those
tables, crash falls back to a small prologue scanner, which is fragile
around exception entries and functions whose frame layout does not match
the simple scanner.
Add initial support for reading the kernel ORC IP table, lookup table and
orc_entry records, and use them while unwinding LoongArch64 kernel text
frames. The unwinder handles CALL and REGS entries, carries
frame-pointer state, and normalizes relocated exception-vector PCs before
using them as unwind targets.
For CALL entries, convert the saved return address back to the call
instruction address before validation, matching the kernel unwinder
convention. This avoids looking up the instruction after the call as the
caller frame.
This intentionally covers core kernel text first. Module and ftrace ORC
handling can be added separately.
Signed-off-by: Ming Wang <wangming01(a)loongson.cn>
---
defs.h | 25 ++++
loongarch64.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 378 insertions(+), 3 deletions(-)
diff --git a/defs.h b/defs.h
index 6373ee1831af..30ad1ef0e0ea 100644
--- a/defs.h
+++ b/defs.h
@@ -7330,6 +7330,30 @@ void loongarch64_dump_machdep_table(ulong);
#define KSYMS_START (0x1)
+struct loongarch64_orc_entry {
+ short sp_offset;
+ short fp_offset;
+ short ra_offset;
+ uint sp_reg;
+ uint fp_reg;
+ uint ra_reg;
+ uint type;
+ uint signal;
+};
+
+struct loongarch64_ORC_data {
+ int enabled;
+ uint lookup_num_blocks;
+ ulong __start_orc_unwind_ip;
+ ulong __stop_orc_unwind_ip;
+ ulong __start_orc_unwind;
+ ulong __stop_orc_unwind;
+ ulong orc_lookup;
+ ulong ip_entry;
+ ulong orc_entry;
+ struct loongarch64_orc_entry orc_entry_data;
+};
+
struct machine_specific {
ulong phys_base;
ulong vmalloc_start_addr;
@@ -7337,6 +7361,7 @@ struct machine_specific {
ulong modules_end;
struct loongarch64_pt_regs *crash_task_regs;
+ struct loongarch64_ORC_data orc;
};
/*
diff --git a/loongarch64.c b/loongarch64.c
index 0b108e062b2e..2e9e28602b9b 100644
--- a/loongarch64.c
+++ b/loongarch64.c
@@ -41,8 +41,33 @@ struct loongarch64_unwind_frame {
unsigned long sp;
unsigned long pc;
unsigned long ra;
+ unsigned long fp;
};
+typedef struct __attribute__((__packed__)) {
+ short sp_offset;
+ short fp_offset;
+ short 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;
+} loongarch64_kernel_orc_entry;
+
+#define LOONGARCH64_ORC_REG_UNDEFINED 0
+#define LOONGARCH64_ORC_REG_PREV_SP 1
+#define LOONGARCH64_ORC_REG_SP 2
+#define LOONGARCH64_ORC_REG_FP 3
+
+#define LOONGARCH64_ORC_TYPE_UNDEFINED 0
+#define LOONGARCH64_ORC_TYPE_END_OF_STACK 1
+#define LOONGARCH64_ORC_TYPE_CALL 2
+#define LOONGARCH64_ORC_TYPE_REGS 3
+
+#define LOONGARCH64_LOOKUP_BLOCK_ORDER 8
+#define LOONGARCH64_LOOKUP_BLOCK_SIZE (1 << LOONGARCH64_LOOKUP_BLOCK_ORDER)
+#define LOONGARCH64_INSN_SIZE 4
#define LOONGARCH64_VECSIZE 0x200
#define LOONGARCH64_EXCEPTION_VECTOR_NUM 128
#define LOONGARCH64_EXCCODE_INT_START 64
@@ -70,6 +95,7 @@ static void loongarch64_dump_backtrace_entry(struct bt_info *bt,
static void loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs);
static int loongarch64_is_exception_entry(struct syment *sym);
static ulong loongarch64_exception_pc(int cpu, ulong pc);
+static int loongarch64_is_unwind_text(ulong pc);
static int loongarch64_eframe_search(struct bt_info *bt);
static void loongarch64_display_full_frame(struct bt_info *bt,
struct loongarch64_unwind_frame *current,
@@ -82,6 +108,10 @@ static int loongarch64_get_frame(struct bt_info *bt, ulong *pcp, ulong
*spp);
static int loongarch64_init_active_task_regs(void);
static int loongarch64_get_crash_notes(void);
static int loongarch64_get_elf_notes(void);
+static void loongarch64_ORC_init(void);
+static int loongarch64_orc_unwind(struct bt_info *bt,
+ struct loongarch64_unwind_frame *current,
+ struct loongarch64_unwind_frame *previous);
/*
* 3 Levels paging PAGE_SIZE=16KB
@@ -454,11 +484,12 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
if (bt->flags & BT_REGS_NOT_FOUND)
return;
- previous.sp = previous.pc = previous.ra = 0;
+ previous.sp = previous.pc = previous.ra = previous.fp = 0;
current.pc = bt->instptr;
current.sp = bt->stkptr;
current.ra = 0;
+ current.fp = 0;
if (!INSTACK(current.sp, bt))
return;
@@ -466,6 +497,7 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
if (bt->machdep) {
regs = (struct loongarch64_pt_regs *)bt->machdep;
previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA];
+ current.fp = regs->regs[LOONGARCH64_EF_FP];
}
while (current.sp <= bt->stacktop - SIZE(pt_regs)) {
@@ -523,12 +555,15 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
}
}
- if (symbol && loongarch64_is_exception_entry(symbol)) {
+ if (loongarch64_orc_unwind(bt, ¤t, &previous)) {
+ /* ORC has already calculated the caller frame. */
+ } else if (symbol && loongarch64_is_exception_entry(symbol)) {
GET_STACK_DATA(current.sp, pt_regs, sizeof(pt_regs));
regs = (struct loongarch64_pt_regs *) (pt_regs + OFFSET(pt_regs_regs));
previous.ra = regs->regs[LOONGARCH64_EF_RA];
previous.sp = regs->regs[LOONGARCH64_EF_SP];
+ previous.fp = regs->regs[LOONGARCH64_EF_FP];
current.ra = regs->csr_epc;
if (CRASHDEBUG(8))
@@ -554,12 +589,13 @@ loongarch64_back_trace_cmd(struct bt_info *bt)
current.pc = current.ra;
current.sp = previous.sp;
current.ra = previous.ra;
+ current.fp = previous.fp;
if (CRASHDEBUG(8))
fprintf(fp, "next %d pc %#lx ra %#lx sp %lx\n",
level, current.pc, current.ra, current.sp);
- previous.sp = previous.pc = previous.ra = 0;
+ previous.sp = previous.pc = previous.ra = previous.fp = 0;
}
}
@@ -811,6 +847,319 @@ loongarch64_exception_pc(int cpu, ulong pc)
return func ? func + offset : pc;
}
+
+static int
+loongarch64_is_unwind_text(ulong pc)
+{
+ struct syment *symbol;
+ ulong offset;
+
+ if (!is_kernel_text(pc))
+ return FALSE;
+
+ symbol = value_search(pc, &offset);
+ if (!symbol || symbol->name[0] == '.' ||
+ STREQ(symbol->name, "_PROCEDURE_LINKAGE_TABLE_") ||
+ STREQ(symbol->name, "empty_zero_page") ||
+ STRNEQ(symbol->name, "__start_") ||
+ STRNEQ(symbol->name, "__end_"))
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+loongarch64_orc_ip(ulong ip_entry_addr, ulong *ip)
+{
+ int ip_entry;
+
+ if (!readmem(ip_entry_addr, KVADDR, &ip_entry, sizeof(ip_entry),
+ "LoongArch ORC ip", RETURN_ON_ERROR|QUIET))
+ return FALSE;
+
+ *ip = ip_entry_addr + ip_entry;
+ return TRUE;
+}
+
+static struct loongarch64_orc_entry *
+loongarch64_orc_get_entry(struct loongarch64_ORC_data *orc)
+{
+ loongarch64_kernel_orc_entry korc;
+ struct loongarch64_orc_entry *entry = &orc->orc_entry_data;
+
+ if (!readmem(orc->orc_entry, KVADDR, &korc, sizeof(korc),
+ "LoongArch ORC entry", RETURN_ON_ERROR|QUIET))
+ return NULL;
+
+ entry->sp_offset = korc.sp_offset;
+ entry->fp_offset = korc.fp_offset;
+ entry->ra_offset = korc.ra_offset;
+ entry->sp_reg = korc.sp_reg;
+ entry->fp_reg = korc.fp_reg;
+ entry->ra_reg = korc.ra_reg;
+ entry->type = korc.type;
+ entry->signal = korc.signal;
+
+ return entry;
+}
+
+static struct loongarch64_orc_entry *
+loongarch64_orc_find_in_table(ulong ip_table, ulong orc_table,
+ uint num_entries, ulong ip)
+{
+ int index;
+ ulong first, last, mid, found, vaddr;
+ struct machine_specific *ms = machdep->machspec;
+ struct loongarch64_ORC_data *orc = &ms->orc;
+
+ if (!num_entries)
+ return NULL;
+
+ first = ip_table;
+ last = ip_table + ((num_entries - 1) * sizeof(int));
+ found = first;
+
+ while (first <= last) {
+ mid = first + (((last - first) / sizeof(int)) / 2) *
+ sizeof(int);
+
+ if (!loongarch64_orc_ip(mid, &vaddr))
+ return NULL;
+
+ if (vaddr <= ip) {
+ found = mid;
+ first = mid + sizeof(int);
+ } else {
+ if (mid == ip_table)
+ break;
+ last = mid - sizeof(int);
+ }
+ }
+
+ index = (found - ip_table) / sizeof(int);
+ orc->ip_entry = found;
+ orc->orc_entry = orc_table + (index * SIZE(orc_entry));
+
+ return loongarch64_orc_get_entry(orc);
+}
+
+static struct loongarch64_orc_entry *
+loongarch64_orc_find(ulong ip)
+{
+ uint idx, start, stop, num_entries;
+ struct machine_specific *ms = machdep->machspec;
+ struct loongarch64_ORC_data *orc = &ms->orc;
+
+ if (!orc->enabled)
+ return NULL;
+
+ if ((ip >= kt->stext) && (ip < kt->etext)) {
+ if (orc->lookup_num_blocks < 2)
+ return NULL;
+
+ idx = (ip - kt->stext) / LOONGARCH64_LOOKUP_BLOCK_SIZE;
+ if (idx >= orc->lookup_num_blocks - 1)
+ return NULL;
+
+ if (!readmem(orc->orc_lookup + (idx * sizeof(uint)), KVADDR,
+ &start, sizeof(start), "LoongArch ORC lookup start",
+ RETURN_ON_ERROR|QUIET))
+ return NULL;
+ if (!readmem(orc->orc_lookup + ((idx + 1) * sizeof(uint)),
+ KVADDR, &stop, sizeof(stop), "LoongArch ORC lookup stop",
+ RETURN_ON_ERROR|QUIET))
+ return NULL;
+ stop++;
+
+ if ((orc->__start_orc_unwind + (start * SIZE(orc_entry))) >=
+ orc->__stop_orc_unwind)
+ return NULL;
+ if ((orc->__start_orc_unwind + (stop * SIZE(orc_entry))) >
+ orc->__stop_orc_unwind)
+ return NULL;
+
+ return loongarch64_orc_find_in_table(
+ orc->__start_orc_unwind_ip + (start * sizeof(int)),
+ orc->__start_orc_unwind + (start * SIZE(orc_entry)),
+ stop - start, ip);
+ }
+
+ if (is_kernel_text(ip)) {
+ num_entries = (orc->__stop_orc_unwind_ip -
+ orc->__start_orc_unwind_ip) / sizeof(int);
+ return loongarch64_orc_find_in_table(orc->__start_orc_unwind_ip,
+ orc->__start_orc_unwind, num_entries, ip);
+ }
+
+ return NULL;
+}
+
+static int
+loongarch64_orc_read_stack(ulong addr, ulong *value)
+{
+ return readmem(addr, KVADDR, value, sizeof(*value),
+ "LoongArch ORC stack", RETURN_ON_ERROR|QUIET);
+}
+
+static ulong
+loongarch64_orc_adjust_pc(int cpu, ulong ra)
+{
+ ra = loongarch64_exception_pc(cpu, ra);
+ return loongarch64_is_unwind_text(ra) ? ra : 0;
+}
+
+static int
+loongarch64_orc_unwind(struct bt_info *bt,
+ struct loongarch64_unwind_frame *current,
+ struct loongarch64_unwind_frame *previous)
+{
+ ulong cfa, pcval, fpval = current->fp;
+ struct loongarch64_orc_entry *orc;
+ struct loongarch64_pt_regs regs;
+
+ orc = loongarch64_orc_find(current->pc);
+ if (!orc)
+ return FALSE;
+
+ if (CRASHDEBUG(8))
+ fprintf(fp,
+ "orc pc %lx sp %lx fp %lx -> spo %d fpo %d rao %d "
+ "spr %u fpr %u rar %u type %u\n",
+ current->pc, current->sp, current->fp, orc->sp_offset,
+ orc->fp_offset, orc->ra_offset, orc->sp_reg, orc->fp_reg,
+ orc->ra_reg, orc->type);
+
+ if (orc->type == LOONGARCH64_ORC_TYPE_UNDEFINED ||
+ orc->type == LOONGARCH64_ORC_TYPE_END_OF_STACK)
+ return FALSE;
+
+ switch (orc->sp_reg) {
+ case LOONGARCH64_ORC_REG_SP:
+ cfa = current->sp + orc->sp_offset;
+ break;
+ case LOONGARCH64_ORC_REG_FP:
+ if (!current->fp)
+ return FALSE;
+ cfa = current->fp;
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (orc->fp_reg) {
+ case LOONGARCH64_ORC_REG_PREV_SP:
+ if (!loongarch64_orc_read_stack(cfa + orc->fp_offset, &fpval))
+ return FALSE;
+ break;
+ case LOONGARCH64_ORC_REG_UNDEFINED:
+ break;
+ default:
+ return FALSE;
+ }
+
+ switch (orc->type) {
+ case LOONGARCH64_ORC_TYPE_CALL:
+ if (orc->ra_reg == LOONGARCH64_ORC_REG_PREV_SP) {
+ if (!loongarch64_orc_read_stack(cfa + orc->ra_offset,
+ &pcval))
+ return FALSE;
+ } else if (orc->ra_reg == LOONGARCH64_ORC_REG_UNDEFINED) {
+ if (!current->ra || current->ra == current->pc)
+ return FALSE;
+ pcval = current->ra;
+ } else {
+ return FALSE;
+ }
+ if (pcval >= LOONGARCH64_INSN_SIZE)
+ pcval -= LOONGARCH64_INSN_SIZE;
+ pcval = loongarch64_orc_adjust_pc(bt->tc->processor, pcval);
+ if (!pcval && current->ra >= LOONGARCH64_INSN_SIZE)
+ pcval = loongarch64_orc_adjust_pc(bt->tc->processor,
+ current->ra - LOONGARCH64_INSN_SIZE);
+ if (!pcval)
+ return FALSE;
+ previous->sp = cfa;
+ previous->fp = fpval;
+ previous->ra = 0;
+ current->ra = pcval;
+ return TRUE;
+
+ case LOONGARCH64_ORC_TYPE_REGS:
+ if (!readmem(cfa, KVADDR, ®s, sizeof(regs),
+ "LoongArch ORC pt_regs", RETURN_ON_ERROR|QUIET))
+ return FALSE;
+ /*
+ * The kernel unwinder treats user-mode or empty pt_regs as a
+ * clean end. Crash does not need to distinguish that from a
+ * fallback here; a non-kernel ERA simply stops ORC unwinding.
+ */
+ pcval = loongarch64_exception_pc(bt->tc->processor, regs.csr_epc);
+ if (!loongarch64_is_unwind_text(pcval))
+ return FALSE;
+ previous->sp = regs.regs[LOONGARCH64_EF_SP];
+ previous->fp = regs.regs[LOONGARCH64_EF_FP];
+ previous->ra = regs.regs[LOONGARCH64_EF_RA];
+ current->ra = pcval;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+loongarch64_ORC_init(void)
+{
+ int i;
+ char *orc_symbols[] = {
+ "lookup_num_blocks",
+ "__start_orc_unwind_ip",
+ "__stop_orc_unwind_ip",
+ "__start_orc_unwind",
+ "__stop_orc_unwind",
+ "orc_lookup",
+ NULL
+ };
+ struct machine_specific *ms = machdep->machspec;
+ struct loongarch64_ORC_data *orc = &ms->orc;
+
+ STRUCT_SIZE_INIT(orc_entry, "orc_entry");
+ if (!VALID_STRUCT(orc_entry) ||
+ SIZE(orc_entry) != sizeof(loongarch64_kernel_orc_entry)) {
+ error(WARNING, "LoongArch64 ORC unwinder: "
+ "orc_entry structure has changed\n");
+ return;
+ }
+
+ if (!MEMBER_EXISTS("orc_entry", "sp_offset") ||
+ !MEMBER_EXISTS("orc_entry", "fp_offset") ||
+ !MEMBER_EXISTS("orc_entry", "ra_offset") ||
+ !MEMBER_EXISTS("orc_entry", "sp_reg") ||
+ !MEMBER_EXISTS("orc_entry", "fp_reg") ||
+ !MEMBER_EXISTS("orc_entry", "ra_reg") ||
+ !MEMBER_EXISTS("orc_entry", "type")) {
+ error(WARNING, "LoongArch64 ORC unwinder: "
+ "orc_entry members have changed\n");
+ return;
+ }
+
+ for (i = 0; orc_symbols[i]; i++) {
+ if (!symbol_exists(orc_symbols[i]))
+ return;
+ }
+
+ if (!readmem(symbol_value("lookup_num_blocks"), KVADDR,
+ &orc->lookup_num_blocks, sizeof(orc->lookup_num_blocks),
+ "LoongArch ORC lookup_num_blocks", RETURN_ON_ERROR|QUIET))
+ return;
+
+ orc->__start_orc_unwind_ip = symbol_value("__start_orc_unwind_ip");
+ orc->__stop_orc_unwind_ip = symbol_value("__stop_orc_unwind_ip");
+ orc->__start_orc_unwind = symbol_value("__start_orc_unwind");
+ orc->__stop_orc_unwind = symbol_value("__stop_orc_unwind");
+ orc->orc_lookup = symbol_value("orc_lookup");
+ orc->enabled = TRUE;
+}
+
/*
* 'bt -f' commend output
* Display all stack data contained in a frame
@@ -1374,6 +1723,7 @@ loongarch64_init(int when)
&machdep->nr_irqs);
loongarch64_stackframe_init();
+ loongarch64_ORC_init();
if (!machdep->hz)
machdep->hz = 250;
--
2.43.0