[PATCH 4/4] crash: add ARM crashdump support
Mika Westerberg
ext-mika.1.westerberg at nokia.com
Wed Jun 30 06:09:12 EDT 2010
This patch adds minimal support for reading ARM crashdumps (currently only
diskdump format is supported). It contains necessary virtual to physical
translations and stack unwinding support.
Signed-off-by: Mika Westerberg <ext-mika.1.westerberg at nokia.com>
---
Makefile | 12 +-
arm.c | 1188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
defs.h | 131 +++++++
unwind_arm.c | 702 ++++++++++++++++++++++++++++++++++
4 files changed, 2031 insertions(+), 2 deletions(-)
create mode 100644 arm.c
create mode 100644 unwind_arm.c
diff --git a/Makefile b/Makefile
index 3ea6539..61d88c0 100644
--- a/Makefile
+++ b/Makefile
@@ -74,11 +74,12 @@ UNWIND_HFILES=unwind.h unwind_i.h rse.h unwind_x86.h unwind_x86_64.h
CFILES=main.c tools.c global_data.c memory.c filesys.c help.c task.c \
kernel.c test.c gdb_interface.c configure.c net.c dev.c \
alpha.c x86.c ppc.c ia64.c s390.c s390x.c s390dbf.c ppc64.c x86_64.c \
+ arm.c \
extensions.c remote.c va_server.c va_server_v1.c symbols.c cmdline.c \
lkcd_common.c lkcd_v1.c lkcd_v2_v3.c lkcd_v5.c lkcd_v7.c lkcd_v8.c\
lkcd_fix_mem.c s390_dump.c lkcd_x86_trace.c \
netdump.c diskdump.c xendump.c unwind.c unwind_decoder.c \
- unwind_x86_32_64.c \
+ unwind_x86_32_64.c unwind_arm.c \
xen_hyper.c xen_hyper_command.c xen_hyper_global_data.c \
xen_hyper_dump_tables.c kvmdump.c qemu.c qemu-load.c
@@ -90,11 +91,12 @@ SOURCE_FILES=${CFILES} ${GENERIC_HFILES} ${MCORE_HFILES} \
OBJECT_FILES=main.o tools.o global_data.o memory.o filesys.o help.o task.o \
build_data.o kernel.o test.o gdb_interface.o net.o dev.o \
alpha.o x86.o ppc.o ia64.o s390.o s390x.o s390dbf.o ppc64.o x86_64.o \
+ arm.o \
extensions.o remote.o va_server.o va_server_v1.o symbols.o cmdline.o \
lkcd_common.o lkcd_v1.o lkcd_v2_v3.o lkcd_v5.o lkcd_v7.o lkcd_v8.o \
lkcd_fix_mem.o s390_dump.o netdump.o diskdump.o xendump.o \
lkcd_x86_trace.o unwind_v1.o unwind_v2.o unwind_v3.o \
- unwind_x86_32_64.o \
+ unwind_x86_32_64.o unwind_arm.o \
xen_hyper.o xen_hyper_command.o xen_hyper_global_data.o \
xen_hyper_dump_tables.o kvmdump.o qemu.o qemu-load.o
@@ -407,6 +409,9 @@ ppc64.o: ${GENERIC_HFILES} ppc64.c
x86_64.o: ${GENERIC_HFILES} ${REDHAT_HFILES} x86_64.c
cc -c ${CRASH_CFLAGS} x86_64.c ${WARNING_OPTIONS} ${WARNING_ERROR}
+arm.o: ${GENERIC_HFILES} ${REDHAT_HFILES} arm.c
+ cc -c ${CRASH_CFLAGS} arm.c ${WARNING_OPTIONS} ${WARNING_ERROR}
+
s390.o: ${GENERIC_HFILES} ${IBM_HFILES} s390.c
cc -c ${CRASH_CFLAGS} s390.c ${WARNING_OPTIONS} ${WARNING_ERROR}
@@ -448,6 +453,9 @@ lkcd_x86_trace.o: ${GENERIC_HFILES} ${LKCD_TRACE_HFILES} lkcd_x86_trace.c
unwind_x86_32_64.o: ${GENERIC_HFILES} ${UNWIND_HFILES} unwind_x86_32_64.c
cc -c ${CRASH_CFLAGS} unwind_x86_32_64.c -o unwind_x86_32_64.o ${WARNING_OPTIONS} ${WARNING_ERROR}
+unwind_arm.o: ${GENERIC_HFILES} ${UNWIND_HFILES} unwind_arm.c
+ cc -c ${CRASH_CFLAGS} unwind_arm.c -o unwind_arm.o ${WARNING_OPTIONS} ${WARNING_ERROR}
+
unwind_v1.o: ${GENERIC_HFILES} ${UNWIND_HFILES} unwind.c unwind_decoder.c
cc -c ${CRASH_CFLAGS} unwind.c -DREDHAT -DUNWIND_V1 -o unwind_v1.o ${WARNING_OPTIONS} ${WARNING_ERROR}
diff --git a/arm.c b/arm.c
new file mode 100644
index 0000000..40c860a
--- /dev/null
+++ b/arm.c
@@ -0,0 +1,1188 @@
+/*
+ * arm.c - core analysis suite
+ *
+ * Created by: Mika Westerberg <ext-mika.1.westerberg at nokia.com>
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifdef ARM
+#include <elf.h>
+
+#include "defs.h"
+
+static int arm_get_crash_notes(void);
+static int arm_verify_symbol(const char *, ulong, char);
+static int arm_is_module_addr(ulong);
+static int arm_is_kvaddr(ulong);
+static int arm_in_exception_text(ulong);
+static void arm_back_trace_cmd(struct bt_info *);
+static ulong arm_processor_speed(void);
+static int arm_translate_pte(ulong, void *, ulonglong);
+static int arm_vtop(ulong, ulong *, physaddr_t *, int);
+static int arm_kvtop(struct task_context *, ulong, physaddr_t *, int);
+static int arm_uvtop(struct task_context *, ulong, physaddr_t *, int);
+static int arm_get_frame(struct bt_info *, ulong *, ulong *);
+static int arm_get_dumpfile_stack_frame(struct bt_info *, ulong *, ulong *);
+static void arm_get_stack_frame(struct bt_info *, ulong *, ulong *);
+static void arm_dump_exception_stack(ulong, ulong);
+static ulong arm_vmalloc_start(void);
+static int arm_is_task_addr(ulong);
+static int arm_dis_filter(ulong, char *);
+static int arm_eframe_search(struct bt_info *);
+static ulong arm_get_task_pgd(ulong);
+static void arm_cmd_mach(void);
+static void arm_display_machine_stats(void);
+static int arm_get_smp_cpus(void);
+static void arm_init_machspec(void);
+
+static struct line_number_hook arm_line_number_hooks[];
+static struct machine_specific arm_machine_specific;
+
+/**
+ * struct arm_cpu_context_save - idle task registers
+ *
+ * This structure holds idle task registers. Only FP, SP, and PC are needed for
+ * unwinding the stack.
+ */
+struct arm_cpu_context_save {
+ ulong fp;
+ ulong sp;
+ ulong pc;
+};
+
+/*
+ * Holds registers during the crash.
+ */
+static struct arm_pt_regs panic_task_regs;
+
+#define PGDIR_SIZE() (4 * PAGESIZE())
+#define PGDIR_OFFSET(X) (((ulong)(X)) & (PGDIR_SIZE() - 1))
+
+#define _SECTION_PAGE_MASK (~((MEGABYTES(1))-1))
+
+#define PMD_TYPE_MASK 3
+#define PMD_TYPE_SECT 2
+#define PMD_TYPE_TABLE 1
+
+static inline ulong *
+pmd_page_addr(ulong pmd)
+{
+ ulong ptr;
+
+ ptr = pmd & ~(PTRS_PER_PTE * sizeof(void *) - 1);
+ ptr += PTRS_PER_PTE * sizeof(void *);
+
+ return (ulong *)ptr;
+}
+
+/*
+ * "Linux" PTE definitions.
+ */
+#define L_PTE_PRESENT (1 << 0)
+#define L_PTE_YOUNG (1 << 1)
+#define L_PTE_FILE (1 << 2)
+#define L_PTE_DIRTY (1 << 6)
+#define L_PTE_WRITE (1 << 7)
+#define L_PTE_USER (1 << 8)
+#define L_PTE_EXEC (1 << 9)
+#define L_PTE_SHARED (1 << 10)
+
+#define pte_val(pte) (pte)
+
+#define pte_present(pte) (pte_val(pte) & L_PTE_PRESENT)
+#define pte_write(pte) (pte_val(pte) & L_PTE_WRITE)
+#define pte_dirty(pte) (pte_val(pte) & L_PTE_DIRTY)
+#define pte_young(pte) (pte_val(pte) & L_PTE_YOUNG)
+
+/*
+ * Following stuff is taken directly from the kernel sources. These are used in
+ * dump_exception_stack() to format an exception stack entry.
+ */
+#define USR26_MODE 0x00000000
+#define FIQ26_MODE 0x00000001
+#define IRQ26_MODE 0x00000002
+#define SVC26_MODE 0x00000003
+#define USR_MODE 0x00000010
+#define FIQ_MODE 0x00000011
+#define IRQ_MODE 0x00000012
+#define SVC_MODE 0x00000013
+#define ABT_MODE 0x00000017
+#define UND_MODE 0x0000001b
+#define SYSTEM_MODE 0x0000001f
+#define MODE32_BIT 0x00000010
+#define MODE_MASK 0x0000001f
+#define PSR_T_BIT 0x00000020
+#define PSR_F_BIT 0x00000040
+#define PSR_I_BIT 0x00000080
+#define PSR_A_BIT 0x00000100
+#define PSR_E_BIT 0x00000200
+#define PSR_J_BIT 0x01000000
+#define PSR_Q_BIT 0x08000000
+#define PSR_V_BIT 0x10000000
+#define PSR_C_BIT 0x20000000
+#define PSR_Z_BIT 0x40000000
+#define PSR_N_BIT 0x80000000
+
+#define isa_mode(regs) \
+ ((((regs)->ARM_cpsr & PSR_J_BIT) >> 23) | \
+ (((regs)->ARM_cpsr & PSR_T_BIT) >> 5))
+
+#define processor_mode(regs) \
+ ((regs)->ARM_cpsr & MODE_MASK)
+
+#define interrupts_enabled(regs) \
+ (!((regs)->ARM_cpsr & PSR_I_BIT))
+
+#define fast_interrupts_enabled(regs) \
+ (!((regs)->ARM_cpsr & PSR_F_BIT))
+
+static const char *processor_modes[] = {
+ "USER_26", "FIQ_26", "IRQ_26", "SVC_26", "UK4_26", "UK5_26",
+ "UK6_26", "UK7_26" , "UK8_26", "UK9_26", "UK10_26", "UK11_26",
+ "UK12_26", "UK13_26", "UK14_26", "UK15_26", "USER_32", "FIQ_32",
+ "IRQ_32", "SVC_32", "UK4_32", "UK5_32", "UK6_32", "ABT_32",
+ "UK8_32", "UK9_32", "UK10_32", "UND_32", "UK12_32", "UK13_32",
+ "UK14_32", "SYS_32",
+};
+
+static const char *isa_modes[] = {
+ "ARM" , "Thumb" , "Jazelle", "ThumbEE",
+};
+
+#define NOT_IMPLEMENTED() \
+ error(FATAL, "%s: N/A\n", __func__)
+
+/*
+ * Do all necessary machine-specific setup here. This is called several times
+ * during initialization.
+ */
+void
+arm_init(int when)
+{
+ switch (when) {
+ case PRE_SYMTAB:
+ machdep->verify_symbol = arm_verify_symbol;
+ machdep->machspec = &arm_machine_specific;
+ if (pc->flags & KERNEL_DEBUG_QUERY)
+ return;
+ machdep->pagesize = memory_page_size();
+ machdep->pageshift = ffs(machdep->pagesize) - 1;
+ machdep->pageoffset = machdep->pagesize - 1;
+ machdep->pagemask = ~((ulonglong)machdep->pageoffset);
+ machdep->stacksize = machdep->pagesize * 2;
+ machdep->last_pgd_read = 0;
+ machdep->last_pmd_read = 0;
+ machdep->last_ptbl_read = 0;
+ machdep->verify_paddr = generic_verify_paddr;
+ machdep->ptrs_per_pgd = PTRS_PER_PGD;
+ break;
+
+ case PRE_GDB:
+ if ((machdep->pgd = (char *)malloc(PGDIR_SIZE())) == NULL)
+ error(FATAL, "cannot malloc pgd space.");
+ if ((machdep->ptbl = (char *)malloc(PAGESIZE())) == NULL)
+ error(FATAL, "cannot malloc ptbl space.");
+
+ /*
+ * Kernel text starts 16k after PAGE_OFFSET.
+ */
+ machdep->kvbase = symbol_value("_stext") & 0xffff0000UL;
+ machdep->identity_map_base = machdep->kvbase;
+ machdep->is_kvaddr = arm_is_kvaddr;
+ machdep->is_uvaddr = generic_is_uvaddr;
+ machdep->eframe_search = arm_eframe_search;
+ machdep->back_trace = arm_back_trace_cmd;
+ machdep->processor_speed = arm_processor_speed;
+ machdep->uvtop = arm_uvtop;
+ machdep->kvtop = arm_kvtop;
+ machdep->get_task_pgd = arm_get_task_pgd;
+ machdep->get_stack_frame = arm_get_stack_frame;
+ machdep->get_stackbase = generic_get_stackbase;
+ machdep->get_stacktop = generic_get_stacktop;
+ machdep->translate_pte = arm_translate_pte;
+ machdep->memory_size = generic_memory_size;
+ machdep->vmalloc_start = arm_vmalloc_start;
+ machdep->is_task_addr = arm_is_task_addr;
+ machdep->dis_filter = arm_dis_filter;
+ machdep->cmd_mach = arm_cmd_mach;
+ machdep->get_smp_cpus = arm_get_smp_cpus;
+ machdep->line_number_hooks = arm_line_number_hooks;
+ machdep->value_to_symbol = generic_machdep_value_to_symbol;
+ machdep->init_kernel_pgd = NULL;
+
+ if (symbol_exists("irq_desc"))
+ machdep->dump_irq = generic_dump_irq;
+
+ arm_init_machspec();
+ break;
+
+ case POST_GDB:
+ if (symbol_exists("irq_desc"))
+ ARRAY_LENGTH_INIT(machdep->nr_irqs, irq_desc,
+ "irq_desc", NULL, 0);
+ /*
+ * Registers for idle threads are saved in
+ * thread_info.cpu_context.
+ */
+ STRUCT_SIZE_INIT(cpu_context_save, "cpu_context_save");
+ MEMBER_OFFSET_INIT(cpu_context_save_fp,
+ "cpu_context_save", "fp");
+ MEMBER_OFFSET_INIT(cpu_context_save_sp,
+ "cpu_context_save", "sp");
+ MEMBER_OFFSET_INIT(cpu_context_save_pc,
+ "cpu_context_save", "pc");
+ MEMBER_OFFSET_INIT(thread_info_cpu_context,
+ "thread_info", "cpu_context");
+
+ /*
+ * We need to have information about note_buf_t which is used to
+ * hold ELF note containing registers and status of the thread
+ * that panic'd.
+ */
+ STRUCT_SIZE_INIT(note_buf, "note_buf_t");
+
+ STRUCT_SIZE_INIT(elf_prstatus, "elf_prstatus");
+ MEMBER_OFFSET_INIT(elf_prstatus_pr_pid, "elf_prstatus",
+ "pr_pid");
+ MEMBER_OFFSET_INIT(elf_prstatus_pr_reg, "elf_prstatus",
+ "pr_reg");
+
+ /*
+ * 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 (!arm_get_crash_notes())
+ error(WARNING, "Couldn't retrieve crash_notes\n");
+ break;
+
+ case POST_VM:
+ machdep->machspec->vmalloc_start_addr = vt->high_memory;
+ /*
+ * Modules are placed in first vmalloc'd area. This is 16MB
+ * below PAGE_OFFSET.
+ */
+ machdep->machspec->modules_vaddr = first_vmalloc_address();
+ machdep->machspec->modules_end = machdep->kvbase - 1;
+
+ if (!init_unwind_tables()) {
+ error(WARNING, "Couldn't initialize unwind tables.\n"
+ "This means that stack unwinding is not\n"
+ "available for this core file.\n");
+ }
+ break;
+ }
+}
+
+void
+arm_dump_machdep_table(ulong arg)
+{
+ const struct machine_specific *ms;
+ int others, i;
+
+ others = 0;
+ fprintf(fp, " flags: %lx (", machdep->flags);
+ if (machdep->flags & KSYMS_START)
+ fprintf(fp, "%sKSYMS_START", others++ ? "|" : "");
+ fprintf(fp, ")\n");
+
+ fprintf(fp, " kvbase: %lx\n", machdep->kvbase);
+ fprintf(fp, " identity_map_base: %lx\n", machdep->kvbase);
+ fprintf(fp, " pagesize: %d\n", machdep->pagesize);
+ fprintf(fp, " pageshift: %d\n", machdep->pageshift);
+ fprintf(fp, " pagemask: %lx\n", (ulong)machdep->pagemask);
+ fprintf(fp, " pageoffset: %lx\n", machdep->pageoffset);
+ fprintf(fp, " stacksize: %ld\n", machdep->stacksize);
+ fprintf(fp, " hz: %d\n", machdep->hz);
+ fprintf(fp, " mhz: %ld\n", machdep->mhz);
+ fprintf(fp, " memsize: %lld (0x%llx)\n",
+ machdep->memsize, machdep->memsize);
+ fprintf(fp, " bits: %d\n", machdep->bits);
+ fprintf(fp, " nr_irqs: %d\n", machdep->nr_irqs);
+ fprintf(fp, " eframe_search: arm_eframe_search()\n");
+ fprintf(fp, " back_trace: arm_back_trace_cmd()\n");
+ fprintf(fp, " processor_speed: arm_processor_speed()\n");
+ fprintf(fp, " uvtop: arm_uvtop()\n");
+ fprintf(fp, " kvtop: arm_kvtop()\n");
+ fprintf(fp, " get_task_pgd: arm_get_task_pgd()\n");
+ fprintf(fp, " dump_irq: generic_dump_irq()\n");
+ fprintf(fp, " get_stack_frame: arm_get_stack_frame()\n");
+ fprintf(fp, " get_stackbase: generic_get_stackbase()\n");
+ fprintf(fp, " get_stacktop: generic_get_stacktop()\n");
+ fprintf(fp, " translate_pte: arm_translate_pte()\n");
+ fprintf(fp, " memory_size: generic_memory_size()\n");
+ fprintf(fp, " vmalloc_start: arm_vmalloc_start()\n");
+ fprintf(fp, " is_task_addr: arm_is_task_addr()\n");
+ fprintf(fp, " verify_symbol: arm_verify_symbol()\n");
+ fprintf(fp, " dis_filter: arm_dis_filter()\n");
+ fprintf(fp, " cmd_mach: arm_cmd_mach()\n");
+ fprintf(fp, " get_smp_cpus: arm_get_smp_cpus()\n");
+ fprintf(fp, " is_kvaddr: generic_is_kvaddr()\n");
+ fprintf(fp, " is_uvaddr: generic_is_uvaddr()\n");
+ fprintf(fp, " verify_paddr: generic_verify_paddr()\n");
+ fprintf(fp, " xendump_p2m_create: NULL\n");
+ fprintf(fp, "xen_kdump_p2m_create: NULL\n");
+ fprintf(fp, " line_number_hooks: arm_line_number_hooks\n");
+ fprintf(fp, " last_pgd_read: %lx\n", machdep->last_pgd_read);
+ fprintf(fp, " last_pmd_read: %lx\n", machdep->last_pmd_read);
+ fprintf(fp, " last_ptbl_read: %lx\n", machdep->last_ptbl_read);
+ fprintf(fp, "clear_machdep_cache: NULL\n");
+ fprintf(fp, " pgd: %lx\n", (ulong)machdep->pgd);
+ fprintf(fp, " pmd: %lx\n", (ulong)machdep->pmd);
+ fprintf(fp, " ptbl: %lx\n", (ulong)machdep->ptbl);
+ fprintf(fp, " ptrs_per_pgd: %d\n", machdep->ptrs_per_pgd);
+ fprintf(fp, " section_size_bits: %ld\n", machdep->section_size_bits);
+ fprintf(fp, " max_physmem_bits: %ld\n", machdep->max_physmem_bits);
+ fprintf(fp, " sections_per_root: %ld\n", machdep->sections_per_root);
+
+ for (i = 0; i < MAX_MACHDEP_ARGS; i++) {
+ fprintf(fp, " cmdline_args[%d]: %s\n",
+ i, machdep->cmdline_args[i] ?
+ machdep->cmdline_args[i] : "(unused)");
+ }
+
+ ms = machdep->machspec;
+
+ fprintf(fp, " machspec: %lx\n", (ulong)ms);
+ fprintf(fp, " phys_base: %lx\n", ms->phys_base);
+ fprintf(fp, " vmalloc_start_addr: %lx\n", ms->vmalloc_start_addr);
+ fprintf(fp, " modules_vaddr: %lx\n", ms->modules_vaddr);
+ fprintf(fp, " modules_end: %lx\n", ms->modules_end);
+ fprintf(fp, " kernel_text_start: %lx\n", ms->kernel_text_start);
+ fprintf(fp, " kernel_text_end: %lx\n", ms->kernel_text_end);
+ fprintf(fp, "exception_text_start: %lx\n", ms->exception_text_start);
+ fprintf(fp, " exception_text_end: %lx\n", ms->exception_text_end);
+ fprintf(fp, " crash_task_pid: %ld\n", ms->crash_task_pid);
+ fprintf(fp, " crash_task_regs: %lx\n", (ulong)ms->crash_task_regs);
+}
+
+/*
+ * Retrieve task registers for the time of the crash.
+ */
+static int
+arm_get_crash_notes(void)
+{
+ struct machine_specific *ms = machdep->machspec;
+ ulong crash_notes;
+ Elf32_Nhdr *note;
+ ulong ptr, offset;
+ char *buf, *p;
+
+ if (!symbol_exists("crash_notes"))
+ return FALSE;
+
+ crash_notes = symbol_value("crash_notes");
+
+ if (kt->cpus > 1)
+ error(WARNING, "only one CPU is currently supported\n");
+
+ /*
+ * Read crash_notes for the first CPU. crash_notes are in standard ELF
+ * note format.
+ */
+ if (!readmem(crash_notes, KVADDR, &ptr, sizeof(ptr), "crash_notes",
+ RETURN_ON_ERROR)) {
+ error(WARNING, "cannot read crash_notes\n");
+ return FALSE;
+ }
+
+ buf = GETBUF(SIZE(note_buf));
+
+ if (!readmem(ptr, 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);
+
+ 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,
+ sizeof(panic_task_regs));
+
+ /*
+ * And finally we have pid and registers for the crashed task. This is
+ * used later on when dumping backtrace.
+ */
+ ms->crash_task_pid = *(ulong *)(p + OFFSET(elf_prstatus_pr_pid));
+ ms->crash_task_regs = &panic_task_regs;
+
+ FREEBUF(buf);
+ return TRUE;
+
+fail:
+ FREEBUF(buf);
+ return FALSE;
+}
+
+/*
+ * Accept or reject a symbol from the kernel namelist.
+ */
+static int
+arm_verify_symbol(const char *name, ulong value, char type)
+{
+ if (STREQ(name, "swapper_pg_dir"))
+ machdep->flags |= KSYMS_START;
+
+ if (!name || !strlen(name) || !(machdep->flags & KSYMS_START))
+ return FALSE;
+
+ if (STREQ(name, "$a") || STREQ(name, "$n") || STREQ(name, "$d"))
+ return FALSE;
+
+ if (CRASHDEBUG(8) && name && strlen(name))
+ fprintf(fp, "%08lx %s\n", value, name);
+
+ return TRUE;
+}
+
+static int
+arm_is_module_addr(ulong vaddr)
+{
+ ulong modules_start;
+ ulong modules_end = machdep->kvbase - 1;
+
+ if (!MODULES_VADDR) {
+ /*
+ * In case we are still initializing, and vm_init() has not been
+ * called, we use defaults here which is 16MB below kernel start
+ * address.
+ */
+ modules_start = machdep->kvbase - 16 * 1024 * 1024;
+ } else {
+ modules_start = MODULES_VADDR;
+ }
+
+ return (vaddr >= modules_start && vaddr <= modules_end);
+}
+
+int
+arm_is_vmalloc_addr(ulong vaddr)
+{
+ if (arm_is_module_addr(vaddr))
+ return TRUE;
+
+ if (!VMALLOC_START)
+ return FALSE;
+
+ return (vaddr >= VMALLOC_START);
+}
+
+/*
+ * Check whether given address falls inside kernel address space (including
+ * modules).
+ */
+static int
+arm_is_kvaddr(ulong vaddr)
+{
+ if (arm_is_module_addr(vaddr))
+ return TRUE;
+
+ return (vaddr >= machdep->kvbase);
+}
+
+/*
+ * Returns TRUE if given pc is in exception area.
+ */
+static int
+arm_in_exception_text(ulong pc)
+{
+ ulong exception_start = machdep->machspec->exception_text_start;
+ ulong exception_end = machdep->machspec->exception_text_end;
+
+ if (exception_start && exception_end)
+ return (pc >= exception_start && pc < exception_end);
+
+ return FALSE;
+}
+
+/*
+ * Unroll a kernel stack.
+ */
+static void
+arm_back_trace_cmd(struct bt_info *bt)
+{
+ if (!(kt->flags & DWARF_UNWIND)) {
+ error(WARNING, "unwinding without unwind tables is not "
+ "supported yet!\n");
+ return;
+ }
+
+ if (!INSTACK(bt->stkptr, bt)) {
+ error(WARNING, "unwinding exception stack is not supported\n");
+ return;
+ }
+
+ unwind_backtrace(bt);
+}
+
+/*
+ * Calculate and return the speed of the processor.
+ */
+static ulong
+arm_processor_speed(void)
+{
+ /*
+ * For now, we don't support reading CPU speed.
+ */
+ return 0;
+}
+
+/*
+ * Translate a PTE, returning TRUE if the page is present. If a physaddr pointer
+ * is passed in, don't print anything.
+ */
+static int
+arm_translate_pte(ulong pte, void *physaddr, ulonglong pae_pte)
+{
+ char ptebuf[BUFSIZE];
+ char physbuf[BUFSIZE];
+ char buf[BUFSIZE];
+ int page_present;
+ ulong paddr;
+ int len1, len2, others;
+
+ page_present = pte_present(pte);
+ paddr = PAGEBASE(pte);
+
+ if (physaddr) {
+ *((ulong *)physaddr) = paddr;
+ return page_present;
+ }
+
+ sprintf(ptebuf, "%lx", pte);
+ len1 = MAX(strlen(ptebuf), strlen("PTE"));
+ fprintf(fp, "%s ", mkstring(buf, len1, CENTER | LJUST, "PTE"));
+
+ if (!page_present && pte) {
+ /* swap page, not handled yet */
+ return page_present;
+ }
+
+ sprintf(physbuf, "%lx", paddr);
+ len2 = MAX(strlen(physbuf), strlen("PHYSICAL"));
+ fprintf(fp, "%s ", mkstring(buf, len2, CENTER | LJUST, "PHYSICAL"));
+
+ fprintf(fp, "FLAGS\n");
+ fprintf(fp, "%s %s ",
+ mkstring(ptebuf, len1, CENTER | RJUST, NULL),
+ mkstring(physbuf, len2, CENTER | RJUST, NULL));
+
+ fprintf(fp, "(");
+ others = 0;
+
+ if (pte) {
+ if (pte_present(pte))
+ fprintf(fp, "%sPRESENT", others++ ? "|" : "");
+ if (pte_write(pte))
+ fprintf(fp, "%sWRITE", others++ ? "|" : "");
+ if (pte_dirty(pte))
+ fprintf(fp, "%sDIRTY", others++ ? "|" : "");
+ if (pte_young(pte))
+ fprintf(fp, "%sYOUNG", others++ ? "|" : "");
+ } else {
+ fprintf(fp, "no mapping");
+ }
+
+ fprintf(fp, ")\n");
+
+ return 0;
+}
+
+/*
+ * Virtual to physical memory translation. This function will be called by both
+ * arm_kvtop() and arm_uvtop().
+ */
+static int
+arm_vtop(ulong vaddr, ulong *pgd, physaddr_t *paddr, int verbose)
+{
+ char buf[BUFSIZE];
+ ulong *page_dir;
+ ulong *page_middle;
+ ulong *page_table;
+ ulong pgd_pte;
+ ulong pmd_pte;
+ ulong pte;
+
+ /*
+ * Page tables in ARM Linux
+ *
+ * In hardware PGD is 16k (having 4096 pointers to PTE) and PTE is 1k
+ * (containing 256 translations).
+ *
+ * Linux, however, wants to have PTEs as page sized entities. This means
+ * that in ARM Linux we have following setup (see also
+ * arch/arm/include/asm/pgtable.h)
+ *
+ * PGD PTE
+ * +---------+
+ * | | 0 ----> +------------+
+ * +- - - - -+ | h/w pt 0 |
+ * | | 4 ----> +------------+ +1024
+ * +- - - - -+ | h/w pt 1 |
+ * . . +------------+ +2048
+ * . . | Linux pt 0 |
+ * . . +------------+ +3072
+ * | | 4095 | Linux pt 1 |
+ * +---------+ +------------+ +4096
+ *
+ * So in Linux implementation we have two hardware pointers to second
+ * level page tables. After these come "Linux" versions of the page
+ * tables.
+ *
+ * Linux PT entries contain bits that are not supported on hardware, for
+ * example "young" and "dirty" flags.
+ *
+ * Our translation scheme only uses Linux PTEs here. Hardware entries
+ * are 1024 bytes below Linux versions.
+ */
+
+ if (verbose)
+ fprintf(fp, "PAGE DIRECTORY: %lx\n", (ulong)pgd);
+
+ /*
+ * pgd_offset(pgd, vaddr)
+ */
+ page_dir = pgd + PGD_OFFSET(vaddr) * 2;
+
+ FILL_PGD(PAGEBASE(pgd), KVADDR, PGDIR_SIZE());
+ pgd_pte = ULONG(machdep->pgd + PGDIR_OFFSET(page_dir));
+
+ if (verbose)
+ fprintf(fp, " PGD: %s => %lx\n",
+ mkstring(buf, VADDR_PRLEN, RJUST | LONG_HEX,
+ MKSTR((ulong)page_dir)), pgd_pte);
+
+ if (!pgd_pte)
+ return FALSE;
+
+ /*
+ * pmd_offset(pgd, vaddr)
+ *
+ * Here PMD is folded into a PGD.
+ */
+ pmd_pte = pgd_pte;
+ page_middle = page_dir;
+
+ if (verbose)
+ fprintf(fp, " PMD: %s => %lx\n",
+ mkstring(buf, VADDR_PRLEN, RJUST | LONG_HEX,
+ MKSTR((ulong)page_middle)), pmd_pte);
+
+ if ((pmd_pte & PMD_TYPE_MASK) == PMD_TYPE_SECT) {
+ if (verbose) {
+ fprintf(fp, " PAGE: %s (1MB)\n\n",
+ mkstring(buf, VADDR_PRLEN, RJUST | LONG_HEX,
+ MKSTR(PAGEBASE(pmd_pte))));
+ }
+ *paddr = PAGEBASE(pmd_pte) + (vaddr & ~_SECTION_PAGE_MASK);
+ return TRUE;
+ }
+
+ /*
+ * pte_offset_map(pmd, vaddr)
+ */
+ page_table = (ulong *)PTOV(pmd_page_addr(pmd_pte)) + PTE_OFFSET(vaddr);
+
+ FILL_PTBL(PAGEBASE(page_table), KVADDR, PAGESIZE());
+ pte = ULONG(machdep->ptbl + PAGEOFFSET(page_table));
+
+ if (verbose) {
+ fprintf(fp, " PTE: %s => %lx\n\n",
+ mkstring(buf, VADDR_PRLEN, RJUST | LONG_HEX,
+ MKSTR((ulong)page_table)), pte);
+ }
+
+ if (!pte_present(pte)) {
+ if (pte && verbose) {
+ fprintf(fp, "\n");
+ arm_translate_pte(pte, 0, 0);
+ }
+ return FALSE;
+ }
+
+ *paddr = PAGEBASE(pte) + PAGEOFFSET(vaddr);
+
+ if (verbose) {
+ fprintf(fp, " PAGE: %s\n\n",
+ mkstring(buf, VADDR_PRLEN, RJUST | LONG_HEX,
+ MKSTR(PAGEBASE(pte))));
+ arm_translate_pte(pte, 0, 0);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Translates a user virtual address to its physical address. cmd_vtop() sets
+ * the verbose flag so that the pte translation gets displayed; all other
+ * callers quietly accept the translation.
+ */
+static int
+arm_uvtop(struct task_context *tc, ulong uvaddr, physaddr_t *paddr, int verbose)
+{
+ ulong *pgd;
+ ulong mm;
+
+ if (!tc)
+ error(FATAL, "current context invalid\n");
+
+ *paddr = 0;
+
+ if (IS_KVADDR(uvaddr))
+ return arm_kvtop(tc, uvaddr, paddr, verbose);
+
+ mm = task_mm(tc->task, TRUE);
+ if (mm)
+ pgd = ULONG_PTR(tt->mm_struct + OFFSET(mm_struct_pgd));
+ else
+ readmem(tc->mm_struct + OFFSET(mm_struct_pgd), KVADDR, &pgd,
+ sizeof(long), "mm_struct pgd", FAULT_ON_ERROR);
+
+ return arm_vtop(uvaddr, pgd, paddr, verbose);
+}
+
+/*
+ * Translates a kernel virtual address to its physical address. cmd_vtop() sets
+ * the verbose flag so that the pte translation gets displayed; all other
+ * callers quietly accept the translation.
+ */
+static int
+arm_kvtop(struct task_context *tc, ulong kvaddr, physaddr_t *paddr, int verbose)
+{
+ if (!IS_KVADDR(kvaddr))
+ return FALSE;
+
+ if (!vt->vmalloc_start) {
+ *paddr = VTOP(kvaddr);
+ return TRUE;
+ }
+
+ if (!IS_VMALLOC_ADDR(kvaddr)) {
+ *paddr = VTOP(kvaddr);
+ if (!verbose)
+ return TRUE;
+ }
+
+ return arm_vtop(kvaddr, (ulong *)vt->kernel_pgd[0], paddr, verbose);
+}
+
+/*
+ * Get SP and PC values for idle tasks.
+ */
+static int
+arm_get_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
+{
+ const char *cpu_context;
+
+ if (!bt->tc || !(tt->flags & THREAD_INFO))
+ return FALSE;
+
+ /*
+ * Update thread_info in tt.
+ */
+ if (!fill_thread_info(bt->tc->thread_info))
+ return FALSE;
+
+ cpu_context = tt->thread_info + OFFSET(thread_info_cpu_context);
+
+#define GET_REG(ptr, cp, off) ((*ptr) = (*((ulong *)((cp) + OFFSET(off)))))
+ /*
+ * Unwinding code needs FP value also so we pass it with bt.
+ */
+ GET_REG(&bt->frameptr, cpu_context, cpu_context_save_fp);
+ GET_REG(spp, cpu_context, cpu_context_save_sp);
+ GET_REG(pcp, cpu_context, cpu_context_save_pc);
+
+ return TRUE;
+}
+
+/*
+ * Get the starting point for the active cpu in a diskdump.
+ *
+ * Note that we currently support only UP machines. In future we might want to
+ * support SMP machines as well.
+ */
+static int
+arm_get_dumpfile_stack_frame(struct bt_info *bt, ulong *nip, ulong *ksp)
+{
+ const struct machine_specific *ms = machdep->machspec;
+
+ if (!ms->crash_task_regs)
+ return FALSE;
+
+ if (tt->panic_task != bt->task || bt->tc->pid != ms->crash_task_pid)
+ return FALSE;
+
+ /*
+ * We got registers for panic task from crash_notes. Just return them.
+ */
+ *nip = ms->crash_task_regs->ARM_pc;
+ *ksp = ms->crash_task_regs->ARM_sp;
+
+ /*
+ * Also store pointer to all registers in case unwinding code needs
+ * to access LR.
+ */
+ bt->machdep = ms->crash_task_regs;
+
+ return TRUE;
+}
+
+/*
+ * Get a stack frame combination of PC and SP from the most relevant spot.
+ */
+static void
+arm_get_stack_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
+{
+ ulong ip, sp;
+ int ret;
+
+ ip = sp = 0;
+ bt->machdep = NULL;
+
+ if (DUMPFILE() && is_task_active(bt->task))
+ ret = arm_get_dumpfile_stack_frame(bt, &ip, &sp);
+ else
+ ret = arm_get_frame(bt, &ip, &sp);
+
+ if (!ret) {
+ error(WARNING, "cannot get stackframe for task\n");
+ return;
+ }
+
+ if (pcp)
+ *pcp = ip;
+ if (spp)
+ *spp = sp;
+}
+
+/*
+ * Prints out exception stack starting from start.
+ */
+static void
+arm_dump_exception_stack(ulong start, ulong end)
+{
+ struct arm_pt_regs regs;
+ ulong flags;
+ char buf[64];
+
+ if (!readmem(start, KVADDR, ®s, sizeof(regs),
+ "exception regs", RETURN_ON_ERROR)) {
+ error(WARNING, "failed to read exception registers\n");
+ return;
+ }
+
+ fprintf(fp, " pc : [<%08lx>] lr : [<%08lx>] psr: %08lx\n"
+ " sp : %08lx ip : %08lx fp : %08lx\n",
+ regs.ARM_pc, regs.ARM_lr, regs.ARM_cpsr,
+ regs.ARM_sp, regs.ARM_ip, regs.ARM_fp);
+ fprintf(fp, " r10: %08lx r9 : %08lx r8 : %08lx\n",
+ regs.ARM_r10, regs.ARM_r9, regs.ARM_r8);
+ fprintf(fp, " r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx\n",
+ regs.ARM_r7, regs.ARM_r6,
+ regs.ARM_r5, regs.ARM_r4);
+ fprintf(fp, " r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx\n",
+ regs.ARM_r3, regs.ARM_r2,
+ regs.ARM_r1, regs.ARM_r0);
+
+ flags = regs.ARM_cpsr;
+ buf[0] = flags & PSR_N_BIT ? 'N' : 'n';
+ buf[1] = flags & PSR_Z_BIT ? 'Z' : 'z';
+ buf[2] = flags & PSR_C_BIT ? 'C' : 'c';
+ buf[3] = flags & PSR_V_BIT ? 'V' : 'v';
+ buf[4] = '\0';
+
+ fprintf(fp, " Flags: %s IRQs o%s FIQs o%s Mode %s ISA %s\n",
+ buf, interrupts_enabled(®s) ? "n" : "ff",
+ fast_interrupts_enabled(®s) ? "n" : "ff",
+ processor_modes[processor_mode(®s)],
+ isa_modes[isa_mode(®s)]);
+}
+
+/*
+ * Pretty prints a single stack frame.
+ */
+void
+arm_dump_backtrace_entry(struct bt_info *bt, int level, ulong where,
+ ulong from, ulong frame)
+{
+ struct load_module *lm;
+ const char *name;
+
+ name = closest_symbol(where);
+
+ if (module_symbol(where, NULL, &lm, NULL, 0)) {
+ fprintf(fp, "%s#%d [<%08lx>] (%s [%s]) from [<%08lx>]\n",
+ level < 10 ? " " : "",
+ level, where, name, lm->mod_name, from);
+ } else {
+ fprintf(fp, "%s#%d [<%08lx>] (%s) from [<%08lx>]\n",
+ level < 10 ? " " : "",
+ level, where, name, from);
+ }
+
+ if (bt->flags & BT_LINE_NUMBERS) {
+ char buf[BUFSIZE];
+
+ get_line_number(where, buf, FALSE);
+ if (strlen(buf))
+ fprintf(fp, " %s\n", buf);
+ }
+
+ if (arm_in_exception_text(where)) {
+ ulong frame_start = frame + 4;
+ ulong frame_end = frame_start + sizeof(struct arm_pt_regs);
+
+ arm_dump_exception_stack(frame_start, frame_end);
+ }
+}
+
+/*
+ * Determine where vmalloc'd memory starts.
+ */
+static ulong
+arm_vmalloc_start(void)
+{
+ return vt->high_memory;
+}
+
+/*
+ * Checks whether given task is valid task address.
+ */
+static int
+arm_is_task_addr(ulong task)
+{
+ if (tt->flags & THREAD_INFO)
+ return IS_KVADDR(task);
+
+ return (IS_KVADDR(task) && ALIGNED_STACK_OFFSET(task) == 0);
+}
+
+/*
+ * Filter dissassembly output if the output radix is not gdb's default 10
+ */
+static int
+arm_dis_filter(ulong vaddr, char *inbuf)
+{
+ char buf1[BUFSIZE];
+ char buf2[BUFSIZE];
+ char *colon, *p1;
+ int argc;
+ char *argv[MAXARGS];
+ ulong value;
+
+ if (!inbuf)
+ return TRUE;
+/*
+ * For some reason gdb can go off into the weeds translating text addresses,
+ * (on alpha -- not necessarily seen on arm) so this routine both fixes the
+ * references as well as imposing the current output radix on the translations.
+ */
+ console("IN: %s", inbuf);
+
+ colon = strstr(inbuf, ":");
+
+ if (colon) {
+ sprintf(buf1, "0x%lx <%s>", vaddr,
+ value_to_symstr(vaddr, buf2, pc->output_radix));
+ sprintf(buf2, "%s%s", buf1, colon);
+ strcpy(inbuf, buf2);
+ }
+
+ strcpy(buf1, inbuf);
+ argc = parse_line(buf1, argv);
+
+ if ((FIRSTCHAR(argv[argc-1]) == '<') &&
+ (LASTCHAR(argv[argc-1]) == '>')) {
+ p1 = rindex(inbuf, '<');
+ while ((p1 > inbuf) && !STRNEQ(p1, " 0x"))
+ p1--;
+
+ if (!STRNEQ(p1, " 0x"))
+ return FALSE;
+ p1++;
+
+ if (!extract_hex(p1, &value, NULLCHAR, TRUE))
+ return FALSE;
+
+ sprintf(buf1, "0x%lx <%s>\n", value,
+ value_to_symstr(value, buf2, pc->output_radix));
+
+ sprintf(p1, buf1);
+ }
+
+ console(" %s", inbuf);
+
+ return TRUE;
+}
+
+/*
+ * Look for likely exception frames in a stack.
+ */
+static int
+arm_eframe_search(struct bt_info *bt)
+{
+ return (NOT_IMPLEMENTED());
+}
+
+/*
+ * Get the relevant page directory pointer from a task structure.
+ */
+static ulong
+arm_get_task_pgd(ulong task)
+{
+ return (NOT_IMPLEMENTED());
+}
+
+/*
+ * Machine dependent command.
+ */
+static void
+arm_cmd_mach(void)
+{
+ int c;
+
+ while ((c = getopt(argcnt, args, "cm")) != -1) {
+ switch (c) {
+ case 'c':
+ case 'm':
+ fprintf(fp, "ARM: '-%c' option is not supported\n", c);
+ break;
+
+ default:
+ argerrs++;
+ break;
+ }
+ }
+
+ if (argerrs)
+ cmd_usage(pc->curcmd, SYNOPSIS);
+
+ arm_display_machine_stats();
+}
+
+static void
+arm_display_machine_stats(void)
+{
+ struct new_utsname *uts;
+ char buf[BUFSIZE];
+ ulong mhz;
+
+ uts = &kt->utsname;
+
+ fprintf(fp, " MACHINE TYPE: %s\n", uts->machine);
+ fprintf(fp, " MEMORY SIZE: %s\n", get_memory_size(buf));
+ fprintf(fp, " CPUS: %d\n", get_cpus_to_display());
+ fprintf(fp, " PROCESSOR SPEED: ");
+ if ((mhz = machdep->processor_speed()))
+ fprintf(fp, "%ld Mhz\n", mhz);
+ else
+ fprintf(fp, "(unknown)\n");
+ fprintf(fp, " HZ: %d\n", machdep->hz);
+ fprintf(fp, " PAGE SIZE: %d\n", PAGESIZE());
+ fprintf(fp, "KERNEL VIRTUAL BASE: %lx\n", machdep->kvbase);
+ fprintf(fp, "KERNEL VMALLOC BASE: %lx\n", vt->vmalloc_start);
+ fprintf(fp, " KERNEL STACK SIZE: %ld\n", STACKSIZE());
+}
+
+static int
+arm_get_smp_cpus(void)
+{
+ return get_cpus_online();
+}
+
+/*
+ * Initialize ARM specific stuff.
+ */
+static void
+arm_init_machspec(void)
+{
+ struct machine_specific *ms = machdep->machspec;
+ ulong phys_base;
+
+ if (!DISKDUMP_DUMPFILE())
+ error(FATAL, "Only diskdump format is currently supported!\n");
+
+ /*
+ * First determine actual phys_base. This was set in place by
+ * makedumpfile so we can just read what diskdump gives us.
+ */
+ if (!diskdump_phys_base(&phys_base))
+ error(FATAL, "Cannot determine phys_base\n");
+
+ ms->phys_base = phys_base;
+
+ if (symbol_exists("__exception_text_start") &&
+ symbol_exists("__exception_text_end")) {
+ ms->exception_text_start = symbol_value("__exception_text_start");
+ ms->exception_text_end = symbol_value("__exception_text_end");
+ }
+
+ if (symbol_exists("_stext") && symbol_exists("_etext")) {
+ ms->kernel_text_start = symbol_value("_stext");
+ ms->kernel_text_end = symbol_value("_etext");
+ }
+
+ if (CRASHDEBUG(1)) {
+ fprintf(fp, "compressed kdump: phys_base: %lx\n",
+ phys_base);
+ fprintf(fp, "kernel text: [%lx - %lx]\n",
+ ms->kernel_text_start, ms->kernel_text_end);
+ fprintf(fp, "exception text: [%lx - %lx]\n",
+ ms->exception_text_start, ms->exception_text_end);
+ }
+}
+
+static const char *hook_files[] = {
+ "arch/arm/kernel/entry-armv.S",
+ "arch/arm/kernel/entry-common.S",
+};
+
+#define ENTRY_ARMV_S ((char **)&hook_files[0])
+#define ENTRY_COMMON_S ((char **)&hook_files[1])
+
+static struct line_number_hook arm_line_number_hooks[] = {
+ { "__dabt_svc", ENTRY_ARMV_S },
+ { "__irq_svc", ENTRY_ARMV_S },
+ { "__und_svc", ENTRY_ARMV_S },
+ { "__pabt_svc", ENTRY_ARMV_S },
+ { "__switch_to", ENTRY_ARMV_S },
+
+ { "ret_fast_syscall", ENTRY_COMMON_S },
+ { "ret_slow_syscall", ENTRY_COMMON_S },
+ { "ret_from_fork", ENTRY_COMMON_S },
+ { NULL, NULL },
+};
+#endif /* ARM */
diff --git a/defs.h b/defs.h
index bd8d492..0f63539 100644
--- a/defs.h
+++ b/defs.h
@@ -84,6 +84,9 @@
#ifdef S390X
#define NR_CPUS (64)
#endif
+#ifdef ARM
+#define NR_CPUS (1)
+#endif
#define BUFSIZE (1500)
#define NULLCHAR ('\0')
@@ -976,6 +979,7 @@ struct offset_table { /* stash of commonly-used offsets */
long thread_info_cpu;
long thread_info_previous_esp;
long thread_info_flags;
+ long thread_info_cpu_context;
long nsproxy_mnt_ns;
long mnt_namespace_root;
long mnt_namespace_list;
@@ -1427,6 +1431,13 @@ struct offset_table { /* stash of commonly-used offsets */
long unwind_table_size;
long unwind_table_link;
long unwind_table_name;
+ long unwind_table_list;
+ long unwind_table_start;
+ long unwind_table_stop;
+ long unwind_table_begin_addr;
+ long unwind_table_end_addr;
+ long unwind_idx_addr;
+ long unwind_idx_insn;
long rq_cfs;
long rq_rt;
long rq_nr_running;
@@ -1504,6 +1515,11 @@ struct offset_table { /* stash of commonly-used offsets */
long mm_rss_stat_count;
long module_module_init;
long module_init_text_size;
+ long cpu_context_save_fp;
+ long cpu_context_save_sp;
+ long cpu_context_save_pc;
+ long elf_prstatus_pr_pid;
+ long elf_prstatus_pr_reg;
};
struct size_table { /* stash of commonly-used sizes */
@@ -1601,6 +1617,7 @@ struct size_table { /* stash of commonly-used sizes */
long mem_section;
long pid_link;
long unwind_table;
+ long unwind_idx;
long rlimit;
long kmem_cache;
long kmem_cache_node;
@@ -1616,6 +1633,9 @@ struct size_table { /* stash of commonly-used sizes */
long module_sect_attr;
long task_struct_utime;
long task_struct_stime;
+ long cpu_context_save;
+ long note_buf;
+ long elf_prstatus;
};
struct array_table {
@@ -2090,6 +2110,49 @@ struct load_module {
* Machine specific stuff
*/
+#ifdef ARM
+#define _32BIT_
+#define MACHINE_TYPE "ARM"
+
+#define PAGEBASE(X) (((ulong)(X)) & (ulong)machdep->pagemask)
+
+#define PTOV(X) \
+ ((unsigned long)(X)-(machdep->machspec->phys_base)+(machdep->kvbase))
+#define VTOP(X) \
+ ((unsigned long)(X)-(machdep->kvbase)+(machdep->machspec->phys_base))
+
+#define IS_VMALLOC_ADDR(X) arm_is_vmalloc_addr((ulong)(X))
+
+#define MODULES_VADDR (machdep->machspec->modules_vaddr)
+#define MODULES_END (machdep->machspec->modules_end)
+#define VMALLOC_START (machdep->machspec->vmalloc_start_addr)
+#define VMALLOC_END (machdep->machspec->vmalloc_end)
+
+#define PGDIR_SHIFT (21)
+#define PTRS_PER_PTE (512)
+#define PTRS_PER_PGD (2048)
+
+#define PGD_OFFSET(vaddr) ((vaddr) >> PGDIR_SHIFT)
+#define PTE_OFFSET(vaddr) (((vaddr) >> PAGESHIFT()) & (PTRS_PER_PTE - 1))
+
+#define __SWP_TYPE_SHIFT 3
+#define __SWP_TYPE_BITS 6
+#define __SWP_TYPE_MASK ((1 << __SWP_TYPE_BITS) - 1)
+#define __SWP_OFFSET_SHIFT (__SWP_TYPE_BITS + __SWP_TYPE_SHIFT)
+
+#define SWP_TYPE(entry) (((entry) >> __SWP_TYPE_SHIFT) & __SWP_TYPE_MASK)
+#define SWP_OFFSET(entry) ((entry) >> __SWP_OFFSET_SHIFT)
+
+#define __swp_type(entry) SWP_TYPE(entry)
+#define __swp_offset(entry) SWP_OFFSET(entry)
+
+#define TIF_SIGPENDING (2)
+
+#define _SECTION_SIZE_BITS 28
+#define _MAX_PHYSMEM_BITS 32
+
+#endif /* ARM */
+
#ifdef X86
#define _32BIT_
#define MACHINE_TYPE "X86"
@@ -2786,6 +2849,10 @@ struct efi_memory_desc_t {
#define SIZEOF_16BIT (2)
#define SIZEOF_8BIT (1)
+#ifdef ARM
+#define MAX_HEXADDR_STRLEN (8)
+#define UVADDR_PRLEN (8)
+#endif
#ifdef X86
#define MAX_HEXADDR_STRLEN (8)
#define UVADDR_PRLEN (8)
@@ -2873,6 +2940,13 @@ struct efi_memory_desc_t {
#define IRQ_LEVEL 64 /* IRQ level triggered */
#define IRQ_MASKED 128 /* IRQ masked - shouldn't be seen again */
+#ifdef ARM
+#define SA_PROBE SA_ONESHOT
+#define SA_SAMPLE_RANDOM SA_RESTART
+#define SA_SHIRQ 0x04000000
+#define SA_RESTORER 0x04000000
+#endif
+
#ifdef X86
#define SA_PROBE SA_ONESHOT
#define SA_SAMPLE_RANDOM SA_RESTART
@@ -3203,6 +3277,9 @@ void program_usage(int);
#define SHORT_FORM (0)
void dump_program_context(void);
void dump_build_data(void);
+#ifdef ARM
+#define machdep_init(X) arm_init(X)
+#endif
#ifdef X86
#define machdep_init(X) x86_init(X)
#endif
@@ -3561,6 +3638,9 @@ void help_init(void);
void cmd_usage(char *, int);
void display_version(void);
void display_help_screen(char *);
+#ifdef ARM
+#define dump_machdep_table(X) arm_dump_machdep_table(X)
+#endif
#ifdef X86
#define dump_machdep_table(X) x86_dump_machdep_table(X)
#endif
@@ -3839,6 +3919,57 @@ void read_in_kernel_config(int);
void dev_init(void);
void dump_dev_table(void);
+#ifdef ARM
+void arm_init(int);
+void arm_dump_machdep_table(ulong);
+void arm_display_idt_table(void);
+int arm_is_vmalloc_addr(ulong);
+void arm_dump_backtrace_entry(struct bt_info *, int, ulong, ulong, ulong);
+#define display_idt_table() \
+ error(FATAL, "-d option is not applicable to ARM architecture\n")
+
+struct arm_pt_regs {
+ ulong uregs[18];
+};
+
+#define ARM_cpsr uregs[16]
+#define ARM_pc uregs[15]
+#define ARM_lr uregs[14]
+#define ARM_sp uregs[13]
+#define ARM_ip uregs[12]
+#define ARM_fp uregs[11]
+#define ARM_r10 uregs[10]
+#define ARM_r9 uregs[9]
+#define ARM_r8 uregs[8]
+#define ARM_r7 uregs[7]
+#define ARM_r6 uregs[6]
+#define ARM_r5 uregs[5]
+#define ARM_r4 uregs[4]
+#define ARM_r3 uregs[3]
+#define ARM_r2 uregs[2]
+#define ARM_r1 uregs[1]
+#define ARM_r0 uregs[0]
+#define ARM_ORIG_r0 uregs[17]
+
+#define KSYMS_START (0x1)
+
+struct machine_specific {
+ ulong phys_base;
+ ulong vmalloc_start_addr;
+ ulong modules_vaddr;
+ ulong modules_end;
+ ulong kernel_text_start;
+ ulong kernel_text_end;
+ ulong exception_text_start;
+ ulong exception_text_end;
+ ulong crash_task_pid;
+ struct arm_pt_regs *crash_task_regs;
+};
+
+int init_unwind_tables(void);
+void unwind_backtrace(struct bt_info *);
+#endif /* ARM */
+
/*
* alpha.c
*/
diff --git a/unwind_arm.c b/unwind_arm.c
new file mode 100644
index 0000000..f3497a8
--- /dev/null
+++ b/unwind_arm.c
@@ -0,0 +1,702 @@
+/*
+ * Stack unwinding support for ARM
+ *
+ * This code is derived from the kernel source:
+ * arch/arm/kernel/unwind.c
+ * Copyright (C) 2008 ARM Limited
+ *
+ * Created by: Mika Westerberg <ext-mika.1.westerberg at nokia.com>
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * For more information about ARM unwind tables see "Exception handling ABI for
+ * the ARM architecture" document at:
+ *
+ * http://infocenter.arm.com/help/topic/com.arm.doc.subset.swdev.abi/index.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifdef ARM
+
+#include "defs.h"
+
+/**
+ * struct unwind_idx - index table entry
+ * @addr: prel31 offset to the start of the function
+ * @insn: index table entry.
+ *
+ * @insn can be encoded as follows:
+ * 1. if bit31 is clear this points to the start of the EHT entry
+ * (prel31 offset)
+ * 2. if bit31 is set, this contains the EHT entry itself
+ * 3. if 0x1, cannot unwind.
+ *
+ * In case 1. @insn points to the EH table that comes directly after index
+ * table. This offset is relative to address of @insn which implies that we must
+ * allocate both index table and EH table in single chunk.
+ */
+struct unwind_idx {
+ ulong addr;
+ ulong insn;
+};
+
+/**
+ * struct unwind_table - per-module unwind table
+ * @idx: pointer to the star of the unwind table
+ * @start: pointer to the start of the index table
+ * @end: pointer to the last element +1 of the index table
+ * @begin_addr: start address which this table covers
+ * @end_addr: end address which this table covers
+ *
+ * Kernel stores per-module unwind tables in this format. There can be more than
+ * one table per module as we have different ELF sections in the module.
+ */
+struct unwind_table {
+ struct unwind_idx *idx;
+ struct unwind_idx *start;
+ struct unwind_idx *end;
+ ulong begin_addr;
+ ulong end_addr;
+};
+
+/*
+ * Unwind table pointers to master kernel table and for modules.
+ */
+static struct unwind_table *kernel_unwind_table;
+static struct unwind_table *module_unwind_tables;
+
+struct unwind_ctrl_block {
+ ulong vrs[16];
+ ulong *insn;
+ int entries;
+ int byte;
+};
+
+enum regs {
+ FP = 11,
+ SP = 13,
+ LR = 14,
+ PC = 15,
+};
+
+struct stackframe {
+ ulong fp;
+ ulong sp;
+ ulong lr;
+ ulong pc;
+};
+
+static int init_kernel_unwind_table(void);
+static void free_kernel_unwind_table(void);
+static int read_module_unwind_table(struct unwind_table *, ulong);
+static int init_module_unwind_tables(void);
+static ulong unwind_get_byte(struct unwind_ctrl_block *);
+static ulong get_value_from_stack(ulong *);
+static int unwind_exec_insn(struct unwind_ctrl_block *);
+static int is_core_kernel_text(ulong);
+static struct unwind_idx *search_index(ulong);
+static ulong *prel31_to_addr(ulong *);
+static int unwind_frame(struct stackframe *, ulong);
+
+/*
+ * Function reads in-memory kernel and module unwind tables and makes
+ * local copy of them for unwinding. If unwinding tables cannot be found, this
+ * function returns FALSE, otherwise TRUE.
+ */
+int
+init_unwind_tables(void)
+{
+ if (!symbol_exists("__start_unwind_idx") ||
+ !symbol_exists("__stop_unwind_idx") ||
+ !symbol_exists("__start_unwind_tab") ||
+ !symbol_exists("__stop_unwind_tab") ||
+ !symbol_exists("unwind_tables")) {
+ return FALSE;
+ }
+
+ if (!init_kernel_unwind_table()) {
+ error(WARNING,
+ "UNWIND: failed to initialize kernel unwind table\n");
+ return FALSE;
+ }
+
+ /*
+ * Initialize symbols for per-module unwind tables. Actually there are
+ * several tables per module (one per code section).
+ */
+ STRUCT_SIZE_INIT(unwind_table, "unwind_table");
+ MEMBER_OFFSET_INIT(unwind_table_list, "unwind_table", "list");
+ MEMBER_OFFSET_INIT(unwind_table_start, "unwind_table", "start");
+ MEMBER_OFFSET_INIT(unwind_table_stop, "unwind_table", "stop");
+ MEMBER_OFFSET_INIT(unwind_table_begin_addr, "unwind_table",
+ "begin_addr");
+ MEMBER_OFFSET_INIT(unwind_table_end_addr, "unwind_table", "end_addr");
+
+ STRUCT_SIZE_INIT(unwind_idx, "unwind_idx");
+ MEMBER_OFFSET_INIT(unwind_idx_addr, "unwind_idx", "addr");
+ MEMBER_OFFSET_INIT(unwind_idx_insn, "unwind_idx", "insn");
+
+ if (!init_module_unwind_tables()) {
+ error(WARNING,
+ "UNWIND: failed to initialize module unwind tables\n");
+ free_kernel_unwind_table();
+ return FALSE;
+ }
+
+ /*
+ * We abuse DWARF_UNWIND flag a little here as ARM unwinding tables are
+ * not in DWARF format but we can use the flags to indicate that we have
+ * unwind tables support ready.
+ */
+ kt->flags |= DWARF_UNWIND_CAPABLE;
+ kt->flags |= DWARF_UNWIND;
+
+ return TRUE;
+}
+
+/*
+ * Allocate and fill master kernel unwind table.
+ */
+static int
+init_kernel_unwind_table(void)
+{
+ ulong idx_start, idx_end, idx_size;
+ ulong tab_end, tab_size;
+
+ kernel_unwind_table = calloc(sizeof(*kernel_unwind_table), 1);
+ if (!kernel_unwind_table)
+ return FALSE;
+
+ idx_start = symbol_value("__start_unwind_idx");
+ idx_end = symbol_value("__stop_unwind_idx");
+ tab_end = symbol_value("__stop_unwind_tab");
+
+ /*
+ * Calculate sizes of the idx table and the EH table.
+ */
+ idx_size = idx_end - idx_start;
+ tab_size = tab_end - idx_start;
+
+ kernel_unwind_table->idx = calloc(tab_size, 1);
+ if (!kernel_unwind_table->idx)
+ goto fail;
+
+ /*
+ * Now read in both the index table and the EH table. We need to read in
+ * both because prel31 offsets in the index table are relative to the
+ * index address.
+ */
+ if (!readmem(idx_start, KVADDR, kernel_unwind_table->idx, tab_size,
+ "master kernel unwind table", RETURN_ON_ERROR))
+ goto fail;
+
+ kernel_unwind_table->start = kernel_unwind_table->idx;
+ kernel_unwind_table->end = (struct unwind_idx *)
+ ((char *)kernel_unwind_table->idx + idx_size);
+ kernel_unwind_table->begin_addr = kernel_unwind_table->start->addr;
+ kernel_unwind_table->end_addr = (kernel_unwind_table->end - 1)->addr;
+
+ if (CRASHDEBUG(1)) {
+ fprintf(fp, "UNWIND: master kernel table start\n");
+ fprintf(fp, "UNWIND: size : %ld\n", tab_size);
+ fprintf(fp, "UNWIND: start : %p\n", kernel_unwind_table->start);
+ fprintf(fp, "UNWIND: end : %p\n", kernel_unwind_table->end);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx\n",
+ kernel_unwind_table->begin_addr);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx\n",
+ kernel_unwind_table->end_addr);
+ fprintf(fp, "UNWIND: master kernel table end\n");
+ }
+
+ return TRUE;
+
+fail:
+ free(kernel_unwind_table->idx);
+ free(kernel_unwind_table);
+ return FALSE;
+}
+
+static void
+free_kernel_unwind_table(void)
+{
+ free(kernel_unwind_table->idx);
+ free(kernel_unwind_table);
+}
+
+/*
+ * Read single module unwind table from addr.
+ */
+static int
+read_module_unwind_table(struct unwind_table *tbl, ulong addr)
+{
+ ulong idx_start, idx_stop, idx_size;
+ char *buf;
+
+ buf = GETBUF(SIZE(unwind_table));
+
+ /*
+ * First read in the unwind table for this module. It then contains
+ * pointers to the index table which we will read later.
+ */
+ if (!readmem(addr, KVADDR, buf, SIZE(unwind_table),
+ "module unwind table", RETURN_ON_ERROR)) {
+ error(WARNING, "UNWIND: cannot read unwind table\n");
+ goto fail;
+ }
+
+#define TABLE_VALUE(b, offs) (*((ulong *)((b) + OFFSET(offs))))
+
+ idx_start = TABLE_VALUE(buf, unwind_table_start);
+ idx_stop = TABLE_VALUE(buf, unwind_table_stop);
+ idx_size = idx_stop - idx_start;
+
+ /*
+ * We know the size of the index table. Allocate memory for the table
+ * (including the EH table) and read the contents from the kernel
+ * memory.
+ */
+ tbl->idx = calloc(idx_size, 1);
+ if (!tbl->idx)
+ goto fail;
+
+ if (!readmem(idx_start, KVADDR, tbl->idx, idx_size,
+ "module unwind index table", RETURN_ON_ERROR)) {
+ free(tbl->idx);
+ goto fail;
+ }
+
+ tbl->start = &tbl->idx[0];
+ tbl->end = (struct unwind_idx *)((char *)tbl->start + idx_size);
+ tbl->begin_addr = TABLE_VALUE(buf, unwind_table_begin_addr);
+ tbl->end_addr = TABLE_VALUE(buf, unwind_table_end_addr);
+
+ if (CRASHDEBUG(1)) {
+ fprintf(fp, "UNWIND: module table start\n");
+ fprintf(fp, "UNWIND: start : %p\n", tbl->start);
+ fprintf(fp, "UNWIND: end : %p\n", tbl->end);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx\n", tbl->begin_addr);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx\n", tbl->end_addr);
+ fprintf(fp, "UNWIND: module table end\n");
+ }
+
+ FREEBUF(buf);
+ return TRUE;
+
+fail:
+ FREEBUF(buf);
+ free(tbl->idx);
+ return FALSE;
+}
+
+/*
+ * Allocate and fill per-module unwind tables.
+ */
+static int
+init_module_unwind_tables(void)
+{
+ ulong head = symbol_value("unwind_tables");
+ struct unwind_table *tbl;
+ struct list_data ld;
+ ulong *table_list;
+ int cnt, i, n;
+
+ BZERO(&ld, sizeof(ld));
+ ld.start = head;
+ ld.member_offset = OFFSET(unwind_table_list);
+
+ if (CRASHDEBUG(1))
+ ld.flags |= VERBOSE;
+
+ /*
+ * Iterate through unwind table list and store start address of each
+ * table in table_list.
+ */
+ hq_open();
+ cnt = do_list(&ld);
+ table_list = (ulong *)GETBUF(cnt * sizeof(ulong));
+ cnt = retrieve_list(table_list, cnt);
+ hq_close();
+
+ module_unwind_tables = calloc(sizeof(struct unwind_table), cnt);
+ if (!module_unwind_tables) {
+ error(WARNING,
+ "UNWIND: failed to allocate memory for (%d tables)\n",
+ cnt);
+ FREEBUF(table_list);
+ return FALSE;
+ }
+
+ /* we skip the first address as it is just head pointer */
+ for (i = 1, n = 0; i < cnt; i++, n++) {
+ tbl = &module_unwind_tables[n];
+ if (!read_module_unwind_table(tbl, table_list[i]))
+ goto fail;
+ }
+
+ /* just in case, zero the last entry (again) */
+ BZERO(&module_unwind_tables[n], sizeof(module_unwind_tables[n]));
+
+ FREEBUF(table_list);
+ return TRUE;
+
+fail:
+ FREEBUF(table_list);
+
+ while (--n >= 0) {
+ tbl = &module_unwind_tables[n];
+ free(tbl->idx);
+ }
+
+ free(module_unwind_tables);
+ return FALSE;
+}
+
+/*
+ * Return next insn byte from ctl or 0 in case of failure. As a side-effect,
+ * changes ctrl according the next byte.
+ */
+static ulong
+unwind_get_byte(struct unwind_ctrl_block *ctrl)
+{
+ ulong ret;
+
+ if (ctrl->entries <= 0) {
+ error(WARNING, "UNWIND: corrupt unwind entry\n");
+ return 0;
+ }
+
+ ret = (*ctrl->insn >> (ctrl->byte * 8)) & 0xff;
+
+ if (!ctrl->byte) {
+ ctrl->insn++;
+ ctrl->entries--;
+ ctrl->byte = 3;
+ } else {
+ ctrl->byte--;
+ }
+
+ return ret;
+}
+
+/*
+ * Gets one value from stack pointed by vsp.
+ */
+static ulong
+get_value_from_stack(ulong *vsp)
+{
+ ulong val;
+
+ /*
+ * We just read the value from kernel memory instead of peeking it from
+ * the bt->stack.
+ */
+ if (!readmem((ulong)vsp, KVADDR, &val, sizeof(val),
+ "unwind stack value", RETURN_ON_ERROR)) {
+ error(FATAL, "unwind: failed to read value from stack\n");
+ }
+
+ return val;
+}
+
+/*
+ * Execute the next unwind instruction.
+ */
+static int
+unwind_exec_insn(struct unwind_ctrl_block *ctrl)
+{
+ ulong insn = unwind_get_byte(ctrl);
+
+ if ((insn & 0xc0) == 0) {
+ /*
+ * 00xx xxxx: vsp = vsp + (xx xxx << 2) + 4
+ *
+ * Note that it seems that there is a typo in the spec and this
+ * is corrected in kernel.
+ */
+ ctrl->vrs[SP] += ((insn & 0x3f) << 2) + 4;
+ } else if ((insn & 0xc0) == 0x40) {
+ /* 00xx xxxx: vsp = vsp + (xx xxx << 2) + 4 */
+ ctrl->vrs[SP] -= ((insn & 0x3f) << 2) + 4;
+ } else if ((insn & 0xf0) == 0x80) {
+ /*
+ * Pop up to 12 integer registers under masks
+ * {r15-r12}, {r11-r4}.
+ */
+ ulong mask;
+ ulong *vsp = (ulong *)ctrl->vrs[SP];
+ int load_sp, reg = 4;
+
+ insn = (insn << 8) | unwind_get_byte(ctrl);
+ mask = insn & 0x0fff;
+ if (mask == 0) {
+ error(WARNING, "UNWIND: refuse to unwind\n");
+ return FALSE;
+ }
+
+ /* pop {r4-r15} according to mask */
+ load_sp = mask & (1 << (13 - 4));
+ while (mask) {
+ if (mask & 1)
+ ctrl->vrs[reg] = get_value_from_stack(vsp++);
+ mask >>= 1;
+ reg++;
+ }
+ if (!load_sp)
+ ctrl->vrs[SP] = (ulong)vsp;
+ } else if ((insn & 0xf0) == 0x90 &&
+ (insn & 0x0d) != 0x0d) {
+ /* 1001 nnnn: set vsp = r[nnnn] */
+ ctrl->vrs[SP] = ctrl->vrs[insn & 0x0f];
+ } else if ((insn & 0xf0) == 0xa0) {
+ /*
+ * 1010 0nnn: pop r4-r[4+nnn]
+ * 1010 1nnn: pop r4-r[4+nnn], r14
+ */
+ ulong *vsp = (ulong *)ctrl->vrs[SP];
+ int reg;
+
+ for (reg = 4; reg <= 4 + (insn & 7); reg++)
+ ctrl->vrs[reg] = get_value_from_stack(vsp++);
+
+ if (insn & 0x80)
+ ctrl->vrs[14] = get_value_from_stack(vsp++);
+
+ ctrl->vrs[SP] = (ulong)vsp;
+ } else if (insn == 0xb0) {
+ /* 1011 0000: finish */
+ if (ctrl->vrs[PC] == 0)
+ ctrl->vrs[PC] = ctrl->vrs[LR];
+ /* no further processing */
+ ctrl->entries = 0;
+ } else if (insn == 0xb1) {
+ /* 1011 0001 xxxx yyyy: spare */
+ ulong mask = unwind_get_byte(ctrl);
+ ulong *vsp = (ulong *)ctrl->vrs[SP];
+ int reg = 0;
+
+ if (mask == 0 || mask & 0xf0) {
+ error(WARNING, "UNWIND: spare error\n");
+ return FALSE;
+ }
+
+ /* pop r0-r3 according to mask */
+ while (mask) {
+ if (mask & 1)
+ ctrl->vrs[reg] = get_value_from_stack(vsp++);
+ mask >>= 1;
+ reg++;
+ }
+ ctrl->vrs[SP] = (ulong)vsp;
+ } else if (insn == 0xb2) {
+ /* 1011 0010 uleb128: vsp = vsp + 0x204 (uleb128 << 2) */
+ ulong uleb128 = unwind_get_byte(ctrl);
+
+ ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
+ } else {
+ error(WARNING, "UNWIND: unhandled instruction: %02lx\n", insn);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+is_core_kernel_text(ulong pc)
+{
+ ulong text_start = machdep->machspec->kernel_text_start;
+ ulong text_end = machdep->machspec->kernel_text_end;
+
+ if (text_start && text_end)
+ return (pc >= text_start && pc <= text_end);
+
+ return FALSE;
+}
+
+static struct unwind_idx *
+search_index(ulong ip)
+{
+ struct unwind_idx *start = NULL;
+ struct unwind_idx *end = NULL;
+
+ /*
+ * First check if this address is in the master kernel unwind table or
+ * some of the module unwind tables.
+ */
+ if (is_core_kernel_text(ip)) {
+ start = kernel_unwind_table->start;
+ end = kernel_unwind_table->end;
+ } else {
+ struct unwind_table *tbl;
+
+ for (tbl = &module_unwind_tables[0]; tbl->idx; tbl++) {
+ if (ip >= tbl->begin_addr && ip < tbl->end_addr) {
+ start = tbl->start;
+ end = tbl->end;
+ break;
+ }
+ }
+ }
+
+ if (start && end) {
+ /*
+ * Do a binary search for the addresses in the index table.
+ * Addresses are guaranteed to be sorted in ascending order.
+ */
+ while (start < end - 1) {
+ struct unwind_idx *mid = start + ((end - start + 1) >> 1);
+
+ if (ip < mid->addr)
+ end = mid;
+ else
+ start = mid;
+ }
+
+ return start;
+ }
+
+ return NULL;
+}
+
+/*
+ * Convert a prel31 symbol to an absolute address.
+ */
+static ulong *
+prel31_to_addr(ulong *ptr)
+{
+ /* sign extend to 32 bits */
+ long offset = (((long)*ptr) << 1) >> 1;
+ return (ulong *)((ulong)ptr + offset);
+}
+
+static int
+unwind_frame(struct stackframe *frame, ulong stacktop)
+{
+ struct unwind_ctrl_block ctrl;
+ struct unwind_idx *idx;
+ ulong low, high;
+
+ low = frame->sp;
+ high = stacktop;
+
+ idx = search_index(frame->pc);
+ if (!idx) {
+ error(WARNING, "UNWIND: cannot find index for %lx\n",
+ frame->pc);
+ return FALSE;
+ }
+
+ ctrl.vrs[FP] = frame->fp;
+ ctrl.vrs[SP] = frame->sp;
+ ctrl.vrs[LR] = frame->lr;
+ ctrl.vrs[PC] = 0;
+
+ if (CRASHDEBUG(5)) {
+ fprintf(fp, "UNWIND: >frame: FP=%lx\n", ctrl.vrs[FP]);
+ fprintf(fp, "UNWIND: >frame: SP=%lx\n", ctrl.vrs[SP]);
+ fprintf(fp, "UNWIND: >frame: LR=%lx\n", ctrl.vrs[LR]);
+ fprintf(fp, "UNWIND: >frame: PC=%lx\n", ctrl.vrs[PC]);
+ }
+
+ if (idx->insn == 1) {
+ /* can't unwind */
+ return FALSE;
+ } else if ((idx->insn & 0x80000000) == 0) {
+ /* insn contains offset to eht entry */
+ ctrl.insn = prel31_to_addr(&idx->insn);
+ } else if ((idx->insn & 0xff000000) == 0x80000000) {
+ /* eht entry is in insn itself */
+ ctrl.insn = &idx->insn;
+ } else {
+ error(WARNING, "UNWIND: unsupported instruction %lx\n",
+ idx->insn);
+ return FALSE;
+ }
+
+ /* check the personality routine */
+ if ((*ctrl.insn & 0xff000000) == 0x80000000) {
+ ctrl.byte = 2;
+ ctrl.entries = 1;
+ } else if ((*ctrl.insn & 0xff000000) == 0x81000000) {
+ ctrl.byte = 1;
+ ctrl.entries = 1 + ((*ctrl.insn & 0x00ff0000) >> 16);
+ } else {
+ error(WARNING, "UNWIND: unsupported personality routine\n");
+ return FALSE;
+ }
+
+ /* now, execute the instructions */
+ while (ctrl.entries > 0) {
+ if (!unwind_exec_insn(&ctrl)) {
+ error(WARNING, "UNWIND: failed to exec instruction\n");
+ return FALSE;
+ }
+
+ if (ctrl.vrs[SP] < low || ctrl.vrs[SP] >= high)
+ return FALSE;
+ }
+
+ if (ctrl.vrs[PC] == 0)
+ ctrl.vrs[PC] = ctrl.vrs[LR];
+
+ if (frame->pc == ctrl.vrs[PC])
+ return FALSE;
+
+ frame->fp = ctrl.vrs[FP];
+ frame->sp = ctrl.vrs[SP];
+ frame->lr = ctrl.vrs[LR];
+ frame->pc = ctrl.vrs[PC];
+
+ if (CRASHDEBUG(5)) {
+ fprintf(fp, "UNWIND: <frame: FP=%lx\n", ctrl.vrs[FP]);
+ fprintf(fp, "UNWIND: <frame: SP=%lx\n", ctrl.vrs[SP]);
+ fprintf(fp, "UNWIND: <frame: LR=%lx\n", ctrl.vrs[LR]);
+ fprintf(fp, "UNWIND: <frame: PC=%lx\n", ctrl.vrs[PC]);
+ }
+
+ return TRUE;
+}
+
+void
+unwind_backtrace(struct bt_info *bt)
+{
+ struct stackframe frame;
+ int n = 0;
+
+ BZERO(&frame, sizeof(frame));
+ frame.fp = bt->frameptr;
+ frame.sp = bt->stkptr;
+ frame.pc = bt->instptr;
+
+ /*
+ * In case bt->machdep contains pointer to a full register set, we take
+ * LR from there.
+ */
+ if (bt->machdep) {
+ const struct arm_pt_regs *regs = bt->machdep;
+
+ frame.lr = regs->ARM_lr;
+ }
+
+ while (1) {
+ ulong where = frame.pc;
+
+ if (!IS_KVADDR(where))
+ break;
+
+ if (!unwind_frame(&frame, bt->stacktop))
+ break;
+
+ /* call back to ARM code to actually print this frame */
+ arm_dump_backtrace_entry(bt, n++, where, frame.pc,
+ frame.sp - 4);
+ }
+}
+#endif /* ARM */
--
1.5.6.5
More information about the linux-arm-kernel
mailing list