From 3a68f6164a4652d027fd2e62d7eb7d5ec5906dbc Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 20 Mar 2015 15:23:53 +0300 Subject: [PATCH] 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 Change-Id: Ib75b50f1bb753b7358fcc08107bfefc3133b4f0c Reviewed-on: http://git-master/r/714784 Reviewed-by: Automatic_Commit_Validation_User GVS: Gerrit_Virtual_Submit Tested-by: Maxim Morin Reviewed-by: Igor Nabirushkin Reviewed-by: Andrey Trachenko Reviewed-by: Bharat Nihalani --- drivers/misc/tegra-profiler/Makefile | 5 +- drivers/misc/tegra-profiler/backtrace.c | 9 +- drivers/misc/tegra-profiler/backtrace.h | 1 + drivers/misc/tegra-profiler/disassembler.c | 379 +++++++++++++++++++++ drivers/misc/tegra-profiler/disassembler.h | 52 +++ drivers/misc/tegra-profiler/dwarf_unwind.c | 3 +- drivers/misc/tegra-profiler/eh_unwind.c | 83 ++++- drivers/misc/tegra-profiler/hrt.c | 1 + drivers/misc/tegra-profiler/version.h | 2 +- include/linux/tegra_profiler.h | 1 + 10 files changed, 510 insertions(+), 26 deletions(-) create mode 100644 drivers/misc/tegra-profiler/disassembler.c create mode 100644 drivers/misc/tegra-profiler/disassembler.h diff --git a/drivers/misc/tegra-profiler/Makefile b/drivers/misc/tegra-profiler/Makefile index bce35f6e422..29aa2988720 100644 --- a/drivers/misc/tegra-profiler/Makefile +++ b/drivers/misc/tegra-profiler/Makefile @@ -10,7 +10,7 @@ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # -# Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved. # ccflags-y += -Werror @@ -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 2d947615978..6ee1de98096 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 724c614a940..a363db69e69 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 00000000000..9196e5bb71a --- /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 +#include +#include + +#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, ""); +} + +/* 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 00000000000..65a693ac6c2 --- /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 2e624352c4b..d48e4b8da81 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 b48765678ae..e3e0955e0bf 100644 --- a/drivers/misc/tegra-profiler/eh_unwind.c +++ b/drivers/misc/tegra-profiler/eh_unwind.c @@ -25,10 +25,13 @@ #include +#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 225937eefcb..4cc595b68fe 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 d3254f99c06..c03ad2ff78e 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 fffa74a7e7d..a289eb7cb13 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, }; -- 2.39.2