misc: tegra-profiler: add unwind entry checking
Dmitry Antipov [Fri, 20 Mar 2015 12:23:53 +0000 (15:23 +0300)]
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>

drivers/misc/tegra-profiler/Makefile
drivers/misc/tegra-profiler/backtrace.c
drivers/misc/tegra-profiler/backtrace.h
drivers/misc/tegra-profiler/disassembler.c [new file with mode: 0644]
drivers/misc/tegra-profiler/disassembler.h [new file with mode: 0644]
drivers/misc/tegra-profiler/dwarf_unwind.c
drivers/misc/tegra-profiler/eh_unwind.c
drivers/misc/tegra-profiler/hrt.c
drivers/misc/tegra-profiler/version.h
include/linux/tegra_profiler.h

index 577920f..29aa298 100644 (file)
@@ -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
 
index 2d94761..6ee1de9 100644 (file)
@@ -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;
index 724c614..a363db6 100644 (file)
@@ -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 (file)
index 0000000..9196e5b
--- /dev/null
@@ -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 (file)
index 0000000..65a693a
--- /dev/null
@@ -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 */
index 2e62435..d48e4b8 100644 (file)
@@ -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);
index b487656..e3e0955 100644 (file)
 
 #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;
 
index 214b79c..3c5221a 100644 (file)
@@ -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;
index d3254f9..c03ad2f 100644 (file)
@@ -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 */
index fffa74a..a289eb7 100644 (file)
@@ -192,6 +192,7 @@ enum {
        QUADD_URC_LEVEL_TOO_DEEP,
        QUADD_URC_FP_INCORRECT,
        QUADD_URC_NONE,
+       QUADD_URC_UNWIND_MISMATCH,
        QUADD_URC_MAX,
 };