377 lines
9.8 KiB
C
377 lines
9.8 KiB
C
|
/*
|
||
|
* drivers/misc/tegra-profiler/disassembler.c
|
||
|
*
|
||
|
* Copyright (c) 2015-2016, 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 "backtrace.h"
|
||
|
#include "disassembler.h"
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
long err;
|
||
|
unsigned long addr;
|
||
|
|
||
|
for (addr = qd->min; addr < qd->max; addr += 4) {
|
||
|
u32 val;
|
||
|
|
||
|
err = read_user_data(&val, (const void __user *)addr,
|
||
|
sizeof(u32));
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
if (((val & 0x0def0ff0) == 0x01a00000) &&
|
||
|
!quadd_stack_found(qd)) {
|
||
|
unsigned int 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 int x = (val >> 12) & 0xf;
|
||
|
unsigned int 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 int x = (val >> 12) & 0xf;
|
||
|
unsigned int 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)
|
||
|
{
|
||
|
long err;
|
||
|
unsigned long addr;
|
||
|
|
||
|
for (addr = qd->min; addr < qd->max; addr += 2) {
|
||
|
u16 val1;
|
||
|
|
||
|
err = read_user_data(&val1, (const void __user *)addr,
|
||
|
sizeof(u16));
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
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;
|
||
|
|
||
|
err = read_user_data(&val2, (const void __user *)
|
||
|
(addr + 2), sizeof(u16));
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
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;
|
||
|
}
|