/* * Copyright (C) 2014 Google, Inc. * Author: Colin Cross * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include "fiq_debugger_priv.h" static char *mode_name(unsigned cpsr) { switch (cpsr & MODE_MASK) { case USR_MODE: return "USR"; case FIQ_MODE: return "FIQ"; case IRQ_MODE: return "IRQ"; case SVC_MODE: return "SVC"; case ABT_MODE: return "ABT"; case UND_MODE: return "UND"; case SYSTEM_MODE: return "SYS"; default: return "???"; } } void fiq_debugger_dump_pc(struct fiq_debugger_output *output, const struct pt_regs *regs) { output->printf(output, " pc %08x cpsr %08x mode %s\n", regs->ARM_pc, regs->ARM_cpsr, mode_name(regs->ARM_cpsr)); } void fiq_debugger_dump_regs(struct fiq_debugger_output *output, const struct pt_regs *regs) { output->printf(output, " r0 %08x r1 %08x r2 %08x r3 %08x\n", regs->ARM_r0, regs->ARM_r1, regs->ARM_r2, regs->ARM_r3); output->printf(output, " r4 %08x r5 %08x r6 %08x r7 %08x\n", regs->ARM_r4, regs->ARM_r5, regs->ARM_r6, regs->ARM_r7); output->printf(output, " r8 %08x r9 %08x r10 %08x r11 %08x mode %s\n", regs->ARM_r8, regs->ARM_r9, regs->ARM_r10, regs->ARM_fp, mode_name(regs->ARM_cpsr)); output->printf(output, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", regs->ARM_ip, regs->ARM_sp, regs->ARM_lr, regs->ARM_pc, regs->ARM_cpsr); } struct mode_regs { unsigned long sp_svc; unsigned long lr_svc; unsigned long spsr_svc; unsigned long sp_abt; unsigned long lr_abt; unsigned long spsr_abt; unsigned long sp_und; unsigned long lr_und; unsigned long spsr_und; unsigned long sp_irq; unsigned long lr_irq; unsigned long spsr_irq; unsigned long r8_fiq; unsigned long r9_fiq; unsigned long r10_fiq; unsigned long r11_fiq; unsigned long r12_fiq; unsigned long sp_fiq; unsigned long lr_fiq; unsigned long spsr_fiq; }; static void __naked get_mode_regs(struct mode_regs *regs) { asm volatile ( "mrs r1, cpsr\n" "msr cpsr_c, #0xd3 @(SVC_MODE | PSR_I_BIT | PSR_F_BIT)\n" "stmia r0!, {r13 - r14}\n" "mrs r2, spsr\n" "msr cpsr_c, #0xd7 @(ABT_MODE | PSR_I_BIT | PSR_F_BIT)\n" "stmia r0!, {r2, r13 - r14}\n" "mrs r2, spsr\n" "msr cpsr_c, #0xdb @(UND_MODE | PSR_I_BIT | PSR_F_BIT)\n" "stmia r0!, {r2, r13 - r14}\n" "mrs r2, spsr\n" "msr cpsr_c, #0xd2 @(IRQ_MODE | PSR_I_BIT | PSR_F_BIT)\n" "stmia r0!, {r2, r13 - r14}\n" "mrs r2, spsr\n" "msr cpsr_c, #0xd1 @(FIQ_MODE | PSR_I_BIT | PSR_F_BIT)\n" "stmia r0!, {r2, r8 - r14}\n" "mrs r2, spsr\n" "stmia r0!, {r2}\n" "msr cpsr_c, r1\n" "bx lr\n"); } void fiq_debugger_dump_allregs(struct fiq_debugger_output *output, const struct pt_regs *regs) { struct mode_regs mode_regs; unsigned long mode = regs->ARM_cpsr & MODE_MASK; fiq_debugger_dump_regs(output, regs); get_mode_regs(&mode_regs); output->printf(output, "%csvc: sp %08x lr %08x spsr %08x\n", mode == SVC_MODE ? '*' : ' ', mode_regs.sp_svc, mode_regs.lr_svc, mode_regs.spsr_svc); output->printf(output, "%cabt: sp %08x lr %08x spsr %08x\n", mode == ABT_MODE ? '*' : ' ', mode_regs.sp_abt, mode_regs.lr_abt, mode_regs.spsr_abt); output->printf(output, "%cund: sp %08x lr %08x spsr %08x\n", mode == UND_MODE ? '*' : ' ', mode_regs.sp_und, mode_regs.lr_und, mode_regs.spsr_und); output->printf(output, "%cirq: sp %08x lr %08x spsr %08x\n", mode == IRQ_MODE ? '*' : ' ', mode_regs.sp_irq, mode_regs.lr_irq, mode_regs.spsr_irq); output->printf(output, "%cfiq: r8 %08x r9 %08x r10 %08x r11 %08x r12 %08x\n", mode == FIQ_MODE ? '*' : ' ', mode_regs.r8_fiq, mode_regs.r9_fiq, mode_regs.r10_fiq, mode_regs.r11_fiq, mode_regs.r12_fiq); output->printf(output, " fiq: sp %08x lr %08x spsr %08x\n", mode_regs.sp_fiq, mode_regs.lr_fiq, mode_regs.spsr_fiq); } struct stacktrace_state { struct fiq_debugger_output *output; unsigned int depth; }; static int report_trace(struct stackframe *frame, void *d) { struct stacktrace_state *sts = d; if (sts->depth) { sts->output->printf(sts->output, " pc: %p (%pF), lr %p (%pF), sp %p, fp %p\n", frame->pc, frame->pc, frame->lr, frame->lr, frame->sp, frame->fp); sts->depth--; return 0; } sts->output->printf(sts->output, " ...\n"); return sts->depth == 0; } struct frame_tail { struct frame_tail *fp; unsigned long sp; unsigned long lr; } __attribute__((packed)); static struct frame_tail *user_backtrace(struct fiq_debugger_output *output, struct frame_tail *tail) { struct frame_tail buftail[2]; /* Also check accessibility of one struct frame_tail beyond */ if (!access_ok(VERIFY_READ, tail, sizeof(buftail))) { output->printf(output, " invalid frame pointer %p\n", tail); return NULL; } if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail))) { output->printf(output, " failed to copy frame pointer %p\n", tail); return NULL; } output->printf(output, " %p\n", buftail[0].lr); /* frame pointers should strictly progress back up the stack * (towards higher addresses) */ if (tail >= buftail[0].fp) return NULL; return buftail[0].fp-1; } void fiq_debugger_dump_stacktrace(struct fiq_debugger_output *output, const struct pt_regs *regs, unsigned int depth, void *ssp) { struct frame_tail *tail; struct thread_info *real_thread_info = THREAD_INFO(ssp); struct stacktrace_state sts; sts.depth = depth; sts.output = output; *current_thread_info() = *real_thread_info; if (!current) output->printf(output, "current NULL\n"); else output->printf(output, "pid: %d comm: %s\n", current->pid, current->comm); fiq_debugger_dump_regs(output, regs); if (!user_mode(regs)) { struct stackframe frame; frame.fp = regs->ARM_fp; frame.sp = regs->ARM_sp; frame.lr = regs->ARM_lr; frame.pc = regs->ARM_pc; output->printf(output, " pc: %p (%pF), lr %p (%pF), sp %p, fp %p\n", regs->ARM_pc, regs->ARM_pc, regs->ARM_lr, regs->ARM_lr, regs->ARM_sp, regs->ARM_fp); walk_stackframe(&frame, report_trace, &sts); return; } tail = ((struct frame_tail *) regs->ARM_fp) - 1; while (depth-- && tail && !((unsigned long) tail & 3)) tail = user_backtrace(output, tail); }