diff options
-rw-r--r-- | arch/arm/kernel/perf_event.c | 320 | ||||
-rw-r--r-- | init/Kconfig | 14 |
2 files changed, 333 insertions, 1 deletions
diff --git a/arch/arm/kernel/perf_event.c b/arch/arm/kernel/perf_event.c index e19edc6f2d15..44d9e574db05 100644 --- a/arch/arm/kernel/perf_event.c +++ b/arch/arm/kernel/perf_event.c @@ -5,6 +5,7 @@ * * Copyright (C) 2009 picoChip Designs, Ltd., Jamie Iles * Copyright (C) 2010 ARM Ltd., Will Deacon <will.deacon@arm.com> + * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. * * This code is based on the sparc64 perf event code, which is in turn based * on the x86 code. Callchain code is based on the ARM OProfile backtrace @@ -533,11 +534,18 @@ int armpmu_register(struct arm_pmu *armpmu, int type) * * This code has been adapted from the ARM OProfile support. */ +#ifndef CONFIG_PERF_ANDROID_BACKTRACE struct frame_tail { struct frame_tail __user *fp; unsigned long sp; unsigned long lr; -} __attribute__((packed)); +} __packed; +#else +struct frame_tail { + unsigned long fp; + unsigned long lr; +} __packed; +#endif /* * Get the return address for a single stackframe and return a pointer to the @@ -557,6 +565,7 @@ user_backtrace(struct frame_tail __user *tail, perf_callchain_store(entry, buftail.lr); +#ifndef CONFIG_PERF_ANDROID_BACKTRACE /* * Frame pointers should strictly progress back up the stack * (towards higher addresses). @@ -565,8 +574,233 @@ user_backtrace(struct frame_tail __user *tail, return NULL; return buftail.fp - 1; +#else + /* + * Frame pointers should strictly progress back up the stack + * (towards higher addresses). + */ + if ((unsigned long *)(tail + 1) >= (unsigned long *)buftail.fp) + return NULL; + + return (struct frame_tail *)(buftail.fp - sizeof(unsigned long)); +#endif +} + + +#ifdef CONFIG_PERF_ANDROID_BACKTRACE + +#define AB_USER_SPACE_MIN_ADDR 0x8000 +#define PERF_CALLCHAIN_CHECK_PROLOG_EPILOG 1 + +#ifdef PERF_CALLCHAIN_CHECK_PROLOG_EPILOG + +enum regs { +#ifdef CONFIG_THUMB2_KERNEL + FP = 7, +#else + FP = 11, +#endif + SP = 13, + LR = 14, + PC = 15 +}; + +#define INSTR_MASK_STR_FP 0xffff0000 +#define INSTR_STR_FP 0xe52d0000 + +#define INSTR_MASK_STMDB 0xffff0000 +#define INSTR_STMDB_SP 0xe92d0000 + +#define INSTR_MASK_LDMIA 0x0fff0000 +#define INSTR_LDMIA_SP 0x08bd0000 + +#define INSTR_MASK_ADD_RN_RD 0xfffff000 +#define INSTR_ADD_FP_SP 0xe28db000 + +#define INSTR_MASK_BRANCH 0x0f000000 +#define INSTR_BRANCH 0x0a000000 + +#define INSTR_MASK_BRANCH_L 0x0f000000 +#define INSTR_BRANCH_L 0x0b000000 + +#define INSTR_MASK_BRANCH_X 0x0ffffff0 +#define INSTR_BRANCH_X 0x012fff10 + +#define INSTR_MASK_BRANCH_LX 0x0ffffff0 +#define INSTR_BRANCH_LX 0x012fff30 + +#define check_in_register_list(val, reg) \ + (((val) & 0xffff) & (1 << (reg))) + +static inline int is_instr_push_with_fp(unsigned long instr) +{ + if ((instr & INSTR_MASK_STMDB) == INSTR_STMDB_SP) { + if (check_in_register_list(instr, SP) || + check_in_register_list(instr, PC)) { + return -1; + } + if (check_in_register_list(instr, FP)) + return 1; + } + + if ((instr & INSTR_MASK_STR_FP) == INSTR_STR_FP) + return 1; + + return 0; +} + +static inline int is_instr_push_with_fp_lr(unsigned long instr) +{ + if ((instr & INSTR_MASK_STMDB) == INSTR_STMDB_SP) { + if (check_in_register_list(instr, SP) || + check_in_register_list(instr, PC)) { + return -1; + } + if (check_in_register_list(instr, FP) && + check_in_register_list(instr, LR)) { + return 1; + } + } + return 0; +} + +#define is_instr_add_fp(instr) \ + ((((instr) & INSTR_MASK_ADD_RN_RD) == INSTR_ADD_FP_SP)) + +#define is_instr_pop(instr) \ + ((((instr) & INSTR_MASK_LDMIA) == INSTR_LDMIA_SP)) + +#define is_instr_pop_fp(instr) \ + ((((instr) & INSTR_MASK_LDMIA) == INSTR_LDMIA_SP) && \ + check_in_register_list(instr, FP)) \ + +#define is_instr_branch(instr) \ + ((((instr) & INSTR_MASK_BRANCH) == INSTR_BRANCH) || \ + (((instr) & INSTR_MASK_BRANCH_L) == INSTR_BRANCH_L) || \ + (((instr) & INSTR_MASK_BRANCH_X) == INSTR_BRANCH_X) || \ + (((instr) & INSTR_MASK_BRANCH_LX) == INSTR_BRANCH_LX)) + +enum { + INSTR_TYPE_PUSH_FP, + INSTR_TYPE_PUSH_FP_LR, + INSTR_TYPE_ADD_FP, + INSTR_TYPE_POP, + INSTR_TYPE_POP_FP, + INSTR_TYPE_BRANCH, + INSTR_TYPE_UNKNOWN, +}; + +#define MAX_CHECK_INSTR_LENGTH 5 + +static unsigned long instr_before[MAX_CHECK_INSTR_LENGTH]; +static int instr_before_nr; + +static unsigned long instr_after[MAX_CHECK_INSTR_LENGTH]; +static int instr_after_nr; + +static int read_instructions(unsigned long *addr, int direction) +{ + int i, delta, type, nr_instr = 0; + unsigned long instr, *c_addr = addr; + unsigned long *instr_p; + + if (direction) { + delta = 1; + instr_p = instr_after; + } else { + delta = -1; + instr_p = instr_before; + } + + for (i = 0; i < MAX_CHECK_INSTR_LENGTH; i++) { + if (get_user(instr, c_addr)) + return -1; + + type = INSTR_TYPE_UNKNOWN; + if (is_instr_push_with_fp(instr)) + type = INSTR_TYPE_PUSH_FP; + else if (is_instr_push_with_fp_lr(instr)) + type = INSTR_TYPE_PUSH_FP_LR; + else if (is_instr_add_fp(instr)) + type = INSTR_TYPE_ADD_FP; + else if (is_instr_pop_fp(instr)) + type = INSTR_TYPE_POP_FP; + else if (is_instr_pop(instr)) + type = INSTR_TYPE_POP; + else if (is_instr_branch(instr)) + type = INSTR_TYPE_BRANCH; + else + type = INSTR_TYPE_UNKNOWN; + + if (type != INSTR_TYPE_UNKNOWN) + instr_p[nr_instr++] = type; + + c_addr += delta; + } + + return nr_instr; +} + +static int is_func_prologue_epilogue(unsigned long *addr) +{ + unsigned long instr_prev, instr_curr, instr_next; + + instr_before_nr = 0; + instr_after_nr = 0; + + if (get_user(instr_curr, addr)) + return 1; + + if (get_user(instr_prev, addr - 1)) + return 1; + + if (get_user(instr_next, addr + 1)) + return 1; + + if (is_instr_pop_fp(instr_curr)) + return 0; + + if (is_instr_push_with_fp(instr_curr) || + is_instr_push_with_fp(instr_next) || + is_instr_push_with_fp(instr_prev) || + is_instr_add_fp(instr_curr) || + is_instr_add_fp(instr_next) || + is_instr_pop_fp(instr_prev)) { + return 1; + } + + instr_before_nr = read_instructions(addr, 0); + if (instr_before_nr < 0) + return 1; + + instr_after_nr = read_instructions(addr, 1); + if (instr_after_nr < 0) + return 1; + + if (!instr_before_nr && !instr_after_nr) + return 0; + + if (instr_before_nr) { + if (instr_before[0] == INSTR_TYPE_ADD_FP) + return 0; + else if (instr_before[0] == INSTR_TYPE_PUSH_FP) + return 1; + } + + if (instr_after_nr) { + if (instr_after[0] == INSTR_TYPE_PUSH_FP) + return 1; + else if (instr_after[0] == INSTR_TYPE_ADD_FP) + return 1; + } + + return 0; } +#endif /* PERF_CALLCHAIN_CHECK_PROLOG_EPILOG */ + +#endif /* CONFIG_PERF_ANDROID_BACKTRACE */ +#ifndef CONFIG_PERF_ANDROID_BACKTRACE void perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs) { @@ -584,6 +818,90 @@ perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs) tail && !((unsigned long)tail & 0x3)) tail = user_backtrace(tail, entry); } +#else /* CONFIG_PERF_ANDROID_BACKTRACE */ +static inline int +perf_check_frame_address(unsigned long addr, struct vm_area_struct *vma) +{ + unsigned long start, end; + + if (vma) { + start = vma->vm_start; + end = vma->vm_end; + if (addr >= start && addr <= end - 2 * sizeof(unsigned long)) + return 0; + } + return -EINVAL; +} + +void +perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs) +{ + struct frame_tail __user *tail; + unsigned long *fp, reg; + struct vm_area_struct *vma; + int pe_flag = 0; + + if (thumb_mode(regs)) + return; + + fp = (unsigned long *)regs->ARM_fp; + if (!access_ok(VERIFY_READ, fp, sizeof(unsigned long))) + return; + + vma = find_vma(current->mm, regs->ARM_sp); + + if (fp < (unsigned long *)regs->ARM_sp) + return; + + if (perf_check_frame_address((unsigned long)fp, vma)) + return; + + if (__copy_from_user_inatomic(®, fp, sizeof(unsigned long))) + return; + +#ifdef PERF_CALLCHAIN_CHECK_PROLOG_EPILOG + if (is_func_prologue_epilogue((unsigned long *)regs->ARM_pc)) + pe_flag = 1; +#endif + + if (pe_flag) { + /* fp->prev frame tail (fp, lr) */ + if (regs->ARM_lr < AB_USER_SPACE_MIN_ADDR) + return; + perf_callchain_store(entry, (unsigned long)regs->ARM_lr); + tail = (struct frame_tail *)(regs->ARM_fp + - sizeof(unsigned long)); + } else { + if ((unsigned long *)reg > fp && + !perf_check_frame_address(reg, vma)) { + /* fp->short frame tail (fp) */ + + if (regs->ARM_lr < AB_USER_SPACE_MIN_ADDR) + return; + perf_callchain_store(entry, regs->ARM_lr); + tail = (struct frame_tail *)(reg + - sizeof(unsigned long)); + } else { + /* fp->current frame tail (fp, lr) */ + tail = (struct frame_tail *)(regs->ARM_fp + - sizeof(unsigned long)); + } + } + + if ((unsigned long *)tail->fp <= fp || + perf_check_frame_address(tail->fp, vma)) { + return; + } + + while (tail && !((unsigned long)tail & 0x3)) { + if (perf_check_frame_address((unsigned long)tail, vma) || + tail->lr < AB_USER_SPACE_MIN_ADDR) { + return; + } + tail = user_backtrace(tail, entry); + } +} +#endif /* CONFIG_PERF_ANDROID_BACKTRACE */ /* * Gets called by walk_stackframe() for every stackframe. This will be called diff --git a/init/Kconfig b/init/Kconfig index d6660c5b4d76..0486e886586a 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1486,6 +1486,20 @@ config PERF_EVENTS Say Y if unsure. +config PERF_ANDROID_BACKTRACE + default n + bool "Backtracing support for the Perf tool on Android" + depends on PERF_EVENTS + help + This config changes default function stack format that Perf + profiler uses for backtracing. With this config enabled Perf + profiler will use stack format of the Android OS for its + operation. + + Say Y only if this kernel will be used with Android OS. + + Say N if unsure. + config DEBUG_PERF_USE_VMALLOC default n bool "Debug: use vmalloc to back perf mmap() buffers" |