--- /dev/null
+/*
+ * drivers/misc/tegra-profiler/disassembler.c
+ *
+ * Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+/* To debug this code, define DEBUG here and QM_DEBUG_DISASSEMBLER
+ at the beginning of disassembler.h. */
+
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/tegra_profiler.h>
+
+#include "tegra.h"
+#include "disassembler.h"
+
+/* FIXME: grossly duplicated */
+
+#define read_user_data(addr, retval) \
+({ \
+ long ret; \
+ \
+ pagefault_disable(); \
+ ret = __get_user(retval, addr); \
+ pagefault_enable(); \
+ \
+ if (ret) { \
+ pr_debug("%s: failed for address: %p\n", \
+ __func__, addr); \
+ ret = -QUADD_URC_EACCESS; \
+ } \
+ \
+ ret; \
+})
+
+static long
+quadd_arm_imm(u32 val)
+{
+ unsigned int rot = (val & 0xf00) >> 7, imm = (val & 0xff);
+ return ((imm << (32 - rot)) | (imm >> rot)) & 0xffffffff;
+}
+
+static int
+quadd_stack_found(struct quadd_disasm_data *qd)
+{
+ return qd->stackreg != -1 || qd->stacksize != 0;
+}
+
+static void
+quadd_print_reg(char *buf, size_t size, int reg)
+{
+ if (reg != -1)
+ snprintf(buf, size, "r%d", reg);
+ else
+ snprintf(buf, size, "<unused>");
+}
+
+/* Search interesting ARM insns in [qd->min...qd->max),
+ may preliminary stop at unconditional branch or pop. */
+
+static long
+quadd_disassemble_arm(struct quadd_disasm_data *qd)
+{
+ unsigned long addr;
+
+ for (addr = qd->min; addr < qd->max; addr += 4) {
+ u32 val;
+
+ if (read_user_data((const u32 __user *) addr, val) < 0)
+ return -QUADD_URC_EACCESS;
+
+ if (((val & 0x0def0ff0) == 0x01a00000) &&
+ !quadd_stack_found(qd)) {
+ unsigned x = (val >> 12) & 0xf, y = (val & 0xf);
+
+ if (y == 13 && x != y) {
+ /* mov x, sp, where x != sp */
+ qd->stackreg = x;
+ qd->stackoff = 0;
+#ifdef QM_DEBUG_DISASSEMBLER
+ qd->stackmethod = 1;
+#endif
+ }
+ } else if ((((val & 0x0fe00000) == 0x02800000) ||
+ ((val & 0x0fe00010) == 0x00800000) ||
+ ((val & 0x0fe00090) == 0x00800010)) &&
+ !quadd_stack_found(qd)) {
+ unsigned x = (val >> 12) & 0xf, y = (val >> 16) & 0xf;
+
+ if (y == 13 && x != y) {
+ /* add x, sp, i, where x != sp */
+ qd->stackreg = x;
+ qd->stackoff = quadd_arm_imm(val);
+#ifdef QM_DEBUG_DISASSEMBLER
+ qd->stackmethod = 2;
+#endif
+ }
+ } else if ((((val & 0x0fe00000) == 0x02400000) ||
+ ((val & 0x0fe00010) == 0x00400000)) &&
+ !quadd_stack_found(qd)) {
+ unsigned x = (val >> 12) & 0xf, y = (val >> 16) & 0xf;
+
+ if (x == 13 && y == 13) {
+ /* sub sp, sp, imm */
+ qd->stacksize += quadd_arm_imm(val);
+#ifdef QM_DEBUG_DISASSEMBLER
+ qd->stackmethod = 3;
+#endif
+ }
+ } else if ((val & 0x0fff0000) == 0x092d0000) {
+ /* push [regset] */
+ qd->r_regset |= (val & 0xffff);
+ } else if ((val & 0x0fff0fff) == 0x052d0004) {
+ /* push [reg] */
+ qd->r_regset |= (1 << ((val >> 12) & 0xf));
+ } else if ((val & 0x0fbf0f01) == 0x0d2d0b00) {
+ /* vpush [reg/reg+off-1] */
+ int reg = ((val >> 12) & 0xf) | ((val >> 18) & 0x10);
+ int off = (val >> 1) & 0x3f;
+
+ if (off == 1)
+ qd->d_regset |= (1 << reg);
+ else if (reg + off <= 32) {
+ int i;
+
+ for (i = reg; i < reg + off; i++)
+ qd->d_regset |= (1 << i);
+ }
+ } else if ((((val & 0x0e000000) == 0x0a000000) ||
+ ((val & 0x0ffffff0) == 0x012fff10)) &&
+ ((val >> 28) & 0xf) == 0xe) {
+ /* b, bl, bx, blx */
+ qd->max = addr + 4;
+ break;
+ } else if (((val & 0x0fff0fff) == 0x049d0004) ||
+ ((val & 0x0fff0000) == 0x08bd0000)) {
+ /* pop [reg], pop [regset] */
+ qd->max = addr + 4;
+ break;
+ }
+ }
+ return QUADD_URC_SUCCESS;
+}
+
+/* Likewise for Thumb. */
+
+static long
+quadd_disassemble_thumb(struct quadd_disasm_data *qd)
+{
+ unsigned long addr;
+
+ for (addr = qd->min; addr < qd->max; addr += 2) {
+ u16 val1;
+
+ if (read_user_data((const u16 __user *) addr, val1) < 0)
+ return -QUADD_URC_EACCESS;
+
+ if ((val1 & 0xf800) == 0xa800 && !quadd_stack_found(qd)) {
+ /* add x, sp, i */
+ qd->stackreg = ((val1 >> 8) & 0x7);
+ qd->stackoff = ((val1 & 0xff) * 4);
+#ifdef QM_DEBUG_DISASSEMBLER
+ qd->stackmethod = 1;
+#endif
+ } else if ((val1 & 0xff80) == 0xb080 &&
+ !quadd_stack_found(qd)) {
+ /* sub sp, imm */
+ qd->stacksize += (val1 & 0x7f) * 4;
+#ifdef QM_DEBUG_DISASSEMBLER
+ qd->stackmethod = 2;
+#endif
+ } else if ((val1 & 0xfe00) == 0xb400) {
+ /* push [regset] */
+ qd->r_regset |= (val1 & 0xff);
+ if (val1 & (1 << 8))
+ /* LR is special */
+ qd->r_regset |= (1 << 14);
+ } else if ((val1 & 0xff80) == 0x4700 ||
+ (val1 & 0xff87) == 0x4780 ||
+ ((val1 & 0xf000) == 0xd000 &&
+ ((val1 >> 8) & 0xf) == 0xe) ||
+ (val1 & 0xf800) == 0xe000) {
+ /* bx, blx, b(1), b(2) */
+ qd->max = addr + 2;
+ break;
+ } else if ((val1 & 0xfe00) == 0xbc00) {
+ /* pop */
+ qd->max = addr + 2;
+ break;
+ } else if (((val1 & 0xf800) == 0xf800
+ || (val1 & 0xf800) == 0xf000
+ || (val1 & 0xf800) == 0xe800)
+ && addr + 2 < qd->max) {
+ /* 4-byte insn still in range */
+ u16 val2;
+ u32 val;
+
+ if (read_user_data((const u16 __user *)
+ (addr + 2), val2) < 0)
+ return -QUADD_URC_EACCESS;
+ val = (val1 << 16) | val2;
+ addr += 2;
+
+ if ((val & 0xfbe08000) == 0xf1a00000
+ && ((val >> 8) & 0xf) == 13
+ && ((val >> 16) & 0xf) == 13) {
+ /* sub.w sp, sp, imm */
+ unsigned int bits = 0, imm, imm8, mod;
+
+ bits |= (val & 0x000000ff);
+ bits |= (val & 0x00007000) >> 4;
+ bits |= (val & 0x04000000) >> 15;
+ imm8 = (bits & 0x0ff);
+ mod = (bits & 0xf00) >> 8;
+ if (mod == 0)
+ imm = imm8;
+ else if (mod == 1)
+ imm = ((imm8 << 16) | imm8);
+ else if (mod == 2)
+ imm = ((imm8 << 24) | (imm8 << 8));
+ else if (mod == 3)
+ imm = ((imm8 << 24) | (imm8 << 16) |
+ (imm8 << 8) | imm8);
+ else {
+ mod = (bits & 0xf80) >> 7;
+ imm8 = (bits & 0x07f) | 0x80;
+ imm = (((imm8 << (32 - mod)) |
+ (imm8 >> mod)) & 0xffffffff);
+ }
+ qd->stacksize += imm;
+ } else if ((val & 0x0fbf0f01) == 0x0d2d0b00) {
+ /* vpush [reg/reg+off-1] */
+ int reg = (((val >> 12) & 0xf) |
+ ((val >> 18) & 0x10));
+ int off = (val >> 1) & 0x3f;
+
+ if (off == 1)
+ qd->d_regset |= (1 << reg);
+ else if (reg + off <= 32) {
+ int i;
+
+ for (i = reg; i < reg + off; i++)
+ qd->d_regset |= (1 << i);
+ }
+ } else if ((val & 0xffd00000) == 0xe9000000
+ && ((val >> 16) & 0xf) == 13) {
+ /* stmdb sp!,[regset] */
+ qd->r_regset |= (val & 0xffff);
+ } else if (((val & 0xf800d000) == 0xf0009000 ||
+ (val & 0xf800d001) == 0xf000c000 ||
+ (val & 0xf800d000) == 0xf000d000 ||
+ (val & 0xf800d000) == 0xf0008000) &&
+ ((val >> 28) & 0xf) >= 0xe) {
+ /* b.w, bl, blx */
+ qd->max = addr + 2;
+ break;
+ } else if ((val & 0xffd00000) == 0xe8900000) {
+ /* ldmia.w */
+ qd->max = addr + 2;
+ break;
+ }
+ }
+ }
+ return QUADD_URC_SUCCESS;
+}
+
+/* Wrapper for the two above, depend on arm/thumb mode. */
+
+long
+quadd_disassemble(struct quadd_disasm_data *qd, unsigned long min,
+ unsigned long max, int thumbflag)
+{
+ qd->thumb = thumbflag;
+ qd->min = min;
+ qd->max = max;
+ qd->stackreg = qd->ustackreg = -1;
+ qd->stackoff = qd->stacksize = qd->r_regset = qd->d_regset = 0;
+#ifdef QM_DEBUG_DISASSEMBLER
+ qd->stackmethod = 0;
+#endif
+ return thumbflag ? quadd_disassemble_thumb(qd) :
+ quadd_disassemble_arm(qd);
+}
+
+#ifdef QM_DEBUG_DISASSEMBLER
+
+static void
+quadd_disassemble_debug(unsigned long pc, struct quadd_disasm_data *qd)
+{
+ int i;
+ char msg[256], *p = msg, *end = p + sizeof(msg);
+
+ pr_debug(" pc %#lx: disassembled in %#lx..%#lx as %s:\n",
+ pc, qd->min, qd->max, (qd->thumb ? "thumb" : "arm"));
+
+ if (quadd_stack_found(qd)) {
+ char regname[16];
+
+ quadd_print_reg(regname, sizeof(regname), qd->stackreg);
+
+ p += snprintf(p, end - p, " method %d", qd->stackmethod);
+ p += snprintf(p, end - p,
+ ", stackreg %s, stackoff %ld, stacksize %ld",
+ regname, qd->stackoff, qd->stacksize);
+ } else
+ p += snprintf(p, end - p, " stack is not used");
+
+ if (qd->r_regset) {
+ p += snprintf(p, end - p, ", core registers:");
+ for (i = 0; i < 16; i++)
+ if (qd->r_regset & (1 << i))
+ p += snprintf(p, end - p, " r%d", i);
+ } else
+ p += snprintf(p, end - p, ", core registers are not saved");
+
+ if (qd->d_regset) {
+ p += snprintf(p, end - p, ", fp registers:");
+ for (i = 0; i < 32; i++)
+ if (qd->d_regset & (1 << i))
+ p += snprintf(p, end - p, " d%d", i);
+ } else
+ p += snprintf(p, end - p, ", fp registers are not saved");
+ pr_debug("%s\n", msg);
+}
+
+#endif /* QM_DEBUG_DISASSEMBLER */
+
+/* If we unwind less stack space than found, or don't unwind a register,
+ or restore sp from wrong register with wrong offset, something is bad. */
+
+long
+quadd_check_unwind_result(unsigned long pc, struct quadd_disasm_data *qd)
+{
+ if (qd->stacksize > 0 || qd->r_regset != 0 || qd->d_regset != 0 ||
+ (qd->stackreg != qd->ustackreg) || qd->stackoff != 0) {
+ int i;
+ char regname[16], uregname[16];
+
+ pr_debug("in %s code at %#lx, unwind %#lx..%#lx mismatch:",
+ (qd->thumb ? "thumb" : "arm"), pc, qd->min, qd->max);
+
+ quadd_print_reg(regname, sizeof(regname), qd->stackreg);
+ quadd_print_reg(uregname, sizeof(uregname), qd->ustackreg);
+
+ pr_debug(" stackreg %s/%s, stackoff %ld\n",
+ regname, uregname, qd->stackoff);
+
+ if (qd->stacksize > 0)
+ pr_debug(" %ld stack bytes was not unwound\n",
+ qd->stacksize);
+ if (qd->r_regset != 0) {
+ for (i = 0; i < 16; i++)
+ if (qd->r_regset & (1 << i))
+ pr_debug(" r%d was not unwound\n", i);
+ }
+ if (qd->d_regset != 0) {
+ for (i = 0; i < 32; i++)
+ if (qd->d_regset & (1 << i))
+ pr_debug(" d%d was not unwound\n", i);
+ }
+#ifdef QM_DEBUG_DISASSEMBLER
+ quadd_disassemble_debug(pc, qd->orig);
+#endif
+ return -QUADD_URC_UNWIND_MISMATCH;
+ }
+ return QUADD_URC_SUCCESS;
+}
#include <linux/tegra_profiler.h>
+#include "hrt.h"
+#include "tegra.h"
#include "eh_unwind.h"
#include "backtrace.h"
#include "comm.h"
#include "dwarf_unwind.h"
+#include "disassembler.h"
#define QUADD_EXTABS_SIZE 0x100
}
static const struct unwind_idx *
-unwind_find_idx(struct ex_region_info *ri, u32 addr)
+unwind_find_idx(struct ex_region_info *ri, u32 addr, unsigned long *lowaddr)
{
u32 value;
unsigned long length;
start = mid;
}
+ if (lowaddr)
+ *lowaddr = mmap_prel31_to_addr(&start->addr_offset,
+ ri, 1, 0, 0);
return start;
}
*/
static long
unwind_exec_insn(struct quadd_mmap_area *mmap,
- struct unwind_ctrl_block *ctrl)
+ struct unwind_ctrl_block *ctrl,
+ struct quadd_disasm_data *qd)
{
long err;
unsigned int i;
if ((insn & 0xc0) == 0x00) {
ctrl->vrs[SP] += ((insn & 0x3f) << 2) + 4;
+ qd->stacksize -= ((insn & 0x3f) << 2) + 4;
pr_debug("CMD_DATA_POP: vsp = vsp + %lu (new: %#x)\n",
((insn & 0x3f) << 2) + 4, ctrl->vrs[SP]);
} else if ((insn & 0xc0) == 0x40) {
ctrl->vrs[SP] -= ((insn & 0x3f) << 2) + 4;
-
+ qd->stackoff -= ((insn & 0x3f) << 2) + 4;
pr_debug("CMD_DATA_PUSH: vsp = vsp – %lu (new: %#x)\n",
((insn & 0x3f) << 2) + 4, ctrl->vrs[SP]);
} else if ((insn & 0xf0) == 0x80) {
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
}
mask >>= 1;
} else if ((insn & 0xf0) == 0x90 &&
(insn & 0x0d) != 0x0d) {
ctrl->vrs[SP] = ctrl->vrs[insn & 0x0f];
+ qd->ustackreg = (insn & 0xf);
pr_debug("CMD_REG_TO_SP: vsp = {r%lu}\n", insn & 0x0f);
} else if ((insn & 0xf0) == 0xa0) {
u32 __user *vsp = (u32 __user *)(unsigned long)ctrl->vrs[SP];
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%u}\n", reg);
}
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << 14);
pr_debug("CMD_REG_POP: pop {r14}\n");
}
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
}
mask >>= 1;
ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
+ qd->stacksize -= 0x204 + (uleb128 << 2);
pr_debug("CMD_DATA_POP: vsp = vsp + %lu (%#lx), new vsp: %#x\n",
0x204 + (uleb128 << 2), 0x204 + (uleb128 << 2),
ctrl->vrs[SP]);
}
for (i = reg_from; i <= reg_to; i++)
- vsp += 2;
+ vsp += 2, qd->d_regset &= ~(1 << i);
if (insn == 0xb3)
vsp++;
reg_to = 8 + data;
for (i = 8; i <= reg_to; i++)
- vsp += 2;
+ vsp += 2, qd->d_regset &= ~(1 << i);
if ((insn & 0xf8) == 0xb8)
vsp++;
* updates the *pc and *sp with the new values.
*/
static long
-unwind_frame(struct ex_region_info *ri,
+unwind_frame(struct quadd_unw_methods um,
+ struct ex_region_info *ri,
struct stackframe *frame,
- struct vm_area_struct *vma_sp)
+ struct vm_area_struct *vma_sp,
+ int thumbflag)
{
- unsigned long high, low;
+ unsigned long high, low, min, max;
const struct unwind_idx *idx;
struct unwind_ctrl_block ctrl;
+ struct quadd_disasm_data qd;
+#ifdef QM_DEBUG_DISASSEMBLER
+ struct quadd_disasm_data orig;
+#endif
long err = 0;
u32 val;
low = frame->sp;
high = vma_sp->vm_end;
- pr_debug("pc: %#lx, lr: %#lx, sp:%#lx, low/high: %#lx/%#lx\n",
- frame->pc, frame->lr, frame->sp, low, high);
+ pr_debug("pc: %#lx, lr: %#lx, sp:%#lx, low/high: %#lx/%#lx, thumb: %d\n",
+ frame->pc, frame->lr, frame->sp, low, high, thumbflag);
- idx = unwind_find_idx(ri, frame->pc);
+ idx = unwind_find_idx(ri, frame->pc, &min);
if (IS_ERR_OR_NULL(idx))
return -QUADD_URC_IDX_NOT_FOUND;
return -QUADD_URC_UNSUPPORTED_PR;
}
+ if (um.ut_ce) {
+ /* guess for the boundaries to disassemble */
+ if (frame->pc - min < QUADD_DISASM_MIN)
+ max = min + QUADD_DISASM_MIN;
+ else
+ max = (frame->pc - min < QUADD_DISASM_MAX)
+ ? frame->pc : min + QUADD_DISASM_MAX;
+ err = quadd_disassemble(&qd, min, max, thumbflag);
+ if (err < 0)
+ return err;
+#ifdef QM_DEBUG_DISASSEMBLER
+ /* saved for verbose unwind mismatch reporting */
+ orig = qd;
+ qd.orig = &orig;
+#endif
+ }
+
while (ctrl.entries > 0) {
- err = unwind_exec_insn(ri->mmap, &ctrl);
+ err = unwind_exec_insn(ri->mmap, &ctrl, &qd);
if (err < 0)
return err;
return -QUADD_URC_SP_INCORRECT;
}
+ if (um.ut_ce && quadd_check_unwind_result(frame->pc, &qd) < 0)
+ return -QUADD_URC_UNWIND_MISMATCH;
+
if (ctrl.vrs[PC] == 0)
ctrl.vrs[PC] = ctrl.vrs[LR];
struct ex_region_info *ri,
struct stackframe *frame,
struct vm_area_struct *vma_sp,
- struct task_struct *task)
+ struct task_struct *task,
+ int thumbflag)
{
struct ex_region_info ri_new;
ri = &ri_new;
}
- err = unwind_frame(ri, frame, vma_sp);
+ err = unwind_frame(cc->um, ri, frame, vma_sp, thumbflag);
if (err < 0) {
pr_debug("end unwind, urc: %ld\n", err);
cc->urc_ut = -err;
break;
}
+ /* determine whether outer frame is ARM or Thumb */
+ thumbflag = (frame->lr & 0x1);
+
pr_debug("function at [<%08lx>] from [<%08lx>]\n",
where, frame->pc);
cc->curr_fp = frame->fp_arm;
cc->curr_fp_thumb = frame->fp_thumb;
cc->curr_pc = frame->pc;
+ cc->curr_lr = frame->lr;
nr_added = quadd_callchain_store(cc, frame->pc,
QUADD_UNW_TYPE_UT);
struct task_struct *task)
{
long err;
- int nr_prev = cc->nr;
+ int nr_prev = cc->nr, thumbflag;
unsigned long ip, sp, lr;
struct vm_area_struct *vma, *vma_sp;
struct mm_struct *mm = task->mm;
if (nr_prev > 0) {
ip = cc->curr_pc;
sp = cc->curr_sp;
- lr = 0;
+ lr = cc->curr_lr;
+ thumbflag = (lr & 1);
frame.fp_thumb = cc->curr_fp_thumb;
frame.fp_arm = cc->curr_fp;
ip = instruction_pointer(regs);
sp = quadd_user_stack_pointer(regs);
lr = quadd_user_link_register(regs);
+ thumbflag = is_thumb_mode(regs);
#ifdef CONFIG_ARM64
frame.fp_thumb = regs->compat_usr(7);
return 0;
}
- unwind_backtrace(cc, &ri, &frame, vma_sp, task);
+ unwind_backtrace(cc, &ri, &frame, vma_sp, task, thumbflag);
pr_debug("%s: exit, cc->nr: %d --> %d\n",
__func__, nr_prev, cc->nr);
if (err)
return 0;
- idx = unwind_find_idx(&ri, addr);
+ idx = unwind_find_idx(&ri, addr, NULL);
if (IS_ERR_OR_NULL(idx))
return 0;