tegrakernel/kernel/nvidia/drivers/misc/tegra-profiler/eh_unwind.c

1620 lines
34 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* drivers/misc/tegra-profiler/eh_unwind.c
*
* Copyright (c) 2015-2019, 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/mm.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/err.h>
#include <linux/rcupdate.h>
#include <linux/tegra_profiler.h>
#include "quadd.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 32
#define GET_NR_PAGES(a, l) \
((PAGE_ALIGN((a) + (l)) - ((a) & PAGE_MASK)) / PAGE_SIZE)
enum regs {
FP_THUMB = 7,
FP_ARM = 11,
SP = 13,
LR = 14,
PC = 15
};
struct regions_data {
struct ex_region_info *entries;
unsigned long nr_entries;
unsigned long size;
pid_t pid;
struct rcu_head rcu;
struct list_head list;
};
struct quadd_unwind_ctx {
struct quadd_ctx *quadd_ctx;
unsigned long ex_tables_size;
struct list_head mm_ex_list;
raw_spinlock_t mm_ex_list_lock;
};
struct unwind_idx {
u32 addr_offset;
u32 insn;
};
struct stackframe {
unsigned long fp_thumb;
unsigned long fp_arm;
unsigned long sp;
unsigned long lr;
unsigned long pc;
};
struct unwind_ctrl_block {
u32 vrs[16]; /* virtual register set */
const u32 *insn; /* pointer to the current instr word */
int entries; /* number of entries left */
int byte; /* current byte in the instr word */
};
struct pin_pages_work {
struct work_struct work;
unsigned long vm_start;
};
struct ex_entry_node {
struct list_head list;
pid_t pid;
unsigned long vm_start;
unsigned long vm_end;
};
static struct quadd_unwind_ctx ctx;
static inline int is_debug_frame(int secid)
{
return (secid == QUADD_SEC_TYPE_DEBUG_FRAME ||
secid == QUADD_SEC_TYPE_DEBUG_FRAME_HDR) ? 1 : 0;
}
unsigned long
get_ex_sec_address(struct ex_region_info *ri, struct extab_info *ti, int secid)
{
struct quadd_mmap_area *mmap;
unsigned long res = ti->addr;
mmap = ri->mmap;
if (unlikely(!mmap)) {
pr_warn_once("%s: !mmap\n", __func__);
return 0;
}
if (!(is_debug_frame(secid) && !mmap->fi.is_shared))
res += ri->vm_start;
return res;
}
static inline int
validate_mmap_addr(struct quadd_mmap_area *mmap,
unsigned long addr, unsigned long nbytes)
{
struct vm_area_struct *vma;
unsigned long data, size;
if (atomic_read(&mmap->state) != QUADD_MMAP_STATE_ACTIVE)
return 0;
vma = mmap->mmap_vma;
size = vma->vm_end - vma->vm_start;
data = (unsigned long)mmap->data;
if (addr & 0x03) {
pr_err_once("%s: error: unaligned address: %#lx, data: %#lx-%#lx, vma: %#lx-%#lx\n",
__func__, addr, data, data + size,
vma->vm_start, vma->vm_end);
return 0;
}
if (addr < data || addr >= data + (size - nbytes)) {
pr_err_once("%s: error: addr: %#lx, data: %#lx-%#lx, vma: %#lx-%#lx\n",
__func__, addr, data, data + size,
vma->vm_start, vma->vm_end);
return 0;
}
return 1;
}
static inline long
read_mmap_data(struct quadd_mmap_area *mmap, const u32 *addr, u32 *retval)
{
if (!validate_mmap_addr(mmap, (unsigned long)addr, sizeof(u32))) {
*retval = 0;
return -QUADD_URC_EACCESS;
}
*retval = *addr;
return 0;
}
static inline unsigned long
ex_addr_to_mmap_addr(unsigned long addr,
struct ex_region_info *ri,
int sec_type)
{
unsigned long offset;
struct extab_info *ti;
struct quadd_mmap_area *mmap;
mmap = ri->mmap;
if (unlikely(!mmap)) {
pr_warn_once("%s: !mmap\n", __func__);
return 0;
}
ti = &mmap->fi.ex_sec[sec_type];
if (unlikely(!ti->length))
return 0;
offset = addr - get_ex_sec_address(ri, ti, sec_type);
return ti->mmap_offset + offset + (unsigned long)mmap->data;
}
static inline unsigned long
mmap_addr_to_ex_addr(unsigned long addr,
struct ex_region_info *ri,
int sec_type)
{
unsigned long offset;
struct extab_info *ti;
struct quadd_mmap_area *mmap;
mmap = ri->mmap;
if (unlikely(!mmap)) {
pr_warn_once("%s: !mmap\n", __func__);
return 0;
}
ti = &mmap->fi.ex_sec[sec_type];
if (unlikely(!ti->length))
return 0;
offset = addr - ti->mmap_offset - (unsigned long)mmap->data;
return get_ex_sec_address(ri, ti, sec_type) + offset;
}
static inline u32 __maybe_unused
prel31_to_addr(const u32 *ptr)
{
long err;
u32 value;
s32 offset;
err = read_user_data(&value, ptr, sizeof(*ptr));
if (err < 0)
return 0;
/* sign-extend to 32 bits */
offset = (((s32)value) << 1) >> 1;
return (u32)(unsigned long)ptr + offset;
}
static unsigned long
mmap_prel31_to_addr(const u32 *ptr, struct ex_region_info *ri,
int src_type, int dst_type, int to_mmap)
{
s32 offset;
u32 value, addr;
unsigned long addr_res;
value = *ptr;
offset = (((s32)value) << 1) >> 1;
addr = mmap_addr_to_ex_addr((unsigned long)ptr, ri, src_type);
if (unlikely(!addr))
return 0;
addr += offset;
addr_res = addr;
if (to_mmap)
addr_res = ex_addr_to_mmap_addr(addr_res, ri, dst_type);
return addr_res;
}
static struct regions_data *rd_alloc(unsigned long size)
{
struct regions_data *rd;
rd = kzalloc(sizeof(*rd), GFP_ATOMIC);
if (!rd)
return ERR_PTR(-ENOMEM);
rd->entries = kcalloc(size, sizeof(*rd->entries), GFP_ATOMIC);
if (!rd->entries) {
kfree(rd);
return ERR_PTR(-ENOMEM);
}
rd->size = size;
rd->nr_entries = 0;
return rd;
}
static void rd_free(struct regions_data *rd)
{
if (rd)
kfree(rd->entries);
kfree(rd);
}
static void mm_ex_list_free_rcu(struct rcu_head *head)
{
struct regions_data *entry =
container_of(head, struct regions_data, rcu);
rd_free(entry);
}
static int
add_ex_region(struct regions_data *rd,
struct ex_region_info *new_entry)
{
unsigned int i_min, i_max, mid;
struct ex_region_info *array = rd->entries;
unsigned long size = rd->nr_entries;
if (!array)
return -ENOMEM;
if (size == 0) {
memcpy(&array[0], new_entry, sizeof(*new_entry));
return 0;
} else if (size == 1 && array[0].vm_start == new_entry->vm_start) {
return -EEXIST;
}
i_min = 0;
i_max = size;
if (array[0].vm_start > new_entry->vm_start) {
memmove(array + 1, array,
size * sizeof(*array));
memcpy(&array[0], new_entry, sizeof(*new_entry));
return 0;
} else if (array[size - 1].vm_start < new_entry->vm_start) {
memcpy(&array[size], new_entry, sizeof(*new_entry));
return 0;
}
while (i_min < i_max) {
mid = i_min + (i_max - i_min) / 2;
if (new_entry->vm_start <= array[mid].vm_start)
i_max = mid;
else
i_min = mid + 1;
}
if (array[i_max].vm_start == new_entry->vm_start)
return -EEXIST;
memmove(array + i_max + 1,
array + i_max,
(size - i_max) * sizeof(*array));
memcpy(&array[i_max], new_entry, sizeof(*new_entry));
return 0;
}
static int
remove_ex_region(struct regions_data *rd,
struct ex_region_info *entry)
{
unsigned int i_min, i_max, mid;
struct ex_region_info *array = rd->entries;
unsigned long size = rd->nr_entries;
if (!array)
return 0;
if (size == 0)
return 0;
if (size == 1) {
if (array[0].vm_start == entry->vm_start)
return 1;
else
return 0;
}
if (array[0].vm_start > entry->vm_start)
return 0;
else if (array[size - 1].vm_start < entry->vm_start)
return 0;
i_min = 0;
i_max = size;
while (i_min < i_max) {
mid = i_min + (i_max - i_min) / 2;
if (entry->vm_start <= array[mid].vm_start)
i_max = mid;
else
i_min = mid + 1;
}
if (array[i_max].vm_start == entry->vm_start) {
memmove(array + i_max,
array + i_max + 1,
(size - i_max) * sizeof(*array));
return 1;
} else {
return 0;
}
}
static struct ex_region_info *
__search_ex_region(struct ex_region_info *array,
unsigned long size,
unsigned long key)
{
unsigned int i_min, i_max, mid;
if (size == 0)
return NULL;
i_min = 0;
i_max = size;
while (i_min < i_max) {
mid = i_min + (i_max - i_min) / 2;
if (key <= array[mid].vm_start)
i_max = mid;
else
i_min = mid + 1;
}
if (array[i_max].vm_start == key)
return &array[i_max];
return NULL;
}
static long
search_ex_region(pid_t pid, unsigned long key, struct ex_region_info *ri)
{
struct regions_data *entry;
struct ex_region_info *ri_p = NULL;
rcu_read_lock();
list_for_each_entry_rcu(entry, &ctx.mm_ex_list, list) {
if (entry->pid == pid) {
ri_p = __search_ex_region(entry->entries,
entry->nr_entries, key);
if (ri_p)
memcpy(ri, ri_p, sizeof(*ri));
break;
}
}
rcu_read_unlock();
return ri_p ? 0 : -ENOENT;
}
static inline int
validate_sections(struct quadd_sections *et)
{
int i;
unsigned long size = 0;
if (et->vm_start >= et->vm_end)
return -EINVAL;
for (i = 0; i < QUADD_SEC_TYPE_MAX; i++)
size += et->sec[i].length;
return size < et->vm_end - et->vm_start;
}
static void
mmap_ex_entry_del(struct quadd_mmap_area *mmap,
pid_t pid, unsigned long vm_start)
{
struct ex_entry_node *e, *n;
list_for_each_entry_safe(e, n, &mmap->ex_entries, list) {
if (e->pid == pid && e->vm_start == vm_start) {
list_del(&e->list);
kfree(e);
break;
}
}
}
static int
is_overlapped(unsigned long a_start, unsigned long a_end,
unsigned long b_start, unsigned long b_end)
{
return ((a_start >= b_start && a_start < b_end) ||
(b_start >= a_start && b_start < a_end));
}
static int
remove_overlapped_regions(struct regions_data *rd, struct ex_region_info *ri)
{
long idx_from = -1, idx_to = -1;
unsigned long i, start, end, nr, nr_removed = 0;
struct ex_region_info *array = rd->entries;
nr = rd->nr_entries;
if (nr == 0)
return 0;
for (i = 0; i < nr; i++) {
start = array[i].vm_start;
end = array[i].vm_end;
if (is_overlapped(start, end, ri->vm_start, ri->vm_end)) {
idx_from = idx_to = i;
for (idx_to = i + 1; idx_to < nr; idx_to++) {
start = array[idx_to].vm_start;
end = array[idx_to].vm_end;
if (!is_overlapped(start, end, ri->vm_start,
ri->vm_end))
break;
}
break;
}
}
if (idx_from >= 0) {
struct ex_region_info *ri_del;
unsigned long nr_copied = nr - idx_to;
nr_removed = idx_to - idx_from;
pr_debug("%s: [%u] new: %#lx-%#lx, rm:%#lx-%#lx...%#lx-%#lx\n",
__func__, rd->pid, ri->vm_start, ri->vm_end,
array[idx_from].vm_start, array[idx_from].vm_end,
array[idx_to - 1].vm_start, array[idx_to - 1].vm_end);
for (i = idx_from; i < idx_to; i++) {
ri_del = &array[i];
mmap_ex_entry_del(ri_del->mmap, rd->pid,
ri_del->vm_start);
}
if (nr_copied > 0)
memmove(array + idx_from, array + idx_to,
nr_copied * sizeof(*array));
rd->nr_entries -= nr_removed;
}
return nr_removed;
}
static int
mm_ex_list_add(struct ex_region_info *ri, pid_t pid)
{
int err = 0;
struct regions_data *entry, *rd, *rd_old = NULL;
unsigned long nr_entries_new;
raw_spin_lock(&ctx.mm_ex_list_lock);
list_for_each_entry(entry, &ctx.mm_ex_list, list) {
if (entry->pid == pid) {
rd_old = entry;
break;
}
}
nr_entries_new = rd_old ? rd_old->nr_entries + 1 : 1;
rd = rd_alloc(nr_entries_new);
if (IS_ERR(rd)) {
err = PTR_ERR(rd);
goto out_unlock;
}
if (rd_old) {
memcpy(rd->entries, rd_old->entries,
rd_old->nr_entries * sizeof(*rd_old->entries));
rd->nr_entries = rd_old->nr_entries;
rd->pid = pid;
remove_overlapped_regions(rd, ri);
}
err = add_ex_region(rd, ri);
if (err < 0)
goto out_free;
rd->nr_entries++;
rd->pid = pid;
INIT_LIST_HEAD(&rd->list);
if (rd_old) {
list_replace_rcu(&rd_old->list, &rd->list);
call_rcu(&rd_old->rcu, mm_ex_list_free_rcu);
} else {
list_add_tail_rcu(&rd->list, &ctx.mm_ex_list);
}
raw_spin_unlock(&ctx.mm_ex_list_lock);
return 0;
out_free:
rd_free(rd);
out_unlock:
raw_spin_unlock(&ctx.mm_ex_list_lock);
return err;
}
static int
mm_ex_list_del(unsigned long vm_start, pid_t pid)
{
int err = 0, nr_removed;
unsigned long nr_entries;
struct regions_data *rd_entry, *rd_new;
struct ex_region_info ex_entry;
ex_entry.vm_start = vm_start;
raw_spin_lock(&ctx.mm_ex_list_lock);
list_for_each_entry(rd_entry, &ctx.mm_ex_list, list) {
if (rd_entry->pid == pid) {
nr_entries = rd_entry->nr_entries;
if (unlikely(nr_entries == 0)) {
pr_warn_once("%s: !nr_entries\n", __func__);
err = -ENOENT;
goto out_unlock;
}
rd_new = rd_alloc(nr_entries);
if (IS_ERR(rd_new)) {
err = PTR_ERR(rd_new);
goto out_unlock;
}
memcpy(rd_new->entries, rd_entry->entries,
nr_entries * sizeof(*rd_entry->entries));
rd_new->nr_entries = nr_entries;
rd_new->pid = pid;
INIT_LIST_HEAD(&rd_new->list);
nr_removed = remove_ex_region(rd_new, &ex_entry);
rd_new->nr_entries -= nr_removed;
if (rd_new->nr_entries > 0) {
list_replace_rcu(&rd_entry->list,
&rd_new->list);
call_rcu(&rd_entry->rcu, mm_ex_list_free_rcu);
} else {
rd_free(rd_new);
list_del_rcu(&rd_entry->list);
call_rcu(&rd_entry->rcu, mm_ex_list_free_rcu);
}
}
}
out_unlock:
raw_spin_unlock(&ctx.mm_ex_list_lock);
return err;
}
static long
get_extabs_ehabi(pid_t pid, unsigned long key, struct ex_region_info *ri)
{
long err = 0;
struct extab_info *ti_exidx;
struct quadd_mmap_area *mmap;
err = search_ex_region(pid, key, ri);
if (err < 0)
return err;
mmap = ri->mmap;
raw_spin_lock(&mmap->state_lock);
if (atomic_read(&mmap->state) != QUADD_MMAP_STATE_ACTIVE) {
err = -ENOENT;
goto out;
}
ti_exidx = &mmap->fi.ex_sec[QUADD_SEC_TYPE_EXIDX];
if (ti_exidx->length)
atomic_inc(&mmap->ref_count);
else
err = -ENOENT;
out:
raw_spin_unlock(&mmap->state_lock);
return err;
}
static void put_extabs_ehabi(struct ex_region_info *ri)
{
struct quadd_mmap_area *mmap = ri->mmap;
raw_spin_lock(&mmap->state_lock);
if (atomic_dec_and_test(&mmap->ref_count) &&
atomic_read(&mmap->state) == QUADD_MMAP_STATE_CLOSING)
atomic_set(&mmap->state, QUADD_MMAP_STATE_CLOSED);
raw_spin_unlock(&mmap->state_lock);
if (atomic_read(&mmap->ref_count) < 0)
pr_err_once("%s: error: mmap ref_count\n", __func__);
}
long
quadd_get_dw_frames(unsigned long key, struct ex_region_info *ri,
struct task_struct *task)
{
pid_t pid;
long err = 0;
struct extab_info *ti, *ti_hdr;
struct quadd_mmap_area *mmap;
pid = task_tgid_nr(task);
err = search_ex_region(pid, key, ri);
if (err < 0)
return err;
mmap = ri->mmap;
raw_spin_lock(&mmap->state_lock);
if (atomic_read(&mmap->state) != QUADD_MMAP_STATE_ACTIVE) {
err = -ENOENT;
goto out;
}
ti = &mmap->fi.ex_sec[QUADD_SEC_TYPE_EH_FRAME];
ti_hdr = &mmap->fi.ex_sec[QUADD_SEC_TYPE_EH_FRAME_HDR];
if (ti->length && ti_hdr->length) {
atomic_inc(&mmap->ref_count);
goto out;
}
ti = &mmap->fi.ex_sec[QUADD_SEC_TYPE_DEBUG_FRAME];
ti_hdr = &mmap->fi.ex_sec[QUADD_SEC_TYPE_DEBUG_FRAME_HDR];
if (ti->length && ti_hdr->length)
atomic_inc(&mmap->ref_count);
else
err = -ENOENT;
out:
raw_spin_unlock(&mmap->state_lock);
return err;
}
void quadd_put_dw_frames(struct ex_region_info *ri)
{
struct quadd_mmap_area *mmap = ri->mmap;
raw_spin_lock(&mmap->state_lock);
if (atomic_dec_and_test(&mmap->ref_count) &&
atomic_read(&mmap->state) == QUADD_MMAP_STATE_CLOSING)
atomic_set(&mmap->state, QUADD_MMAP_STATE_CLOSED);
raw_spin_unlock(&mmap->state_lock);
if (atomic_read(&mmap->ref_count) < 0)
pr_err_once("%s: error: mmap ref_count\n", __func__);
}
int quadd_unwind_set_extab(struct quadd_sections *extabs,
struct quadd_mmap_area *mmap)
{
int i, err;
struct ex_region_info ri_entry;
struct ex_entry_node *mmap_ex_entry;
if (mmap->type != QUADD_MMAP_TYPE_EXTABS)
return -EIO;
err = validate_sections(extabs);
if (err < 0)
return err;
if (extabs->user_mmap_start) {
mmap->fi.is_shared =
(extabs->flags & QUADD_SECTIONS_FLAG_IS_SHARED) != 0;
for (i = 0; i < QUADD_SEC_TYPE_MAX; i++) {
struct quadd_sec_info *si = &extabs->sec[i];
struct extab_info *ti = &mmap->fi.ex_sec[i];
ti->tf_start = 0;
ti->tf_end = 0;
if (!si->addr) {
ti->addr = 0;
ti->length = 0;
ti->mmap_offset = 0;
continue;
}
ti->addr = si->addr;
ti->length = si->length;
ti->mmap_offset = si->mmap_offset;
}
}
ri_entry.vm_start = extabs->vm_start;
ri_entry.vm_end = extabs->vm_end;
ri_entry.file_hash = extabs->file_hash;
ri_entry.mmap = mmap;
mmap_ex_entry = kzalloc(sizeof(*mmap_ex_entry), GFP_ATOMIC);
if (!mmap_ex_entry) {
err = -ENOMEM;
goto err_out;
}
mmap_ex_entry->vm_start = ri_entry.vm_start;
mmap_ex_entry->vm_end = ri_entry.vm_end;
mmap_ex_entry->pid = extabs->pid;
err = mm_ex_list_add(&ri_entry, extabs->pid);
if (err < 0)
goto out_ex_entry_free;
INIT_LIST_HEAD(&mmap_ex_entry->list);
list_add_tail(&mmap_ex_entry->list, &mmap->ex_entries);
pr_debug("%s: pid: %u: vma: %#lx - %#lx, file_hash: %#x\n",
__func__, extabs->pid, (unsigned long)extabs->vm_start,
(unsigned long)extabs->vm_end, extabs->file_hash);
return 0;
out_ex_entry_free:
kfree(mmap_ex_entry);
err_out:
return err;
}
void
quadd_unwind_set_tail_info(struct ex_region_info *ri,
int secid,
unsigned long tf_start,
unsigned long tf_end,
struct task_struct *task)
{
struct quadd_mmap_area *mmap;
raw_spin_lock(&ctx.quadd_ctx->mmaps_lock);
mmap = ri->mmap;
mmap->fi.ex_sec[secid].tf_start = tf_start;
mmap->fi.ex_sec[secid].tf_end = tf_end;
pr_debug("%s: pid: %u, secid: %d, tf: %#lx - %#lx\n",
__func__, task_tgid_nr(task), secid, tf_start, tf_end);
raw_spin_unlock(&ctx.quadd_ctx->mmaps_lock);
}
static void
clean_mmap(struct quadd_mmap_area *mmap)
{
struct ex_entry_node *entry, *next;
if (atomic_read(&mmap->ref_count)) {
pr_warn_once("%s: ref_count != 0\n", __func__);
return;
}
if (!mmap || mmap->type != QUADD_MMAP_TYPE_EXTABS)
return;
list_for_each_entry_safe(entry, next, &mmap->ex_entries, list) {
mm_ex_list_del(entry->vm_start, entry->pid);
list_del(&entry->list);
kfree(entry);
}
}
static void mmap_wait_for_close(struct quadd_mmap_area *mmap)
{
int state;
raw_spin_lock(&mmap->state_lock);
state = atomic_read(&mmap->ref_count) > 0 ?
QUADD_MMAP_STATE_CLOSING : QUADD_MMAP_STATE_CLOSED;
atomic_set(&mmap->state, state);
raw_spin_unlock(&mmap->state_lock);
while (atomic_read(&mmap->state) != QUADD_MMAP_STATE_CLOSED)
cpu_relax();
}
void quadd_unwind_clean_mmap(struct quadd_mmap_area *mmap)
{
mmap_wait_for_close(mmap);
clean_mmap(mmap);
}
static const struct unwind_idx *
unwind_find_idx(struct ex_region_info *ri, u32 addr, unsigned long *lowaddr)
{
u32 value;
unsigned long length;
struct extab_info *ti;
struct unwind_idx *start;
struct unwind_idx *stop;
struct unwind_idx *mid = NULL;
ti = &ri->mmap->fi.ex_sec[QUADD_SEC_TYPE_EXIDX];
length = ti->length / sizeof(*start);
if (unlikely(!length))
return NULL;
start = (struct unwind_idx *)((char *)ri->mmap->data + ti->mmap_offset);
stop = start + length - 1;
value = (u32)mmap_prel31_to_addr(&start->addr_offset, ri,
QUADD_SEC_TYPE_EXIDX,
QUADD_SEC_TYPE_EXTAB, 0);
if (!value || addr < value)
return NULL;
value = (u32)mmap_prel31_to_addr(&stop->addr_offset, ri,
QUADD_SEC_TYPE_EXIDX,
QUADD_SEC_TYPE_EXTAB, 0);
if (!value || addr >= value)
return NULL;
while (start < stop - 1) {
mid = start + ((stop - start) >> 1);
value = (u32)mmap_prel31_to_addr(&mid->addr_offset, ri,
QUADD_SEC_TYPE_EXIDX,
QUADD_SEC_TYPE_EXTAB, 0);
if (!value)
return NULL;
if (addr < value)
stop = mid;
else
start = mid;
}
if (lowaddr)
*lowaddr = mmap_prel31_to_addr(&start->addr_offset,
ri, 1, 0, 0);
return start;
}
static unsigned long
unwind_get_byte(struct quadd_mmap_area *mmap,
struct unwind_ctrl_block *ctrl, long *err)
{
unsigned long ret;
u32 insn_word;
*err = 0;
if (ctrl->entries <= 0) {
pr_err_once("%s: error: corrupt unwind table\n", __func__);
*err = -QUADD_URC_TBL_IS_CORRUPT;
return 0;
}
*err = read_mmap_data(mmap, ctrl->insn, &insn_word);
if (*err < 0)
return 0;
ret = (insn_word >> (ctrl->byte * 8)) & 0xff;
if (ctrl->byte == 0) {
ctrl->insn++;
ctrl->entries--;
ctrl->byte = 3;
} else
ctrl->byte--;
return ret;
}
static long
read_uleb128(struct quadd_mmap_area *mmap,
struct unwind_ctrl_block *ctrl,
unsigned long *ret)
{
long err = 0;
unsigned long result;
unsigned char byte;
int shift, count;
result = 0;
shift = 0;
count = 0;
while (1) {
byte = unwind_get_byte(mmap, ctrl, &err);
if (err < 0)
return err;
count++;
result |= (byte & 0x7f) << shift;
shift += 7;
if (!(byte & 0x80))
break;
}
*ret = result;
return count;
}
/*
* Execute the current unwind instruction.
*/
static long
unwind_exec_insn(struct quadd_mmap_area *mmap,
struct unwind_ctrl_block *ctrl,
struct quadd_disasm_data *qd)
{
long err;
unsigned int i;
unsigned long insn = unwind_get_byte(mmap, ctrl, &err);
if (err < 0)
return err;
pr_debug("%s: insn = %08lx\n", __func__, insn);
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) {
unsigned long mask;
u32 __user *vsp = (u32 __user *)(unsigned long)ctrl->vrs[SP];
int load_sp, reg = 4;
insn = (insn << 8) | unwind_get_byte(mmap, ctrl, &err);
if (err < 0)
return err;
mask = insn & 0x0fff;
if (mask == 0) {
pr_debug("CMD_REFUSED: unwind: 'Refuse to unwind' instruction %04lx\n",
insn);
return -QUADD_URC_REFUSE_TO_UNWIND;
}
/* pop R4-R15 according to mask */
load_sp = mask & (1 << (13 - 4));
while (mask) {
if (mask & 1) {
err = read_user_data(&ctrl->vrs[reg], vsp++,
sizeof(u32));
if (err < 0)
return err;
qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
}
mask >>= 1;
reg++;
}
if (!load_sp)
ctrl->vrs[SP] = (unsigned long)vsp;
pr_debug("new vsp: %#x\n", ctrl->vrs[SP]);
} 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];
unsigned int reg;
/* pop R4-R[4+bbb] */
for (reg = 4; reg <= 4 + (insn & 7); reg++) {
err = read_user_data(&ctrl->vrs[reg], vsp++,
sizeof(u32));
if (err < 0)
return err;
qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%u}\n", reg);
}
if (insn & 0x08) {
err = read_user_data(&ctrl->vrs[14], vsp++,
sizeof(u32));
if (err < 0)
return err;
qd->r_regset &= ~(1 << 14);
pr_debug("CMD_REG_POP: pop {r14}\n");
}
ctrl->vrs[SP] = (u32)(unsigned long)vsp;
pr_debug("new vsp: %#x\n", ctrl->vrs[SP]);
} else if (insn == 0xb0) {
if (ctrl->vrs[PC] == 0)
ctrl->vrs[PC] = ctrl->vrs[LR];
/* no further processing */
ctrl->entries = 0;
pr_debug("CMD_FINISH\n");
} else if (insn == 0xb1) {
unsigned long mask = unwind_get_byte(mmap, ctrl, &err);
u32 __user *vsp = (u32 __user *)(unsigned long)ctrl->vrs[SP];
int reg = 0;
if (err < 0)
return err;
if (mask == 0 || mask & 0xf0) {
pr_debug("unwind: Spare encoding %04lx\n",
(insn << 8) | mask);
return -QUADD_URC_SPARE_ENCODING;
}
/* pop R0-R3 according to mask */
while (mask) {
if (mask & 1) {
err = read_user_data(&ctrl->vrs[reg], vsp++,
sizeof(u32));
if (err < 0)
return err;
qd->r_regset &= ~(1 << reg);
pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
}
mask >>= 1;
reg++;
}
ctrl->vrs[SP] = (u32)(unsigned long)vsp;
pr_debug("new vsp: %#x\n", ctrl->vrs[SP]);
} else if (insn == 0xb2) {
long count;
unsigned long uleb128 = 0;
count = read_uleb128(mmap, ctrl, &uleb128);
if (count < 0)
return count;
if (count == 0)
return -QUADD_URC_TBL_IS_CORRUPT;
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]);
} else if (insn == 0xb3 || insn == 0xc8 || insn == 0xc9) {
unsigned long data, reg_from, reg_to;
u32 __user *vsp = (u32 __user *)(unsigned long)ctrl->vrs[SP];
data = unwind_get_byte(mmap, ctrl, &err);
if (err < 0)
return err;
reg_from = (data & 0xf0) >> 4;
reg_to = reg_from + (data & 0x0f);
if (insn == 0xc8) {
reg_from += 16;
reg_to += 16;
}
for (i = reg_from; i <= reg_to; i++)
vsp += 2, qd->d_regset &= ~(1 << i);
if (insn == 0xb3)
vsp++;
ctrl->vrs[SP] = (u32)(unsigned long)vsp;
pr_debug("CMD_VFP_POP (%#lx %#lx): pop {D%lu-D%lu}\n",
insn, data, reg_from, reg_to);
pr_debug("new vsp: %#x\n", ctrl->vrs[SP]);
} else if ((insn & 0xf8) == 0xb8 || (insn & 0xf8) == 0xd0) {
unsigned long reg_to;
unsigned long data = insn & 0x07;
u32 __user *vsp = (u32 __user *)(unsigned long)ctrl->vrs[SP];
reg_to = 8 + data;
for (i = 8; i <= reg_to; i++)
vsp += 2, qd->d_regset &= ~(1 << i);
if ((insn & 0xf8) == 0xb8)
vsp++;
ctrl->vrs[SP] = (u32)(unsigned long)vsp;
pr_debug("CMD_VFP_POP (%#lx): pop {D8-D%lu}\n",
insn, reg_to);
pr_debug("new vsp: %#x\n", ctrl->vrs[SP]);
} else {
pr_debug("error: unhandled instruction %02lx\n", insn);
return -QUADD_URC_UNHANDLED_INSTRUCTION;
}
pr_debug("%s: fp_arm: %#x, fp_thumb: %#x, sp: %#x, lr = %#x, pc: %#x\n",
__func__,
ctrl->vrs[FP_ARM], ctrl->vrs[FP_THUMB], ctrl->vrs[SP],
ctrl->vrs[LR], ctrl->vrs[PC]);
return 0;
}
/*
* Unwind a single frame starting with *sp for the symbol at *pc. It
* updates the *pc and *sp with the new values.
*/
static long
unwind_frame(struct quadd_unw_methods um,
struct ex_region_info *ri,
struct stackframe *frame,
struct vm_area_struct *vma_sp,
int thumbflag,
struct task_struct *task)
{
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;
if (!validate_stack_addr(frame->sp, vma_sp, sizeof(u32), 0))
return -QUADD_URC_SP_INCORRECT;
/* only go to a higher address on the stack */
low = frame->sp;
high = vma_sp->vm_end;
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, &min);
if (IS_ERR_OR_NULL(idx))
return -QUADD_URC_IDX_NOT_FOUND;
pr_debug("index was found by pc (%#lx): %p\n", frame->pc, idx);
ctrl.vrs[FP_THUMB] = frame->fp_thumb;
ctrl.vrs[FP_ARM] = frame->fp_arm;
ctrl.vrs[SP] = frame->sp;
ctrl.vrs[LR] = frame->lr;
ctrl.vrs[PC] = 0;
err = read_mmap_data(ri->mmap, &idx->insn, &val);
if (err < 0)
return err;
if (val == 1) {
/* can't unwind */
return -QUADD_URC_CANTUNWIND;
} else if ((val & 0x80000000) == 0) {
/* prel31 to the unwind table */
ctrl.insn = (u32 *)(unsigned long)
mmap_prel31_to_addr(&idx->insn, ri,
QUADD_SEC_TYPE_EXIDX,
QUADD_SEC_TYPE_EXTAB, 1);
if (!ctrl.insn)
return -QUADD_URC_TBL_LINK_INCORRECT;
} else if ((val & 0xff000000) == 0x80000000) {
/* only personality routine 0 supported in the index */
ctrl.insn = &idx->insn;
} else {
pr_debug("unsupported personality routine %#x in the index at %p\n",
val, idx);
return -QUADD_URC_UNSUPPORTED_PR;
}
err = read_mmap_data(ri->mmap, ctrl.insn, &val);
if (err < 0)
return err;
/* check the personality routine */
if ((val & 0xff000000) == 0x80000000) {
ctrl.byte = 2;
ctrl.entries = 1;
} else if ((val & 0xff000000) == 0x81000000) {
ctrl.byte = 1;
ctrl.entries = 1 + ((val & 0x00ff0000) >> 16);
} else {
pr_debug("unsupported personality routine %#x at %p\n",
val, ctrl.insn);
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, &qd);
if (err < 0)
return err;
if (ctrl.vrs[SP] & 0x03 ||
ctrl.vrs[SP] < low || ctrl.vrs[SP] >= high)
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];
if (!validate_pc_addr(ctrl.vrs[PC], sizeof(u32)))
return -QUADD_URC_PC_INCORRECT;
frame->fp_thumb = ctrl.vrs[FP_THUMB];
frame->fp_arm = ctrl.vrs[FP_ARM];
frame->sp = ctrl.vrs[SP];
frame->lr = ctrl.vrs[LR];
frame->pc = ctrl.vrs[PC];
return 0;
}
static void
unwind_backtrace(struct quadd_callchain *cc,
struct ex_region_info *ri,
struct stackframe *frame,
struct vm_area_struct *vma_sp,
struct task_struct *task,
int thumbflag)
{
struct ex_region_info ri_new, *prev_ri = NULL;
cc->urc_ut = QUADD_URC_FAILURE;
pr_debug("fp_arm: %#lx, fp_thumb: %#lx, sp: %#lx, lr: %#lx, pc: %#lx\n",
frame->fp_arm, frame->fp_thumb,
frame->sp, frame->lr, frame->pc);
pr_debug("vma_sp: %#lx - %#lx, length: %#lx\n",
vma_sp->vm_start, vma_sp->vm_end,
vma_sp->vm_end - vma_sp->vm_start);
while (1) {
long err;
int nr_added;
struct extab_info *ti;
unsigned long addr, where = frame->pc;
struct vm_area_struct *vma_pc;
struct mm_struct *mm = task->mm;
if (!mm)
break;
if (!validate_stack_addr(frame->sp, vma_sp, sizeof(u32), 0)) {
cc->urc_ut = QUADD_URC_SP_INCORRECT;
break;
}
vma_pc = find_vma(mm, frame->pc);
if (!vma_pc)
break;
ti = &ri->mmap->fi.ex_sec[QUADD_SEC_TYPE_EXIDX];
addr = get_ex_sec_address(ri, ti, QUADD_SEC_TYPE_EXIDX);
if (!is_vma_addr(addr, vma_pc, sizeof(u32))) {
if (prev_ri) {
put_extabs_ehabi(prev_ri);
prev_ri = NULL;
}
err = get_extabs_ehabi(task_tgid_nr(task),
vma_pc->vm_start, &ri_new);
if (err) {
cc->urc_ut = QUADD_URC_TBL_NOT_EXIST;
break;
}
prev_ri = ri = &ri_new;
}
err = unwind_frame(cc->um, ri, frame, vma_sp, thumbflag, task);
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);
cc->curr_sp = frame->sp;
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);
if (nr_added == 0)
break;
}
if (prev_ri)
put_extabs_ehabi(prev_ri);
}
unsigned int
quadd_get_user_cc_arm32_ehabi(struct quadd_event_context *event_ctx,
struct quadd_callchain *cc)
{
long err;
int nr_prev = cc->nr, thumbflag;
unsigned long ip, sp, lr;
struct vm_area_struct *vma, *vma_sp;
struct ex_region_info ri;
struct stackframe frame;
struct pt_regs *regs = event_ctx->regs;
struct task_struct *task = event_ctx->task;
struct mm_struct *mm = task->mm;
if (!regs || !mm)
return 0;
#ifdef CONFIG_ARM64
if (!compat_user_mode(regs))
return 0;
#endif
if (cc->urc_ut == QUADD_URC_LEVEL_TOO_DEEP)
return nr_prev;
cc->urc_ut = QUADD_URC_FAILURE;
if (cc->curr_sp) {
ip = cc->curr_pc;
sp = cc->curr_sp;
lr = cc->curr_lr;
thumbflag = (lr & 1);
frame.fp_thumb = cc->curr_fp_thumb;
frame.fp_arm = cc->curr_fp;
} else {
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);
frame.fp_arm = regs->compat_usr(11);
#else
frame.fp_thumb = regs->ARM_r7;
frame.fp_arm = regs->ARM_fp;
#endif
}
frame.pc = ip;
frame.sp = sp;
frame.lr = lr;
pr_debug("pc: %#lx, lr: %#lx\n", ip, lr);
pr_debug("sp: %#lx, fp_arm: %#lx, fp_thumb: %#lx\n",
sp, frame.fp_arm, frame.fp_thumb);
vma = find_vma(mm, ip);
if (!vma)
return 0;
vma_sp = find_vma(mm, sp);
if (!vma_sp)
return 0;
err = get_extabs_ehabi(task_tgid_nr(task), vma->vm_start, &ri);
if (err) {
cc->urc_ut = QUADD_URC_TBL_NOT_EXIST;
return 0;
}
unwind_backtrace(cc, &ri, &frame, vma_sp, task, thumbflag);
put_extabs_ehabi(&ri);
pr_debug("%s: exit, cc->nr: %d --> %d\n",
__func__, nr_prev, cc->nr);
return cc->nr;
}
int
quadd_is_ex_entry_exist_arm32_ehabi(struct quadd_event_context *event_ctx,
unsigned long addr)
{
int ret;
long err;
u32 value;
const struct unwind_idx *idx;
struct ex_region_info ri;
struct vm_area_struct *vma;
struct pt_regs *regs = event_ctx->regs;
struct mm_struct *mm = event_ctx->task->mm;
if (!regs || !mm)
return 0;
vma = find_vma(mm, addr);
if (!vma)
return 0;
err = get_extabs_ehabi(task_tgid_nr(event_ctx->task),
vma->vm_start, &ri);
if (err)
return 0;
idx = unwind_find_idx(&ri, addr, NULL);
if (IS_ERR_OR_NULL(idx)) {
ret = 0;
goto out;
}
err = read_mmap_data(ri.mmap, &idx->insn, &value);
if (err < 0) {
ret = 0;
goto out;
}
/* EXIDX_CANTUNWIND */
if (value == 1) {
ret = 0;
goto out;
}
ret = 1;
out:
put_extabs_ehabi(&ri);
return ret;
}
int quadd_unwind_start(struct task_struct *task)
{
int err;
err = quadd_dwarf_unwind_start();
if (err < 0)
return err;
ctx.ex_tables_size = 0;
return 0;
}
void quadd_unwind_stop(void)
{
struct quadd_mmap_area *entry;
raw_spin_lock(&ctx.quadd_ctx->mmaps_lock);
list_for_each_entry(entry, &ctx.quadd_ctx->mmap_areas, list)
quadd_unwind_clean_mmap(entry);
raw_spin_unlock(&ctx.quadd_ctx->mmaps_lock);
quadd_dwarf_unwind_stop();
}
int quadd_unwind_init(struct quadd_ctx *quadd_ctx)
{
int err;
ctx.quadd_ctx = quadd_ctx;
err = quadd_dwarf_unwind_init();
if (err)
return err;
INIT_LIST_HEAD(&ctx.mm_ex_list);
raw_spin_lock_init(&ctx.mm_ex_list_lock);
return 0;
}
void quadd_unwind_deinit(void)
{
quadd_unwind_stop();
rcu_barrier();
}