runtime/linux/uprobes2/uprobes.c - systemtap
Global variables defined
Data types defined
Functions defined
Macros defined
Source code
#include <linux/types.h>
#include <linux/hash.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/rcupdate.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/utrace.h>
#include <linux/regset.h>
#include <linux/file.h>
#include <linux/version.h>
#define UPROBES_IMPLEMENTATION 1
#ifdef UTRACE_API_VERSION
#define utrace_attached_engine utrace_engine
#endif
#include "uprobes.h"
#include <linux/tracehook.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <asm/errno.h>
#include <asm/mman.h>
#define UPROBE_SET_FLAGS 1
#define UPROBE_CLEAR_FLAGS 0
#define MAX_SSOL_SLOTS 1024
#define SLOT_SIZE MAX_UINSN_BYTES
#define NO_ACCESS_PROCESS_VM_EXPORT
#ifdef NO_ACCESS_PROCESS_VM_EXPORT
static int __access_process_vm(struct task_struct *tsk, unsigned long addr,
void *buf, int len, int write);
#define access_process_vm __access_process_vm
#else
extern int access_process_vm(struct task_struct *tsk, unsigned long addr,
void *buf, int len, int write);
#endif
static int utask_fake_quiesce(struct uprobe_task *utask);
static void uretprobe_handle_entry(struct uprobe *u, struct pt_regs *regs,
struct uprobe_task *utask);
static void uretprobe_handle_return(struct pt_regs *regs,
struct uprobe_task *utask);
static void uretprobe_set_trampoline(struct uprobe_process *uproc,
struct task_struct *tsk);
static void zap_uretprobe_instances(struct uprobe *u,
struct uprobe_process *uproc);
typedef void (*uprobe_handler_t)(struct uprobe*, struct pt_regs*);
#define URETPROBE_HANDLE_ENTRY ((uprobe_handler_t)-1L)
#define is_uretprobe(u) (u->handler == URETPROBE_HANDLE_ENTRY)
static struct uprobe_probept uretprobe_trampoline_dummy_probe;
static struct hlist_head uproc_table[UPROBE_TABLE_SIZE];
static DEFINE_MUTEX(uproc_mutex);
static struct hlist_head utask_table[UPROBE_TABLE_SIZE];
static DEFINE_SPINLOCK(utask_table_lock);
#define lock_uproc_table() mutex_lock(&uproc_mutex)
#define unlock_uproc_table() mutex_unlock(&uproc_mutex)
#define lock_utask_table(flags) spin_lock_irqsave(&utask_table_lock, (flags))
#define unlock_utask_table(flags) \
spin_unlock_irqrestore(&utask_table_lock, (flags))
static const struct utrace_engine_ops *p_uprobe_utrace_ops;
struct deferred_registration {
struct list_head list;
struct uprobe *uprobe;
int regflag; enum uprobe_type type;
};
struct delayed_signal {
struct list_head list;
siginfo_t info;
};
static struct uprobe_task *uprobe_find_utask_locked(struct task_struct *tsk)
{
struct hlist_head *head;
struct hlist_node *node;
struct uprobe_task *utask;
head = &utask_table[hash_ptr(tsk, UPROBE_HASH_BITS)];
hlist_for_each_entry(utask, node, head, hlist) {
if (utask->tsk == tsk)
return utask;
}
return NULL;
}
static struct uprobe_task *uprobe_find_utask(struct task_struct *tsk)
{
struct uprobe_task *utask;
unsigned long flags;
lock_utask_table(flags);
utask = uprobe_find_utask_locked(tsk);
unlock_utask_table(flags);
return utask;
}
static void uprobe_hash_utask(struct uprobe_task *utask)
{
struct hlist_head *head;
unsigned long flags;
INIT_HLIST_NODE(&utask->hlist);
lock_utask_table(flags);
head = &utask_table[hash_ptr(utask->tsk, UPROBE_HASH_BITS)];
hlist_add_head(&utask->hlist, head);
unlock_utask_table(flags);
}
static void uprobe_unhash_utask(struct uprobe_task *utask)
{
unsigned long flags;
lock_utask_table(flags);
hlist_del(&utask->hlist);
unlock_utask_table(flags);
}
static inline struct uprobe_process * uprobe_get_process(struct uprobe_process *uproc)
{
if (atomic_inc_not_zero(&uproc->refcount))
return uproc;
return NULL;
}
static inline void uprobe_decref_process(struct uprobe_process *uproc)
{
if (atomic_dec_and_test(&uproc->refcount))
BUG();
}
static struct uprobe_process *uprobe_find_process(struct pid *tg_leader)
{
struct hlist_head *head;
struct hlist_node *node;
struct uprobe_process *uproc;
head = &uproc_table[hash_ptr(tg_leader, UPROBE_HASH_BITS)];
hlist_for_each_entry(uproc, node, head, hlist) {
if (uproc->tg_leader == tg_leader && !uproc->finished) {
uproc = uprobe_get_process(uproc);
if (uproc)
down_write(&uproc->rwsem);
return uproc;
}
}
return NULL;
}
static struct uprobe_probept *uprobe_find_probept(struct uprobe_process *uproc,
unsigned long vaddr)
{
struct uprobe_probept *ppt;
struct hlist_node *node;
struct hlist_head *head = &uproc->uprobe_table[hash_long(vaddr,
UPROBE_HASH_BITS)];
hlist_for_each_entry(ppt, node, head, ut_node) {
if (ppt->vaddr == vaddr && ppt->state != UPROBE_DISABLED)
return ppt;
}
return NULL;
}
static int set_bp(struct uprobe_probept *ppt, struct task_struct *tsk)
{
uprobe_opcode_t bp_insn = BREAKPOINT_INSTRUCTION;
return access_process_vm(tsk, ppt->vaddr, &bp_insn, BP_INSN_SIZE, 1);
}
static int set_orig_insn(struct uprobe_probept *ppt, struct task_struct *tsk)
{
return access_process_vm(tsk, ppt->vaddr, &ppt->opcode, BP_INSN_SIZE,
1);
}
static void bkpt_insertion_failed(struct uprobe_probept *ppt, const char *why)
{
printk(KERN_ERR "Can't place uprobe at pid %d vaddr %#lx: %s\n",
pid_nr(ppt->uproc->tg_leader), ppt->vaddr, why);
}
static void insert_bkpt(struct uprobe_probept *ppt, struct task_struct *tsk)
{
struct uprobe_kimg *uk;
long result = 0;
int len;
if (!tsk) {
result = -ESRCH;
goto out;
}
len = access_process_vm(tsk, ppt->vaddr, ppt->insn, MAX_UINSN_BYTES, 0);
if (len < BP_INSN_SIZE) {
bkpt_insertion_failed(ppt,
"error reading original instruction");
result = -EIO;
goto out;
}
memcpy(&ppt->opcode, ppt->insn, BP_INSN_SIZE);
if (ppt->opcode == BREAKPOINT_INSTRUCTION) {
result = -EEXIST;
goto out;
}
if ((result = arch_validate_probed_insn(ppt, tsk)) < 0) {
bkpt_insertion_failed(ppt, "instruction type cannot be probed");
goto out;
}
len = set_bp(ppt, tsk);
if (len < BP_INSN_SIZE) {
bkpt_insertion_failed(ppt, "failed to insert bkpt instruction");
result = -EIO;
goto out;
}
out:
ppt->state = (result ? UPROBE_DISABLED : UPROBE_BP_SET);
list_for_each_entry(uk, &ppt->uprobe_list, list)
uk->status = result;
wake_up_all(&ppt->waitq);
}
static void remove_bkpt(struct uprobe_probept *ppt, struct task_struct *tsk)
{
int len;
if (tsk) {
len = set_orig_insn(ppt, tsk);
if (len < BP_INSN_SIZE) {
printk(KERN_ERR
"Error removing uprobe at pid %d vaddr %#lx:"
" can't restore original instruction\n",
tsk->tgid, ppt->vaddr);
}
}
ppt->state = UPROBE_DISABLED;
wake_up_all(&ppt->waitq);
}
static void handle_pending_uprobes(struct uprobe_process *uproc,
struct task_struct *tsk)
{
struct uprobe_probept *ppt, *tmp;
list_for_each_entry_safe(ppt, tmp, &uproc->pending_uprobes, pd_node) {
switch (ppt->state) {
case UPROBE_INSERTING:
insert_bkpt(ppt, tsk);
break;
case UPROBE_REMOVING:
remove_bkpt(ppt, tsk);
break;
default:
BUG();
}
list_del(&ppt->pd_node);
}
}
static void utask_adjust_flags(struct uprobe_task *utask, int set,
unsigned long flags)
{
unsigned long newflags, oldflags;
newflags = oldflags = utask->engine->flags;
if (set)
newflags |= flags;
else
newflags &= ~flags;
if (newflags != oldflags) {
rcu_read_lock();
if (utrace_set_events_pid(utask->pid, utask->engine,
newflags) != 0)
;
rcu_read_unlock();
}
}
static inline void clear_utrace_quiesce(struct uprobe_task *utask, bool resume)
{
utask_adjust_flags(utask, UPROBE_CLEAR_FLAGS, UTRACE_EVENT(QUIESCE));
if (resume) {
rcu_read_lock();
if (utrace_control_pid(utask->pid, utask->engine,
UTRACE_RESUME) != 0)
;
rcu_read_unlock();
}
}
static void rouse_all_threads(struct uprobe_process *uproc)
{
struct uprobe_task *utask;
list_for_each_entry(utask, &uproc->thread_list, list) {
if (utask->quiescing) {
utask->quiescing = 0;
if (utask->state == UPTASK_QUIESCENT) {
utask->state = UPTASK_RUNNING;
uproc->n_quiescent_threads--;
clear_utrace_quiesce(utask, true);
}
}
}
wake_up_all(&uproc->waitq);
}
static int check_uproc_quiesced(struct uprobe_process *uproc,
struct task_struct *tsk)
{
if (uproc->n_quiescent_threads >= uproc->nthreads) {
handle_pending_uprobes(uproc, tsk);
rouse_all_threads(uproc);
return 1;
}
return 0;
}
static void uprobe_stop_thread(struct uprobe_task *utask)
{
int result;
BUG_ON(utask->tsk == current);
rcu_read_lock();
result = utrace_control_pid(utask->pid, utask->engine, UTRACE_STOP);
rcu_read_unlock();
if (result == 0) {
utask->state = UPTASK_QUIESCENT;
utask->uproc->n_quiescent_threads++;
} else if (result == -EINPROGRESS) {
if (utask->tsk->state & TASK_INTERRUPTIBLE) {
utask->state = UPTASK_QUIESCENT;
utask->uproc->n_quiescent_threads++;
} else {
rcu_read_lock();
result = utrace_control_pid(utask->pid, utask->engine,
UTRACE_INTERRUPT);
if (result != 0)
;
rcu_read_unlock();
}
}
}
static bool quiesce_all_threads(struct uprobe_process *uproc,
struct uprobe_task **cur_utask_quiescing)
{
struct uprobe_task *utask;
struct task_struct *survivor = NULL; bool survivors = false;
*cur_utask_quiescing = NULL;
list_for_each_entry(utask, &uproc->thread_list, list) {
if (!survivors) {
rcu_read_lock();
survivor = pid_task(utask->pid, PIDTYPE_PID);
rcu_read_unlock();
if (survivor)
survivors = true;
}
if (!utask->quiescing) {
utask->quiescing = 1;
if (utask->tsk == current)
*cur_utask_quiescing = utask;
else if (utask->state == UPTASK_RUNNING) {
utask_adjust_flags(utask, UPROBE_SET_FLAGS,
UTRACE_EVENT(QUIESCE));
uprobe_stop_thread(utask);
}
}
}
check_uproc_quiesced(uproc, survivor);
return survivors;
}
static void utask_free_uretprobe_instances(struct uprobe_task *utask)
{
struct uretprobe_instance *ri;
struct hlist_node *r1, *r2;
hlist_for_each_entry_safe(ri, r1, r2, &utask->uretprobe_instances,
hlist) {
hlist_del(&ri->hlist);
kfree(ri);
uprobe_decref_process(utask->uproc);
}
}
static void uprobe_free_task(struct uprobe_task *utask, bool in_callback)
{
struct deferred_registration *dr, *d;
struct delayed_signal *ds, *ds2;
uprobe_unhash_utask(utask);
if (utask->engine && (utask->tsk != current || !in_callback)) {
rcu_read_lock();
if (utrace_control_pid(utask->pid, utask->engine,
UTRACE_DETACH) != 0)
;
rcu_read_unlock();
}
put_pid(utask->pid);
list_del(&utask->list);
list_for_each_entry_safe(dr, d, &utask->deferred_registrations, list) {
list_del(&dr->list);
kfree(dr);
}
list_for_each_entry_safe(ds, ds2, &utask->delayed_signals, list) {
list_del(&ds->list);
kfree(ds);
}
utask_free_uretprobe_instances(utask);
kfree(utask);
}
static void uprobe_free_process(struct uprobe_process *uproc, int in_callback)
{
struct uprobe_task *utask, *tmp;
struct uprobe_ssol_area *area = &uproc->ssol_area;
if (area->slots)
kfree(area->slots);
if (!hlist_unhashed(&uproc->hlist))
hlist_del(&uproc->hlist);
list_for_each_entry_safe(utask, tmp, &uproc->thread_list, list)
uprobe_free_task(utask, in_callback);
put_pid(uproc->tg_leader);
up_write(&uproc->rwsem); kfree(uproc);
}
static int uprobe_put_process(struct uprobe_process *uproc, bool in_callback)
{
int freed = 0;
if (atomic_dec_and_test(&uproc->refcount)) {
lock_uproc_table();
down_write(&uproc->rwsem);
if (unlikely(atomic_read(&uproc->refcount) != 0)) {
up_write(&uproc->rwsem);
} else {
uprobe_free_process(uproc, in_callback);
freed = 1;
}
unlock_uproc_table();
}
if (freed)
module_put(THIS_MODULE);
return freed;
}
static struct uprobe_kimg *uprobe_mk_kimg(struct uprobe *u)
{
struct uprobe_kimg *uk = (struct uprobe_kimg*)kzalloc(sizeof *uk,
GFP_USER);
if (unlikely(!uk))
return ERR_PTR(-ENOMEM);
u->kdata = uk;
uk->uprobe = u;
uk->ppt = NULL;
INIT_LIST_HEAD(&uk->list);
uk->status = -EBUSY;
return uk;
}
static struct uprobe_task *uprobe_add_task(struct pid *p,
struct uprobe_process *uproc)
{
struct uprobe_task *utask;
struct utrace_attached_engine *engine;
struct task_struct *t;
rcu_read_lock();
t = pid_task(p, PIDTYPE_PID);
rcu_read_unlock();
if (!t)
return NULL;
utask = (struct uprobe_task *)kzalloc(sizeof *utask, GFP_USER);
if (unlikely(utask == NULL))
return ERR_PTR(-ENOMEM);
utask->pid = p;
utask->tsk = t;
utask->state = UPTASK_RUNNING;
utask->quiescing = 0;
utask->uproc = uproc;
utask->active_probe = NULL;
utask->doomed = 0;
INIT_HLIST_HEAD(&utask->uretprobe_instances);
INIT_LIST_HEAD(&utask->deferred_registrations);
INIT_LIST_HEAD(&utask->delayed_signals);
INIT_LIST_HEAD(&utask->list);
list_add_tail(&utask->list, &uproc->thread_list);
uprobe_hash_utask(utask);
engine = utrace_attach_pid(p, UTRACE_ATTACH_CREATE,
p_uprobe_utrace_ops, utask);
if (IS_ERR(engine)) {
long err = PTR_ERR(engine);
printk("uprobes: utrace_attach_task failed, returned %ld\n",
err);
uprobe_free_task(utask, 0);
if (err == -ESRCH)
return NULL;
return ERR_PTR(err);
}
utask->engine = engine;
utask_adjust_flags(utask, UPROBE_SET_FLAGS,
UTRACE_EVENT(SIGNAL) | UTRACE_EVENT(SIGNAL_IGN) |
UTRACE_EVENT(SIGNAL_CORE) | UTRACE_EVENT(EXEC) |
UTRACE_EVENT(CLONE) | UTRACE_EVENT(EXIT));
return utask;
}
static struct pid *find_next_thread_to_add(struct uprobe_process *uproc,
struct pid *start_pid)
{
struct task_struct *t, *start;
struct uprobe_task *utask;
struct pid *pid = NULL;
rcu_read_lock();
t = start = pid_task(start_pid, PIDTYPE_PID);
if (t) {
do {
if (unlikely(t->flags & PF_EXITING))
goto dont_add;
list_for_each_entry(utask, &uproc->thread_list, list) {
if (utask->tsk == t)
goto dont_add;
}
pid = get_pid(task_pid(t));
break;
dont_add:
t = next_thread(t);
} while (t != start);
}
rcu_read_unlock();
return pid;
}
static struct uprobe_process *uprobe_mk_process(struct pid *tg_leader,
bool at_fork)
{
struct uprobe_process *uproc;
struct uprobe_task *utask;
struct pid *add_me;
int i;
long err;
uproc = (struct uprobe_process *)kzalloc(sizeof *uproc, GFP_USER);
if (unlikely(uproc == NULL))
return ERR_PTR(-ENOMEM);
atomic_set(&uproc->refcount, 1);
init_rwsem(&uproc->rwsem);
if (!at_fork)
down_write(&uproc->rwsem);
init_waitqueue_head(&uproc->waitq);
for (i = 0; i < UPROBE_TABLE_SIZE; i++)
INIT_HLIST_HEAD(&uproc->uprobe_table[i]);
uproc->nppt = 0;
INIT_LIST_HEAD(&uproc->pending_uprobes);
INIT_LIST_HEAD(&uproc->thread_list);
uproc->nthreads = 0;
uproc->n_quiescent_threads = 0;
INIT_HLIST_NODE(&uproc->hlist);
uproc->tg_leader = get_pid(tg_leader);
rcu_read_lock();
uproc->tgid = pid_task(tg_leader, PIDTYPE_PID)->tgid;
rcu_read_unlock();
uproc->finished = 0;
uproc->uretprobe_trampoline_addr = NULL;
uproc->ssol_area.insn_area = NULL;
uproc->ssol_area.initialized = 0;
mutex_init(&uproc->ssol_area.setup_mutex);
#ifdef CONFIG_UPROBES_SSOL
uproc->sstep_out_of_line = 1;
#else
uproc->sstep_out_of_line = 0;
#endif
add_me = tg_leader;
while ((add_me = find_next_thread_to_add(uproc, add_me)) != NULL) {
utask = uprobe_add_task(add_me, uproc);
if (IS_ERR(utask)) {
err = PTR_ERR(utask);
goto fail;
}
if (utask)
uproc->nthreads++;
}
if (uproc->nthreads == 0) {
err = -ESRCH;
goto fail;
}
return uproc;
fail:
uprobe_free_process(uproc, 0);
return ERR_PTR(err);
}
static struct uprobe_probept *uprobe_add_probept(struct uprobe_kimg *uk,
struct uprobe_process *uproc)
{
struct uprobe_probept *ppt;
ppt = (struct uprobe_probept *)kzalloc(sizeof *ppt, GFP_USER);
if (unlikely(ppt == NULL))
return ERR_PTR(-ENOMEM);
init_waitqueue_head(&ppt->waitq);
mutex_init(&ppt->ssil_mutex);
mutex_init(&ppt->slot_mutex);
ppt->slot = NULL;
INIT_LIST_HEAD(&ppt->uprobe_list);
list_add_tail(&uk->list, &ppt->uprobe_list);
uk->ppt = ppt;
uk->status = -EBUSY;
ppt->vaddr = uk->uprobe->vaddr;
ppt->state = UPROBE_INSERTING;
ppt->uproc = uproc;
INIT_LIST_HEAD(&ppt->pd_node);
list_add_tail(&ppt->pd_node, &uproc->pending_uprobes);
INIT_HLIST_NODE(&ppt->ut_node);
hlist_add_head(&ppt->ut_node,
&uproc->uprobe_table[hash_long(ppt->vaddr, UPROBE_HASH_BITS)]);
uproc->nppt++;
uprobe_get_process(uproc);
return ppt;
}
static void uprobe_free_slot(struct uprobe_probept *ppt)
{
struct uprobe_ssol_slot *slot = ppt->slot;
if (slot) {
down_write(&slot->rwsem);
if (slot->owner == ppt) {
unsigned long flags;
struct uprobe_ssol_area *area = &ppt->uproc->ssol_area;
spin_lock_irqsave(&area->lock, flags);
slot->state = SSOL_FREE;
slot->owner = NULL;
area->nfree++;
spin_unlock_irqrestore(&area->lock, flags);
}
up_write(&slot->rwsem);
}
}
static void uprobe_free_probept(struct uprobe_probept *ppt)
{
struct uprobe_process *uproc = ppt->uproc;
uprobe_free_slot(ppt);
hlist_del(&ppt->ut_node);
uproc->nppt--;
kfree(ppt);
uprobe_decref_process(uproc);
}
static void uprobe_free_kimg(struct uprobe_kimg *uk)
{
uk->uprobe->kdata = NULL;
kfree(uk);
}
static void purge_uprobe(struct uprobe_kimg *uk)
{
struct uprobe_probept *ppt = uk->ppt;
list_del(&uk->list);
uprobe_free_kimg(uk);
if (list_empty(&ppt->uprobe_list))
uprobe_free_probept(ppt);
}
TODOstatic int uprobe_validate_vma(struct task_struct *t, unsigned long vaddr)
{
struct vm_area_struct *vma;
struct mm_struct *mm;
int ret = 0;
mm = get_task_mm(t);
if (!mm)
return -EINVAL;
down_read(&mm->mmap_sem);
vma = find_vma(mm, vaddr);
if (!vma || vaddr < vma->vm_start)
ret = -ENOENT;
else if (!(vma->vm_flags & VM_EXEC))
ret = -EFAULT;
up_read(&mm->mmap_sem);
mmput(mm);
return ret;
}
static int uprobe_validate_vaddr(struct pid *p, unsigned long vaddr,
struct uprobe_process *uproc)
{
struct task_struct *t;
struct vm_area_struct *vma;
struct mm_struct *mm = NULL;
int ret = -EINVAL;
rcu_read_lock();
t = pid_task(p, PIDTYPE_PID);
if (t)
mm = get_task_mm(t);
rcu_read_unlock();
if (!mm)
return -EINVAL;
down_read(&mm->mmap_sem);
vma = find_vma(mm, vaddr);
if (vma && vaddr >= vma->vm_start && (vma->vm_flags & VM_EXEC) &&
vma->vm_start != (unsigned long) uproc->ssol_area.insn_area)
ret = 0;
up_read(&mm->mmap_sem);
mmput(mm);
return ret;
}
static int defer_registration(struct uprobe *u, int regflag,
struct uprobe_task *utask)
{
struct deferred_registration *dr =
kmalloc(sizeof(struct deferred_registration), GFP_USER);
if (!dr)
return -ENOMEM;
dr->type = (is_uretprobe(u) ? UPTY_URETPROBE : UPTY_UPROBE);
dr->uprobe = u;
dr->regflag = regflag;
INIT_LIST_HEAD(&dr->list);
list_add_tail(&dr->list, &utask->deferred_registrations);
return -EINPROGRESS;
}
static struct pid *uprobe_get_tg_leader(pid_t p)
{
struct pid *pid;
rcu_read_lock();
pid = get_pid(find_pid_ns(p, &init_pid_ns));
rcu_read_unlock();
return pid;
}
int register_uprobe(struct uprobe *u)
{
struct pid *p;
struct uprobe_process *uproc;
struct uprobe_kimg *uk;
struct uprobe_probept *ppt;
struct uprobe_task *cur_utask, *cur_utask_quiescing = NULL;
int ret = 0, uproc_is_new = 0;
bool survivors;
if (!u || !u->handler)
return -EINVAL;
p = uprobe_get_tg_leader(u->pid);
if (!p)
return -ESRCH;
cur_utask = uprobe_find_utask(current);
if (cur_utask && cur_utask->active_probe) {
put_pid(p);
return defer_registration(u, 1, cur_utask);
}
lock_uproc_table();
uproc = uprobe_find_process(p);
if (uproc)
unlock_uproc_table();
else {
if (!try_module_get(THIS_MODULE)) {
ret = -ENOSYS;
unlock_uproc_table();
goto fail_tsk;
}
uproc = uprobe_mk_process(p, 0);
if (IS_ERR(uproc)) {
ret = (int) PTR_ERR(uproc);
unlock_uproc_table();
module_put(THIS_MODULE);
goto fail_tsk;
}
uproc_is_new = 1;
}
if (is_uretprobe(u) && IS_ERR(uproc->uretprobe_trampoline_addr)) {
ret = -ENOMEM;
goto fail_uproc;
}
if ((ret = uprobe_validate_vaddr(p, u->vaddr, uproc)) < 0)
goto fail_uproc;
if (u->kdata) {
ret = -EBUSY;
goto fail_uproc;
}
uk = uprobe_mk_kimg(u);
if (IS_ERR(uk)) {
ret = (int) PTR_ERR(uk);
goto fail_uproc;
}
ppt = (uproc_is_new ? NULL : uprobe_find_probept(uproc, u->vaddr));
if (ppt) {
uk->ppt = ppt;
list_add_tail(&uk->list, &ppt->uprobe_list);
switch (ppt->state) {
case UPROBE_INSERTING:
uk->status = -EBUSY; if (uproc->tg_leader == task_tgid(current)) {
cur_utask_quiescing = cur_utask;
BUG_ON(!cur_utask_quiescing);
}
break;
case UPROBE_REMOVING:
ppt->state = UPROBE_BP_SET;
list_del(&ppt->pd_node); wake_up_all(&ppt->waitq); case UPROBE_BP_SET:
uk->status = 0;
break;
default:
BUG();
}
up_write(&uproc->rwsem);
put_pid(p);
if (uk->status == 0) {
uprobe_decref_process(uproc);
return 0;
}
goto await_bkpt_insertion;
} else {
ppt = uprobe_add_probept(uk, uproc);
if (IS_ERR(ppt)) {
ret = (int) PTR_ERR(ppt);
goto fail_uk;
}
}
if (uproc_is_new) {
hlist_add_head(&uproc->hlist,
&uproc_table[hash_ptr(uproc->tg_leader,
UPROBE_HASH_BITS)]);
unlock_uproc_table();
}
put_pid(p);
survivors = quiesce_all_threads(uproc, &cur_utask_quiescing);
if (!survivors) {
purge_uprobe(uk);
up_write(&uproc->rwsem);
uprobe_put_process(uproc, false);
return -ESRCH;
}
up_write(&uproc->rwsem);
await_bkpt_insertion:
if (cur_utask_quiescing)
(void) utask_fake_quiesce(cur_utask_quiescing);
else
wait_event(ppt->waitq, ppt->state != UPROBE_INSERTING);
ret = uk->status;
if (ret != 0) {
down_write(&uproc->rwsem);
purge_uprobe(uk);
up_write(&uproc->rwsem);
}
uprobe_put_process(uproc, false);
return ret;
fail_uk:
uprobe_free_kimg(uk);
fail_uproc:
if (uproc_is_new) {
uprobe_free_process(uproc, 0);
unlock_uproc_table();
module_put(THIS_MODULE);
} else {
up_write(&uproc->rwsem);
uprobe_put_process(uproc, false);
}
fail_tsk:
put_pid(p);
return ret;
}
EXPORT_SYMBOL_GPL(register_uprobe);
void __unregister_uprobe(struct uprobe *u, bool remove_bkpt)
{
struct pid *p;
struct uprobe_process *uproc;
struct uprobe_kimg *uk;
struct uprobe_probept *ppt;
struct uprobe_task *cur_utask, *cur_utask_quiescing = NULL;
if (!u)
return;
p = uprobe_get_tg_leader(u->pid);
if (!p)
return;
cur_utask = uprobe_find_utask(current);
if (cur_utask && cur_utask->active_probe) {
put_pid(p);
(void) defer_registration(u, 0, cur_utask);
return;
}
lock_uproc_table();
uproc = uprobe_find_process(p);
unlock_uproc_table();
put_pid(p);
if (!uproc)
return;
uk = (struct uprobe_kimg *)u->kdata;
if (!uk)
goto done;
if (uk->status == -EBUSY)
goto done;
ppt = uk->ppt;
list_del(&uk->list);
uprobe_free_kimg(uk);
if (is_uretprobe(u))
zap_uretprobe_instances(u, uproc);
if (!list_empty(&ppt->uprobe_list))
goto done;
if (!remove_bkpt) {
uprobe_free_probept(ppt);
goto done;
}
ppt->state = UPROBE_REMOVING;
list_add_tail(&ppt->pd_node, &uproc->pending_uprobes);
(void) quiesce_all_threads(uproc, &cur_utask_quiescing);
up_write(&uproc->rwsem);
if (cur_utask_quiescing)
(void) utask_fake_quiesce(cur_utask_quiescing);
else
wait_event(ppt->waitq, ppt->state != UPROBE_REMOVING);
if (likely(ppt->state == UPROBE_DISABLED)) {
down_write(&uproc->rwsem);
uprobe_free_probept(ppt);
up_write(&uproc->rwsem);
}
uprobe_put_process(uproc, false);
return;
done:
up_write(&uproc->rwsem);
uprobe_put_process(uproc, false);
}
void unregister_uprobe(struct uprobe *u)
{
__unregister_uprobe(u, true);
}
EXPORT_SYMBOL_GPL(unregister_uprobe);
void unmap_uprobe(struct uprobe *u)
{
__unregister_uprobe(u, false);
}
EXPORT_SYMBOL_GPL(unmap_uprobe);
static struct task_struct *find_surviving_thread(struct uprobe_process *uproc)
{
struct uprobe_task *utask;
list_for_each_entry(utask, &uproc->thread_list, list) {
if (!(utask->tsk->flags & PF_EXITING))
return utask->tsk;
}
return NULL;
}
static void uprobe_run_def_regs(struct list_head *drlist)
{
struct deferred_registration *dr, *d;
list_for_each_entry_safe(dr, d, drlist, list) {
int result = 0;
struct uprobe *u = dr->uprobe;
if (dr->type == UPTY_URETPROBE) {
struct uretprobe *rp =
container_of(u, struct uretprobe, u);
if (dr->regflag)
result = register_uretprobe(rp);
else
unregister_uretprobe(rp);
} else {
if (dr->regflag)
result = register_uprobe(u);
else
unregister_uprobe(u);
}
if (u && u->registration_callback)
u->registration_callback(u, dr->regflag, dr->type,
result);
list_del(&dr->list);
kfree(dr);
}
}
#define UPROBES_SSOL_VMA_TAG "uprobes vma"
#define UPROBES_SSOL_TAGSZ ((int)sizeof(UPROBES_SSOL_VMA_TAG))
static void uprobe_tag_vma(struct uprobe_ssol_area *area)
{
static const char *buf = UPROBES_SSOL_VMA_TAG;
struct uprobe_ssol_slot *slot = &area->slots[area->next_slot];
if (access_process_vm(current, (unsigned long) slot->insn, (void*)buf,
UPROBES_SSOL_TAGSZ, 1) == UPROBES_SSOL_TAGSZ) {
int nb;
for (nb = 0; nb < UPROBES_SSOL_TAGSZ; nb += SLOT_SIZE) {
slot->state = SSOL_RESERVED;
slot++;
area->next_slot++;
area->nfree--;
}
} else {
printk(KERN_ERR "Failed to tag uprobes SSOL vma: "
"pid/tgid=%d/%d, vaddr=%p\n",
current->pid, current->tgid, slot->insn);
}
}
static unsigned long find_next_possible_ssol_vma(unsigned long ceiling)
{
struct mm_struct *mm;
struct rb_node *rb_node;
struct vm_area_struct *vma;
unsigned long good_flags = VM_EXEC | VM_DONTEXPAND;
unsigned long bad_flags = VM_WRITE | VM_GROWSDOWN | VM_GROWSUP;
unsigned long addr = 0;
mm = get_task_mm(current);
if (!mm)
return 0;
down_read(&mm->mmap_sem);
for (rb_node=rb_last(&mm->mm_rb); rb_node; rb_node=rb_prev(rb_node)) {
vma = rb_entry(rb_node, struct vm_area_struct, vm_rb);
if (ceiling && vma->vm_start >= ceiling)
continue;
if ((vma->vm_flags & good_flags) != good_flags)
continue;
if ((vma->vm_flags & bad_flags) != 0)
continue;
addr = vma->vm_start;
break;
}
up_read(&mm->mmap_sem);
mmput(mm);
return addr;
}
static noinline unsigned long find_old_ssol_vma(void)
{
unsigned long addr;
unsigned long ceiling = 0; char buf[UPROBES_SSOL_TAGSZ];
while ((addr = find_next_possible_ssol_vma(ceiling)) != 0) {
ceiling = addr;
if (copy_from_user(buf, (const void __user*)addr,
UPROBES_SSOL_TAGSZ))
continue;
if (!strcmp(buf, UPROBES_SSOL_VMA_TAG))
return addr;
}
return 0;
}
static noinline unsigned long uprobe_setup_ssol_vma(unsigned long nbytes)
{
unsigned long addr;
struct mm_struct *mm;
struct vm_area_struct *vma;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
struct file *file;
#endif
BUG_ON(nbytes & ~PAGE_MASK);
if ((addr = find_old_ssol_vma()) != 0)
return addr;
mm = get_task_mm(current);
if (!mm)
return (unsigned long) (-ESRCH);
down_write(&mm->mmap_sem);
vma = rb_entry(rb_last(&mm->mm_rb), struct vm_area_struct, vm_rb);
addr = vma->vm_end + PAGE_SIZE;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
file = shmem_file_setup("uprobes/ssol", nbytes, VM_NORESERVE);
if (file) {
addr = do_mmap_pgoff(file, addr, nbytes, PROT_EXEC,
MAP_PRIVATE, 0);
fput(file);
}
if (!file || addr & ~PAGE_MASK) {
#else
addr = do_mmap_pgoff(NULL, addr, nbytes, PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, 0);
if (addr & ~PAGE_MASK) {
#endif
up_write(&mm->mmap_sem);
mmput(mm);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
if (!file)
printk(KERN_ERR "Uprobes shmem_file_setup failed while"
" allocating vma for pid/tgid %d/%d for"
" single-stepping out of line.\n",
current->pid, current->tgid);
else
#endif
printk(KERN_ERR "Uprobes failed to allocate a vma for"
" pid/tgid %d/%d for single-stepping out of"
" line.\n", current->pid, current->tgid);
return addr;
}
vma = find_vma(mm, addr);
BUG_ON(!vma);
vma->vm_flags |= VM_DONTEXPAND;
up_write(&mm->mmap_sem);
mmput(mm);
return addr;
}
static void uprobe_init_ssol(struct uprobe_process *uproc,
struct task_struct *tsk, __user uprobe_opcode_t *insn_area)
{
struct uprobe_ssol_area *area = &uproc->ssol_area;
struct uprobe_ssol_slot *slot;
int i;
char *slot_addr;
uproc->uretprobe_trampoline_addr = ERR_PTR(-ENOMEM);
if (insn_area) {
BUG_ON(IS_ERR(insn_area));
area->insn_area = insn_area;
} else {
BUG_ON(tsk != current);
area->insn_area =
(uprobe_opcode_t *) uprobe_setup_ssol_vma(PAGE_SIZE);
if (IS_ERR(area->insn_area))
return;
}
area->nfree = area->nslots = PAGE_SIZE / SLOT_SIZE;
if (area->nslots > MAX_SSOL_SLOTS)
area->nfree = area->nslots = MAX_SSOL_SLOTS;
area->slots = (struct uprobe_ssol_slot *)
kzalloc(sizeof(struct uprobe_ssol_slot) * area->nslots,
GFP_USER);
if (!area->slots) {
area->insn_area = ERR_PTR(-ENOMEM);
return;
}
mutex_init(&area->populate_mutex);
spin_lock_init(&area->lock);
area->next_slot = 0;
slot_addr = (char*) area->insn_area;
for (i = 0; i < area->nslots; i++) {
slot = &area->slots[i];
init_rwsem(&slot->rwsem);
slot->state = SSOL_FREE;
slot->owner = NULL;
slot->last_used = 0;
slot->insn = (__user uprobe_opcode_t *) slot_addr;
slot_addr += SLOT_SIZE;
}
uprobe_tag_vma(area);
uretprobe_set_trampoline(uproc, tsk);
area->first_ssol_slot = area->next_slot;
}
static __user uprobe_opcode_t
*uprobe_verify_ssol(struct uprobe_process *uproc)
{
struct uprobe_ssol_area *area = &uproc->ssol_area;
if (unlikely(!area->initialized)) {
mutex_lock(&uproc->ssol_area.setup_mutex);
if (likely(!area->initialized)) {
uprobe_init_ssol(uproc, current, NULL);
area->initialized = 1;
}
mutex_unlock(&uproc->ssol_area.setup_mutex);
}
return area->insn_area;
}
static inline int advance_slot(int slot, struct uprobe_ssol_area *area)
{
slot++;
if (unlikely(slot >= area->nslots))
slot = area->first_ssol_slot;
return slot;
}
static int uprobe_lru_insn_slot(struct uprobe_ssol_area *area)
{
#define MAX_LRU_TESTS 10
struct uprobe_ssol_slot *s;
int lru_slot = -1;
unsigned long lru_time = ULONG_MAX;
int nr_lru_tests = 0;
int slot = area->next_slot;
do {
s = &area->slots[slot];
if (likely(s->state == SSOL_ASSIGNED)) {
if( lru_time > s->last_used) {
lru_time = s->last_used;
lru_slot = slot;
}
if (++nr_lru_tests >= MAX_LRU_TESTS)
break;
}
slot = advance_slot(slot, area);
} while (slot != area->next_slot);
if (unlikely(lru_slot < 0))
return area->next_slot;
else
return lru_slot;
}
static struct uprobe_ssol_slot
*uprobe_take_insn_slot(struct uprobe_probept *ppt)
{
struct uprobe_process *uproc = ppt->uproc;
struct uprobe_ssol_area *area = &uproc->ssol_area;
struct uprobe_ssol_slot *s;
int len, slot;
unsigned long flags;
spin_lock_irqsave(&area->lock, flags);
if (area->nfree) {
for (slot = 0; slot < area->nslots; slot++) {
if (area->slots[slot].state == SSOL_FREE) {
area->nfree--;
goto found_slot;
}
}
area->nfree = 0;
}
slot = uprobe_lru_insn_slot(area);
found_slot:
area->next_slot = advance_slot(slot, area);
s = &area->slots[slot];
s->state = SSOL_BEING_STOLEN;
spin_unlock_irqrestore(&area->lock, flags);
down_write(&s->rwsem);
ppt->slot = s;
s->owner = ppt;
s->last_used = jiffies;
s->state = SSOL_ASSIGNED;
mutex_lock(&area->populate_mutex);
len = access_process_vm(current, (unsigned long)s->insn,
ppt->insn, MAX_UINSN_BYTES, 1);
mutex_unlock(&area->populate_mutex);
if (unlikely(len < MAX_UINSN_BYTES)) {
up_write(&s->rwsem);
printk(KERN_ERR "Failed to copy instruction at %#lx"
" to SSOL area (%#lx)\n", ppt->vaddr,
(unsigned long) area->slots);
return NULL;
}
downgrade_write(&s->rwsem);
return s;
}
static struct uprobe_ssol_slot
*uprobe_find_insn_slot(struct uprobe_probept *ppt)
{
struct uprobe_ssol_slot *slot;
mutex_lock(&ppt->slot_mutex);
slot = ppt->slot;
if (unlikely(slot && slot->owner == ppt)) {
down_read(&slot->rwsem);
if (likely(slot->owner == ppt)) {
slot->last_used = jiffies;
mutex_unlock(&ppt->slot_mutex);
return slot;
}
up_read(&slot->rwsem);
}
slot = uprobe_take_insn_slot(ppt);
mutex_unlock(&ppt->slot_mutex);
return slot;
}
static
struct uprobe_ssol_slot *uprobe_get_insn_slot(struct uprobe_probept *ppt)
{
struct uprobe_ssol_slot *slot;
retry:
slot = ppt->slot;
if (unlikely(!slot))
return uprobe_find_insn_slot(ppt);
down_read(&slot->rwsem);
if (unlikely(slot != ppt->slot)) {
up_read(&slot->rwsem);
goto retry;
}
if (unlikely(slot->owner != ppt)) {
up_read(&slot->rwsem);
return uprobe_find_insn_slot(ppt);
}
slot->last_used = jiffies;
return slot;
}
static int utask_fake_quiesce(struct uprobe_task *utask)
{
struct uprobe_process *uproc = utask->uproc;
enum uprobe_task_state prev_state = utask->state;
down_write(&uproc->rwsem);
clear_utrace_quiesce(utask, false);
if (uproc->n_quiescent_threads == uproc->nthreads-1) {
handle_pending_uprobes(uproc, utask->tsk);
rouse_all_threads(uproc);
up_write(&uproc->rwsem);
return 0;
} else {
utask->state = UPTASK_SLEEPING;
uproc->n_quiescent_threads++;
uprobe_get_process(uproc);
up_write(&uproc->rwsem);
wait_event(uproc->waitq, !utask->quiescing);
down_write(&uproc->rwsem);
utask->state = prev_state;
uproc->n_quiescent_threads--;
up_write(&uproc->rwsem);
return uprobe_put_process(uproc, false);
}
}
static inline void uprobe_pre_ssin(struct uprobe_task *utask,
struct uprobe_probept *ppt, struct pt_regs *regs)
{
int len;
arch_reset_ip_for_sstep(regs);
mutex_lock(&ppt->ssil_mutex);
len = set_orig_insn(ppt, utask->tsk);
if (unlikely(len != BP_INSN_SIZE)) {
printk("Failed to temporarily restore original "
"instruction for single-stepping: "
"pid/tgid=%d/%d, vaddr=%#lx\n",
utask->tsk->pid, utask->tsk->tgid, ppt->vaddr);
utask->doomed = 1;
}
}
static inline void uprobe_post_ssin(struct uprobe_task *utask,
struct uprobe_probept *ppt)
{
int len = set_bp(ppt, utask->tsk);
if (unlikely(len != BP_INSN_SIZE)) {
printk("Couldn't restore bp: pid/tgid=%d/%d, addr=%#lx\n",
utask->tsk->pid, utask->tsk->tgid, ppt->vaddr);
ppt->state = UPROBE_DISABLED;
}
mutex_unlock(&ppt->ssil_mutex);
}
static bool utask_quiesce(struct uprobe_task *utask)
{
if (utask->quiescing) {
if (utask->state != UPTASK_QUIESCENT) {
utask->state = UPTASK_QUIESCENT;
utask->uproc->n_quiescent_threads++;
}
return check_uproc_quiesced(utask->uproc, current);
} else {
clear_utrace_quiesce(utask, false);
return true;
}
}
static void uprobe_delay_signal(struct uprobe_task *utask, siginfo_t *info)
{
struct delayed_signal *ds = kmalloc(sizeof(*ds), GFP_USER);
if (ds) {
ds->info = *info;
INIT_LIST_HEAD(&ds->list);
list_add_tail(&ds->list, &utask->delayed_signals);
}
}
static void uprobe_inject_delayed_signals(struct list_head *delayed_signals)
{
struct delayed_signal *ds, *tmp;
list_for_each_entry_safe(ds, tmp, delayed_signals, list) {
send_sig_info(ds->info.si_signo, &ds->info, current);
list_del(&ds->list);
kfree(ds);
}
}
static u32 uprobe_report_signal(u32 action,
struct utrace_attached_engine *engine,
#if !(defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216))
struct task_struct *tsk,
#endif
struct pt_regs *regs,
siginfo_t *info,
const struct k_sigaction *orig_ka,
struct k_sigaction *return_ka)
{
struct uprobe_task *utask;
struct uprobe_probept *ppt;
struct uprobe_process *uproc;
struct uprobe_kimg *uk;
unsigned long probept;
enum utrace_signal_action signal_action = utrace_signal_action(action);
enum utrace_resume_action resume_action;
int hit_uretprobe_trampoline = 0;
rcu_read_lock();
utask = (struct uprobe_task *)rcu_dereference(engine->data);
BUG_ON(!utask);
uproc = uprobe_get_process(utask->uproc);
rcu_read_unlock();
if (!uproc)
return UTRACE_SIGNAL_IGN | UTRACE_DETACH;
if (utask->state == UPTASK_SSTEP)
resume_action = UTRACE_SINGLESTEP;
else
resume_action = UTRACE_RESUME;
if (unlikely(signal_action == UTRACE_SIGNAL_REPORT)) {
bool done_quiescing;
if (utask->active_probe) {
uprobe_decref_process(uproc);
return UTRACE_SIGNAL_IGN | resume_action;
}
down_write(&uproc->rwsem);
done_quiescing = utask_quiesce(utask);
up_write(&uproc->rwsem);
if (uprobe_put_process(uproc, true))
resume_action = UTRACE_DETACH;
else if (done_quiescing)
resume_action = UTRACE_RESUME;
else
resume_action = UTRACE_STOP;
return UTRACE_SIGNAL_IGN | resume_action;
}
TODO BUG_ON(!info);
if (signal_action == UTRACE_SIGNAL_DELIVER && utask->active_probe &&
info->si_signo != SSTEP_SIGNAL) {
uprobe_delay_signal(utask, info);
uprobe_decref_process(uproc);
return UTRACE_SIGNAL_IGN | UTRACE_SINGLESTEP;
}
if (info->si_signo != BREAKPOINT_SIGNAL &&
info->si_signo != SSTEP_SIGNAL)
goto no_interest;
#ifdef CONFIG_UPROBES_SSOL
if (uproc->sstep_out_of_line &&
unlikely(IS_ERR(uprobe_verify_ssol(uproc))))
uproc->sstep_out_of_line = 0;
#elif defined(CONFIG_URETPROBES)
(void) uprobe_verify_ssol(uproc);
#endif
switch (utask->state) {
case UPTASK_RUNNING:
if (info->si_signo != BREAKPOINT_SIGNAL)
goto no_interest;
down_read(&uproc->rwsem);
clear_utrace_quiesce(utask, false);
probept = arch_get_probept(regs);
hit_uretprobe_trampoline = (probept == (unsigned long)
uproc->uretprobe_trampoline_addr);
if (hit_uretprobe_trampoline) {
uretprobe_handle_return(regs, utask);
goto bkpt_done;
}
ppt = uprobe_find_probept(uproc, probept);
if (!ppt) {
up_read(&uproc->rwsem);
goto no_interest;
}
utask->active_probe = ppt;
utask->state = UPTASK_BP_HIT;
if (likely(ppt->state == UPROBE_BP_SET)) {
list_for_each_entry(uk, &ppt->uprobe_list, list) {
struct uprobe *u = uk->uprobe;
if (is_uretprobe(u))
uretprobe_handle_entry(u, regs, utask);
else if (u->handler)
u->handler(u, regs);
}
}
if (uprobe_emulate_insn(regs, ppt))
goto bkpt_done;
utask->state = UPTASK_PRE_SSTEP;
#ifdef CONFIG_UPROBES_SSOL
if (uproc->sstep_out_of_line)
uprobe_pre_ssout(utask, ppt, regs);
else
#endif
uprobe_pre_ssin(utask, ppt, regs);
if (unlikely(utask->doomed)) {
uprobe_decref_process(uproc);
do_exit(SIGSEGV);
}
utask->state = UPTASK_SSTEP;
utask_adjust_flags(utask, UPROBE_SET_FLAGS,
UTRACE_EVENT(QUIESCE));
resume_action = UTRACE_SINGLESTEP;
signal_action = UTRACE_SIGNAL_IGN;
break;
case UPTASK_SSTEP:
if (info->si_signo != SSTEP_SIGNAL)
goto no_interest;
clear_utrace_quiesce(utask, false);
ppt = utask->active_probe;
BUG_ON(!ppt);
utask->state = UPTASK_POST_SSTEP;
#ifdef CONFIG_UPROBES_SSOL
if (uproc->sstep_out_of_line)
uprobe_post_ssout(utask, ppt, regs);
else
#endif
uprobe_post_ssin(utask, ppt);
bkpt_done:
if (unlikely(utask->doomed)) {
uprobe_decref_process(uproc);
do_exit(SIGSEGV);
}
utask->active_probe = NULL;
utask->state = UPTASK_RUNNING;
if (utask->quiescing) {
int uproc_freed;
up_read(&uproc->rwsem);
uproc_freed = utask_fake_quiesce(utask);
BUG_ON(uproc_freed);
} else
up_read(&uproc->rwsem);
if (hit_uretprobe_trampoline)
uprobe_decref_process(uproc);
uprobe_run_def_regs(&utask->deferred_registrations);
uprobe_inject_delayed_signals(&utask->delayed_signals);
resume_action = UTRACE_RESUME;
signal_action = UTRACE_SIGNAL_IGN;
break;
default:
goto no_interest;
}
no_interest:
if (uprobe_put_process(uproc, true))
resume_action = UTRACE_DETACH;
return (signal_action | resume_action);
}
#if 0
#endif
static u32 uprobe_report_quiesce(
#if defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216)
u32 action,
struct utrace_attached_engine *engine,
#else
enum utrace_resume_action action,
struct utrace_attached_engine *engine,
struct task_struct *tsk,
#endif
unsigned long event)
{
struct uprobe_task *utask;
struct uprobe_process *uproc;
bool done_quiescing = false;
rcu_read_lock();
utask = (struct uprobe_task *)rcu_dereference(engine->data);
BUG_ON(!utask);
#if !(defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216))
BUG_ON(tsk != current); #endif
rcu_read_unlock();
if (utask->state == UPTASK_SSTEP)
return UTRACE_SINGLESTEP;
BUG_ON(utask->active_probe);
uproc = uprobe_get_process(utask->uproc);
down_write(&uproc->rwsem);
#if 0
#endif
done_quiescing = utask_quiesce(utask);
up_write(&uproc->rwsem);
uprobe_put_process(utask->uproc, true);
return (done_quiescing ? UTRACE_RESUME : UTRACE_STOP);
}
static void uprobe_cleanup_process(struct uprobe_process *uproc)
{
int i;
struct uprobe_probept *ppt;
struct hlist_node *pnode1, *pnode2;
struct hlist_head *head;
struct uprobe_kimg *uk, *unode;
struct uprobe_task *utask;
uproc->finished = 1;
for (i = 0; i < UPROBE_TABLE_SIZE; i++) {
head = &uproc->uprobe_table[i];
hlist_for_each_entry_safe(ppt, pnode1, pnode2, head, ut_node) {
if (ppt->state == UPROBE_INSERTING ||
ppt->state == UPROBE_REMOVING) {
ppt->state = UPROBE_DISABLED;
list_del(&ppt->pd_node);
list_for_each_entry_safe(uk, unode,
&ppt->uprobe_list, list)
uk->status = -ESRCH;
wake_up_all(&ppt->waitq);
} else if (ppt->state == UPROBE_BP_SET) {
list_for_each_entry_safe(uk, unode,
&ppt->uprobe_list, list) {
list_del(&uk->list);
uprobe_free_kimg(uk);
}
uprobe_free_probept(ppt);
}
}
}
list_for_each_entry(utask, &uproc->thread_list, list)
utask_free_uretprobe_instances(utask);
}
static u32 uprobe_report_exit(enum utrace_resume_action action,
struct utrace_attached_engine *engine,
#if !(defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216))
struct task_struct *tsk,
#endif
long orig_code, long *code)
{
#if defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216)
struct task_struct *tsk = current;
#endif
struct uprobe_task *utask;
struct uprobe_process *uproc;
struct uprobe_probept *ppt;
int utask_quiescing;
rcu_read_lock();
utask = (struct uprobe_task *)rcu_dereference(engine->data);
BUG_ON(!utask);
uproc = uprobe_get_process(utask->uproc);
rcu_read_unlock();
if (!uproc)
return UTRACE_DETACH;
ppt = utask->active_probe;
if (ppt) {
if (utask->state == UPTASK_TRAMPOLINE_HIT)
printk(KERN_WARNING "Task died during uretprobe return:"
" pid/tgid = %d/%d\n", tsk->pid, tsk->tgid);
else
printk(KERN_WARNING "Task died at uprobe probepoint:"
" pid/tgid = %d/%d, probepoint = %#lx\n",
tsk->pid, tsk->tgid, ppt->vaddr);
if (uproc->sstep_out_of_line) {
if (utask->state == UPTASK_SSTEP
&& ppt->slot && ppt->slot->owner == ppt)
up_read(&ppt->slot->rwsem);
} else {
switch (utask->state) {
case UPTASK_PRE_SSTEP:
case UPTASK_SSTEP:
case UPTASK_POST_SSTEP:
mutex_unlock(&ppt->ssil_mutex);
break;
default:
break;
}
}
up_read(&uproc->rwsem);
if (utask->state == UPTASK_TRAMPOLINE_HIT ||
utask->state == UPTASK_BP_HIT)
uprobe_decref_process(uproc);
}
down_write(&uproc->rwsem);
utask_quiescing = utask->quiescing;
uprobe_free_task(utask, 1);
uproc->nthreads--;
if (uproc->nthreads) {
if (utask_quiescing)
(void) check_uproc_quiesced(uproc,
find_surviving_thread(uproc));
} else {
uprobe_cleanup_process(uproc);
}
up_write(&uproc->rwsem);
uprobe_put_process(uproc, true);
return UTRACE_DETACH;
}
static int uprobe_fork_uretprobe_instances(struct uprobe_task *parent_utask,
struct uprobe_task *child_utask)
{
struct uprobe_process *parent_uproc = parent_utask->uproc;
struct uprobe_process *child_uproc = child_utask->uproc;
__user uprobe_opcode_t *trampoline_addr =
child_uproc->uretprobe_trampoline_addr;
struct hlist_node *tmp, *tail;
struct uretprobe_instance *pri, *cri;
BUG_ON(trampoline_addr != parent_uproc->uretprobe_trampoline_addr);
tail = NULL;
hlist_for_each_entry(pri, tmp, &parent_utask->uretprobe_instances,
hlist) {
if (pri->ret_addr == (unsigned long) trampoline_addr)
continue;
cri = kmalloc(sizeof(*cri), GFP_USER);
if (!cri)
return -ENOMEM;
cri->rp = NULL;
cri->ret_addr = pri->ret_addr;
cri->sp = pri->sp;
INIT_HLIST_NODE(&cri->hlist);
if (tail)
hlist_add_after(tail, &cri->hlist);
else
hlist_add_head(&cri->hlist,
&child_utask->uretprobe_instances);
tail = &cri->hlist;
uprobe_get_process(child_uproc);
}
BUG_ON(hlist_empty(&child_utask->uretprobe_instances));
return 0;
}
static int uprobe_fork_uproc(struct uprobe_process *parent_uproc,
struct uprobe_task *parent_utask,
struct task_struct *child_tsk)
{
int ret = 0;
struct uprobe_process *child_uproc;
struct uprobe_task *child_utask;
struct pid *child_pid;
BUG_ON(parent_uproc->tgid == child_tsk->tgid);
BUG_ON(!parent_uproc->uretprobe_trampoline_addr ||
IS_ERR(parent_uproc->uretprobe_trampoline_addr));
ret = uprobe_validate_vma(child_tsk,
(unsigned long) parent_uproc->ssol_area.insn_area);
if (ret) {
int ret2;
printk(KERN_ERR "uprobes: Child %d failed to inherit"
" parent %d's SSOL vma at %p. Error = %d\n",
child_tsk->pid, parent_utask->tsk->pid,
parent_uproc->ssol_area.insn_area, ret);
ret2 = uprobe_validate_vma(parent_utask->tsk,
(unsigned long) parent_uproc->ssol_area.insn_area);
if (ret2 != 0)
printk(KERN_ERR "uprobes: Parent %d's SSOL vma"
" is no longer valid. Error = %d\n",
parent_utask->tsk->pid, ret2);
return ret;
}
if (!try_module_get(THIS_MODULE))
return -ENOSYS;
child_pid = get_pid(task_pid(child_tsk));
if (!child_pid) {
module_put(THIS_MODULE);
return -ESRCH;
}
child_uproc = uprobe_mk_process(child_pid, 1);
put_pid(child_pid);
if (IS_ERR(child_uproc)) {
ret = (int) PTR_ERR(child_uproc);
module_put(THIS_MODULE);
return ret;
}
mutex_lock(&child_uproc->ssol_area.setup_mutex);
uprobe_init_ssol(child_uproc, child_tsk,
parent_uproc->ssol_area.insn_area);
child_uproc->ssol_area.initialized = 1;
mutex_unlock(&child_uproc->ssol_area.setup_mutex);
child_utask = uprobe_find_utask(child_tsk);
BUG_ON(!child_utask);
ret = uprobe_fork_uretprobe_instances(parent_utask, child_utask);
hlist_add_head(&child_uproc->hlist,
&uproc_table[hash_ptr(child_pid, UPROBE_HASH_BITS)]);
uprobe_decref_process(child_uproc);
return ret;
}
TODOstatic u32 uprobe_report_clone(enum utrace_resume_action action,
struct utrace_attached_engine *engine,
#if !(defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216))
struct task_struct *parent,
#endif
unsigned long clone_flags,
struct task_struct *child)
{
#if defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216)
struct task_struct *parent = current;
#endif
int len;
struct uprobe_process *uproc;
struct uprobe_task *ptask, *ctask;
rcu_read_lock();
ptask = (struct uprobe_task *)rcu_dereference(engine->data);
uproc = ptask->uproc;
rcu_read_unlock();
lock_uproc_table();
down_write(&uproc->rwsem);
if (clone_flags & (CLONE_THREAD|CLONE_VM)) {
ctask = uprobe_find_utask(child);
if (unlikely(ctask)) {
} else {
struct pid *child_pid = get_pid(task_pid(child));
BUG_ON(!child_pid);
ctask = uprobe_add_task(child_pid, uproc);
BUG_ON(!ctask);
if (IS_ERR(ctask))
goto done;
uproc->nthreads++;
FIXME }
} else {
int i;
struct uprobe_probept *ppt;
struct hlist_node *node;
struct hlist_head *head;
for (i = 0; i < UPROBE_TABLE_SIZE; i++) {
head = &uproc->uprobe_table[i];
hlist_for_each_entry(ppt, node, head, ut_node) {
len = set_orig_insn(ppt, child);
if (len != BP_INSN_SIZE) {
printk(KERN_ERR "Pid %d forked %d;"
" failed to remove probepoint"
" at %#lx in child\n",
parent->pid, child->pid,
ppt->vaddr);
}
}
}
if (!hlist_empty(&ptask->uretprobe_instances)) {
int result = uprobe_fork_uproc(uproc, ptask, child);
if (result != 0)
printk(KERN_ERR "Failed to create"
" uprobe_process on fork: child=%d,"
" parent=%d, error=%d\n",
child->pid, parent->pid, result);
}
}
done:
up_write(&uproc->rwsem);
unlock_uproc_table();
return UTRACE_RESUME;
}
static u32 uprobe_report_exec(
#if defined(UTRACE_API_VERSION) && (UTRACE_API_VERSION >= 20091216)
u32 action,
struct utrace_attached_engine *engine,
#else
enum utrace_resume_action action,
struct utrace_attached_engine *engine,
struct task_struct *parent,
#endif
const struct linux_binfmt *fmt,
const struct linux_binprm *bprm,
struct pt_regs *regs)
{
struct uprobe_process *uproc;
struct uprobe_task *utask;
u32 ret = UTRACE_RESUME;
rcu_read_lock();
utask = (struct uprobe_task *)rcu_dereference(engine->data);
BUG_ON(!utask);
uproc = uprobe_get_process(utask->uproc);
rcu_read_unlock();
if (!uproc)
return UTRACE_DETACH;
down_write(&uproc->rwsem);
if (uproc->nthreads == 1) {
uprobe_cleanup_process(uproc);
TODO clear_utrace_quiesce(utask, false);
} else {
uprobe_free_task(utask, 1);
uproc->nthreads--;
ret = UTRACE_DETACH;
}
up_write(&uproc->rwsem);
if (uprobe_put_process(uproc, true))
ret = UTRACE_DETACH;
return ret;
}
static const struct utrace_engine_ops uprobe_utrace_ops =
{
.report_quiesce = uprobe_report_quiesce,
.report_signal = uprobe_report_signal,
.report_exit = uprobe_report_exit,
.report_clone = uprobe_report_clone,
.report_exec = uprobe_report_exec
};
static int __init init_uprobes(void)
{
int i;
for (i = 0; i < UPROBE_TABLE_SIZE; i++) {
INIT_HLIST_HEAD(&uproc_table[i]);
INIT_HLIST_HEAD(&utask_table[i]);
}
p_uprobe_utrace_ops = &uprobe_utrace_ops;
return 0;
}
static void __exit exit_uprobes(void)
{
}
module_init(init_uprobes);
module_exit(exit_uprobes);
#ifdef CONFIG_URETPROBES
static inline bool compare_stack_ptrs(unsigned long cursp,
unsigned long ri_sp)
{
#ifdef CONFIG_STACK_GROWSUP
if (cursp < ri_sp)
return true;
#else
if (cursp > ri_sp)
return true;
#endif
return false;
}
static inline void uretprobe_bypass_instances(unsigned long cursp,
struct uprobe_task *utask)
{
struct hlist_node *r1, *r2;
struct uretprobe_instance *ri;
struct hlist_head *head = &utask->uretprobe_instances;
hlist_for_each_entry_safe(ri, r1, r2, head, hlist) {
if (compare_stack_ptrs(cursp, ri->sp)) {
hlist_del(&ri->hlist);
kfree(ri);
uprobe_decref_process(utask->uproc);
} else
return;
}
}
static void uretprobe_handle_entry(struct uprobe *u, struct pt_regs *regs,
struct uprobe_task *utask)
{
struct uretprobe_instance *ri;
unsigned long trampoline_addr;
if (IS_ERR(utask->uproc->uretprobe_trampoline_addr))
return;
trampoline_addr = (unsigned long)
utask->uproc->uretprobe_trampoline_addr;
ri = (struct uretprobe_instance *)
kmalloc(sizeof(struct uretprobe_instance), GFP_USER);
if (!ri)
return;
ri->ret_addr = arch_hijack_uret_addr(trampoline_addr, regs, utask);
if (likely(ri->ret_addr)) {
ri->sp = arch_predict_sp_at_ret(regs, utask->tsk);
uretprobe_bypass_instances(ri->sp, utask);
ri->rp = container_of(u, struct uretprobe, u);
INIT_HLIST_NODE(&ri->hlist);
hlist_add_head(&ri->hlist, &utask->uretprobe_instances);
uprobe_get_process(utask->uproc);
} else
kfree(ri);
}
static unsigned long uretprobe_run_handlers(struct uprobe_task *utask,
struct pt_regs *regs, unsigned long trampoline_addr)
{
unsigned long ret_addr, cur_sp;
struct hlist_head *head = &utask->uretprobe_instances;
struct uretprobe_instance *ri;
struct hlist_node *r1, *r2;
cur_sp = arch_get_cur_sp(regs);
uretprobe_bypass_instances(cur_sp, utask);
hlist_for_each_entry_safe(ri, r1, r2, head, hlist) {
if (ri->rp && ri->rp->handler)
ri->rp->handler(ri, regs);
ret_addr = ri->ret_addr;
hlist_del(&ri->hlist);
kfree(ri);
uprobe_decref_process(utask->uproc);
if (ret_addr != trampoline_addr)
return ret_addr;
}
printk(KERN_ERR "No uretprobe instance with original return address!"
" pid/tgid=%d/%d", current->pid, current->tgid);
utask->doomed = 1;
return 0;
}
static void uretprobe_handle_return(struct pt_regs *regs,
struct uprobe_task *utask)
{
unsigned long orig_ret_addr;
uprobe_get_process(utask->uproc);
utask->state = UPTASK_TRAMPOLINE_HIT;
utask->active_probe = &uretprobe_trampoline_dummy_probe;
orig_ret_addr = uretprobe_run_handlers(utask, regs,
(unsigned long) utask->uproc->uretprobe_trampoline_addr);
arch_restore_uret_addr(orig_ret_addr, regs);
}
int register_uretprobe(struct uretprobe *rp)
{
if (!rp || !rp->handler)
return -EINVAL;
rp->u.handler = URETPROBE_HANDLE_ENTRY;
return register_uprobe(&rp->u);
}
EXPORT_SYMBOL_GPL(register_uretprobe);
static void zap_uretprobe_instances(struct uprobe *u,
struct uprobe_process *uproc)
{
struct uprobe_task *utask;
struct uretprobe *rp = container_of(u, struct uretprobe, u);
if (!uproc)
return;
list_for_each_entry(utask, &uproc->thread_list, list) {
struct hlist_node *r;
struct uretprobe_instance *ri;
hlist_for_each_entry(ri, r, &utask->uretprobe_instances, hlist)
if (ri->rp == rp)
ri->rp = NULL;
}
}
void unregister_uretprobe(struct uretprobe *rp)
{
if (!rp)
return;
unregister_uprobe(&rp->u);
}
EXPORT_SYMBOL_GPL(unregister_uretprobe);
void unmap_uretprobe(struct uretprobe *rp)
{
if (!rp)
return;
unmap_uprobe(&rp->u);
}
EXPORT_SYMBOL_GPL(unmap_uretprobe);
static void uretprobe_set_trampoline(struct uprobe_process *uproc,
struct task_struct *tsk)
{
uprobe_opcode_t bp_insn = BREAKPOINT_INSTRUCTION;
struct uprobe_ssol_area *area = &uproc->ssol_area;
struct uprobe_ssol_slot *slot = &area->slots[area->next_slot];
if (access_process_vm(tsk, (unsigned long) slot->insn,
&bp_insn, BP_INSN_SIZE, 1) == BP_INSN_SIZE) {
uproc->uretprobe_trampoline_addr = slot->insn;
slot->state = SSOL_RESERVED;
area->next_slot++;
area->nfree--;
} else {
printk(KERN_ERR "uretprobes disabled for pid %d:"
" cannot set uretprobe trampoline at %p\n",
pid_nr(uproc->tg_leader), slot->insn);
}
}
static inline unsigned long lookup_uretprobe(struct hlist_node *r,
struct uprobe_process *uproc,
unsigned long pc,
unsigned long sp)
{
struct uretprobe_instance *ret_inst;
unsigned long trampoline_addr;
if (IS_ERR(uproc->uretprobe_trampoline_addr))
return pc;
trampoline_addr = (unsigned long)uproc->uretprobe_trampoline_addr;
if (pc != trampoline_addr)
return pc;
hlist_for_each_entry_from(ret_inst, r, hlist) {
if (ret_inst->ret_addr == trampoline_addr)
continue;
if (ret_inst->sp == sp || compare_stack_ptrs(ret_inst->sp, sp))
return ret_inst->ret_addr;
}
printk(KERN_ERR "Original return address for trampoline not found at "
"0x%lx pid/tgid=%d/%d\n", sp, current->pid, current->tgid);
return 0;
}
unsigned long uprobe_get_pc(struct uretprobe_instance *ri, unsigned long pc,
unsigned long sp)
{
struct uretprobe *rp;
struct uprobe_kimg *uk;
struct uprobe_task *utask;
struct uprobe_process *uproc;
struct hlist_node *r;
if (ri == GET_PC_URETPROBE_NONE) {
utask = uprobe_find_utask(current);
if (!utask)
return 0;
uproc = utask->uproc;
r = utask->uretprobe_instances.first;
} else {
rp = ri->rp;
uk = (struct uprobe_kimg *)rp->u.kdata;
if (!uk)
return 0;
uproc = uk->ppt->uproc;
r = &ri->hlist;
}
return lookup_uretprobe(r, uproc, pc, sp);
}
EXPORT_SYMBOL_GPL(uprobe_get_pc);
unsigned long uprobe_get_pc_task(struct task_struct *task, unsigned long pc,
unsigned long sp)
{
struct uprobe_task *utask;
struct uprobe_process *uproc;
unsigned long result;
utask = uprobe_find_utask(task);
if (!utask) {
return pc;
} else if (current == task && utask->active_probe) {
return uprobe_get_pc(GET_PC_URETPROBE_NONE, pc, sp);
}
uproc = utask->uproc;
down_read(&uproc->rwsem);
result = lookup_uretprobe(utask->uretprobe_instances.first, uproc, pc,
sp);
up_read(&uproc->rwsem);
return result;
}
EXPORT_SYMBOL_GPL(uprobe_get_pc_task);
#else
static void uretprobe_handle_entry(struct uprobe *u, struct pt_regs *regs,
struct uprobe_task *utask)
{
}
static void uretprobe_handle_return(struct pt_regs *regs,
struct uprobe_task *utask)
{
}
static void uretprobe_set_trampoline(struct uprobe_process *uproc,
struct task_struct *tsk)
{
}
static void zap_uretprobe_instances(struct uprobe *u,
struct uprobe_process *uproc)
{
}
#endif
#define UPROBES_DEBUG
#ifdef UPROBES_DEBUG
struct uprobe_task *updebug_find_utask(struct task_struct *tsk)
{
return uprobe_find_utask(tsk);
}
EXPORT_SYMBOL_GPL(updebug_find_utask);
struct uprobe_process *updebug_find_process(pid_t tgid)
{
struct hlist_head *head;
struct hlist_node *node;
struct uprobe_process *uproc;
struct pid *p;
p = uprobe_get_tg_leader(tgid);
head = &uproc_table[hash_ptr(p, UPROBE_HASH_BITS)];
hlist_for_each_entry(uproc, node, head, hlist) {
if (uproc->tg_leader == p && !uproc->finished)
return uproc;
}
return NULL;
}
EXPORT_SYMBOL_GPL(updebug_find_process);
struct uprobe_probept *updebug_find_probept(struct uprobe_process *uproc,
unsigned long vaddr)
{
return uprobe_find_probept(uproc, vaddr);
}
EXPORT_SYMBOL_GPL(updebug_find_probept);
#endif
#ifdef NO_ACCESS_PROCESS_VM_EXPORT
#include <linux/pagemap.h>
#include <asm/cacheflush.h>
static int __access_process_vm(struct task_struct *tsk, unsigned long addr, void *buf, int len, int write)
{
struct mm_struct *mm;
struct vm_area_struct *vma;
struct page *page;
void *old_buf = buf;
mm = get_task_mm(tsk);
if (!mm)
return 0;
down_read(&mm->mmap_sem);
while (len) {
int bytes, ret, offset;
void *maddr;
ret = get_user_pages(tsk, mm, addr, 1,
write, 1, &page, &vma);
if (ret <= 0)
break;
bytes = len;
offset = addr & (PAGE_SIZE-1);
if (bytes > PAGE_SIZE-offset)
bytes = PAGE_SIZE-offset;
maddr = kmap(page);
if (write) {
copy_to_user_page(vma, page, addr,
maddr + offset, buf, bytes);
set_page_dirty_lock(page);
} else {
copy_from_user_page(vma, page, addr,
buf, maddr + offset, bytes);
}
kunmap(page);
page_cache_release(page);
len -= bytes;
buf += bytes;
addr += bytes;
}
up_read(&mm->mmap_sem);
mmput(mm);
return buf - old_buf;
}
#endif
#include "uprobes_arch.c"
MODULE_LICENSE("GPL");