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. Signed-off-by: Jan Karlsson <jan.karlsson@xxxxxxxxxxxxxxxx> Signed-off-by: Thomas Fänge <thomas.fange@xxxxxxxxxxxxxxxx> Signed-off-by: Mika Westerberg <ext-mika.1.westerberg@xxxxxxxxx> --- Makefile | 12 +- arm.c | 1741 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ defs.h | 137 +++++ diskdump.c | 8 + unwind_arm.c | 697 +++++++++++++++++++++++ 5 files changed, 2593 insertions(+), 2 deletions(-) create mode 100644 arm.c create mode 100644 unwind_arm.c diff --git a/Makefile b/Makefile index 038c887..f79f6a9 100644 --- a/Makefile +++ b/Makefile @@ -80,11 +80,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 @@ -96,11 +97,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 @@ -419,6 +421,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} @@ -460,6 +465,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..f6d1b27 --- /dev/null +++ b/arm.c @@ -0,0 +1,1741 @@ +/* + * arm.c - core analysis suite + * + * Authors: + * Thomas Fänge <thomas.fange@xxxxxxxxxxxxxxxx> + * Jan Karlsson <jan.karlsson@xxxxxxxxxxxxxxxx> + * Mika Westerberg <ext-mika.1.westerberg@xxxxxxxxx> + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Sony Ericsson. All rights reserved. + * + * 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 void arm_parse_cmdline_args(void); +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(struct bt_info *); +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 void arm_display_full_frame(struct bt_info *, 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 print_irq_member(char *, ulong, char *); +static void arm_dump_irq(int); +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; + + if (machdep->cmdline_args[0]) + arm_parse_cmdline_args(); + 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 = arm_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); + if (MEMBER_EXISTS("irq_desc", "name")) + MEMBER_OFFSET_INIT(irq_desc_t_name, "irq_desc", + "name"); + } + + /* + * 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 (!ACTIVE() && !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()) { + if (CRASHDEBUG(1)) + fprintf(fp, "using unwind tables\n"); + } else { + if (CRASHDEBUG(1)) + fprintf(fp, "using framepointers\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++ ? "|" : ""); + if (machdep->flags & PHYS_BASE) + fprintf(fp, "%sPHYS_BASE", 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"); + if (symbol_exists("irq_desc")) + fprintf(fp, " dump_irq: arm_dump_irq()\n"); + else + fprintf(fp, " dump_irq: NULL\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); +} + +/* + * Parse machine dependent command line arguments. + * + * Force the phys_base address via: + * + * --machdep phys_base=<address> + */ +static void +arm_parse_cmdline_args(void) +{ + int index, i, c, err; + char *arglist[MAXARGS]; + char buf[BUFSIZE]; + char *p; + ulong value; + + for (index = 0; index < MAX_MACHDEP_ARGS; index++) { + if (!machdep->cmdline_args[index]) + break; + + if (!strstr(machdep->cmdline_args[index], "=")) { + error(WARNING, "ignoring --machdep option: %x\n", + machdep->cmdline_args[index]); + continue; + } + + strcpy(buf, machdep->cmdline_args[index]); + + for (p = buf; *p; p++) { + if (*p == ',') + *p = ' '; + } + + c = parse_line(buf, arglist); + + for (i = 0; i < c; i++) { + err = 0; + + if (STRNEQ(arglist[i], "phys_base=")) { + int megabytes = FALSE; + int flags = RETURN_ON_ERROR | QUIET; + + if ((LASTCHAR(arglist[i]) == 'm') || + (LASTCHAR(arglist[i]) == 'M')) { + LASTCHAR(arglist[i]) = NULLCHAR; + megabytes = TRUE; + } + + p = arglist[i] + strlen("phys_base="); + if (strlen(p)) { + if (megabytes) + value = dtol(p, flags, &err); + else + value = htol(p, flags, &err); + } + + if (!err) { + if (megabytes) + value = MEGABYTES(value); + + machdep->machspec->phys_base = value; + + error(NOTE, + "setting phys_base to: 0x%lx\n", + machdep->machspec->phys_base); + + machdep->flags |= PHYS_BASE; + continue; + } + } + + error(WARNING, "ignoring --machdep option: %s\n", + arglist[i]); + } + } +} + +/* + * 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 the kernel stack using a minimal amount of gdb services. + */ +static void +arm_back_trace(struct bt_info *bt) +{ + int n = 0; + + /* + * In case bt->machdep contains pointer to a full register set, we take + * FP from there. + */ + if (bt->machdep) { + const struct arm_pt_regs *regs = bt->machdep; + bt->frameptr = regs->ARM_fp; + } + + /* + * Stack frame layout: + * optionally saved caller registers (r4 - r10) + * saved fp + * saved sp + * saved lr + * frame => saved pc + * optionally saved arguments (r0 - r3) + * saved sp => <next word> + * + * Functions start with the following code sequence: + * mov ip, sp + * stmfd sp!, {r0 - r3} (optional) + * corrected pc => stmfd sp!, {..., fp, ip, lr, pc} + */ + while (bt->frameptr && INSTACK(bt->frameptr, bt)) { + ulong from; + ulong sp; + + /* + * We correct the PC to point to the actual instruction (current + * value is PC + 8). + */ + bt->instptr = GET_STACK_ULONG(bt->frameptr - 0); + bt->instptr -= 8; + + /* + * Now get LR, saved SP and FP from the frame as well. + */ + from = GET_STACK_ULONG(bt->frameptr - 4); + sp = GET_STACK_ULONG(bt->frameptr - 8); + bt->frameptr = GET_STACK_ULONG(bt->frameptr - 12); + + arm_dump_backtrace_entry(bt, n++, from, sp); + + bt->stkptr = sp; + } +} + +/* + * Unroll a kernel stack. + */ +static void +arm_back_trace_cmd(struct bt_info *bt) +{ + if (kt->flags & DWARF_UNWIND) + unwind_backtrace(bt); + else + arm_back_trace(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. + */ +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)]); +} + +static void +arm_display_full_frame(struct bt_info *bt, ulong sp) +{ + ulong words, addr; + ulong *up; + char buf[BUFSIZE]; + int i, u_idx; + + if (!INSTACK(sp, bt) || !INSTACK(bt->stkptr, bt)) + return; + + words = (sp - bt->stkptr) / sizeof(ulong); + + if (words == 0) { + fprintf(fp, " (no frame)\n"); + return; + } + + addr = bt->stkptr; + u_idx = (bt->stkptr - bt->stackbase) / sizeof(ulong); + for (i = 0; i < words; i++, u_idx++) { + if ((i % 4) == 0) + fprintf(fp, "%s %lx: ", i ? "\n" : "", addr); + + up = (ulong *)(&bt->stackbuf[u_idx * sizeof(ulong)]); + fprintf(fp, "%s ", format_stack_entry(bt, buf, *up, 0)); + addr += sizeof(ulong); + } + fprintf(fp, "\n"); +} + +/* + * Prints out a single stack frame. What is printed depends on flags passed in + * with bt. + * + * What is expected when calling this function: + * bt->frameptr = current FP (or 0 if there is no such) + * bt->stkptr = current SP + * bt->instptr = current PC + * + * from = LR + * sp = previous/saved SP + */ +void +arm_dump_backtrace_entry(struct bt_info *bt, int level, ulong from, ulong sp) +{ + struct load_module *lm; + const char *name; + + name = closest_symbol(bt->instptr); + + if (module_symbol(bt->instptr, NULL, &lm, NULL, 0)) { + fprintf(fp, "%s#%d [<%08lx>] (%s [%s]) from [<%08lx>]\n", + level < 10 ? " " : "", + level, bt->instptr, name, lm->mod_name, from); + } else { + fprintf(fp, "%s#%d [<%08lx>] (%s) from [<%08lx>]\n", + level < 10 ? " " : "", + level, bt->instptr, name, from); + } + + if (bt->flags & BT_LINE_NUMBERS) { + char buf[BUFSIZE]; + + get_line_number(bt->instptr, buf, FALSE); + if (strlen(buf)) + fprintf(fp, " %s\n", buf); + } + + if (arm_in_exception_text(bt->instptr)) + arm_dump_exception_stack(sp, sp + sizeof(struct arm_pt_regs)); + + if (bt->flags & BT_FULL) { + if (kt->flags & DWARF_UNWIND) { + fprintf(fp, " " + "[PC: %08lx LR: %08lx SP: %08lx SIZE: %d]\n", + bt->instptr, from, bt->stkptr, sp - bt->stkptr); + } else { + fprintf(fp, " " + "[PC: %08lx LR: %08lx SP: %08lx FP: %08lx " + "SIZE: %d]\n", + bt->instptr, from, bt->stkptr, bt->frameptr, + sp - bt->stkptr); + } + arm_display_full_frame(bt, sp); + } +} + +/* + * 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(); +} + +static void +print_irq_member(char *irq, ulong val, char *ind) +{ + char buf[BUFSIZE]; + ulong val2; + + fprintf(fp, "%17s: %8lx ", irq, val); + if (val) { + if (is_kernel_text(val)) + fprintf(fp, "<%s>", value_to_symstr(val, buf, 0)); + else if (readmem(val, KVADDR, &val2, + sizeof(ulong), ind, RETURN_ON_ERROR|QUIET) && + is_kernel_text(val2)) + fprintf(fp, "<%s>", value_to_symstr(val2, buf, 0)); + } + fprintf(fp, "\n"); +} + +/* + * Do the work for cmd_irq(). + */ +static void +arm_dump_irq(int irq) +{ + struct datatype_member datatype_member, *dm; + ulong irq_desc_addr; + ulong irq_desc_ptr; + long len; + char buf[BUFSIZE]; + int type, status, depth, others; + ulong handler, action, value; + ulong tmp1, tmp2; + + dm = &datatype_member; + + if (!VALID_STRUCT(irq_desc_t)) + error(FATAL, "cannot determine size of irq_desc\n"); + len = SIZE(irq_desc_t); + + if (symbol_exists("irq_desc")) + irq_desc_addr = symbol_value("irq_desc") + (len * irq); + else if (symbol_exists("_irq_desc")) + irq_desc_addr = symbol_value("_irq_desc") + (len * irq); + else if (symbol_exists("irq_desc_ptrs")) { + get_symbol_data("irq_desc_ptrs", sizeof(void *), &irq_desc_ptr); + irq_desc_ptr += (irq * sizeof(void *)); + readmem(irq_desc_ptr, KVADDR, &irq_desc_addr, + sizeof(void *), "irq_desc_ptrs entry", + FAULT_ON_ERROR); + if (!irq_desc_addr) { + fprintf(fp, " IRQ: %d (unused)\n\n", irq); + return; + } + } else { + irq_desc_addr = 0; + error(FATAL, + "neither irq_desc, _irq_desc, nor irq_desc_ptrs " + "symbols exist\n"); + } + + readmem(irq_desc_addr + OFFSET(irq_desc_t_status), KVADDR, &status, + sizeof(int), "irq_desc entry", FAULT_ON_ERROR); + if (VALID_MEMBER(irq_desc_t_handler)) + readmem(irq_desc_addr + OFFSET(irq_desc_t_handler), KVADDR, + &handler, sizeof(long), "irq_desc entry", + FAULT_ON_ERROR); + else if (VALID_MEMBER(irq_desc_t_chip)) + readmem(irq_desc_addr + OFFSET(irq_desc_t_chip), KVADDR, + &handler, sizeof(long), "irq_desc entry", + FAULT_ON_ERROR); + readmem(irq_desc_addr + OFFSET(irq_desc_t_action), KVADDR, &action, + sizeof(long), "irq_desc entry", FAULT_ON_ERROR); + readmem(irq_desc_addr + OFFSET(irq_desc_t_depth), KVADDR, &depth, + sizeof(int), "irq_desc entry", FAULT_ON_ERROR); + + if (!action && (handler == (ulong)pc->curcmd_private)) + return; + + fprintf(fp, " IRQ: %d\n", irq); + fprintf(fp, "ADDRESS: %08lx\n", irq_desc_addr); + + if (VALID_MEMBER(irq_desc_t_name)) { + readmem(irq_desc_addr+OFFSET(irq_desc_t_name), + KVADDR, &tmp1, sizeof(void *), + "irq_desc name", FAULT_ON_ERROR); + if (tmp1) { + fprintf(fp, " NAME: %lx", tmp1); + BZERO(buf, BUFSIZE); + if (read_string(tmp1, buf, BUFSIZE-1)) + fprintf(fp, " \"%s\"", buf); + fprintf(fp, "\n"); + } + } + + type = status & IRQ_TYPE_SENSE_MASK; + fprintf(fp, " TYPE: %x %s", type, type ? "(" : ""); + others = 0; + + if (type & IRQ_TYPE_EDGE_BOTH == IRQ_TYPE_EDGE_BOTH) + fprintf(fp, "%sEDGE_BOTH", others++ ? "|" : ""); + else { + if (type & IRQ_TYPE_EDGE_RISING) + fprintf(fp, "%sEDGE_RISING", others++ ? "|" : ""); + if (type & IRQ_TYPE_EDGE_FALLING) + fprintf(fp, "%sEDGE_FALLING", others++ ? "|" : ""); + } + if (type & IRQ_TYPE_LEVEL_HIGH) + fprintf(fp, "%sLEVEL_HIGH", others++ ? "|" : ""); + if (type & IRQ_TYPE_LEVEL_LOW) + fprintf(fp, "%sLEVEL_LOW", others++ ? "|" : ""); + fprintf(fp, "%s\n", type ? ")" : ""); + + fprintf(fp, " PROBE: %s\n", status & IRQ_TYPE_PROBE ? "true" : "false"); + + fprintf(fp, " STATUS: %x %s", status, status ? "(" : ""); + others = 0; + if (status & IRQ_INPROGRESS) { + fprintf(fp, "IRQ_INPROGRESS"); + others++; + } + if (status & IRQ_DISABLED) + fprintf(fp, "%sIRQ_DISABLED", others++ ? "|" : ""); + if (status & IRQ_PENDING) + fprintf(fp, "%sIRQ_PENDING", others++ ? "|" : ""); + if (status & IRQ_REPLAY) + fprintf(fp, "%sIRQ_REPLAY", others++ ? "|" : ""); + if (status & IRQ_AUTODETECT) + fprintf(fp, "%sIRQ_AUTODETECT", others++ ? "|" : ""); + if (status & IRQ_WAITING) + fprintf(fp, "%sIRQ_WAITING", others++ ? "|" : ""); + if (status & IRQ_LEVEL) + fprintf(fp, "%sIRQ_LEVEL", others++ ? "|" : ""); + if (status & IRQ_MASKED) + fprintf(fp, "%sIRQ_MASKED", others++ ? "|" : ""); + + if (status & IRQ_PER_CPU) + fprintf(fp, "%sIRQ_PER_CPU", others++ ? "|" : ""); + if (status & IRQ_NOPROBE) + fprintf(fp, "%sIRQ_NOPROBE", others++ ? "|" : ""); + if (status & IRQ_NOREQUEST) + fprintf(fp, "%sIRQ_NOREQUEST", others++ ? "|" : ""); + if (status & IRQ_NOAUTOEN) + fprintf(fp, "%sIRQ_NOAUTOEN", others++ ? "|" : ""); + if (status & IRQ_WAKEUP) + fprintf(fp, "%sIRQ_WAKEUP", others++ ? "|" : ""); + if (status & IRQ_MOVE_PENDING) + fprintf(fp, "%sIRQ_MOVE_PENDING", others++ ? "|" : ""); + if (status & IRQ_NO_BALANCING) + fprintf(fp, "%sIRQ_NO_BALANCING", others++ ? "|" : ""); + if (status & IRQ_SPURIOUS_DISABLED) + fprintf(fp, "%sIRQ_SPURIOUS_DISABLED", others++ ? "|" : ""); + if (status & IRQ_MOVE_PCNTXT) + fprintf(fp, "%sIRQ_MOVE_PCNTXT", others++ ? "|" : ""); + if (status & IRQ_AFFINITY_SET) + fprintf(fp, "%sIRQ_AFFINITY_SET", others++ ? "|" : ""); + + + fprintf(fp, "%s\n", status ? ")" : ""); + + fprintf(fp, "HANDLER: %8lx ", handler); + if (value_symbol(handler)) { + pad_line(fp, VADDR_PRLEN == 8 ? VADDR_PRLEN+2 : VADDR_PRLEN-6, ' '); + fprintf(fp, "<%s>", value_symbol(handler)); + } + fprintf(fp, "\n", handler); + +#define CHECK_HANDLER_OPT_MEMBER_PRT(x) \ + if (VALID_MEMBER(hw_interrupt_type_ ## x)) { \ + readmem(handler+OFFSET(hw_interrupt_type_ ## x), KVADDR, \ + &tmp1, sizeof(void *), "hw_interrupt_type " #x, \ + FAULT_ON_ERROR); \ + print_irq_member(#x, tmp1, #x " indirection"); \ + } else if (VALID_MEMBER(irq_chip_ ## x)) { \ + readmem(handler+OFFSET(irq_chip_ ## x),KVADDR, &tmp1, \ + sizeof(void *), "irq_chip " #x, FAULT_ON_ERROR); \ + print_irq_member(#x, tmp1, #x " indirection"); \ + } + +#define CHECK_HANDLER_OPT_MEMBER(x) \ + if (VALID_MEMBER(hw_interrupt_type_ ## x)) \ + readmem(handler+OFFSET(hw_interrupt_type_ ## x), KVADDR, \ + &tmp1, sizeof(void *), "hw_interrupt_type " #x, \ + FAULT_ON_ERROR); \ + else if (VALID_MEMBER(irq_chip_ ## x)) \ + readmem(handler+OFFSET(irq_chip_ ## x),KVADDR, &tmp1, \ + sizeof(void *), "irq_chip " #x, FAULT_ON_ERROR); \ + +#define CHECK_HANDLER_MEMBER_PRT(x,s1,s2) \ + if (VALID_MEMBER(x)) { \ + readmem(handler+OFFSET(x), KVADDR, &tmp1, sizeof(void *),\ + s1, FAULT_ON_ERROR); \ + print_irq_member(s2, tmp1, s2 " indirection"); \ + } + + if (handler) { + CHECK_HANDLER_OPT_MEMBER(typename); + + fprintf(fp, " typename: %lx ", tmp1); + BZERO(buf, BUFSIZE); + if (read_string(tmp1, buf, BUFSIZE-1)) + fprintf(fp, "\"%s\"", buf); + fprintf(fp, "\n"); + + CHECK_HANDLER_OPT_MEMBER(startup); + + print_irq_member("startup", tmp1, "startup indirection"); + + CHECK_HANDLER_OPT_MEMBER(shutdown); + + print_irq_member("shutdown", tmp1, "shutdown indirection"); + + CHECK_HANDLER_MEMBER_PRT(hw_interrupt_type_handle, + "hw_interrupt_type handle", + "handle"); + + CHECK_HANDLER_OPT_MEMBER(enable); + + print_irq_member("enable", tmp1, "enable indirection"); + + CHECK_HANDLER_OPT_MEMBER(disable); + + print_irq_member("disable", tmp1, "disable indirection"); + + CHECK_HANDLER_OPT_MEMBER(ack); + + print_irq_member("ack", tmp1, "ack indirection"); + + CHECK_HANDLER_MEMBER_PRT(irq_chip_mask, "irq_chip mask","mask"); + + CHECK_HANDLER_MEMBER_PRT(irq_chip_mask_ack, "irq_chip mask_ack", + "mask_ack"); + + CHECK_HANDLER_MEMBER_PRT(irq_chip_unmask, "irq_chip unmask", + "unmask"); + + CHECK_HANDLER_MEMBER_PRT(irq_chip_eoi,"irq_chip eoi","eoi"); + + CHECK_HANDLER_OPT_MEMBER_PRT(startup); + + CHECK_HANDLER_OPT_MEMBER_PRT(set_affinity); + + CHECK_HANDLER_MEMBER_PRT(irq_chip_retrigger, + "irq_chip retrigger", "retrigger"); + CHECK_HANDLER_MEMBER_PRT(irq_chip_set_type, + "irq_chip set_type", "set_type"); + CHECK_HANDLER_MEMBER_PRT(irq_chip_set_wake, + "irq_chip set_wake", "set_wake"); + } + +do_linked_action: + + fprintf(fp, " ACTION: "); + if (value_symbol(action)) { + fprintf(fp, "%lx ", action); + pad_line(fp, VADDR_PRLEN == 8 ? + VADDR_PRLEN+2 : VADDR_PRLEN - 6, ' '); + fprintf(fp, "<%s>\n", value_symbol(action)); + } else if (action) { + fprintf(fp, "%8lx\n", action); + } else { + fprintf(fp, "(none)\n"); + } + + if (action) { + readmem(action+OFFSET(irqaction_handler), KVADDR, + &tmp1, sizeof(void *), + "irqaction handler", FAULT_ON_ERROR); + + print_irq_member("handler", tmp1, "handler indirection"); + + readmem(action+OFFSET(irqaction_flags), KVADDR, + &value, sizeof(void *), + "irqaction flags", FAULT_ON_ERROR); + fprintf(fp, " flags: %8lx\n", value); + + if (VALID_MEMBER(irqaction_mask)) { + readmem(action+OFFSET(irqaction_mask), KVADDR, + &tmp1, sizeof(void *), + "irqaction mask", FAULT_ON_ERROR); + fprintf(fp, " mask: %8lx\n", tmp1); + } + + readmem(action+OFFSET(irqaction_name), KVADDR, + &tmp1, sizeof(void *), + "irqaction name", FAULT_ON_ERROR); + fprintf(fp, " name: %8lx ", tmp1); + BZERO(buf, BUFSIZE); + if (read_string(tmp1, buf, BUFSIZE-1)) + fprintf(fp, "\"%s\"", buf); + fprintf(fp, "\n"); + + readmem(action+OFFSET(irqaction_dev_id), KVADDR, + &tmp1, sizeof(void *), + "irqaction dev_id", FAULT_ON_ERROR); + fprintf(fp, " dev_id: %8lx\n", tmp1); + + readmem(action+OFFSET(irqaction_next), KVADDR, + &action, sizeof(void *), + "irqaction dev_id", FAULT_ON_ERROR); + fprintf(fp, " next: %8lx\n", action); + } + + if (action) + goto do_linked_action; + + fprintf(fp, " DEPTH: %d\n\n", 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]\n", + ms->kernel_text_start, ms->kernel_text_end); + fprintf(fp, "exception text: [%lx - %lx]\n", + 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.\n" + "Using default value of 0. If this is not correct,\n" + "consider using '--machdep phys_base=<addr>'\n"); + } + + if (CRASHDEBUG(1)) + fprintf(fp, "using %lx as phys_base\n", 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 ('\0') @@ -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 */ }; struct array_table { @@ -2099,6 +2124,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" @@ -2795,6 +2863,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) @@ -2971,6 +3043,13 @@ struct efi_memory_desc_t { #define IRQ_AFFINITY_SET \ (THIS_KERNEL_VERSION >= LINUX(2,6,21) ? IRQ_AFFINITY_SET_2_6_21 : 0) +#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 @@ -3301,6 +3380,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 @@ -3659,6 +3741,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 @@ -3937,6 +4022,58 @@ 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); +int arm_is_vmalloc_addr(ulong); +void arm_dump_backtrace_entry(struct bt_info *, int, 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) +#define PHYS_BASE (0x2) + +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/diskdump.c b/diskdump.c index 3aab338..e52e603 100644 --- a/diskdump.c +++ b/diskdump.c @@ -528,7 +528,15 @@ page_is_cached(physaddr_t paddr) static ulong paddr_to_pfn(physaddr_t paddr) { +#ifdef ARM + /* + * In ARM, PFN 0 means first page in kernel direct-mapped view. + * This is also first page in mem_map as well. + */ + return (paddr - machdep->machspec->phys_base) >> dd->block_shift; +#else return paddr >> dd->block_shift; +#endif } /* 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@xxxxxxxxx> + * 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\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 (IS_KVADDR(bt->instptr)) { + if (!unwind_frame(&frame, bt->stacktop)) + break; + + arm_dump_backtrace_entry(bt, n++, frame.lr, frame.sp); + + bt->instptr = frame.pc; + bt->stkptr = frame.sp; + } +} +#endif /* ARM */ -- 1.5.6.5 -- Crash-utility mailing list Crash-utility@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/crash-utility