summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Antipov <dantipov@nvidia.com>2015-03-20 15:23:53 +0300
committerWinnie Hsu <whsu@nvidia.com>2015-05-29 14:27:19 -0700
commit4f59e14109052efb86d911d6fbe229d68e35bf61 (patch)
treecedca1282d3d4e35b2bd02bc4866fb2ddc61be7b
parent7b3154608145006f8f1373ba31b4f0cc9e579054 (diff)
misc: tegra-profiler: add unwind entry checking
Use simple disassembler to verify unwind table entry against function code. Bug 1618651 Signed-off-by: Dmitry Antipov <dantipov@nvidia.com> Change-Id: Ib75b50f1bb753b7358fcc08107bfefc3133b4f0c Reviewed-on: http://git-master/r/714784 (cherry picked from commit 3a68f6164a4652d027fd2e62d7eb7d5ec5906dbc) Reviewed-on: http://git-master/r/748087 GVS: Gerrit_Virtual_Submit Reviewed-by: Andrey Trachenko <atrachenko@nvidia.com> Tested-by: Igor Nabirushkin <inabirushkin@nvidia.com> Reviewed-by: Winnie Hsu <whsu@nvidia.com>
-rw-r--r--drivers/misc/tegra-profiler/Makefile3
-rw-r--r--drivers/misc/tegra-profiler/backtrace.c9
-rw-r--r--drivers/misc/tegra-profiler/backtrace.h1
-rw-r--r--drivers/misc/tegra-profiler/disassembler.c379
-rw-r--r--drivers/misc/tegra-profiler/disassembler.h52
-rw-r--r--drivers/misc/tegra-profiler/dwarf_unwind.c3
-rw-r--r--drivers/misc/tegra-profiler/eh_unwind.c83
-rw-r--r--drivers/misc/tegra-profiler/hrt.c1
-rw-r--r--drivers/misc/tegra-profiler/version.h2
-rw-r--r--include/linux/tegra_profiler.h1
10 files changed, 509 insertions, 25 deletions
diff --git a/drivers/misc/tegra-profiler/Makefile b/drivers/misc/tegra-profiler/Makefile
index 577920fd018c..29aa2988720b 100644
--- a/drivers/misc/tegra-profiler/Makefile
+++ b/drivers/misc/tegra-profiler/Makefile
@@ -29,7 +29,8 @@ tegra-profiler-y := \
auth.o \
quadd_proc.o \
eh_unwind.o \
- dwarf_unwind.o
+ dwarf_unwind.o \
+ disassembler.o
obj-$(CONFIG_CACHE_L2X0) += pl310.o
diff --git a/drivers/misc/tegra-profiler/backtrace.c b/drivers/misc/tegra-profiler/backtrace.c
index 2d947615978d..6ee1de98096d 100644
--- a/drivers/misc/tegra-profiler/backtrace.c
+++ b/drivers/misc/tegra-profiler/backtrace.c
@@ -156,7 +156,7 @@ user_backtrace(struct pt_regs *regs,
cc->curr_fp = value_fp;
cc->curr_sp = (unsigned long)tail + sizeof(value_fp) * 2;
- cc->curr_pc = value_lr;
+ cc->curr_pc = cc->curr_lr = value_lr;
} else {
/* gcc arm frame */
if (__copy_from_user_inatomic(&value_fp, tail - 1,
@@ -170,7 +170,7 @@ user_backtrace(struct pt_regs *regs,
cc->curr_fp = value_fp;
cc->curr_sp = (unsigned long)tail + sizeof(value_fp);
- cc->curr_pc = value_lr = value;
+ cc->curr_pc = cc->curr_lr = value_lr = value;
}
fp_prev = (unsigned long __user *)value_fp;
@@ -342,7 +342,7 @@ user_backtrace_compat(struct pt_regs *regs,
cc->curr_fp = value_fp;
cc->curr_sp = (unsigned long)tail + sizeof(value_fp) * 2;
- cc->curr_pc = value_lr;
+ cc->curr_pc = cc->curr_lr = value_lr;
} else {
/* gcc arm frame */
if (__copy_from_user_inatomic(&value_fp, tail - 1,
@@ -356,7 +356,7 @@ user_backtrace_compat(struct pt_regs *regs,
cc->curr_fp = value_fp;
cc->curr_sp = (unsigned long)tail + sizeof(value_fp);
- cc->curr_pc = value_lr = value;
+ cc->curr_pc = cc->curr_lr = value_lr = value;
}
fp_prev = (u32 __user *)(unsigned long)value_fp;
@@ -576,6 +576,7 @@ quadd_get_user_callchain(struct pt_regs *regs,
cc->curr_fp = 0;
cc->curr_fp_thumb = 0;
cc->curr_pc = 0;
+ cc->curr_lr = 0;
#ifdef CONFIG_ARM64
cc->cs_64 = compat_user_mode(regs) ? 0 : 1;
diff --git a/drivers/misc/tegra-profiler/backtrace.h b/drivers/misc/tegra-profiler/backtrace.h
index 724c614a9408..a363db69e69c 100644
--- a/drivers/misc/tegra-profiler/backtrace.h
+++ b/drivers/misc/tegra-profiler/backtrace.h
@@ -57,6 +57,7 @@ struct quadd_callchain {
unsigned long curr_fp;
unsigned long curr_fp_thumb;
unsigned long curr_pc;
+ unsigned long curr_lr;
struct quadd_hrt_ctx *hrt;
};
diff --git a/drivers/misc/tegra-profiler/disassembler.c b/drivers/misc/tegra-profiler/disassembler.c
new file mode 100644
index 000000000000..9196e5bb71a4
--- /dev/null
+++ b/drivers/misc/tegra-profiler/disassembler.c
@@ -0,0 +1,379 @@
+/*
+ * 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;
+}
diff --git a/drivers/misc/tegra-profiler/disassembler.h b/drivers/misc/tegra-profiler/disassembler.h
new file mode 100644
index 000000000000..65a693ac6c21
--- /dev/null
+++ b/drivers/misc/tegra-profiler/disassembler.h
@@ -0,0 +1,52 @@
+/*
+ * drivers/misc/tegra-profiler/disassembler.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef __QUADD_DISASSEMBLER_H
+#define __QUADD_DISASSEMBLER_H
+
+/* By default we disassemble at most 128 bytes from the beginning
+ of the function... */
+#define QUADD_DISASM_MAX 128
+
+/* ...but not less than 16 bytes to get meaningful results. */
+#define QUADD_DISASM_MIN 16
+
+struct quadd_disasm_data {
+ int thumb;
+ unsigned long min, max;
+ int r_regset;
+ int d_regset;
+ /* Used if stack is adjusted with sub sp, sp, [stacksize] (ARM)
+ or sub sp, [stacksize] (Thumb). */
+ long stacksize;
+ /* Otherwise used if sp is saved with add [stackreg], sp, [stackoff]
+ or mov [stackreg], sp. */
+ int stackreg;
+ long stackoff;
+ /* If the latter, actual register used to restore sp during unwind. */
+ int ustackreg;
+#ifdef QM_DEBUG_DISASSEMBLER
+ int stackmethod;
+ struct quadd_disasm_data *orig;
+#endif
+};
+
+extern long quadd_disassemble(struct quadd_disasm_data *, unsigned long,
+ unsigned long, int);
+extern long quadd_check_unwind_result(unsigned long,
+ struct quadd_disasm_data *);
+
+#endif /* __QUADD_DISASSEMBLER_H */
diff --git a/drivers/misc/tegra-profiler/dwarf_unwind.c b/drivers/misc/tegra-profiler/dwarf_unwind.c
index 2e624352c4b7..d48e4b8da817 100644
--- a/drivers/misc/tegra-profiler/dwarf_unwind.c
+++ b/drivers/misc/tegra-profiler/dwarf_unwind.c
@@ -1989,6 +1989,7 @@ unwind_backtrace(struct quadd_callchain *cc,
cc->curr_fp_thumb = sf->vregs[ARM32_FP_THUMB];
cc->curr_pc = sf->pc;
+ cc->curr_lr = sf->vregs[regnum_lr(mode)];
nr_added = quadd_callchain_store(cc, sf->pc, unw_type);
if (nr_added == 0)
@@ -2048,7 +2049,7 @@ quadd_get_user_cc_dwarf(struct pt_regs *regs,
sp = cc->curr_sp;
fp = cc->curr_fp;
fp_thumb = cc->curr_fp_thumb;
- lr = 0;
+ lr = cc->curr_lr;
} else {
ip = instruction_pointer(regs);
lr = quadd_user_link_register(regs);
diff --git a/drivers/misc/tegra-profiler/eh_unwind.c b/drivers/misc/tegra-profiler/eh_unwind.c
index b48765678aee..e3e0955e0bff 100644
--- a/drivers/misc/tegra-profiler/eh_unwind.c
+++ b/drivers/misc/tegra-profiler/eh_unwind.c
@@ -25,10 +25,13 @@
#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
@@ -639,7 +642,7 @@ error_out:
}
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;
@@ -683,6 +686,9 @@ unwind_find_idx(struct ex_region_info *ri, u32 addr)
start = mid;
}
+ if (lowaddr)
+ *lowaddr = mmap_prel31_to_addr(&start->addr_offset,
+ ri, 1, 0, 0);
return start;
}
@@ -755,7 +761,8 @@ read_uleb128(struct quadd_mmap_area *mmap,
*/
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;
@@ -768,12 +775,13 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
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) {
@@ -800,6 +808,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
}
mask >>= 1;
@@ -812,6 +821,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
} 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];
@@ -823,6 +833,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%u}\n", reg);
}
@@ -831,6 +842,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << 14);
pr_debug("CMD_REG_POP: pop {r14}\n");
}
@@ -864,6 +876,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
if (err < 0)
return err;
+ qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
}
mask >>= 1;
@@ -885,6 +898,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
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]);
@@ -905,7 +919,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
}
for (i = reg_from; i <= reg_to; i++)
- vsp += 2;
+ vsp += 2, qd->d_regset &= ~(1 << i);
if (insn == 0xb3)
vsp++;
@@ -923,7 +937,7 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
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++;
@@ -951,13 +965,19 @@ unwind_exec_insn(struct quadd_mmap_area *mmap,
* 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;
@@ -968,10 +988,10 @@ unwind_frame(struct ex_region_info *ri,
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;
@@ -1025,8 +1045,25 @@ unwind_frame(struct ex_region_info *ri,
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;
@@ -1035,6 +1072,9 @@ unwind_frame(struct ex_region_info *ri,
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];
@@ -1056,7 +1096,8 @@ unwind_backtrace(struct quadd_callchain *cc,
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;
@@ -1101,13 +1142,16 @@ unwind_backtrace(struct quadd_callchain *cc,
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);
@@ -1115,6 +1159,7 @@ unwind_backtrace(struct quadd_callchain *cc,
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);
@@ -1129,7 +1174,7 @@ quadd_get_user_cc_arm32_ehabi(struct pt_regs *regs,
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;
@@ -1152,7 +1197,8 @@ quadd_get_user_cc_arm32_ehabi(struct pt_regs *regs,
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;
@@ -1160,6 +1206,7 @@ quadd_get_user_cc_arm32_ehabi(struct pt_regs *regs,
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);
@@ -1192,7 +1239,7 @@ quadd_get_user_cc_arm32_ehabi(struct pt_regs *regs,
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);
@@ -1223,7 +1270,7 @@ quadd_is_ex_entry_exist_arm32_ehabi(struct pt_regs *regs,
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;
diff --git a/drivers/misc/tegra-profiler/hrt.c b/drivers/misc/tegra-profiler/hrt.c
index 214b79c56aeb..3c5221abd4c9 100644
--- a/drivers/misc/tegra-profiler/hrt.c
+++ b/drivers/misc/tegra-profiler/hrt.c
@@ -406,6 +406,7 @@ read_all_sources(struct pt_regs *regs, struct task_struct *task)
cc->curr_sp = 0;
cc->curr_fp = 0;
cc->curr_pc = 0;
+ cc->curr_lr = 0;
if (ctx->param.backtrace) {
cc->um = hrt.um;
diff --git a/drivers/misc/tegra-profiler/version.h b/drivers/misc/tegra-profiler/version.h
index d3254f99c063..c03ad2ff78ec 100644
--- a/drivers/misc/tegra-profiler/version.h
+++ b/drivers/misc/tegra-profiler/version.h
@@ -18,7 +18,7 @@
#ifndef __QUADD_VERSION_H
#define __QUADD_VERSION_H
-#define QUADD_MODULE_VERSION "1.97"
+#define QUADD_MODULE_VERSION "1.98"
#define QUADD_MODULE_BRANCH "Dev"
#endif /* __QUADD_VERSION_H */
diff --git a/include/linux/tegra_profiler.h b/include/linux/tegra_profiler.h
index fffa74a7e7df..a289eb7cb13e 100644
--- a/include/linux/tegra_profiler.h
+++ b/include/linux/tegra_profiler.h
@@ -192,6 +192,7 @@ enum {
QUADD_URC_LEVEL_TOO_DEEP,
QUADD_URC_FP_INCORRECT,
QUADD_URC_NONE,
+ QUADD_URC_UNWIND_MISMATCH,
QUADD_URC_MAX,
};