runtime/linux/uprobes-inode.c - systemtap
Global variables defined
Data types defined
Functions defined
Macros defined
Source code
#ifndef _UPROBES_INODE_C_
#define _UPROBES_INODE_C_
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/namei.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/uprobes.h>
#if !defined(CONFIG_UPROBES)
#error "not to be built without CONFIG_UPROBES"
#endif
#if !defined(STAPCONF_UPROBE_REGISTER_EXPORTED)
#if defined(STAPCONF_OLD_INODE_UPROBES)
typedef typeof(®ister_uprobe) uprobe_register_fn;
#else
typedef typeof(&uprobe_register) uprobe_register_fn;
#endif
#define uprobe_register (* (uprobe_register_fn)kallsyms_uprobe_register)
#elif defined(STAPCONF_OLD_INODE_UPROBES)
#define uprobe_register register_uprobe
#endif
#if !defined(STAPCONF_UPROBE_UNREGISTER_EXPORTED)
#if defined(STAPCONF_OLD_INODE_UPROBES)
typedef typeof(&unregister_uprobe) uprobe_unregister_fn;
#else
typedef typeof(&uprobe_unregister) uprobe_unregister_fn;
#endif
#define uprobe_unregister (* (uprobe_unregister_fn)kallsyms_uprobe_unregister)
#elif defined(STAPCONF_OLD_INODE_UPROBES)
#define uprobe_unregister unregister_uprobe
#endif
#ifndef UPROBE_HANDLER_MASK
#define STAPIU_NEEDS_REG_IP 1
#if !defined(STAPCONF_UPROBE_GET_SWBP_ADDR_EXPORTED)
typedef typeof(&uprobe_get_swbp_addr) uprobe_get_swbp_addr_fn;
#define uprobe_get_swbp_addr (* (uprobe_get_swbp_addr_fn)kallsyms_uprobe_get_swbp_addr)
#endif
#endif
struct stapiu_target {
struct list_head consumers;
struct list_head processes; rwlock_t process_lock;
struct stap_task_finder_target finder;
const char * const filename;
struct inode *inode;
struct mutex inode_lock;
};
struct stapiu_consumer {
struct uprobe_consumer consumer;
const unsigned return_p:1;
unsigned registered:1;
struct list_head target_consumer;
struct stapiu_target * const target;
loff_t offset; loff_t sdt_sem_offset;
long perf_counters_dim;
long *perf_counters;
const struct stap_probe * const probe;
};
static struct stapiu_process {
struct list_head target_process;
unsigned long relocation; unsigned long base; pid_t tgid;
} stapiu_process_slots[MAXUPROBES];
static DEFINE_SPINLOCK(stapiu_process_slots_lock);
#if defined(UPROBES_HITCOUNT)
static atomic_t prehandler_hitcount = ATOMIC_INIT(0);
static atomic_t handler_hitcount = ATOMIC_INIT(0);
#endif
static int
stapiu_probe_handler (struct stapiu_consumer *sup, struct pt_regs *regs);
static int
stapiu_probe_prehandler (struct uprobe_consumer *inst, struct pt_regs *regs)
{
int ret;
struct stapiu_consumer *sup =
container_of(inst, struct stapiu_consumer, consumer);
struct stapiu_target *target = sup->target;
struct stapiu_process *p, *process = NULL;
#if defined(UPROBES_HITCOUNT)
atomic_inc(&prehandler_hitcount);
#endif
read_lock(&target->process_lock);
list_for_each_entry(p, &target->processes, target_process) {
if (p->tgid == current->tgid) {
process = p;
break;
}
}
read_unlock(&target->process_lock);
if (!process) {
#ifdef UPROBE_HANDLER_REMOVE
if (stap_task_finder_complete())
return UPROBE_HANDLER_REMOVE;
#endif
return 0;
}
#ifdef STAPIU_NEEDS_REG_IP
{
unsigned long saved_ip = REG_IP(regs);
SET_REG_IP(regs, uprobe_get_swbp_addr(regs));
#endif
#if defined(UPROBES_HITCOUNT)
atomic_inc(&handler_hitcount);
#endif
ret = stapiu_probe_handler(sup, regs);
#ifdef STAPIU_NEEDS_REG_IP
SET_REG_IP(regs, saved_ip);
}
#endif
return ret;
}
static int
stapiu_retprobe_prehandler (struct uprobe_consumer *inst,
unsigned long func __attribute__((unused)),
struct pt_regs *regs)
{
return stapiu_probe_prehandler(inst, regs);
}
static int
stapiu_register (struct inode* inode, struct stapiu_consumer* c)
{
int ret = 0;
if (!c->return_p) {
c->consumer.handler = stapiu_probe_prehandler;
} else {
#if defined(STAPCONF_INODE_URETPROBES)
c->consumer.ret_handler = stapiu_retprobe_prehandler;
#else
ret = EINVAL;
#endif
}
if (ret == 0)
ret = uprobe_register (inode, c->offset, &c->consumer);
c->registered = (ret ? 0 : 1);
return ret;
}
static void
stapiu_unregister (struct inode* inode, struct stapiu_consumer* c)
{
uprobe_unregister (inode, c->offset, &c->consumer);
c->registered = 0;
}
static inline void
stapiu_target_lock(struct stapiu_target *target)
{
mutex_lock(&target->inode_lock);
}
static inline void
stapiu_target_unlock(struct stapiu_target *target)
{
mutex_unlock(&target->inode_lock);
}
static int
stapiu_write_semaphore(unsigned long addr, unsigned short delta)
{
int rc = 0;
unsigned short __user* sdt_addr = (unsigned short __user*) addr;
unsigned short sdt_semaphore = 0; XXX rc = get_user(sdt_semaphore, sdt_addr);
if (!rc) {
sdt_semaphore += delta;
rc = put_user(sdt_semaphore, sdt_addr);
}
return rc;
}
static int
stapiu_write_task_semaphore(struct task_struct* task,
unsigned long addr, unsigned short delta)
{
int count, rc = 0;
unsigned short sdt_semaphore = 0; XXX count = __access_process_vm_noflush(task, addr,
&sdt_semaphore, sizeof(sdt_semaphore), 0);
if (count != sizeof(sdt_semaphore))
rc = 1;
else {
sdt_semaphore += delta;
count = __access_process_vm_noflush(task, addr,
&sdt_semaphore, sizeof(sdt_semaphore), 1);
rc = (count == sizeof(sdt_semaphore)) ? 0 : 1;
}
return rc;
}
static void
stapiu_decrement_process_semaphores(struct stapiu_process *p,
struct list_head *consumers)
{
struct task_struct *task;
rcu_read_lock();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
task = pid_task(find_pid_ns(p->tgid, &init_pid_ns), PIDTYPE_PID);
#else
task = find_task_by_pid(p->tgid);
#endif
if (task) {
struct stapiu_consumer *c;
get_task_struct(task);
rcu_read_unlock();
list_for_each_entry(c, consumers, target_consumer) {
if (c->sdt_sem_offset) {
unsigned long addr = p->base + c->sdt_sem_offset;
stapiu_write_task_semaphore(task, addr,
(unsigned short) -1);
}
}
put_task_struct(task);
}
else {
rcu_read_unlock();
}
}
static void
stapiu_decrement_semaphores(struct stapiu_target *targets, size_t ntargets)
{
size_t i;
might_sleep();
for (i = 0; i < ntargets; ++i) {
struct stapiu_target *ut = &targets[i];
struct stapiu_consumer *c;
struct stapiu_process *p;
int has_semaphores = 0;
list_for_each_entry(c, &ut->consumers, target_consumer) {
if (c->sdt_sem_offset) {
has_semaphores = 1;
break;
}
}
if (!has_semaphores)
continue;
list_for_each_entry(p, &ut->processes, target_process)
stapiu_decrement_process_semaphores(p, &ut->consumers);
}
}
static void
stapiu_target_unreg(struct stapiu_target *target)
{
struct stapiu_consumer *c;
if (! target->inode)
return;
list_for_each_entry(c, &target->consumers, target_consumer) {
if (c->registered)
stapiu_unregister(target->inode, c);
}
}
static int
stapiu_target_reg(struct stapiu_target *target, struct task_struct* task)
{
int ret = 0;
struct stapiu_consumer *c;
list_for_each_entry(c, &target->consumers, target_consumer) {
if (! c->registered) {
int i;
for (i=0; i < c->perf_counters_dim; i++) {
if ((c->perf_counters)[i] > -1)
_stp_perf_read_init ((c->perf_counters)[i], task);
}
if (!c->probe->cond_enabled) {
dbug_otf("not registering (u%sprobe) pidx %zu\n",
c->return_p ? "ret" : "", c->probe->index);
continue;
}
if (stapiu_register(target->inode, c) != 0)
_stp_warn("probe %s inode-offset %p registration error (rc %d)",
c->probe->pp, (void*) (uintptr_t) c->offset, ret);
}
}
if (ret)
stapiu_target_unreg(target);
return ret;
}
static void
stapiu_target_refresh(struct stapiu_target *target)
{
struct stapiu_consumer *c;
list_for_each_entry(c, &target->consumers, target_consumer) {
if (c->registered && !c->probe->cond_enabled) {
dbug_otf("unregistering (u%sprobe) pidx %zu\n",
c->return_p ? "ret" : "", c->probe->index);
stapiu_unregister(target->inode, c);
} else if (!c->registered && c->probe->cond_enabled) {
dbug_otf("registering (u%sprobe) pidx %zu\n",
c->return_p ? "ret" : "", c->probe->index);
if (stapiu_register(target->inode, c) != 0)
dbug_otf("couldn't register (u%sprobe) pidx %zu\n",
c->return_p ? "ret" : "", c->probe->index);
}
}
}
static void
stapiu_exit_targets(struct stapiu_target *targets, size_t ntargets)
{
size_t i;
for (i = 0; i < ntargets; ++i) {
struct stapiu_target *ut = &targets[i];
stapiu_target_unreg(ut);
if (ut->inode) {
iput(ut->inode);
ut->inode = NULL;
}
}
}
static int
stapiu_init_targets(struct stapiu_target *targets, size_t ntargets)
{
int ret = 0;
size_t i;
for (i = 0; i < ntargets; ++i) {
struct stapiu_target *ut = &targets[i];
INIT_LIST_HEAD(&ut->consumers);
INIT_LIST_HEAD(&ut->processes);
rwlock_init(&ut->process_lock);
mutex_init(&ut->inode_lock);
ret = stap_register_task_finder_target(&ut->finder);
if (ret != 0) {
_stp_error("Couldn't register task finder target for file '%s': %d\n",
ut->filename, ret);
break;
}
}
return ret;
}
static int
stapiu_init(struct stapiu_target *targets, size_t ntargets,
struct stapiu_consumer *consumers, size_t nconsumers)
{
int ret = stapiu_init_targets(targets, ntargets);
if (!ret) {
size_t i;
for (i = 0; i < nconsumers; ++i) {
struct stapiu_consumer *uc = &consumers[i];
list_add(&uc->target_consumer,
&uc->target->consumers);
}
}
return ret;
}
static void
stapiu_refresh(struct stapiu_target *targets, size_t ntargets)
{
size_t i;
for (i = 0; i < ntargets; ++i) {
struct stapiu_target *target = &targets[i];
stapiu_target_lock(target);
if (target->inode)
stapiu_target_refresh(target);
stapiu_target_unlock(target);
}
}
static void
stapiu_exit(struct stapiu_target *targets, size_t ntargets,
struct stapiu_consumer *consumers, size_t nconsumers)
{
stapiu_decrement_semaphores(targets, ntargets);
stapiu_exit_targets(targets, ntargets);
#if defined(UPROBES_HITCOUNT)
_stp_printf("stapiu_probe_prehandler() called %d times\n",
atomic_read(&prehandler_hitcount));
_stp_printf("stapiu_probe_handler() called %d times\n",
atomic_read(&handler_hitcount));
_stp_print_flush();
#endif
}
static int
stapiu_change_plus(struct stapiu_target* target, struct task_struct *task,
unsigned long relocation, unsigned long length,
unsigned long offset, unsigned long vm_flags,
struct inode *inode)
{
size_t i;
struct stapiu_process *p;
int rc;
stapiu_target_lock(target);
if (! target->inode) {
if (! inode) {
rc = -EINVAL;
stapiu_target_unlock(target);
return rc;
}
target->inode = igrab(inode);
if (!target->inode) {
_stp_error("Couldn't get inode for file '%s'\n",
target->filename);
rc = -EINVAL;
stapiu_target_unlock(target);
return rc;
}
if ((rc = _stp_usermodule_check(task, target->filename,
relocation))) {
iput(target->inode);
target->inode = NULL;
stapiu_target_unlock(target);
return rc;
}
rc = stapiu_target_reg(target, task);
if (rc) {
iput(target->inode);
target->inode = NULL;
stapiu_target_unlock(target);
return rc;
}
}
stapiu_target_unlock(target);
spin_lock(&stapiu_process_slots_lock);
write_lock(&target->process_lock);
for (i = 0; i < MAXUPROBES; ++i) {
p = &stapiu_process_slots[i];
if (!p->tgid) {
p->tgid = task->tgid;
p->relocation = relocation;
p->base = relocation - offset;
list_add(&p->target_process, &target->processes);
break;
}
}
write_unlock(&target->process_lock);
spin_unlock(&stapiu_process_slots_lock);
return 0; XXX}
static int
stapiu_change_semaphore_plus(struct stapiu_target* target, struct task_struct *task,
unsigned long relocation, unsigned long length)
{
int rc = 0;
struct stapiu_process *p, *process = NULL;
struct stapiu_consumer *c;
read_lock(&target->process_lock);
list_for_each_entry(p, &target->processes, target_process) {
if (p->tgid == task->tgid) {
process = p;
break;
}
}
read_unlock(&target->process_lock);
if (!process)
return 0;
list_for_each_entry(c, &target->consumers, target_consumer) {
if (c->sdt_sem_offset) {
unsigned long addr = process->base + c->sdt_sem_offset;
if (addr >= relocation && addr < relocation + length) {
int rc2 = stapiu_write_task_semaphore(task,
addr, +1);
if (!rc)
rc = rc2;
}
}
}
return rc;
}
static int
stapiu_change_minus(struct stapiu_target* target, struct task_struct *task,
unsigned long relocation, unsigned long length)
{
struct stapiu_process *p, *tmp;
spin_lock(&stapiu_process_slots_lock);
write_lock(&target->process_lock);
list_for_each_entry_safe(p, tmp, &target->processes, target_process) {
if (p->tgid == task->tgid && (relocation <= p->relocation &&
p->relocation < relocation+length)) {
list_del(&p->target_process);
memset(p, 0, sizeof(*p));
}
}
write_unlock(&target->process_lock);
spin_unlock(&stapiu_process_slots_lock);
return 0;
}
static struct inode *
stapiu_get_task_inode(struct task_struct *task)
{
struct mm_struct *mm;
struct file* vm_file;
struct inode *inode = NULL;
mm = task->mm;
if (! mm) {
return NULL;
}
down_read(&mm->mmap_sem);
vm_file = stap_find_exe_file(mm);
if (vm_file && vm_file->f_path.dentry)
inode = vm_file->f_path.dentry->d_inode;
up_read(&mm->mmap_sem);
return inode;
}
static int
stapiu_process_found(struct stap_task_finder_target *tf_target,
struct task_struct *task, int register_p, int process_p)
{
struct stapiu_target *target =
container_of(tf_target, struct stapiu_target, finder);
if (!process_p)
return 0;
if (register_p) {
int rc = -EINVAL;
struct inode *inode = stapiu_get_task_inode(task);
if (inode) {
rc = stapiu_change_plus(target, task, 0, TASK_SIZE,
0, 0, inode);
stapiu_change_semaphore_plus(target, task, 0,
TASK_SIZE);
}
return rc;
} else
return stapiu_change_minus(target, task, 0, TASK_SIZE);
}
static int
stapiu_mmap_found(struct stap_task_finder_target *tf_target,
struct task_struct *task,
char *path, struct dentry *dentry,
unsigned long addr, unsigned long length,
unsigned long offset, unsigned long vm_flags)
{
int rc = 0;
struct stapiu_target *target =
container_of(tf_target, struct stapiu_target, finder);
if (target->inode && dentry->d_inode != target->inode)
return 0;
if (!path || strcmp (path, target->filename))
return 0;
if ((vm_flags & VM_EXEC) && !(vm_flags & VM_WRITE))
rc = stapiu_change_plus(target, task, addr, length,
offset, vm_flags, dentry->d_inode);
if ((rc == 0) && (vm_flags & VM_WRITE))
rc = stapiu_change_semaphore_plus(target, task, addr, length);
return rc;
}
static int
stapiu_munmap_found(struct stap_task_finder_target *tf_target,
struct task_struct *task,
unsigned long addr, unsigned long length)
{
struct stapiu_target *target =
container_of(tf_target, struct stapiu_target, finder);
return stapiu_change_minus(target, task, addr, length);
}
static int
stapiu_process_munmap(struct stap_task_finder_target *tf_target,
struct task_struct *task,
int register_p, int process_p)
{
struct stapiu_target *target =
container_of(tf_target, struct stapiu_target, finder);
if (!process_p)
return 0;
if (!register_p)
return stapiu_change_minus(target, task, 0, TASK_SIZE);
return 0;
}
#endif