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.
",
+ 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
");
+
+ *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
");
+ 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
");
+ return;
+ }
+
+ fprintf(fp, " pc : [<%08lx>] lr : [<%08lx>] psr: %08lx
"
+ " sp : %08lx ip : %08lx fp : %08lx
",
+ 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
",
+ regs.ARM_r10, regs.ARM_r9, regs.ARM_r8);
+ fprintf(fp, " r7 : %08lx r6 : %08lx r5 : %08lx r4 : %08lx
",
+ regs.ARM_r7, regs.ARM_r6,
+ regs.ARM_r5, regs.ARM_r4);
+ fprintf(fp, " r3 : %08lx r2 : %08lx r1 : %08lx r0 : %08lx
",
+ 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] = ' ';
+
+ fprintf(fp, " Flags: %s IRQs o%s FIQs o%s Mode %s ISA %s
",
+ 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>]
",
+ level < 10 ? " " : "",
+ level, where, name, lm->mod_name, from);
+ } else {
+ fprintf(fp, "%s#%d [<%08lx>] (%s) from [<%08lx>]
",
+ 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
", 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>
", 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
", 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
", uts->machine);
+ fprintf(fp, " MEMORY SIZE: %s
", get_memory_size(buf));
+ fprintf(fp, " CPUS: %d
", get_cpus_to_display());
+ fprintf(fp, " PROCESSOR SPEED: ");
+ if ((mhz = machdep->processor_speed()))
+ fprintf(fp, "%ld Mhz
", mhz);
+ else
+ fprintf(fp, "(unknown)
");
+ fprintf(fp, " HZ: %d
", machdep->hz);
+ fprintf(fp, " PAGE SIZE: %d
", PAGESIZE());
+ fprintf(fp, "KERNEL VIRTUAL BASE: %lx
", machdep->kvbase);
+ fprintf(fp, "KERNEL VMALLOC BASE: %lx
", vt->vmalloc_start);
+ fprintf(fp, " KERNEL STACK SIZE: %ld
", 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!
");
+
+ /*
+ * 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
");
+
+ 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
",
+ phys_base);
+ fprintf(fp, "kernel text: [%lx - %lx]
",
+ ms->kernel_text_start, ms->kernel_text_end);
+ fprintf(fp, "exception text: [%lx - %lx]
",
+ 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 (' ')
@@ -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;
};
+#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
")
+
+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@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
");
+ 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
");
+ 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
");
+ fprintf(fp, "UNWIND: size : %ld
", tab_size);
+ fprintf(fp, "UNWIND: start : %p
", kernel_unwind_table->start);
+ fprintf(fp, "UNWIND: end : %p
", kernel_unwind_table->end);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx
",
+ kernel_unwind_table->begin_addr);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx
",
+ kernel_unwind_table->end_addr);
+ fprintf(fp, "UNWIND: master kernel table end
");
+ }
+
+ 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
");
+ 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
");
+ fprintf(fp, "UNWIND: start : %p
", tbl->start);
+ fprintf(fp, "UNWIND: end : %p
", tbl->end);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx
", tbl->begin_addr);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx
", tbl->end_addr);
+ fprintf(fp, "UNWIND: module table end
");
+ }
+
+ 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)
",
+ 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
");
+ 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
");
+ }
+
+ 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
");
+ 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
");
+ 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
", 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
",
+ 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
", ctrl.vrs[FP]);
+ fprintf(fp, "UNWIND: >frame: SP=%lx
", ctrl.vrs[SP]);
+ fprintf(fp, "UNWIND: >frame: LR=%lx
", ctrl.vrs[LR]);
+ fprintf(fp, "UNWIND: >frame: PC=%lx
", 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
",
+ 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
");
+ return FALSE;
+ }
+
+ /* now, execute the instructions */
+ while (ctrl.entries > 0) {
+ if (!unwind_exec_insn(&ctrl)) {
+ error(WARNING, "UNWIND: failed to exec instruction
");
+ 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
", ctrl.vrs[FP]);
+ fprintf(fp, "UNWIND: <frame: SP=%lx
", ctrl.vrs[SP]);
+ fprintf(fp, "UNWIND: <frame: LR=%lx
", ctrl.vrs[LR]);
+ fprintf(fp, "UNWIND: <frame: PC=%lx
", 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
--
Crash-utility mailing list
Crash-utility@redhat.com
https://www.redhat.com/mailman/listinfo/crash-utility
08-26-2010, 12:02 PM
Mika Westerberg
crash: add ARM crashdump support
This patch adds minimal support for reading ARM crashdumps. Currently supported
dumpfiles are /dev/mem, diskdump and vmcore. Stack unwinding can be done using
framepointers or unwinding tables depending on kernel configuration.
", depth);
+}
+
+/*
+ * Initialize ARM specific stuff.
+ */
+static void
+arm_init_machspec(void)
+{
+ struct machine_specific *ms = machdep->machspec;
+ ulong 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, "kernel text: [%lx - %lx]
",
+ ms->kernel_text_start, ms->kernel_text_end);
+ fprintf(fp, "exception text: [%lx - %lx]
",
+ ms->exception_text_start, ms->exception_text_end);
+ }
+
+ if (machdep->flags & PHYS_BASE) /* --machdep override */
+ return;
+
+ /*
+ * Next determine suitable value for phys_base. User can override this
+ * by passing valid '--machdep phys_base=<addr>' option.
+ */
+ ms->phys_base = 0;
+
+ if (ACTIVE()) {
+ char buf[BUFSIZE];
+ char *p1;
+ int errflag;
+ FILE *fp;
+
+ if ((fp = fopen("/proc/iomem", "r")) == NULL)
+ return;
+
+ /*
+ * Memory regions are sorted in ascending order. We take the
+ * first region which should be correct for most uses.
+ */
+ errflag = 1;
+ while (fgets(buf, BUFSIZE, fp)) {
+ if (strstr(buf, ": System RAM")) {
+ clean_line(buf);
+ errflag = 0;
+ break;
+ }
+ }
+ fclose(fp);
+
+ if (errflag)
+ return;
+
+ if (!(p1 = strstr(buf, "-")))
+ return;
+
+ *p1 = NULLCHAR;
+
+ phys_base = htol(buf, RETURN_ON_ERROR | QUIET, &errflag);
+ if (errflag)
+ return;
+
+ ms->phys_base = phys_base;
+ } else if (DISKDUMP_DUMPFILE() && diskdump_phys_base(&phys_base)) {
+ ms->phys_base = phys_base;
+ } else if (KDUMP_DUMPFILE() && kdump_phys_base(&phys_base)) {
+ ms->phys_base = phys_base;
+ } else {
+ error(WARNING,
+ "phys_base cannot be determined from the dumpfile.
"
+ "Using default value of 0. If this is not correct,
"
+ "consider using '--machdep phys_base=<addr>'
");
+ }
+
+ if (CRASHDEBUG(1))
+ fprintf(fp, "using %lx as phys_base
", ms->phys_base);
+}
+
+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 c8bae00..f9118f0 100755
--- 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 (' ')
@@ -1513,6 +1516,22 @@ struct offset_table { /* stash of commonly-used offsets */
long mm_rss_stat_count;
long module_module_init;
long module_init_text_size;
+#ifdef ARM
+ 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;
+ long irq_desc_t_name;
+ long thread_info_cpu_context;
+ 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;
+#endif /* ARM */
};
struct size_table { /* stash of commonly-used sizes */
@@ -1625,6 +1644,12 @@ struct size_table { /* stash of commonly-used sizes */
long module_sect_attr;
long task_struct_utime;
long task_struct_stime;
+#ifdef ARM
+ long cpu_context_save;
+ long elf_prstatus;
+ long note_buf;
+ long unwind_idx;
+#endif /* ARM */
};
/*
diff --git a/unwind_arm.c b/unwind_arm.c
new file mode 100644
index 0000000..8352e2d
--- /dev/null
+++ b/unwind_arm.c
@@ -0,0 +1,697 @@
+/*
+ * 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@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;
+};
+
+struct stackframe {
+ ulong fp;
+ ulong sp;
+ ulong lr;
+ ulong pc;
+};
+
+enum regs {
+ FP = 11,
+ SP = 13,
+ LR = 14,
+ PC = 15,
+};
+
+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
");
+ 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
");
+ 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
");
+ fprintf(fp, "UNWIND: size : %ld
", tab_size);
+ fprintf(fp, "UNWIND: start : %p
", kernel_unwind_table->start);
+ fprintf(fp, "UNWIND: end : %p
", kernel_unwind_table->end);
+ fprintf(fp, "UNWIND: begin_addr: 0x%lx
",
+ kernel_unwind_table->begin_a