summaryrefslogtreecommitdiffstats
path: root/runtime/task_finder.c
diff options
context:
space:
mode:
authorDavid Smith <dsmith@redhat.com>2008-06-23 12:41:45 -0500
committerDavid Smith <dsmith@redhat.com>2008-06-23 12:43:23 -0500
commita21d81ec8b00571cb5987fe04ce74a3fe873351c (patch)
treed38eae80a5cfaf3cb5bdf0a8da6cbe0b8b98cd88 /runtime/task_finder.c
parentb857a3649c0f8e4e2d8a7209424f23c5d55adac7 (diff)
downloadsystemtap-steved-a21d81ec8b00571cb5987fe04ce74a3fe873351c.tar.gz
systemtap-steved-a21d81ec8b00571cb5987fe04ce74a3fe873351c.tar.xz
systemtap-steved-a21d81ec8b00571cb5987fe04ce74a3fe873351c.zip
Major update to memory map change notification code.
2008-06-23 David Smith <dsmith@redhat.com> * tapsets.cxx (utrace_derived_probe_group::emit_probe_decl): Handles UDPF_NONE value. (utrace_derived_probe_group::emit_vm_callback_probe_decl): New function. (utrace_derived_probe_group::emit_module_decls): Calls emit_vm_callback_probe_decl() to set up vm_callbacks. 2008-06-23 David Smith <dsmith@redhat.com> * task_finder.c (__stp_tf_vm_cb): New function. (stap_register_task_finder_target): Sets up syscall entry and syscall exit handlers. (__stp_find_file_based_vma): New function. (__stp_utrace_task_finder_target_syscall_entry): New function. Saves vma information off at syscall entry. (__stp_target_call_vm_callback): New function. (__stp_utrace_task_finder_target_syscall_exit): New function. Handles changes to memory maps based on information saved at syscall entry. * syscall.h: New file containing syscall function. * task_finder_vma.c: New file containing saved vma information handling functions.
Diffstat (limited to 'runtime/task_finder.c')
-rw-r--r--runtime/task_finder.c317
1 files changed, 313 insertions, 4 deletions
diff --git a/runtime/task_finder.c b/runtime/task_finder.c
index 7391f104..abb76e09 100644
--- a/runtime/task_finder.c
+++ b/runtime/task_finder.c
@@ -1,5 +1,7 @@
#include <linux/list.h>
#include <linux/binfmts.h>
+#include "syscall.h"
+#include "task_finder_vma.c"
static LIST_HEAD(__stp_task_finder_list);
@@ -35,6 +37,20 @@ typedef int (*stap_task_finder_vm_callback)(struct task_struct *tsk,
unsigned long vm_end,
unsigned long vm_pgoff);
+#ifdef DEBUG_TASK_FINDER_VMA
+int __stp_tf_vm_cb(struct task_struct *tsk,
+ int map_p, char *vm_path,
+ unsigned long vm_start,
+ unsigned long vm_end,
+ unsigned long vm_pgoff)
+{
+ _stp_dbug(__FUNCTION__, __LINE__,
+ "vm_cb: tsk %d:%d path %s, start 0x%08lx, end 0x%08lx, offset 0x%lx\n",
+ tsk->pid, map_p, vm_path, vm_start, vm_end, vm_pgoff);
+ return 0;
+}
+#endif
+
struct stap_task_finder_target {
/* private: */
struct list_head list; /* __stp_task_finder_list linkage */
@@ -59,6 +75,16 @@ static u32
__stp_utrace_task_finder_target_quiesce(struct utrace_attached_engine *engine,
struct task_struct *tsk);
+static u32
+__stp_utrace_task_finder_target_syscall_entry(struct utrace_attached_engine *engine,
+ struct task_struct *tsk,
+ struct pt_regs *regs);
+
+static u32
+__stp_utrace_task_finder_target_syscall_exit(struct utrace_attached_engine *engine,
+ struct task_struct *tsk,
+ struct pt_regs *regs);
+
static int
stap_register_task_finder_target(struct stap_task_finder_target *new_tgt)
{
@@ -82,6 +108,10 @@ stap_register_task_finder_target(struct stap_task_finder_target *new_tgt)
memset(&new_tgt->ops, 0, sizeof(new_tgt->ops));
new_tgt->ops.report_death = &__stp_utrace_task_finder_target_death;
new_tgt->ops.report_quiesce = &__stp_utrace_task_finder_target_quiesce;
+ new_tgt->ops.report_syscall_entry = \
+ &__stp_utrace_task_finder_target_syscall_entry;
+ new_tgt->ops.report_syscall_exit = \
+ &__stp_utrace_task_finder_target_syscall_exit;
// Search the list for an existing entry for pathname/pid.
list_for_each(node, &__stp_task_finder_list) {
@@ -224,9 +254,13 @@ __stp_get_mm_path(struct mm_struct *mm, char *buf, int buflen)
#define __STP_ATTACHED_TASK_BASE_EVENTS (UTRACE_EVENT(DEATH))
-#define __STP_ATTACHED_TASK_VM_EVENTS (__STP_ATTACHED_TASK_BASE_EVENTS \
- | UTRACE_ACTION_QUIESCE \
- | UTRACE_EVENT(QUIESCE))
+#define __STP_ATTACHED_TASK_VM_BASE_EVENTS (__STP_ATTACHED_TASK_BASE_EVENTS \
+ | UTRACE_EVENT(SYSCALL_ENTRY) \
+ | UTRACE_EVENT(SYSCALL_EXIT))
+
+#define __STP_ATTACHED_TASK_VM_EVENTS (__STP_ATTACHED_TASK_VM_BASE_EVENTS \
+ | UTRACE_ACTION_QUIESCE \
+ | UTRACE_EVENT(QUIESCE))
#define __STP_ATTACHED_TASK_EVENTS(tgt) \
((((tgt)->vm_callback) == NULL) ? __STP_ATTACHED_TASK_BASE_EVENTS \
@@ -509,7 +543,7 @@ __stp_utrace_task_finder_target_quiesce(struct utrace_attached_engine *engine,
struct stap_task_finder_target *tgt = engine->data;
// Turn off quiesce handling.
- utrace_set_flags(tsk, engine, __STP_ATTACHED_TASK_BASE_EVENTS);
+ utrace_set_flags(tsk, engine, __STP_ATTACHED_TASK_VM_BASE_EVENTS);
if (atomic_read(&__stp_task_finder_state) != __STP_TF_RUNNING) {
debug_task_finder_detach();
@@ -577,6 +611,281 @@ utftq_out:
return (UTRACE_ACTION_NEWSTATE | UTRACE_ACTION_RESUME);
}
+
+struct vm_area_struct *
+__stp_find_file_based_vma(struct mm_struct *mm, unsigned long addr)
+{
+ struct vm_area_struct *vma = find_vma(mm, addr);
+
+ // I'm not positive why the checking for vm_start > addr is
+ // necessary, but it seems to be (sometimes find_vma() returns
+ // a vma that addr doesn't belong to).
+ if (vma && (vma->vm_file == NULL || vma->vm_start > addr))
+ vma = NULL;
+ return vma;
+}
+
+static u32
+__stp_utrace_task_finder_target_syscall_entry(struct utrace_attached_engine *engine,
+ struct task_struct *tsk,
+ struct pt_regs *regs)
+{
+ struct stap_task_finder_target *tgt = engine->data;
+ unsigned long syscall_no;
+ struct mm_struct *mm;
+ struct vm_area_struct *vma;
+ unsigned long *arg0_addr, arg0;
+ int rc;
+
+ if (atomic_read(&__stp_task_finder_state) != __STP_TF_RUNNING) {
+ debug_task_finder_detach();
+ return UTRACE_ACTION_DETACH;
+ }
+
+ if (tgt == NULL || tgt->vm_callback == NULL)
+ return UTRACE_ACTION_RESUME;
+
+ // See if syscall is one we're interested in.
+ //
+ // FIXME: do we need to handle mremap()?
+ syscall_no = __stp_user_syscall_nr(regs);
+ if (syscall_no != MMAP_SYSCALL_NO(tsk)
+ && syscall_no != MPROTECT_SYSCALL_NO(tsk)
+ && syscall_no != MUNMAP_SYSCALL_NO(tsk))
+ return UTRACE_ACTION_RESUME;
+
+
+ // We need the first syscall argument to see what address
+ // we're operating on.
+ arg0_addr = __stp_user_syscall_arg(tsk, regs, 0);
+ if ((rc = __stp_get_user(arg0, arg0_addr)) != 0) {
+ _stp_error("couldn't read syscall arg 0 for pid %d: %d",
+ tsk->pid, rc);
+ }
+ else if (arg0 != (unsigned long)NULL) {
+ mm = get_task_mm(tsk);
+ if (mm) {
+ down_read(&mm->mmap_sem);
+
+ // If we can find a matching vma associated
+ // with a file, save off its details.
+ vma = __stp_find_file_based_vma(mm, arg0);
+ if (vma != NULL) {
+ __stp_tf_add_vma(tsk, arg0, vma);
+ }
+
+ up_read(&mm->mmap_sem);
+ mmput(mm);
+ }
+ }
+ return UTRACE_ACTION_RESUME;
+}
+
+static void
+__stp_target_call_vm_callback(struct stap_task_finder_target *tgt,
+ struct task_struct *tsk,
+ struct vm_area_struct *vma)
+{
+ char *mmpath_buf;
+ char *mmpath;
+ int rc;
+
+ // Allocate space for a path
+ mmpath_buf = _stp_kmalloc(PATH_MAX);
+ if (mmpath_buf == NULL) {
+ _stp_error("Unable to allocate space for path");
+ return;
+ }
+
+ // Grab the path associated with this vma.
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+ mmpath = d_path(vma->vm_file->f_dentry, vma->vm_file->f_vfsmnt,
+ mmpath_buf, PATH_MAX);
+#else
+ mmpath = d_path(&(vma->vm_file->f_path), mmpath_buf, PATH_MAX);
+#endif
+ if (mmpath == NULL || IS_ERR(mmpath)) {
+ rc = -PTR_ERR(mmpath);
+ _stp_error("Unable to get path (error %d) for pid %d",
+ rc, (int)tsk->pid);
+ }
+ else {
+ rc = tgt->vm_callback(tsk, 1, mmpath, vma->vm_start,
+ vma->vm_end, vma->vm_pgoff);
+ if (rc != 0) {
+ _stp_error("vm callback for %d failed: %d",
+ (int)tsk->pid, rc);
+ }
+ }
+ _stp_kfree(mmpath_buf);
+}
+
+static u32
+__stp_utrace_task_finder_target_syscall_exit(struct utrace_attached_engine *engine,
+ struct task_struct *tsk,
+ struct pt_regs *regs)
+{
+ struct stap_task_finder_target *tgt = engine->data;
+ unsigned long syscall_no;
+ unsigned long *rv_addr, rv;
+ unsigned long *arg0_addr, arg0;
+ int rc;
+ struct mm_struct *mm;
+ struct vm_area_struct *vma;
+ struct __stp_tf_vma_entry *entry = NULL;
+
+ if (atomic_read(&__stp_task_finder_state) != __STP_TF_RUNNING) {
+ debug_task_finder_detach();
+ return UTRACE_ACTION_DETACH;
+ }
+
+ if (tgt == NULL || tgt->vm_callback == NULL)
+ return UTRACE_ACTION_RESUME;
+
+ // See if syscall is one we're interested in.
+ //
+ // FIXME: do we need to handle mremap()?
+ syscall_no = __stp_user_syscall_nr(regs);
+ if (syscall_no != MMAP_SYSCALL_NO(tsk)
+ && syscall_no != MPROTECT_SYSCALL_NO(tsk)
+ && syscall_no != MUNMAP_SYSCALL_NO(tsk))
+ return UTRACE_ACTION_RESUME;
+
+ // Get return value
+ rv_addr = __stp_user_syscall_return_value(tsk, regs);
+ if ((rc = __stp_get_user(rv, rv_addr)) != 0) {
+ _stp_error("couldn't read syscall return value for pid %d: %d",
+ tsk->pid, rc);
+ return UTRACE_ACTION_RESUME;
+ }
+
+ // We need the first syscall argument to see what address we
+ // were operating on.
+ arg0_addr = __stp_user_syscall_arg(tsk, regs, 0);
+ if ((rc = __stp_get_user(arg0, arg0_addr)) != 0) {
+ _stp_error("couldn't read syscall arg 0 for pid %d: %d",
+ tsk->pid, rc);
+ return UTRACE_ACTION_RESUME;
+ }
+
+#ifdef DEBUG_TASK_FINDER_VMA
+ _stp_dbug(__FUNCTION__, __LINE__,
+ "tsk %d found %s(0x%lx), returned 0x%lx\n",
+ tsk->pid,
+ ((syscall_no == MMAP_SYSCALL_NO(tsk)) ? "mmap"
+ : ((syscall_no == MPROTECT_SYSCALL_NO(tsk)) ? "mprotect"
+ : ((syscall_no == MUNMAP_SYSCALL_NO(tsk)) ? "munmap"
+ : "UNKNOWN"))),
+ arg0, rv);
+#endif
+
+ // Try to find the vma info we might have saved.
+ if (arg0 != (unsigned long)NULL)
+ entry = __stp_tf_get_vma_entry(tsk, arg0);
+
+ // If entry is NULL, this means we didn't find a file based
+ // vma to store in the syscall_entry routine. This could mean
+ // we just created a new vma.
+ if (entry == NULL) {
+ mm = get_task_mm(tsk);
+ if (mm) {
+ down_read(&mm->mmap_sem);
+ vma = __stp_find_file_based_vma(mm, rv);
+ if (vma != NULL) {
+ __stp_target_call_vm_callback(tgt, tsk,
+ vma);
+ }
+ up_read(&mm->mmap_sem);
+ mmput(mm);
+ }
+ }
+ // If we found saved vma information, try to match it up with
+ // what currently exists.
+ else {
+#ifdef DEBUG_TASK_FINDER_VMA
+ _stp_dbug(__FUNCTION__, __LINE__,
+ "** found stored vma 0x%lx/0x%lx/0x%lx!\n",
+ entry->vm_start, entry->vm_end, entry->vm_pgoff);
+#endif
+ mm = get_task_mm(tsk);
+ if (mm) {
+ down_read(&mm->mmap_sem);
+ vma = __stp_find_file_based_vma(mm, entry->vm_start);
+
+ // We couldn't find the vma at all. The
+ // original vma was deleted.
+ if (vma == NULL) {
+ // FIXME: We'll need to figure out to
+ // retrieve the path of a deleted
+ // vma.
+ rc = tgt->vm_callback(tsk, 0, NULL,
+ entry->vm_start,
+ entry->vm_end,
+ entry->vm_pgoff);
+ if (rc != 0) {
+ _stp_error("vm callback for %d failed: %d",
+ (int)tsk->pid, rc);
+ }
+ }
+
+ // If nothing has changed, there is no
+ // need to call the callback.
+ else if (vma->vm_start == entry->vm_start
+ && vma->vm_end == entry->vm_end
+ && vma->vm_pgoff == entry->vm_pgoff) {
+ // do nothing
+ }
+
+ // The original vma has been changed. It is
+ // possible that calling mprotect (e.g.) split
+ // up an existing vma into 2 or 3 new vma's
+ // (assuming it protected a portion of the
+ // original vma at the beginning, middle, or
+ // end). Try to determine what happened.
+ else {
+ unsigned long tmp;
+
+ // First report that the original vma
+ // is gone.
+ //
+ // FIXME: We'll need to figure out to
+ // retrieve the path of a deleted
+ // vma.
+ rc = tgt->vm_callback(tsk, 0, NULL,
+ entry->vm_start,
+ entry->vm_end,
+ entry->vm_pgoff);
+ if (rc != 0) {
+ _stp_error("vm callback for %d failed: %d",
+ (int)tsk->pid, rc);
+ }
+
+ // Now find all the new vma's that
+ // made up the original vma's address
+ // space and call the callback on each
+ // new vma.
+ tmp = entry->vm_start;
+ while (((vma = __stp_find_file_based_vma(mm,
+ tmp))
+ != NULL)
+ && vma->vm_end <= entry->vm_end) {
+ __stp_target_call_vm_callback(tgt, tsk,
+ vma);
+ if (vma->vm_end >= entry->vm_end)
+ break;
+ tmp = vma->vm_end;
+ }
+ }
+ up_read(&mm->mmap_sem);
+ mmput(mm);
+ }
+
+ // Cleanup by deleting the saved vma info.
+ __stp_tf_remove_vma_entry(entry);
+ }
+ return UTRACE_ACTION_RESUME;
+}
+
struct utrace_engine_ops __stp_utrace_task_finder_ops = {
.report_clone = __stp_utrace_task_finder_report_clone,
.report_exec = __stp_utrace_task_finder_report_exec,