| /* |
| * drivers/misc/tegra-profiler/backtrace.c |
| * |
| * Copyright (c) 2013, 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 |
| |
| #include <linux/module.h> |
| #include <asm-generic/uaccess.h> |
| |
| #include <linux/tegra_profiler.h> |
| #include <linux/mm.h> |
| |
| #include "backtrace.h" |
| |
| #define QUADD_USER_SPACE_MIN_ADDR 0x8000 |
| |
| static inline void |
| quadd_callchain_store(struct quadd_callchain *callchain_data, u32 ip) |
| { |
| if (callchain_data->nr < QUADD_MAX_STACK_DEPTH) { |
| /* pr_debug("[%d] Add entry: %#llx\n", |
| callchain_data->nr, ip); */ |
| callchain_data->callchain[callchain_data->nr++] = ip; |
| } |
| } |
| |
| static int |
| check_vma_address(unsigned long addr, struct vm_area_struct *vma) |
| { |
| unsigned long start, end, length; |
| |
| if (vma) { |
| start = vma->vm_start; |
| end = vma->vm_end; |
| length = end - start; |
| if (length > sizeof(unsigned long) && |
| addr >= start && addr <= end - sizeof(unsigned long)) |
| return 0; |
| } |
| return -EINVAL; |
| } |
| |
| static unsigned long __user * |
| user_backtrace(unsigned long __user *tail, |
| struct quadd_callchain *callchain_data, |
| struct vm_area_struct *stack_vma) |
| { |
| unsigned long value, value_lr = 0, value_fp = 0; |
| unsigned long __user *fp_prev = NULL; |
| |
| if (check_vma_address((unsigned long)tail, stack_vma)) |
| return NULL; |
| |
| if (__copy_from_user_inatomic(&value, tail, sizeof(unsigned long))) |
| return NULL; |
| |
| if (!check_vma_address(value, stack_vma)) { |
| /* clang's frame */ |
| value_fp = value; |
| |
| if (check_vma_address((unsigned long)(tail + 1), stack_vma)) |
| return NULL; |
| |
| if (__copy_from_user_inatomic(&value_lr, tail + 1, |
| sizeof(unsigned long))) |
| return NULL; |
| } else { |
| /* gcc's frame */ |
| if (__copy_from_user_inatomic(&value_fp, tail - 1, |
| sizeof(unsigned long))) |
| return NULL; |
| |
| if (check_vma_address(value_fp, stack_vma)) |
| return NULL; |
| |
| value_lr = value; |
| } |
| |
| fp_prev = (unsigned long __user *)value_fp; |
| |
| if (value_lr < QUADD_USER_SPACE_MIN_ADDR) |
| return NULL; |
| |
| quadd_callchain_store(callchain_data, value_lr); |
| |
| if (fp_prev <= tail) |
| return NULL; |
| |
| return fp_prev; |
| } |
| |
| unsigned int |
| quadd_get_user_callchain(struct pt_regs *regs, |
| struct quadd_callchain *callchain_data) |
| { |
| unsigned long fp, sp, pc, reg; |
| struct vm_area_struct *vma, *vma_pc; |
| unsigned long __user *tail = NULL; |
| struct mm_struct *mm = current->mm; |
| |
| callchain_data->nr = 0; |
| |
| if (!regs || !user_mode(regs) || !mm) |
| return 0; |
| |
| if (thumb_mode(regs)) |
| return 0; |
| |
| fp = regs->ARM_fp; |
| sp = regs->ARM_sp; |
| pc = regs->ARM_pc; |
| |
| if (fp == 0 || fp < sp || fp & 0x3) |
| return 0; |
| |
| vma = find_vma(mm, sp); |
| if (check_vma_address(fp, vma)) |
| return 0; |
| |
| if (__copy_from_user_inatomic(®, (unsigned long __user *)fp, |
| sizeof(unsigned long))) |
| return 0; |
| |
| if (reg > fp && |
| !check_vma_address(reg, vma)) { |
| unsigned long value; |
| int read_lr = 0; |
| |
| if (!check_vma_address(fp + sizeof(unsigned long), vma)) { |
| if (__copy_from_user_inatomic( |
| &value, |
| (unsigned long __user *)fp + 1, |
| sizeof(unsigned long))) |
| return 0; |
| |
| vma_pc = find_vma(mm, pc); |
| read_lr = 1; |
| } |
| |
| if (!read_lr || check_vma_address(value, vma_pc)) { |
| /* gcc: fp --> short frame tail (fp) */ |
| |
| if (regs->ARM_lr < QUADD_USER_SPACE_MIN_ADDR) |
| return 0; |
| |
| quadd_callchain_store(callchain_data, regs->ARM_lr); |
| tail = (unsigned long __user *)reg; |
| } |
| } |
| |
| if (!tail) |
| tail = (unsigned long __user *)fp; |
| |
| while (tail && !((unsigned long)tail & 0x3)) |
| tail = user_backtrace(tail, callchain_data, vma); |
| |
| return callchain_data->nr; |
| } |