From: Rabin Vincent <rabinv(a)axis.com>
The mips stackframe handling has two problems:
(1) It does not read the registers from the kernel's crash_notes if ELF
notes are not available for some reason. This is important when
debugging raw memory dumps.
(2) If one of the online CPUs did not save the ELF notes, then the
mapping of ELF notes to CPUs may be incorrect.
ARM has an implementation which handles both these cases, so we borrow
it. Note that this code is entirely copy/pasted from arm, so perhaps
we could make a common function out of this which could be shared
between the two architectures?
---
defs.h | 2 +
mips.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
netdump.c | 9 ++-
3 files changed, 187 insertions(+), 11 deletions(-)
diff --git a/defs.h b/defs.h
index d64db92..0384f4e 100644
--- a/defs.h
+++ b/defs.h
@@ -5990,6 +5990,8 @@ struct machine_specific {
#define _PAGE_NO_EXEC (machdep->machspec->_page_no_exec)
#define _PAGE_DIRTY (machdep->machspec->_page_dirty)
#define _PFN_SHIFT (machdep->machspec->_pfn_shift)
+
+ struct mips_regset *crash_task_regs;
};
#endif /* MIPS */
diff --git a/mips.c b/mips.c
index cf3e1ae..e3f1569 100644
--- a/mips.c
+++ b/mips.c
@@ -54,6 +54,11 @@ typedef ulong pte_t;
static struct machine_specific mips_machine_specific = { 0 };
+/*
+ * Holds registers during the crash.
+ */
+static struct mips_regset *panic_task_regs;
+
static void
mips_display_machine_stats(void)
{
@@ -533,6 +538,9 @@ mips_back_trace_cmd(struct bt_info *bt)
struct mips_unwind_frame current, previous;
int level = 0;
+ if (bt->flags & BT_REGS_NOT_FOUND)
+ return;
+
previous.sp = previous.pc = previous.ra = 0;
current.pc = bt->instptr;
@@ -629,22 +637,35 @@ mips_back_trace_cmd(struct bt_info *bt)
}
}
-static void
+static int
mips_dumpfile_stack_frame(struct bt_info *bt, ulong *nip, ulong *ksp)
{
+ const struct machine_specific *ms = machdep->machspec;
struct mips_regset *regs;
+ ulong epc, r29;
- regs = bt->machdep;
- if (!regs) {
- fprintf(fp, "0%lx: Register values not available\n",
- bt->task);
- return;
+ if (!ms->crash_task_regs) {
+ bt->flags |= BT_REGS_NOT_FOUND;
+ return FALSE;
+ }
+
+ regs = &ms->crash_task_regs[bt->tc->processor];
+ epc = regs->regs[MIPS32_EF_CPU0_EPC];
+ r29 = regs->regs[MIPS32_EF_R29];
+
+ if (!epc && !r29) {
+ bt->flags |= BT_REGS_NOT_FOUND;
+ return FALSE;
}
if (nip)
- *nip = regs->regs[MIPS32_EF_CPU0_EPC];
+ *nip = epc;
if (ksp)
- *ksp = regs->regs[MIPS32_EF_R29];
+ *ksp = r29;
+
+ bt->machdep = regs;
+
+ return TRUE;
}
static int
@@ -698,14 +719,20 @@ mips_stackframe_init(void)
static void
mips_get_stack_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
{
+ int ret;
+
*pcp = 0;
*spp = 0;
+ bt->machdep = NULL;
if (DUMPFILE() && is_task_active(bt->task))
- mips_dumpfile_stack_frame(bt, pcp, spp);
+ ret = mips_dumpfile_stack_frame(bt, pcp, spp);
else
- mips_get_frame(bt, pcp, spp);
+ ret = mips_get_frame(bt, pcp, spp);
+ if (!ret)
+ error(WARNING, "cannot determine starting stack frame for task %lx\n",
+ bt->task);
}
static int
@@ -747,6 +774,130 @@ mips_vmalloc_start(void)
return first_vmalloc_address();
}
+/*
+ * Retrieve task registers for the time of the crash.
+ */
+static int
+mips_get_crash_notes(void)
+{
+ struct machine_specific *ms = machdep->machspec;
+ ulong crash_notes;
+ Elf32_Nhdr *note;
+ ulong offset;
+ char *buf, *p;
+ ulong *notes_ptrs;
+ ulong i;
+
+ if (!symbol_exists("crash_notes"))
+ return FALSE;
+
+ crash_notes = symbol_value("crash_notes");
+
+ notes_ptrs = (ulong *)GETBUF(kt->cpus*sizeof(notes_ptrs[0]));
+
+ /*
+ * Read crash_notes for the first CPU. crash_notes are in standard ELF
+ * note format.
+ */
+ if (!readmem(crash_notes, KVADDR, ¬es_ptrs[kt->cpus-1],
+ sizeof(notes_ptrs[kt->cpus-1]), "crash_notes",
+ RETURN_ON_ERROR)) {
+ error(WARNING, "cannot read crash_notes\n");
+ FREEBUF(notes_ptrs);
+ return FALSE;
+ }
+
+ if (symbol_exists("__per_cpu_offset")) {
+
+ /* Add __per_cpu_offset for each cpu to form the pointer to the notes */
+ for (i = 0; i<kt->cpus; i++)
+ notes_ptrs[i] = notes_ptrs[kt->cpus-1] + kt->__per_cpu_offset[i];
+ }
+
+ buf = GETBUF(SIZE(note_buf));
+
+ if (!(panic_task_regs = calloc((size_t)kt->cpus, sizeof(*panic_task_regs))))
+ error(FATAL, "cannot calloc panic_task_regs space\n");
+
+ for (i=0;i<kt->cpus;i++) {
+
+ if (!readmem(notes_ptrs[i], KVADDR, buf, SIZE(note_buf), "note_buf_t",
+ RETURN_ON_ERROR)) {
+ error(WARNING, "failed to read note_buf_t\n");
+ goto fail;
+ }
+
+ /*
+ * Do some sanity checks for this note before reading registers from it.
+ */
+ note = (Elf32_Nhdr *)buf;
+ p = buf + sizeof(Elf32_Nhdr);
+
+ /*
+ * dumpfiles created with qemu won't have crash_notes, but there will
+ * be elf notes; dumpfiles created by kdump do not create notes for
+ * offline cpus.
+ */
+ if (note->n_namesz == 0 && (DISKDUMP_DUMPFILE() || KDUMP_DUMPFILE())) {
+ if (DISKDUMP_DUMPFILE())
+ note = diskdump_get_prstatus_percpu(i);
+ else if (KDUMP_DUMPFILE())
+ note = netdump_get_prstatus_percpu(i);
+ if (note) {
+ /*
+ * SIZE(note_buf) accounts for a "final note", which is a
+ * trailing empty elf note header.
+ */
+ long notesz = SIZE(note_buf) - sizeof(Elf32_Nhdr);
+
+ if (sizeof(Elf32_Nhdr) + roundup(note->n_namesz, 4) +
+ note->n_descsz == notesz)
+ BCOPY((char *)note, buf, notesz);
+ } else {
+ error(WARNING,
+ "cannot find NT_PRSTATUS note for cpu: %d\n", i);
+ continue;
+ }
+ }
+
+ if (note->n_type != NT_PRSTATUS) {
+ error(WARNING, "invalid note (n_type != NT_PRSTATUS)\n");
+ goto fail;
+ }
+ if (p[0] != 'C' || p[1] != 'O' || p[2] != 'R' || p[3] !=
'E') {
+ error(WARNING, "invalid note (name != \"CORE\"\n");
+ goto fail;
+ }
+
+ /*
+ * Find correct location of note data. This contains elf_prstatus
+ * structure which has registers etc. for the crashed task.
+ */
+ offset = sizeof(Elf32_Nhdr);
+ offset = roundup(offset + note->n_namesz, 4);
+ p = buf + offset; /* start of elf_prstatus */
+
+ BCOPY(p + OFFSET(elf_prstatus_pr_reg), &panic_task_regs[i],
+ sizeof(panic_task_regs[i]));
+ }
+
+ /*
+ * And finally we have the registers for the crashed task. This is
+ * used later on when dumping backtrace.
+ */
+ ms->crash_task_regs = panic_task_regs;
+
+ FREEBUF(buf);
+ FREEBUF(notes_ptrs);
+ return TRUE;
+
+fail:
+ FREEBUF(buf);
+ FREEBUF(notes_ptrs);
+ free(panic_task_regs);
+ return FALSE;
+}
+
static int
mips_verify_symbol(const char *name, ulong value, char type)
{
@@ -911,7 +1062,23 @@ mips_init(int when)
if (!machdep->hz)
machdep->hz = 100;
+
+ MEMBER_OFFSET_INIT(elf_prstatus_pr_reg, "elf_prstatus",
+ "pr_reg");
+
+ STRUCT_SIZE_INIT(note_buf, "note_buf_t");
break;
+ case POST_VM:
+ /*
+ * crash_notes contains machine specific information about the
+ * crash. In particular, it contains CPU registers at the time
+ * of the crash. We need this information to extract correct
+ * backtraces from the panic task.
+ */
+ if (!ACTIVE() && !mips_get_crash_notes())
+ error(WARNING,
+ "cannot retrieve registers for active task%s\n\n",
+ kt->cpus > 1 ? "s" : "");
}
}
diff --git a/netdump.c b/netdump.c
index 2b7f1ce..3350c28 100644
--- a/netdump.c
+++ b/netdump.c
@@ -40,6 +40,7 @@ static void get_netdump_regs_ppc(struct bt_info *, ulong *, ulong *);
static void get_netdump_regs_ppc64(struct bt_info *, ulong *, ulong *);
static void get_netdump_regs_arm(struct bt_info *, ulong *, ulong *);
static void get_netdump_regs_arm64(struct bt_info *, ulong *, ulong *);
+static void get_netdump_regs_mips(struct bt_info *, ulong *, ulong *);
static void check_dumpfile_size(char *);
static int proc_kcore_init_32(FILE *fp);
static int proc_kcore_init_64(FILE *fp);
@@ -2436,7 +2437,7 @@ get_netdump_regs(struct bt_info *bt, ulong *eip, ulong *esp)
break;
case EM_MIPS:
- return get_netdump_regs_32(bt, eip, esp);
+ return get_netdump_regs_mips(bt, eip, esp);
break;
default:
@@ -3628,6 +3629,12 @@ get_netdump_regs_arm64(struct bt_info *bt, ulong *eip, ulong *esp)
machdep->get_stack_frame(bt, eip, esp);
}
+static void
+get_netdump_regs_mips(struct bt_info *bt, ulong *eip, ulong *esp)
+{
+ machdep->get_stack_frame(bt, eip, esp);
+}
+
int
is_partial_netdump(void)
{
--
2.1.4