/* * arm.c - core analysis suite * * Authors: * Thomas Fänge * Jan Karlsson * Mika Westerberg * * Copyright (C) 2010-2011 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 #include "defs.h" static void arm_parse_cmdline_args(void); static void 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_is_uvaddr(ulong, struct task_context *); static int arm_in_exception_text(ulong); static int arm_in_ret_from_syscall(ulong, int *); 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 *, unsigned int); static int arm_eframe_search(struct bt_info *); static ulong arm_get_task_pgd(ulong); static void arm_cmd_mach(void); static void arm_display_machine_stats(void); static int arm_get_smp_cpus(void); static void arm_init_machspec(void); static struct line_number_hook arm_line_number_hooks[]; static struct machine_specific arm_machine_specific; /** * struct arm_cpu_context_save - idle task registers * * This structure holds idle task registers. Only FP, SP, and PC are needed for * unwinding the stack. */ struct arm_cpu_context_save { ulong fp; ulong sp; ulong pc; }; /* * Holds registers during the crash. */ static struct arm_pt_regs *panic_task_regs; #define PGDIR_SIZE() (4 * PAGESIZE()) #define PGDIR_OFFSET(X) (((ulong)(X)) & (PGDIR_SIZE() - 1)) #define _SECTION_PAGE_MASK (~((MEGABYTES(1))-1)) #define PMD_TYPE_MASK 3 #define PMD_TYPE_SECT 2 #define PMD_TYPE_TABLE 1 #define PMD_TYPE_SECT_LPAE 1 static inline ulong * pmd_page_addr(ulong pmd) { ulong ptr; if (machdep->flags & PGTABLE_V2) { ptr = PAGEBASE(pmd); } else { 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_RDONLY L_PTE_WRITE #define L_PTE_USER (1 << 8) #define L_PTE_EXEC (1 << 9) #define L_PTE_XN L_PTE_EXEC #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_rdonly(pte) (pte_val(pte) & L_PTE_RDONLY) #define pte_dirty(pte) (pte_val(pte) & L_PTE_DIRTY) #define pte_young(pte) (pte_val(pte) & L_PTE_YOUNG) #define pte_exec(pte) (pte_val(pte) & L_PTE_EXEC) #define pte_xn(pte) (pte_val(pte) & L_PTE_XN) /* * 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) { ulong vaddr; char *string; struct syment *sp; #if defined(__i386__) || defined(__x86_64__) if (ACTIVE()) error(FATAL, "compiled for the ARM architecture\n"); #endif 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->pmd = (char *)malloc(PMDSIZE())) == NULL) error(FATAL, "cannot malloc pmd space."); if ((machdep->ptbl = (char *)malloc(PAGESIZE())) == NULL) error(FATAL, "cannot malloc ptbl space."); /* * LPAE requires an additional page for the PGD, * so PG_DIR_SIZE = 0x5000 for LPAE */ if ((string = pc->read_vmcoreinfo("CONFIG_ARM_LPAE"))) { machdep->flags |= PAE; free(string); } else if ((sp = next_symbol("swapper_pg_dir", NULL)) && (sp->value - symbol_value("swapper_pg_dir")) == 0x5000) machdep->flags |= PAE; machdep->kvbase = symbol_value("_stext") & ~KVBASE_MASK; machdep->identity_map_base = machdep->kvbase; machdep->is_kvaddr = arm_is_kvaddr; machdep->is_uvaddr = arm_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; machdep->dump_irq = generic_dump_irq; machdep->show_interrupts = generic_show_interrupts; machdep->get_irq_affinity = generic_get_irq_affinity; arm_init_machspec(); break; case POST_GDB: /* * Starting from 2.6.38 hardware and Linux page tables * were reordered. See also mainline kernel commit * d30e45eeabe (ARM: pgtable: switch order of Linux vs * hardware page tables). */ if (THIS_KERNEL_VERSION > LINUX(2,6,37) || STRUCT_EXISTS("pteval_t")) machdep->flags |= PGTABLE_V2; if (THIS_KERNEL_VERSION >= LINUX(3,3,0) || symbol_exists("idmap_pgd")) machdep->flags |= IDMAP_PGD; if (machdep->flags & PAE) { machdep->section_size_bits = _SECTION_SIZE_BITS_LPAE; machdep->max_physmem_bits = _MAX_PHYSMEM_BITS_LPAE; } else { machdep->section_size_bits = _SECTION_SIZE_BITS; machdep->max_physmem_bits = _MAX_PHYSMEM_BITS; } if (symbol_exists("irq_desc")) ARRAY_LENGTH_INIT(machdep->nr_irqs, irq_desc, "irq_desc", NULL, 0); else if (kernel_symbol_exists("nr_irqs")) get_symbol_data("nr_irqs", sizeof(unsigned int), &machdep->nr_irqs); /* * 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_r7, "cpu_context_save", "r7"); 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"); if (!machdep->hz) machdep->hz = 100; 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_end = machdep->kvbase - 1; vaddr = first_vmalloc_address(); if (vaddr > machdep->machspec->modules_end) machdep->machspec->modules_vaddr = DEFAULT_MODULES_VADDR; else machdep->machspec->modules_vaddr = vaddr; /* * 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(); if (init_unwind_tables()) { if (CRASHDEBUG(1)) fprintf(fp, "using unwind tables\n"); } else { if (CRASHDEBUG(1)) fprintf(fp, "using framepointers\n"); } break; case LOG_ONLY: machdep->machspec = &arm_machine_specific; machdep->kvbase = kt->vmcoreinfo._stext_SYMBOL & 0xffff0000UL; arm_init_machspec(); 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++ ? "|" : ""); if (machdep->flags & PGTABLE_V2) fprintf(fp, "%sPGTABLE_V2", others++ ? "|" : ""); if (machdep->flags & IDMAP_PGD) fprintf(fp, "%sIDMAP_PGD", others++ ? "|" : ""); if (machdep->flags & PAE) fprintf(fp, "%sPAE", others++ ? "|" : ""); fprintf(fp, ")\n"); fprintf(fp, " kvbase: %lx\n", machdep->kvbase); fprintf(fp, " identity_map_base: %lx\n", machdep->kvbase); fprintf(fp, " pagesize: %d\n", machdep->pagesize); fprintf(fp, " pageshift: %d\n", machdep->pageshift); fprintf(fp, " pagemask: %lx\n", (ulong)machdep->pagemask); fprintf(fp, " pageoffset: %lx\n", machdep->pageoffset); fprintf(fp, " stacksize: %ld\n", machdep->stacksize); fprintf(fp, " hz: %d\n", machdep->hz); fprintf(fp, " mhz: %ld\n", machdep->mhz); fprintf(fp, " memsize: %lld (0x%llx)\n", machdep->memsize, machdep->memsize); fprintf(fp, " bits: %d\n", machdep->bits); fprintf(fp, " nr_irqs: %d\n", machdep->nr_irqs); fprintf(fp, " eframe_search: arm_eframe_search()\n"); fprintf(fp, " back_trace: arm_back_trace_cmd()\n"); fprintf(fp, " processor_speed: arm_processor_speed()\n"); fprintf(fp, " uvtop: arm_uvtop()\n"); fprintf(fp, " kvtop: arm_kvtop()\n"); fprintf(fp, " get_task_pgd: arm_get_task_pgd()\n"); fprintf(fp, " dump_irq: generic_dump_irq()\n"); fprintf(fp, " get_stack_frame: arm_get_stack_frame()\n"); fprintf(fp, " get_stackbase: generic_get_stackbase()\n"); fprintf(fp, " get_stacktop: generic_get_stacktop()\n"); fprintf(fp, " translate_pte: arm_translate_pte()\n"); fprintf(fp, " memory_size: generic_memory_size()\n"); fprintf(fp, " vmalloc_start: arm_vmalloc_start()\n"); fprintf(fp, " is_task_addr: arm_is_task_addr()\n"); fprintf(fp, " verify_symbol: arm_verify_symbol()\n"); fprintf(fp, " dis_filter: arm_dis_filter()\n"); fprintf(fp, " cmd_mach: arm_cmd_mach()\n"); fprintf(fp, " get_smp_cpus: arm_get_smp_cpus()\n"); fprintf(fp, " is_kvaddr: arm_is_kvaddr()\n"); fprintf(fp, " is_uvaddr: arm_is_uvaddr()\n"); fprintf(fp, " verify_paddr: generic_verify_paddr()\n"); fprintf(fp, " show_interrupts: generic_show_interrupts()\n"); fprintf(fp, " get_irq_affinity: generic_get_irq_affinity()\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_regs: %lx\n", (ulong)ms->crash_task_regs); fprintf(fp, "unwind_index_prel31: %d\n", ms->unwind_index_prel31); } /* * Parse machine dependent command line arguments. * * Force the phys_base address via: * * --machdep phys_base=
*/ static void arm_parse_cmdline_args(void) { int index, i, c, err; char *arglist[MAXARGS]; char buf[BUFSIZE]; char *p; ulong value = 0; 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 void arm_get_crash_notes(void) { struct machine_specific *ms = machdep->machspec; ulong crash_notes; Elf32_Nhdr *note; ulong offset; char *buf, *p; ulong *notes_ptrs; ulong i, found; if (!symbol_exists("crash_notes")) return; crash_notes = symbol_value("crash_notes"); notes_ptrs = (ulong *)GETBUF(kt->cpus*sizeof(notes_ptrs[0])); /* * Read crash_notes for the first CPU. crash_notes are in standard ELF * note format. */ if (!readmem(crash_notes, KVADDR, ¬es_ptrs[kt->cpus-1], sizeof(notes_ptrs[kt->cpus-1]), "crash_notes", RETURN_ON_ERROR)) { error(WARNING, "cannot read crash_notes\n"); FREEBUF(notes_ptrs); return; } if (symbol_exists("__per_cpu_offset")) { /* Add __per_cpu_offset for each cpu to form the pointer to the notes */ for (i = 0; icpus; i++) notes_ptrs[i] = notes_ptrs[kt->cpus-1] + kt->__per_cpu_offset[i]; } buf = GETBUF(SIZE(note_buf)); if (!(panic_task_regs = calloc((size_t)kt->cpus, sizeof(*panic_task_regs)))) error(FATAL, "cannot calloc panic_task_regs space\n"); for (i = found = 0; icpus; i++) { if (!readmem(notes_ptrs[i], KVADDR, buf, SIZE(note_buf), "note_buf_t", RETURN_ON_ERROR)) { error(WARNING, "cpu %d: cannot read NT_PRSTATUS note\n", i); continue; } /* * Do some sanity checks for this note before reading registers from it. */ note = (Elf32_Nhdr *)buf; p = buf + sizeof(Elf32_Nhdr); /* * dumpfiles created with qemu won't have crash_notes, but there will * be elf notes; dumpfiles created by kdump do not create notes for * offline cpus. */ if (note->n_namesz == 0 && (DISKDUMP_DUMPFILE() || KDUMP_DUMPFILE())) { if (DISKDUMP_DUMPFILE()) note = diskdump_get_prstatus_percpu(i); else if (KDUMP_DUMPFILE()) note = netdump_get_prstatus_percpu(i); if (note) { /* * SIZE(note_buf) accounts for a "final note", which is a * trailing empty elf note header. */ long notesz = SIZE(note_buf) - sizeof(Elf32_Nhdr); if (sizeof(Elf32_Nhdr) + roundup(note->n_namesz, 4) + note->n_descsz == notesz) BCOPY((char *)note, buf, notesz); } else { error(WARNING, "cpu %d: cannot find NT_PRSTATUS note\n", i); continue; } } /* * Check the sanity of NT_PRSTATUS note only for each online cpu. * If this cpu has invalid note, continue to find the crash notes * for other online cpus. */ if (note->n_type != NT_PRSTATUS) { error(WARNING, "cpu %d: invalid NT_PRSTATUS note (n_type != NT_PRSTATUS)\n", i); continue; } if (!STRNEQ(p, "CORE")) { error(WARNING, "cpu %d: invalid NT_PRSTATUS note (name != \"CORE\")\n", i); continue; } /* * Find correct location of note data. This contains elf_prstatus * structure which has registers etc. for the crashed task. */ offset = sizeof(Elf32_Nhdr); offset = roundup(offset + note->n_namesz, 4); p = buf + offset; /* start of elf_prstatus */ BCOPY(p + OFFSET(elf_prstatus_pr_reg), &panic_task_regs[i], sizeof(panic_task_regs[i])); found++; } /* * And finally we have the registers for the crashed task. This is * used later on when dumping backtrace. */ ms->crash_task_regs = panic_task_regs; FREEBUF(buf); FREEBUF(notes_ptrs); if (!found) { free(panic_task_regs); ms->crash_task_regs = NULL; } } /* * 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 (STREQ(name, "PRRR") || STREQ(name, "NMRR")) return FALSE; if ((type == 'A') && STRNEQ(name, "__crc_")) 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->machspec->modules_end; 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 = DEFAULT_MODULES_VADDR; } 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); } static int arm_is_uvaddr(ulong vaddr, struct task_context *unused) { if (arm_is_module_addr(vaddr)) return FALSE; 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; } /* * Returns TRUE if given pc points to a return from syscall * entrypoint. In case the function returns TRUE and if offset is given, * it is filled with the offset that should be added to the SP to get * address of the exception frame where the user registers are. */ static int arm_in_ret_from_syscall(ulong pc, int *offset) { /* * On fast syscall return path, the stack looks like: * * SP + 0 {r4, r5} * SP + 8 user pt_regs * * The asm syscall handler pushes fifth and sixth registers * onto the stack before calling the actual syscall handler. * * So in order to print out the user registers at the time * the syscall was made, we need to adjust SP for 8. */ if (pc == symbol_value("ret_fast_syscall")) { if (offset) *offset = 8; return TRUE; } /* * In case we are on the slow syscall path, the SP already * points to the start of the user registers hence no * adjustments needs to be done. */ if (pc == symbol_value("ret_slow_syscall")) { if (offset) *offset = 0; return TRUE; } 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 => * * 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 (bt->flags & BT_REGS_NOT_FOUND) return; 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 lpae_pte) { char ptebuf[BUFSIZE]; char physbuf[BUFSIZE]; char buf[BUFSIZE]; int page_present; ulonglong paddr; int len1, len2, others; if (machdep->flags & PAE) { paddr = LPAE_PAGEBASE(lpae_pte); sprintf(ptebuf, "%llx", lpae_pte); pte = (ulong)lpae_pte; } else { paddr = PAGEBASE(pte); sprintf(ptebuf, "%lx", pte); } page_present = pte_present(pte); if (physaddr) { if (machdep->flags & PAE) *((ulonglong *)physaddr) = paddr; else *((ulong *)physaddr) = (ulong)paddr; return page_present; } 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, "%llx", 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_dirty(pte)) fprintf(fp, "%sDIRTY", others++ ? "|" : ""); if (pte_young(pte)) fprintf(fp, "%sYOUNG", others++ ? "|" : ""); if (machdep->flags & PGTABLE_V2) { if (!pte_rdonly(pte)) fprintf(fp, "%sWRITE", others++ ? "|" : ""); if (!pte_xn(pte)) fprintf(fp, "%sEXEC", others++ ? "|" : ""); } else { if (pte_write(pte)) fprintf(fp, "%sWRITE", others++ ? "|" : ""); if (pte_exec(pte)) fprintf(fp, "%sEXEC", 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) * * Before 2.6.38 * * PGD PTE * +---------+ * | | 0 ----> +------------+ * +- - - - -+ | h/w pt 0 | * | | 4 ----> +------------+ +1024 * +- - - - -+ | h/w pt 1 | * . . +------------+ +2048 * . . | Linux pt 0 | * . . +------------+ +3072 * | | 4095 | Linux pt 1 | * +---------+ +------------+ +4096 * * Starting from 2.6.38 * * PGD PTE * +---------+ * | | 0 ----> +------------+ * +- - - - -+ | Linux pt 0 | * | | 4 ----> +------------+ +1024 * +- - - - -+ | Linux pt 1 | * . . +------------+ +2048 * . . | h/w pt 0 | * . . +------------+ +3072 * | | 4095 | h/w pt 1 | * +---------+ +------------+ +4096 * * So in Linux implementation we have two hardware pointers to second * level page tables. Depending on the kernel version, the "Linux" page * tables either follow or precede the hardware 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. */ if (verbose) fprintf(fp, "PAGE DIRECTORY: %lx\n", (ulong)pgd); /* * pgd_offset(pgd, vaddr) */ page_dir = pgd + PGD_OFFSET(vaddr) * 2; /* The unity-mapped region is mapped using 1MB pages, * hence 1-level translation if bit 20 is set; if we * are 1MB apart physically, we move the page_dir in * case bit 20 is set. */ if (((vaddr) >> (20)) & 1) page_dir = page_dir + 1; 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) { ulong sectionbase = pmd_pte & _SECTION_PAGE_MASK; if (verbose) { fprintf(fp, " PAGE: %s (1MB)\n\n", mkstring(buf, VADDR_PRLEN, RJUST | LONG_HEX, MKSTR(sectionbase))); } *paddr = sectionbase + (vaddr & ~_SECTION_PAGE_MASK); return TRUE; } /* * pte_offset_map(pmd, vaddr) */ page_table = pmd_page_addr(pmd_pte) + PTE_OFFSET(vaddr); FILL_PTBL(PAGEBASE(page_table), PHYSADDR, 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; } /* * Virtual to physical memory translation when "CONFIG_ARM_LPAE=y". * This function will be called by both arm_kvtop() and arm_uvtop(). */ static int arm_lpae_vtop(ulong vaddr, ulong *pgd, physaddr_t *paddr, int verbose) { char buf[BUFSIZE]; physaddr_t page_dir; physaddr_t page_middle; physaddr_t page_table; pgd_t pgd_pmd; pmd_t pmd_pte; pte_t pte; if (IS_KVADDR(vaddr)) { if (!vt->vmalloc_start) { *paddr = LPAE_VTOP(vaddr); return TRUE; } if (!IS_VMALLOC_ADDR(vaddr)) { *paddr = LPAE_VTOP(vaddr); if (!verbose) return TRUE; } } if (verbose) fprintf(fp, "PAGE DIRECTORY: %lx\n", (ulong)pgd); /* * pgd_offset(pgd, vaddr) */ page_dir = LPAE_VTOP((ulong)pgd + LPAE_PGD_OFFSET(vaddr) * 8); FILL_PGD_LPAE(LPAE_VTOP(pgd), PHYSADDR, LPAE_PGDIR_SIZE()); pgd_pmd = ULONGLONG(machdep->pgd + LPAE_PGDIR_OFFSET(page_dir)); if (verbose) fprintf(fp, " PGD: %8llx => %llx\n", (ulonglong)page_dir, pgd_pmd); if (!pgd_pmd) return FALSE; /* * pmd_offset(pgd, vaddr) */ page_middle = LPAE_PAGEBASE(pgd_pmd) + LPAE_PMD_OFFSET(vaddr) * 8; FILL_PMD_LPAE(LPAE_PAGEBASE(pgd_pmd), PHYSADDR, LPAE_PMDIR_SIZE()); pmd_pte = ULONGLONG(machdep->pmd + LPAE_PMDIR_OFFSET(page_middle)); if (!pmd_pte) return FALSE; if ((pmd_pte & PMD_TYPE_MASK) == PMD_TYPE_SECT_LPAE) { ulonglong sectionbase = LPAE_PAGEBASE(pmd_pte) & LPAE_SECTION_PAGE_MASK; if (verbose) fprintf(fp, " PAGE: %8llx (2MB)\n\n", (ulonglong)sectionbase); *paddr = sectionbase + (vaddr & ~LPAE_SECTION_PAGE_MASK); return TRUE; } /* * pte_offset_map(pmd, vaddr) */ page_table = LPAE_PAGEBASE(pmd_pte) + PTE_OFFSET(vaddr) * 8; FILL_PTBL_LPAE(LPAE_PAGEBASE(pmd_pte), PHYSADDR, LPAE_PTEDIR_SIZE()); pte = ULONGLONG(machdep->ptbl + LPAE_PTEDIR_OFFSET(page_table)); if (verbose) { fprintf(fp, " PTE: %8llx => %llx\n\n", (ulonglong)page_table, pte); } if (!pte_present(pte)) { if (pte && verbose) { fprintf(fp, "\n"); arm_translate_pte(0, 0, pte); } return FALSE; } *paddr = LPAE_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(0, 0, pte); } 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; if (!tc) error(FATAL, "current context invalid\n"); /* * Before idmap_pgd was introduced with upstream commit 2c8951ab0c * (ARM: idmap: use idmap_pgd when setting up mm for reboot), the * panic task pgd was overwritten by soft reboot code, so we can't do * any vtop translations. */ if (!(machdep->flags & IDMAP_PGD) && tc->task == tt->panic_task) error(FATAL, "panic task pgd is trashed by soft reboot code\n"); *paddr = 0; if (is_kernel_thread(tc->task) && IS_KVADDR(uvaddr)) { ulong active_mm; readmem(tc->task + OFFSET(task_struct_active_mm), KVADDR, &active_mm, sizeof(void *), "task active_mm contents", FAULT_ON_ERROR); if (!active_mm) error(FATAL, "no active_mm for this kernel thread\n"); readmem(active_mm + OFFSET(mm_struct_pgd), KVADDR, &pgd, sizeof(long), "mm_struct pgd", FAULT_ON_ERROR); } else { ulong mm; 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); } if (machdep->flags & PAE) return arm_lpae_vtop(uvaddr, pgd, paddr, verbose); 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 (machdep->flags & PAE) return arm_lpae_vtop(kvaddr, (ulong *)vt->kernel_pgd[0], paddr, verbose); 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))))) GET_REG(spp, cpu_context, cpu_context_save_sp); GET_REG(pcp, cpu_context, cpu_context_save_pc); /* * Unwinding code needs FP (R7 for Thumb code) value also so we pass it * with bt. */ if (*pcp & 1) GET_REG(&bt->frameptr, cpu_context, cpu_context_save_r7); else GET_REG(&bt->frameptr, cpu_context, cpu_context_save_fp); return TRUE; } /* * Get the starting point for the active cpu in a diskdump. */ 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 || (!ms->crash_task_regs[bt->tc->processor].ARM_pc && !ms->crash_task_regs[bt->tc->processor].ARM_sp)) { bt->flags |= BT_REGS_NOT_FOUND; return FALSE; } /* * We got registers for panic task from crash_notes. Just return them. */ *nip = ms->crash_task_regs[bt->tc->processor].ARM_pc; *ksp = ms->crash_task_regs[bt->tc->processor].ARM_sp; /* * Also store pointer to all registers in case unwinding code needs * to access LR. */ bt->machdep = &(ms->crash_task_regs[bt->tc->processor]); 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 determine starting stack frame for task %lx\n", bt->task); 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; int offset = 0; struct syment *symp; ulong symbol_offset; char *name_plus_offset; char buf[BUFSIZE]; name = closest_symbol(bt->instptr); name_plus_offset = NULL; if (bt->flags & BT_SYMBOL_OFFSET) { symp = value_search(bt->instptr, &symbol_offset); if (symp && symbol_offset) name_plus_offset = value_to_symstr(bt->instptr, buf, bt->radix); } 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_plus_offset ? name_plus_offset : name, lm->mod_name, from); } else { fprintf(fp, "%s#%d [<%08lx>] (%s) from [<%08lx>]\n", level < 10 ? " " : "", level, bt->instptr, name_plus_offset ? name_plus_offset : 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)); } else if (arm_in_ret_from_syscall(from, &offset)) { ulong nsp = sp + offset; arm_dump_exception_stack(nsp, nsp + sizeof(struct arm_pt_regs)); } if (bt->flags & BT_FULL) { if (kt->flags & DWARF_UNWIND) { fprintf(fp, " " "[PC: %08lx LR: %08lx SP: %08lx SIZE: %ld]\n", bt->instptr, from, bt->stkptr, sp - bt->stkptr); } else { fprintf(fp, " " "[PC: %08lx LR: %08lx SP: %08lx FP: %08lx " "SIZE: %ld]\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) { machdep->machspec->vmalloc_start_addr = vt->high_memory; 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, unsigned int output_radix) { 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, 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, output_radix)); sprintf(p1, "%s", 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 MODULES BASE: %lx\n", MODULES_VADDR); 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) { int cpus; if ((cpus = get_cpus_present())) return cpus; else return MAX(get_cpus_online(), get_highest_cpu_online()+1); } /* * 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=' 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() && arm_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='\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 */