548 lines
14 KiB
C
548 lines
14 KiB
C
/**
|
|
* Copyright (C) ARM Limited 2010-2015. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
*/
|
|
|
|
#include <linux/mount.h>
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
|
|
|
|
struct mount {
|
|
struct mount *mnt_parent;
|
|
struct dentry *mnt_mountpoint;
|
|
struct vfsmount mnt;
|
|
};
|
|
|
|
static inline struct mount *real_mount(struct vfsmount *mnt)
|
|
{
|
|
return container_of(mnt, struct mount, mnt);
|
|
}
|
|
|
|
#define GET_MNT_ROOT(mount) (mount)->mnt.mnt_root
|
|
|
|
#else
|
|
|
|
#define GET_MNT_ROOT(mount) (mount)->mnt_root
|
|
|
|
#endif
|
|
|
|
/* must be power of 2 */
|
|
#define COOKIEMAP_ENTRIES 1024
|
|
/* must be a power of 2 - 512/4 = 128 entries */
|
|
#define TRANSLATE_BUFFER_SIZE 512
|
|
#define TRANSLATE_TEXT_SIZE 256
|
|
#define MAX_COLLISIONS 2
|
|
|
|
static uint32_t *gator_crc32_table;
|
|
static unsigned int translate_buffer_mask;
|
|
|
|
struct cookie_args {
|
|
struct task_struct *task;
|
|
const char *text;
|
|
};
|
|
|
|
static DEFINE_PER_CPU(char *, translate_text);
|
|
static DEFINE_PER_CPU(char *, scratch);
|
|
static DEFINE_PER_CPU(uint32_t, cookie_next_key);
|
|
static DEFINE_PER_CPU(uint64_t *, cookie_keys);
|
|
static DEFINE_PER_CPU(uint32_t *, cookie_values);
|
|
static DEFINE_PER_CPU(int, translate_buffer_read);
|
|
static DEFINE_PER_CPU(int, translate_buffer_write);
|
|
static DEFINE_PER_CPU(struct cookie_args *, translate_buffer);
|
|
|
|
static uint32_t get_cookie(int cpu, struct task_struct *task, const char *text, bool from_wq);
|
|
static void wq_cookie_handler(struct work_struct *unused);
|
|
static DECLARE_WORK(cookie_work, wq_cookie_handler);
|
|
static struct timer_list app_process_wake_up_timer;
|
|
static void app_process_wake_up_handler(unsigned long unused_data);
|
|
|
|
static uint32_t cookiemap_code(uint64_t value64)
|
|
{
|
|
uint32_t value = (uint32_t)((value64 >> 32) + value64);
|
|
uint32_t cookiecode = (value >> 24) & 0xff;
|
|
|
|
cookiecode = cookiecode * 31 + ((value >> 16) & 0xff);
|
|
cookiecode = cookiecode * 31 + ((value >> 8) & 0xff);
|
|
cookiecode = cookiecode * 31 + ((value >> 0) & 0xff);
|
|
cookiecode &= (COOKIEMAP_ENTRIES - 1);
|
|
return cookiecode * MAX_COLLISIONS;
|
|
}
|
|
|
|
static uint32_t gator_chksum_crc32(const char *data)
|
|
{
|
|
register unsigned long crc;
|
|
const unsigned char *block = data;
|
|
int i, length = strlen(data);
|
|
|
|
crc = 0xFFFFFFFF;
|
|
for (i = 0; i < length; i++)
|
|
crc = ((crc >> 8) & 0x00FFFFFF) ^ gator_crc32_table[(crc ^ *block++) & 0xFF];
|
|
|
|
return (crc ^ 0xFFFFFFFF);
|
|
}
|
|
|
|
/*
|
|
* Exists
|
|
* Pre: [0][1][v][3]..[n-1]
|
|
* Post: [v][0][1][3]..[n-1]
|
|
*/
|
|
static uint32_t cookiemap_exists(uint64_t key)
|
|
{
|
|
unsigned long x, flags, retval = 0;
|
|
int cpu = get_physical_cpu();
|
|
uint32_t cookiecode = cookiemap_code(key);
|
|
uint64_t *keys = &(per_cpu(cookie_keys, cpu)[cookiecode]);
|
|
uint32_t *values = &(per_cpu(cookie_values, cpu)[cookiecode]);
|
|
|
|
/* Can be called from interrupt handler or from work queue */
|
|
local_irq_save(flags);
|
|
for (x = 0; x < MAX_COLLISIONS; x++) {
|
|
if (keys[x] == key) {
|
|
uint32_t value = values[x];
|
|
|
|
for (; x > 0; x--) {
|
|
keys[x] = keys[x - 1];
|
|
values[x] = values[x - 1];
|
|
}
|
|
keys[0] = key;
|
|
values[0] = value;
|
|
retval = value;
|
|
break;
|
|
}
|
|
}
|
|
local_irq_restore(flags);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Add
|
|
* Pre: [0][1][2][3]..[n-1]
|
|
* Post: [v][0][1][2]..[n-2]
|
|
*/
|
|
static void cookiemap_add(uint64_t key, uint32_t value)
|
|
{
|
|
int cpu = get_physical_cpu();
|
|
int cookiecode = cookiemap_code(key);
|
|
uint64_t *keys = &(per_cpu(cookie_keys, cpu)[cookiecode]);
|
|
uint32_t *values = &(per_cpu(cookie_values, cpu)[cookiecode]);
|
|
int x;
|
|
|
|
for (x = MAX_COLLISIONS - 1; x > 0; x--) {
|
|
keys[x] = keys[x - 1];
|
|
values[x] = values[x - 1];
|
|
}
|
|
keys[0] = key;
|
|
values[0] = value;
|
|
}
|
|
|
|
#ifndef CONFIG_PREEMPT_RT_FULL
|
|
static void translate_buffer_write_args(int cpu, struct task_struct *task, const char *text)
|
|
{
|
|
unsigned long flags;
|
|
int write;
|
|
int next_write;
|
|
struct cookie_args *args;
|
|
|
|
local_irq_save(flags);
|
|
|
|
write = per_cpu(translate_buffer_write, cpu);
|
|
next_write = (write + 1) & translate_buffer_mask;
|
|
|
|
/* At least one entry must always remain available as when read == write, the queue is empty not full */
|
|
if (next_write != per_cpu(translate_buffer_read, cpu)) {
|
|
args = &per_cpu(translate_buffer, cpu)[write];
|
|
args->task = task;
|
|
args->text = text;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39)
|
|
get_task_struct(task);
|
|
#endif
|
|
per_cpu(translate_buffer_write, cpu) = next_write;
|
|
}
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
#endif
|
|
|
|
static void translate_buffer_read_args(int cpu, struct cookie_args *args)
|
|
{
|
|
unsigned long flags;
|
|
int read;
|
|
|
|
local_irq_save(flags);
|
|
|
|
read = per_cpu(translate_buffer_read, cpu);
|
|
*args = per_cpu(translate_buffer, cpu)[read];
|
|
per_cpu(translate_buffer_read, cpu) = (read + 1) & translate_buffer_mask;
|
|
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
static void wq_cookie_handler(struct work_struct *unused)
|
|
{
|
|
struct cookie_args args;
|
|
int cpu = get_physical_cpu(), cookie;
|
|
|
|
mutex_lock(&start_mutex);
|
|
|
|
if (gator_started != 0) {
|
|
while (per_cpu(translate_buffer_read, cpu) != per_cpu(translate_buffer_write, cpu)) {
|
|
translate_buffer_read_args(cpu, &args);
|
|
cookie = get_cookie(cpu, args.task, args.text, true);
|
|
marshal_link(cookie, args.task->tgid, args.task->pid);
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39)
|
|
put_task_struct(args.task);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&start_mutex);
|
|
}
|
|
|
|
static void app_process_wake_up_handler(unsigned long unused_data)
|
|
{
|
|
/* had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater */
|
|
schedule_work(&cookie_work);
|
|
}
|
|
|
|
/* Retrieve full name from proc/pid/cmdline for java processes on Android */
|
|
static int translate_app_process(const char **text, int cpu, struct task_struct *task, bool from_wq)
|
|
{
|
|
void *maddr;
|
|
unsigned int len;
|
|
unsigned long addr;
|
|
struct mm_struct *mm;
|
|
struct page *page = NULL;
|
|
struct vm_area_struct *page_vma;
|
|
int bytes, offset, retval = 0;
|
|
char *buf = per_cpu(translate_text, cpu);
|
|
|
|
#ifndef CONFIG_PREEMPT_RT_FULL
|
|
/* Push work into a work queue if in atomic context as the kernel
|
|
* functions below might sleep. Rely on the in_interrupt variable
|
|
* rather than in_irq() or in_interrupt() kernel functions, as the
|
|
* value of these functions seems inconsistent during a context
|
|
* switch between android/linux versions
|
|
*/
|
|
if (!from_wq) {
|
|
/* Check if already in buffer */
|
|
int pos = per_cpu(translate_buffer_read, cpu);
|
|
|
|
while (pos != per_cpu(translate_buffer_write, cpu)) {
|
|
if (per_cpu(translate_buffer, cpu)[pos].task == task)
|
|
goto out;
|
|
pos = (pos + 1) & translate_buffer_mask;
|
|
}
|
|
|
|
translate_buffer_write_args(cpu, task, *text);
|
|
|
|
/* Not safe to call in RT-Preempt full in schedule switch context */
|
|
mod_timer(&app_process_wake_up_timer, jiffies + 1);
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
mm = get_task_mm(task);
|
|
if (!mm)
|
|
goto out;
|
|
if (!mm->arg_end)
|
|
goto outmm;
|
|
addr = mm->arg_start;
|
|
len = mm->arg_end - mm->arg_start;
|
|
|
|
if (len > TRANSLATE_TEXT_SIZE)
|
|
len = TRANSLATE_TEXT_SIZE;
|
|
|
|
down_read(&mm->mmap_sem);
|
|
while (len) {
|
|
if (get_user_pages(task, mm, addr, 1, 0, 1, &page, &page_vma) <= 0)
|
|
goto outsem;
|
|
|
|
maddr = kmap(page);
|
|
offset = addr & (PAGE_SIZE - 1);
|
|
bytes = len;
|
|
if (bytes > PAGE_SIZE - offset)
|
|
bytes = PAGE_SIZE - offset;
|
|
|
|
copy_from_user_page(page_vma, page, addr, buf, maddr + offset, bytes);
|
|
|
|
/* release page allocated by get_user_pages() */
|
|
kunmap(page);
|
|
page_cache_release(page);
|
|
|
|
len -= bytes;
|
|
buf += bytes;
|
|
addr += bytes;
|
|
|
|
*text = per_cpu(translate_text, cpu);
|
|
retval = 1;
|
|
}
|
|
|
|
/* On app_process startup, /proc/pid/cmdline is initially "zygote" then "<pre-initialized>" but changes after an initial startup period */
|
|
if (strcmp(*text, "zygote") == 0 || strcmp(*text, "<pre-initialized>") == 0)
|
|
retval = 0;
|
|
|
|
outsem:
|
|
up_read(&mm->mmap_sem);
|
|
outmm:
|
|
mmput(mm);
|
|
out:
|
|
return retval;
|
|
}
|
|
|
|
static const char APP_PROCESS[] = "app_process";
|
|
|
|
static uint32_t get_cookie(int cpu, struct task_struct *task, const char *text, bool from_wq)
|
|
{
|
|
unsigned long flags, cookie;
|
|
uint64_t key;
|
|
|
|
key = gator_chksum_crc32(text);
|
|
key = (key << 32) | (uint32_t)task->tgid;
|
|
|
|
cookie = cookiemap_exists(key);
|
|
if (cookie)
|
|
return cookie;
|
|
|
|
/* On 64-bit android app_process can be app_process32 or app_process64 */
|
|
if (strstr(text, APP_PROCESS) != NULL) {
|
|
if (!translate_app_process(&text, cpu, task, from_wq))
|
|
return UNRESOLVED_COOKIE;
|
|
}
|
|
|
|
/* Can be called from interrupt handler or from work queue or from scheduler trace */
|
|
local_irq_save(flags);
|
|
|
|
cookie = UNRESOLVED_COOKIE;
|
|
if (marshal_cookie_header(text)) {
|
|
cookie = per_cpu(cookie_next_key, cpu) += nr_cpu_ids;
|
|
cookiemap_add(key, cookie);
|
|
marshal_cookie(cookie, text);
|
|
}
|
|
|
|
local_irq_restore(flags);
|
|
|
|
return cookie;
|
|
}
|
|
|
|
/* Can't call d_path in interrupt context so create something similar */
|
|
static const char *gator_d_path(const struct path *path, char *buf, int buflen)
|
|
{
|
|
struct dentry *dentry = path->dentry;
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0)
|
|
struct mount *mount = real_mount(path->mnt);
|
|
#else
|
|
struct vfsmount *mount = path->mnt;
|
|
#endif
|
|
int pos = buflen - 1;
|
|
int len;
|
|
|
|
buf[pos] = '\0';
|
|
|
|
for (;;) {
|
|
if (dentry == NULL) {
|
|
pr_err("gator: dentry is null!\n");
|
|
break;
|
|
}
|
|
if (dentry->d_name.name[0] == '\0') {
|
|
pr_err("gator: path is empty string\n");
|
|
break;
|
|
}
|
|
if (dentry->d_name.name[0] == '/' && dentry->d_name.name[1] == '\0') {
|
|
/* Normal operation */
|
|
/* pr_err("gator: path is /\n"); */
|
|
break;
|
|
}
|
|
|
|
len = strlen(dentry->d_name.name);
|
|
if (pos < len) {
|
|
pr_err("gator: path is too long\n");
|
|
break;
|
|
}
|
|
pos -= len;
|
|
memcpy(buf + pos, dentry->d_name.name, len);
|
|
|
|
if (pos == 0) {
|
|
pr_err("gator: no room for slash\n");
|
|
/* Fall back to name only */
|
|
return path->dentry->d_name.name;
|
|
}
|
|
--pos;
|
|
buf[pos] = '/';
|
|
|
|
if (dentry->d_parent == GET_MNT_ROOT(mount)) {
|
|
/* pr_err("gator: filesystem is complete, moving to next '%s'\n", buf + pos); */
|
|
dentry = mount->mnt_mountpoint;
|
|
mount = mount->mnt_parent;
|
|
continue;
|
|
}
|
|
if (dentry == dentry->d_parent) {
|
|
pr_err("gator: parent is self\n");
|
|
break;
|
|
}
|
|
dentry = dentry->d_parent;
|
|
}
|
|
|
|
if (pos < 0) {
|
|
pr_err("gator: pos is somenow negative\n");
|
|
/* Fall back to name only */
|
|
return path->dentry->d_name.name;
|
|
}
|
|
|
|
return buf + pos;
|
|
}
|
|
|
|
#define d_path gator_d_path
|
|
|
|
static int get_exec_cookie(int cpu, struct task_struct *task)
|
|
{
|
|
struct mm_struct *mm = task->mm;
|
|
const char *text;
|
|
|
|
/* kernel threads have no address space */
|
|
if (!mm)
|
|
return NO_COOKIE;
|
|
|
|
if (task && task->mm && task->mm->exe_file) {
|
|
text = d_path(&task->mm->exe_file->f_path, per_cpu(scratch, get_physical_cpu()), PAGE_SIZE);
|
|
return get_cookie(cpu, task, text, false);
|
|
}
|
|
|
|
return UNRESOLVED_COOKIE;
|
|
}
|
|
|
|
static unsigned long get_address_cookie(int cpu, struct task_struct *task, unsigned long addr, off_t *offset)
|
|
{
|
|
unsigned long cookie = NO_COOKIE;
|
|
struct mm_struct *mm = task->mm;
|
|
struct vm_area_struct *vma;
|
|
const char *text;
|
|
|
|
if (!mm)
|
|
return cookie;
|
|
|
|
for (vma = find_vma(mm, addr); vma; vma = vma->vm_next) {
|
|
if (addr < vma->vm_start || addr >= vma->vm_end)
|
|
continue;
|
|
|
|
if (vma->vm_file) {
|
|
text = d_path(&vma->vm_file->f_path, per_cpu(scratch, get_physical_cpu()), PAGE_SIZE);
|
|
cookie = get_cookie(cpu, task, text, false);
|
|
*offset = (vma->vm_pgoff << PAGE_SHIFT) + addr - vma->vm_start;
|
|
} else {
|
|
/* must be an anonymous map */
|
|
*offset = addr;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (!vma)
|
|
cookie = UNRESOLVED_COOKIE;
|
|
|
|
return cookie;
|
|
}
|
|
|
|
static int cookies_initialize(void)
|
|
{
|
|
uint32_t crc, poly;
|
|
int i, j, cpu, size, err = 0;
|
|
|
|
translate_buffer_mask = TRANSLATE_BUFFER_SIZE / sizeof(per_cpu(translate_buffer, 0)[0]) - 1;
|
|
|
|
for_each_present_cpu(cpu) {
|
|
per_cpu(cookie_next_key, cpu) = nr_cpu_ids + cpu;
|
|
|
|
size = COOKIEMAP_ENTRIES * MAX_COLLISIONS * sizeof(uint64_t);
|
|
per_cpu(cookie_keys, cpu) = kmalloc(size, GFP_KERNEL);
|
|
if (!per_cpu(cookie_keys, cpu)) {
|
|
err = -ENOMEM;
|
|
goto cookie_setup_error;
|
|
}
|
|
memset(per_cpu(cookie_keys, cpu), 0, size);
|
|
|
|
size = COOKIEMAP_ENTRIES * MAX_COLLISIONS * sizeof(uint32_t);
|
|
per_cpu(cookie_values, cpu) = kmalloc(size, GFP_KERNEL);
|
|
if (!per_cpu(cookie_values, cpu)) {
|
|
err = -ENOMEM;
|
|
goto cookie_setup_error;
|
|
}
|
|
memset(per_cpu(cookie_values, cpu), 0, size);
|
|
|
|
per_cpu(translate_buffer, cpu) = kmalloc(TRANSLATE_BUFFER_SIZE, GFP_KERNEL);
|
|
if (!per_cpu(translate_buffer, cpu)) {
|
|
err = -ENOMEM;
|
|
goto cookie_setup_error;
|
|
}
|
|
|
|
per_cpu(translate_buffer_write, cpu) = 0;
|
|
per_cpu(translate_buffer_read, cpu) = 0;
|
|
|
|
per_cpu(translate_text, cpu) = kmalloc(TRANSLATE_TEXT_SIZE, GFP_KERNEL);
|
|
if (!per_cpu(translate_text, cpu)) {
|
|
err = -ENOMEM;
|
|
goto cookie_setup_error;
|
|
}
|
|
|
|
per_cpu(scratch, cpu) = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (!per_cpu(scratch, cpu)) {
|
|
err = -ENOMEM;
|
|
goto cookie_setup_error;
|
|
}
|
|
}
|
|
|
|
/* build CRC32 table */
|
|
poly = 0x04c11db7;
|
|
gator_crc32_table = kmalloc(256 * sizeof(*gator_crc32_table), GFP_KERNEL);
|
|
if (!gator_crc32_table) {
|
|
err = -ENOMEM;
|
|
goto cookie_setup_error;
|
|
}
|
|
for (i = 0; i < 256; i++) {
|
|
crc = i;
|
|
for (j = 8; j > 0; j--) {
|
|
if (crc & 1)
|
|
crc = (crc >> 1) ^ poly;
|
|
else
|
|
crc >>= 1;
|
|
}
|
|
gator_crc32_table[i] = crc;
|
|
}
|
|
|
|
setup_deferrable_timer_on_stack(&app_process_wake_up_timer, app_process_wake_up_handler, 0);
|
|
|
|
cookie_setup_error:
|
|
return err;
|
|
}
|
|
|
|
static void cookies_release(void)
|
|
{
|
|
int cpu;
|
|
|
|
for_each_present_cpu(cpu) {
|
|
kfree(per_cpu(cookie_keys, cpu));
|
|
per_cpu(cookie_keys, cpu) = NULL;
|
|
|
|
kfree(per_cpu(cookie_values, cpu));
|
|
per_cpu(cookie_values, cpu) = NULL;
|
|
|
|
kfree(per_cpu(translate_buffer, cpu));
|
|
per_cpu(translate_buffer, cpu) = NULL;
|
|
per_cpu(translate_buffer_read, cpu) = 0;
|
|
per_cpu(translate_buffer_write, cpu) = 0;
|
|
|
|
kfree(per_cpu(translate_text, cpu));
|
|
per_cpu(translate_text, cpu) = NULL;
|
|
|
|
kfree(per_cpu(scratch, cpu));
|
|
per_cpu(scratch, cpu) = NULL;
|
|
}
|
|
|
|
del_timer_sync(&app_process_wake_up_timer);
|
|
kfree(gator_crc32_table);
|
|
gator_crc32_table = NULL;
|
|
}
|