| /* |
| * Copyright (C) 2014 Google, Inc. |
| * Author: Colin Cross <ccross@android.com> |
| * |
| * 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 <linux/ptrace.h> |
| #include <linux/uaccess.h> |
| |
| #include <asm/stacktrace.h> |
| |
| #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); |
| } |