diff options
author | William Cohen <wcohen@redhat.com> | 2010-01-13 13:54:26 -0500 |
---|---|---|
committer | William Cohen <wcohen@redhat.com> | 2010-01-13 13:54:26 -0500 |
commit | cc52276b5ecd4501271d3846ad3519c7db03b54f (patch) | |
tree | d8a27ff500f6ed486e11f0e647438449d96c1ded /runtime | |
parent | 75de0a1f306ecedfe992b9b3ad8b8f2f76d8d24a (diff) | |
download | systemtap-steved-cc52276b5ecd4501271d3846ad3519c7db03b54f.tar.gz systemtap-steved-cc52276b5ecd4501271d3846ad3519c7db03b54f.tar.xz systemtap-steved-cc52276b5ecd4501271d3846ad3519c7db03b54f.zip |
Move userspace probing boiler plate code in translator to runtime library.
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/uprobes-common.c | 289 | ||||
-rw-r--r-- | runtime/uprobes-common.h | 37 |
2 files changed, 326 insertions, 0 deletions
diff --git a/runtime/uprobes-common.c b/runtime/uprobes-common.c new file mode 100644 index 00000000..b0273ba4 --- /dev/null +++ b/runtime/uprobes-common.c @@ -0,0 +1,289 @@ +/* -*- linux-c -*- + * uprobe Functions + * Copyright (C) 2010 Red Hat Inc. + * + * This file is part of systemtap, and is free software. You can + * redistribute it and/or modify it under the terms of the GNU General + * Public License (GPL); either version 2, or (at your option) any + * later version. + */ + +#ifndef _UPROBE_COMMON_C_ +#define _UPROBE_COMMON_C_ + +/* NB: Because these utrace callbacks only occur before / after + userspace instructions run, there is no concurrency control issue + between active uprobe callbacks and these registration / + unregistration pieces. + + We protect the stap_uprobe->spec_index (which also serves as a + free/busy flag) value with the outer protective stap_probes_lock + spinlock, to protect it against concurrent registration / + unregistration. +*/ + +static int stap_uprobe_change_plus (struct task_struct *tsk, unsigned long relocation, unsigned long length, const struct stap_uprobe_tf *stf, unsigned long offset, unsigned long vm_flags) { + int tfi = (stf - stap_uprobe_finders); + int spec_index; + /* iterate over stap_uprobe_spec[] that use this same stap_uprobe_tf */ + for (spec_index=0; spec_index<sizeof(stap_uprobe_specs)/sizeof(stap_uprobe_specs[0]); spec_index++) { + int handled_p = 0; + int slotted_p = 0; + const struct stap_uprobe_spec *sups = &stap_uprobe_specs [spec_index]; + struct stap_uprobe *sup; + pid_t sdt_sem_pid; + int rc = 0; + int i; + if (likely(sups->tfi != tfi)) continue; + /* skip probes with an address beyond this map event; should not + happen unless a shlib/exec got mmapped in weirdly piecemeal */ + if (likely((vm_flags & VM_EXEC) && ((sups->address >= length) || (sups->sdt_sem_offset >= length)))) continue; + + /* Found a uprobe_spec for this stap_uprobe_tf. Need to lock the + stap_uprobes[] array to allocate a free spot, but then we can + unlock and do the register_*probe subsequently. */ + + mutex_lock (& stap_uprobes_lock); + for (i=0; i<MAXUPROBES; i++) { /* XXX: slow linear search */ + sup = & stap_uprobes[i]; + + /* register new uprobe + We make two passes for semaphores; + see _stap_uprobe_change_semaphore_plus */ + + if (sup->spec_index < 0 || (sups->sdt_sem_offset && vm_flags & VM_WRITE && sup->spec_index == spec_index)) { + #if (UPROBES_API_VERSION < 2) + /* See PR6829 comment. */ + if (sup->spec_index == -1 && sup->up.kdata != NULL) continue; + else if (sup->spec_index == -2 && sup->urp.u.kdata != NULL) continue; + #endif + sup->spec_index = spec_index; + slotted_p = 1; + break; + } + } + mutex_unlock (& stap_uprobes_lock); + #ifdef DEBUG_UPROBES + _stp_dbug(__FUNCTION__,__LINE__, "+uprobe spec %d idx %d process %s[%d] addr %p pp %s\n", spec_index, (slotted_p ? i : -1), tsk->comm, tsk->tgid, (void*)(relocation+sups->address), sups->pp); + #endif + + /* Here, slotted_p implies that `i' points to the single + stap_uprobes[] element that has been slotted in for registration + or unregistration processing. !slotted_p implies that the table + was full (registration; MAXUPROBES) or that no matching entry was + found (unregistration; should not happen). */ + + sdt_sem_pid = (sups->return_p ? sup->urp.u.pid : sup->up.pid); + if (sups->sdt_sem_offset && (sdt_sem_pid != tsk->tgid || sup->sdt_sem_address == 0)) { + /* If the probe is in the executable itself, the offset *is* the address. */ + if (vm_flags & VM_EXECUTABLE) { + sup->sdt_sem_address = relocation + sups->sdt_sem_offset; + } + else { + sup->sdt_sem_address = (relocation - offset) + sups->sdt_sem_offset; + } + } /* sdt_sem_offset */ + if (slotted_p) { + struct stap_uprobe *sup = & stap_uprobes[i]; + if (sups->return_p) { + sup->urp.u.pid = tsk->tgid; + sup->urp.u.vaddr = relocation + sups->address; + sup->urp.handler = &enter_uretprobe_probe; + rc = register_uretprobe (& sup->urp); + } else { + sup->up.pid = tsk->tgid; + sup->up.vaddr = relocation + sups->address; + sup->up.handler = &enter_uprobe_probe; + rc = register_uprobe (& sup->up); + } + if (rc) { /* failed to register */ + _stp_warn ("u*probe failed %s[%d] '%s' addr %p rc %d\n", tsk->comm, tsk->tgid, sups->pp, (void*)(relocation + sups->address), rc); + /* NB: we need to release this slot, + so we need to borrow the mutex temporarily. */ + mutex_lock (& stap_uprobes_lock); + sup->spec_index = -1; + mutex_unlock (& stap_uprobes_lock); + } else { + handled_p = 1; + } + } + /* NB: handled_p implies slotted_p */ + if (unlikely (! handled_p)) { + #ifdef STP_TIMING + atomic_inc (& skipped_count_uprobe_reg); + #endif + /* NB: duplicates common_entryfn_epilogue, + but then this is not a probe entry fn epilogue. */ + if (unlikely (atomic_inc_return (& skipped_count) > MAXSKIPPED)) { + if (unlikely (pseudo_atomic_cmpxchg(& session_state, STAP_SESSION_RUNNING, STAP_SESSION_ERROR) == STAP_SESSION_RUNNING)) + _stp_error ("Skipped too many probes, check MAXSKIPPED or try again with stap -t for more details."); + } + } + } /* close iteration over stap_uprobe_spec[] */ + return 0; /* XXX: or rc? */ +} + +static int stap_uprobe_change_semaphore_plus (struct task_struct *tsk, unsigned long relocation, unsigned long length, const struct stap_uprobe_tf *stf) { + int tfi = (stf - stap_uprobe_finders); + int spec_index; + int rc = 0; + struct stap_uprobe *sup; + int i; + + /* We make two passes for semaphores. + The first pass, stap_uprobe_change_plus, calculates the address of the + semaphore. If the probe is in a .so, we calculate the + address when the initial mmap maps the entire solib, e.g. + 7f089885a000-7f089885b000 rw-p- libtcl.so + A subsequent mmap maps in the writeable segment where the + semaphore control variable lives, e.g. + 7f089850d000-7f0898647000 r-xp- libtcl.so + 7f0898647000-7f0898846000 ---p libtcl.so + 7f0898846000-7f089885b000 rw-p- libtcl.so + The second pass, stap_uprobe_change_semaphore_plus, sets the semaphore. + If the probe is in a .so this will be when the writeable segment of the .so + is mapped in. If the task changes, then recalculate the address. + */ + + for (i=0; i<MAXUPROBES; i++) { /* XXX: slow linear search */ + sup = & stap_uprobes[i]; + if (sup->spec_index == -1) continue; + if (sup->sdt_sem_address != 0 && !(sup->up.pid == tsk->tgid && sup->sdt_sem_address >= relocation && sup->sdt_sem_address < relocation+length)) continue; + if (sup->sdt_sem_address) { + unsigned short sdt_semaphore = 0; /* NB: fixed size */ + if ((rc = get_user (sdt_semaphore, (unsigned short __user*) sup->sdt_sem_address)) == 0) { + sdt_semaphore ++; + #ifdef DEBUG_UPROBES + { + const struct stap_uprobe_spec *sups = &stap_uprobe_specs [sup->spec_index]; + _stp_dbug(__FUNCTION__,__LINE__, "+semaphore %#x @ %#lx spec %d idx %d task %d\n", sdt_semaphore, sup->sdt_sem_address, sup->spec_index, i, tsk->tgid); + } + #endif + rc = put_user (sdt_semaphore, (unsigned short __user*) sup->sdt_sem_address); + /* XXX: need to analyze possibility of race condition */ + } + } + } + return rc; +} + +/* Removing/unmapping a uprobe is simpler than adding one (in the + _plus function above). We need not care about stap_uprobe_finders + or anything, we just scan through stap_uprobes[] for a live probe + within the given address range, and kill it. */ +static int stap_uprobe_change_minus (struct task_struct *tsk, unsigned long relocation, unsigned long length, const struct stap_uprobe_tf *stf) { + int i; + + /* NB: it's not an error for us not to find a live uprobe within the + given range. We might have received a callback for a part of a + shlib that was unmapped and unprobed. */ + + for (i=0; i<MAXUPROBES; i++) { /* XXX: slow linear search */ + struct stap_uprobe *sup = & stap_uprobes[i]; + struct stap_uprobe_spec *sups; + if (sup->spec_index < 0) continue; /* skip free uprobes slot */ + sups = (struct stap_uprobe_spec*) & stap_uprobe_specs[sup->spec_index]; + mutex_lock (& stap_uprobes_lock); + + /* PR6829, PR9940: + Here we're unregistering for one of two reasons: + 1. the process image is going away (or gone) due to exit or exec; or + 2. the vma containing the probepoint has been unmapped. + In case 1, it's sort of a nop, because uprobes will notice the event + and dispose of the probes eventually, if it hasn't already. But by + calling unmap_u[ret]probe() ourselves, we free up sup right away. + + In both cases, we must use unmap_u[ret]probe instead of + unregister_u[ret]probe, so uprobes knows not to try to restore the + original opcode. + */ + + /* URETPROBE */ + if (sups->return_p && sup->urp.u.pid == tsk->tgid && sup->urp.u.vaddr >= relocation && sup->urp.u.vaddr < relocation+length) { /* in range */ + + #ifdef DEBUG_UPROBES + _stp_dbug (__FUNCTION__,__LINE__, "-uretprobe spec %d idx %d process %s[%d] addr %p pp %s\n", sup->spec_index, i, tsk->comm, tsk->tgid, (void*) sup->urp.u.vaddr, sups->pp); + #endif + #if (UPROBES_API_VERSION >= 2) + unmap_uretprobe (& sup->urp); + sup->spec_index = -1; + #else + /* Uprobes lacks unmap_uretprobe. Before reusing sup, we must wait + until uprobes turns loose of the uretprobe on its own, as indicated + by uretprobe.kdata = NULL. */ + sup->spec_index = -2; + #endif + /* UPROBE */ + } else if (!sups->return_p && sup->up.pid == tsk->tgid && sup->up.vaddr >= relocation && sup->up.vaddr < relocation+length) { /* in range */ + + #ifdef DEBUG_UPROBES + _stp_dbug (__FUNCTION__,__LINE__, "-uprobe spec %d idx %d process %s[%d] reloc %p pp %s\n", sup->spec_index, i, tsk->comm, tsk->tgid, (void*) sup->up.vaddr, sups->pp); + #endif + #if (UPROBES_API_VERSION >= 2) + unmap_uprobe (& sup->up); + sup->spec_index = -1; + #else + /* Uprobes lacks unmap_uprobe. Before reusing sup, we must wait + until uprobes turns loose of the uprobe on its own, as indicated + by uprobe.kdata = NULL. */ + sup->spec_index = -1; + #endif + /* PR10655: we don't need to fidget with the ENABLED semaphore either, + as the process is gone, buh-bye, toodaloo, au revoir, see ya later! */ + } + mutex_unlock (& stap_uprobes_lock); + } /* close iteration over stap_uprobes[] */ + return 0; /* XXX: or !handled_p */ +} + +/* The task_finder_callback we use for ET_EXEC targets. + We used to perform uprobe insertion/removal here, but not any more. + (PR10524) */ +static int stap_uprobe_process_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p) { + const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); + if (! process_p) return 0; /* ignore threads */ + #ifdef DEBUG_TASK_FINDER_VMA + _stp_dbug (__FUNCTION__,__LINE__, "%cproc pid %d stf %p %p path %s\n", register_p?'+':'-', tsk->tgid, tgt, stf, stf->pathname); + #endif + /* ET_EXEC events are like shlib events, but with 0 relocation bases */ + if (register_p) { + int rc = stap_uprobe_change_plus (tsk, 0, TASK_SIZE, stf, 0, 0); + stap_uprobe_change_semaphore_plus (tsk, 0, TASK_SIZE, stf); + return rc; + } else + return stap_uprobe_change_minus (tsk, 0, TASK_SIZE, stf); +} + +/* The task_finder_mmap_callback */ +static int stap_uprobe_mmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, char *path, unsigned long addr, unsigned long length, unsigned long offset, unsigned long vm_flags) { + const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); + /* 1 - shared libraries' executable segments load from offset 0 + - ld.so convention offset != 0 is now allowed + so stap_uprobe_change_plus can set a semaphore, + i.e. a static extern, in a shared object + 2 - the shared library we're interested in + 3 - mapping should be executable or writeable (for semaphore in .so) */ + if (path == NULL || strcmp (path, stf->pathname)) return 0; + if (vm_flags & VM_EXEC) { + #ifdef DEBUG_TASK_FINDER_VMA + _stp_dbug (__FUNCTION__,__LINE__, "+mmap R-X pid %d path %s addr %p length %u offset %p stf %p %p path %s\n", tsk->tgid, path, (void *) addr, (unsigned)length, (void*) offset, tgt, stf, stf->pathname); + #endif + return stap_uprobe_change_plus (tsk, addr, length, stf, offset, vm_flags); + } else if (vm_flags & VM_WRITE) { + #ifdef DEBUG_TASK_FINDER_VMA + _stp_dbug (__FUNCTION__,__LINE__, "+mmap RW- pid %d path %s addr %p length %u offset %p stf %p %p path %s\n", tsk->tgid, path, (void *) addr, (unsigned)length, (void*) offset, tgt, stf, stf->pathname); + #endif + return stap_uprobe_change_semaphore_plus (tsk, addr, length, stf); + } else return 0; +} + +/* The task_finder_munmap_callback */ +static int stap_uprobe_munmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, unsigned long addr, unsigned long length) { + const struct stap_uprobe_tf *stf = container_of(tgt, struct stap_uprobe_tf, finder); + #ifdef DEBUG_TASK_FINDER_VMA + _stp_dbug (__FUNCTION__,__LINE__, "-mmap pid %d addr %p length %lu stf %p %p path %s\n", tsk->tgid, (void *) addr, length, tgt, stf, stf->pathname); + #endif + return stap_uprobe_change_minus (tsk, addr, length, stf); +} + +#endif /* _UPROBE_COMMON_C_ */ diff --git a/runtime/uprobes-common.h b/runtime/uprobes-common.h new file mode 100644 index 00000000..68741f4d --- /dev/null +++ b/runtime/uprobes-common.h @@ -0,0 +1,37 @@ +/* -*- linux-c -*- + * Copyright (C) 2010 Red Hat Inc. + * + * This file is part of systemtap, and is free software. You can + * redistribute it and/or modify it under the terms of the GNU General + * Public License (GPL); either version 2, or (at your option) any + * later version. + */ + +#ifndef _UPROBE_COMMON_H_ +#define _UPROBE_COMMON_H_ + +struct stap_uprobe { + union { struct uprobe up; struct uretprobe urp; }; + int spec_index; /* index into stap_uprobe_specs; <0 == free && unregistered */ + unsigned long sdt_sem_address; +}; + +struct stap_uprobe_tf { + struct stap_task_finder_target finder; + const char *pathname; +}; + +struct stap_uprobe_spec { + unsigned tfi; + unsigned return_p:1; + unsigned long address; + const char *pp; + void (*ph) (struct context*); + unsigned long sdt_sem_offset; + }; + +static int stap_uprobe_process_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, int register_p, int process_p); +static int stap_uprobe_mmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, char *path, unsigned long addr, unsigned long length, unsigned long offset, unsigned long vm_flags); +static int stap_uprobe_munmap_found (struct stap_task_finder_target *tgt, struct task_struct *tsk, unsigned long addr, unsigned long length); + +#endif /* _UPROBE_COMMON_H_ */ |