lkml.org 
[lkml]   [2011]   [Sep]   [20]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Date
Subject[PATCH v5 3.1.0-rc4-tip 4/26] uprobes: Define hooks for mmap/munmap.

If an executable vma is getting mapped, search and insert corresponding
probes. On unmap, make sure the per-mm count is decremented by appropriate
amount.

Signed-off-by: Srikar Dronamraju <srikar@linux.vnet.ibm.com>
---
include/linux/mm_types.h | 3 +
include/linux/uprobes.h | 12 +++
kernel/fork.c | 5 +
kernel/uprobes.c | 174 +++++++++++++++++++++++++++++++++++++++++++---
mm/memory.c | 4 +
mm/mmap.c | 6 ++
6 files changed, 194 insertions(+), 10 deletions(-)

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 774b895..9aeb64f 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -349,6 +349,9 @@ struct mm_struct {
#ifdef CONFIG_CPUMASK_OFFSTACK
struct cpumask cpumask_allocation;
#endif
+#ifdef CONFIG_UPROBES
+ atomic_t mm_uprobes_count;
+#endif
};

static inline void mm_init_cpumask(struct mm_struct *mm)
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h
index 6d5a3fe..b4de058 100644
--- a/include/linux/uprobes.h
+++ b/include/linux/uprobes.h
@@ -25,6 +25,8 @@

#include <linux/rbtree.h>

+struct vm_area_struct;
+
struct uprobe_consumer {
int (*handler)(struct uprobe_consumer *self, struct pt_regs *regs);
/*
@@ -40,6 +42,7 @@ struct uprobe {
struct rb_node rb_node; /* node in the rb tree */
atomic_t ref;
struct rw_semaphore consumer_rwsem;
+ struct list_head pending_list;
struct uprobe_consumer *consumers;
struct inode *inode; /* Also hold a ref to inode */
loff_t offset;
@@ -50,6 +53,8 @@ extern int register_uprobe(struct inode *inode, loff_t offset,
struct uprobe_consumer *consumer);
extern void unregister_uprobe(struct inode *inode, loff_t offset,
struct uprobe_consumer *consumer);
+extern int mmap_uprobe(struct vm_area_struct *vma);
+extern void munmap_uprobe(struct vm_area_struct *vma);
#else /* CONFIG_UPROBES is not defined */
static inline int register_uprobe(struct inode *inode, loff_t offset,
struct uprobe_consumer *consumer)
@@ -60,5 +65,12 @@ static inline void unregister_uprobe(struct inode *inode, loff_t offset,
struct uprobe_consumer *consumer)
{
}
+static inline int mmap_uprobe(struct vm_area_struct *vma)
+{
+ return 0;
+}
+static inline void munmap_uprobe(struct vm_area_struct *vma)
+{
+}
#endif /* CONFIG_UPROBES */
#endif /* _LINUX_UPROBES_H */
diff --git a/kernel/fork.c b/kernel/fork.c
index 8e6b6f4..7cc0b51 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -66,6 +66,7 @@
#include <linux/user-return-notifier.h>
#include <linux/oom.h>
#include <linux/khugepaged.h>
+#include <linux/uprobes.h>

#include <asm/pgtable.h>
#include <asm/pgalloc.h>
@@ -739,6 +740,10 @@ struct mm_struct *dup_mm(struct task_struct *tsk)
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
mm->pmd_huge_pte = NULL;
#endif
+#ifdef CONFIG_UPROBES
+ atomic_set(&mm->mm_uprobes_count,
+ atomic_read(&oldmm->mm_uprobes_count));
+#endif

if (!mm_init(mm, tsk))
goto fail_nomem;
diff --git a/kernel/uprobes.c b/kernel/uprobes.c
index eeb6ed5..5bc3f90 100644
--- a/kernel/uprobes.c
+++ b/kernel/uprobes.c
@@ -29,6 +29,7 @@

static struct rb_root uprobes_tree = RB_ROOT;
static DEFINE_SPINLOCK(uprobes_treelock); /* serialize (un)register */
+static DEFINE_MUTEX(uprobes_mmap_mutex); /* uprobe->pending_list */

/*
* Maintain a temporary per vma info that can be used to search if a vma
@@ -58,13 +59,23 @@ static bool valid_vma(struct vm_area_struct *vma)
return false;
}

-static int match_uprobe(struct uprobe *l, struct uprobe *r)
+static int match_uprobe(struct uprobe *l, struct uprobe *r, int *match_inode)
{
+ /*
+ * if match_inode is non NULL then indicate if the
+ * inode atleast match.
+ */
+ if (match_inode)
+ *match_inode = 0;
+
if (l->inode < r->inode)
return -1;
if (l->inode > r->inode)
return 1;
else {
+ if (match_inode)
+ *match_inode = 1;
+
if (l->offset < r->offset)
return -1;

@@ -75,16 +86,20 @@ static int match_uprobe(struct uprobe *l, struct uprobe *r)
return 0;
}

-static struct uprobe *__find_uprobe(struct inode * inode, loff_t offset)
+static struct uprobe *__find_uprobe(struct inode * inode, loff_t offset,
+ struct rb_node **close_match)
{
struct uprobe u = { .inode = inode, .offset = offset };
struct rb_node *n = uprobes_tree.rb_node;
struct uprobe *uprobe;
- int match;
+ int match, match_inode;

while (n) {
uprobe = rb_entry(n, struct uprobe, rb_node);
- match = match_uprobe(&u, uprobe);
+ match = match_uprobe(&u, uprobe, &match_inode);
+ if (close_match && match_inode)
+ *close_match = n;
+
if (!match) {
atomic_inc(&uprobe->ref);
return uprobe;
@@ -108,7 +123,7 @@ static struct uprobe *find_uprobe(struct inode * inode, loff_t offset)
unsigned long flags;

spin_lock_irqsave(&uprobes_treelock, flags);
- uprobe = __find_uprobe(inode, offset);
+ uprobe = __find_uprobe(inode, offset, NULL);
spin_unlock_irqrestore(&uprobes_treelock, flags);
return uprobe;
}
@@ -123,7 +138,7 @@ static struct uprobe *__insert_uprobe(struct uprobe *uprobe)
while (*p) {
parent = *p;
u = rb_entry(parent, struct uprobe, rb_node);
- match = match_uprobe(uprobe, u);
+ match = match_uprobe(uprobe, u, NULL);
if (!match) {
atomic_inc(&u->ref);
return u;
@@ -179,6 +194,7 @@ static struct uprobe *alloc_uprobe(struct inode *inode, loff_t offset)
uprobe->inode = igrab(inode);
uprobe->offset = offset;
init_rwsem(&uprobe->consumer_rwsem);
+ INIT_LIST_HEAD(&uprobe->pending_list);

/* add to uprobes_tree, sorted on inode:offset */
cur_uprobe = insert_uprobe(uprobe);
@@ -232,15 +248,21 @@ static bool del_consumer(struct uprobe *uprobe,
return ret;
}

-static int install_breakpoint(struct mm_struct *mm)
+
+static int install_breakpoint(struct mm_struct *mm, struct uprobe *uprobe)
{
/* Placeholder: Yet to be implemented */
+ if (!uprobe->consumers)
+ return 0;
+
+ atomic_inc(&mm->mm_uprobes_count);
return 0;
}

-static void remove_breakpoint(struct mm_struct *mm)
+static void remove_breakpoint(struct mm_struct *mm, struct uprobe *uprobe)
{
/* Placeholder: Yet to be implemented */
+ atomic_dec(&mm->mm_uprobes_count);
return;
}

@@ -340,7 +362,7 @@ static int __register_uprobe(struct inode *inode, loff_t offset,
mmput(mm);
continue;
}
- ret = install_breakpoint(mm);
+ ret = install_breakpoint(mm, uprobe);
if (ret && (ret != -ESRCH || ret != -EEXIST)) {
up_read(&mm->mmap_sem);
mmput(mm);
@@ -382,7 +404,7 @@ static void __unregister_uprobe(struct inode *inode, loff_t offset,
mmput(mm);
continue;
}
- remove_breakpoint(mm);
+ remove_breakpoint(mm, uprobe);
up_read(&mm->mmap_sem);
mmput(mm);
}
@@ -476,3 +498,135 @@ void unregister_uprobe(struct inode *inode, loff_t offset,
put_uprobe(uprobe);
iput(inode);
}
+
+/*
+ * For a given inode, build a list of probes that need to be inserted.
+ */
+static void build_probe_list(struct inode *inode, struct list_head *head)
+{
+ struct uprobe *uprobe;
+ struct rb_node *n;
+ unsigned long flags;
+
+ n = uprobes_tree.rb_node;
+ spin_lock_irqsave(&uprobes_treelock, flags);
+ uprobe = __find_uprobe(inode, 0, &n);
+ /*
+ * If indeed there is a probe for the inode and with offset zero,
+ * then lets release its reference. (ref got thro __find_uprobe)
+ */
+ if (uprobe)
+ put_uprobe(uprobe);
+ for (; n; n = rb_next(n)) {
+ uprobe = rb_entry(n, struct uprobe, rb_node);
+ if (uprobe->inode != inode)
+ break;
+ list_add(&uprobe->pending_list, head);
+ atomic_inc(&uprobe->ref);
+ }
+ spin_unlock_irqrestore(&uprobes_treelock, flags);
+}
+
+/*
+ * Called from mmap_region.
+ * called with mm->mmap_sem acquired.
+ *
+ * Return -ve no if we fail to insert probes and we cannot
+ * bail-out.
+ * Return 0 otherwise. i.e :
+ * - successful insertion of probes
+ * - (or) no possible probes to be inserted.
+ * - (or) insertion of probes failed but we can bail-out.
+ */
+int mmap_uprobe(struct vm_area_struct *vma)
+{
+ struct list_head tmp_list;
+ struct uprobe *uprobe, *u;
+ struct inode *inode;
+ int ret = 0;
+
+ if (!valid_vma(vma))
+ return ret; /* Bail-out */
+
+ inode = igrab(vma->vm_file->f_mapping->host);
+ if (!inode)
+ return ret;
+
+ INIT_LIST_HEAD(&tmp_list);
+ mutex_lock(&uprobes_mmap_mutex);
+ build_probe_list(inode, &tmp_list);
+ list_for_each_entry_safe(uprobe, u, &tmp_list, pending_list) {
+ loff_t vaddr;
+
+ list_del(&uprobe->pending_list);
+ if (!ret && uprobe->consumers) {
+ vaddr = vma->vm_start + uprobe->offset;
+ vaddr -= vma->vm_pgoff << PAGE_SHIFT;
+ if (vaddr < vma->vm_start || vaddr >= vma->vm_end)
+ continue;
+ ret = install_breakpoint(vma->vm_mm, uprobe);
+
+ if (ret && (ret == -ESRCH || ret == -EEXIST))
+ ret = 0;
+ }
+ put_uprobe(uprobe);
+ }
+
+ mutex_unlock(&uprobes_mmap_mutex);
+ iput(inode);
+ return ret;
+}
+
+static void dec_mm_uprobes_count(struct vm_area_struct *vma,
+ struct inode *inode)
+{
+ struct uprobe *uprobe;
+ struct rb_node *n;
+ unsigned long flags;
+
+ n = uprobes_tree.rb_node;
+ spin_lock_irqsave(&uprobes_treelock, flags);
+ uprobe = __find_uprobe(inode, 0, &n);
+
+ /*
+ * If indeed there is a probe for the inode and with offset zero,
+ * then lets release its reference. (ref got thro __find_uprobe)
+ */
+ if (uprobe)
+ put_uprobe(uprobe);
+ for (; n; n = rb_next(n)) {
+ loff_t vaddr;
+
+ uprobe = rb_entry(n, struct uprobe, rb_node);
+ if (uprobe->inode != inode)
+ break;
+ vaddr = vma->vm_start + uprobe->offset;
+ vaddr -= vma->vm_pgoff << PAGE_SHIFT;
+ if (vaddr < vma->vm_start || vaddr >= vma->vm_end)
+ continue;
+ atomic_dec(&vma->vm_mm->mm_uprobes_count);
+ }
+ spin_unlock_irqrestore(&uprobes_treelock, flags);
+}
+
+/*
+ * Called in context of a munmap of a vma.
+ */
+void munmap_uprobe(struct vm_area_struct *vma)
+{
+ struct inode *inode;
+
+ if (!valid_vma(vma))
+ return; /* Bail-out */
+
+ if (!atomic_read(&vma->vm_mm->mm_uprobes_count))
+ return;
+
+ inode = igrab(vma->vm_file->f_mapping->host);
+ if (!inode)
+ return;
+
+ dec_mm_uprobes_count(vma, inode);
+ iput(inode);
+ return;
+}
diff --git a/mm/memory.c b/mm/memory.c
index a56e3ba..a65fd1f 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -57,6 +57,7 @@
#include <linux/swapops.h>
#include <linux/elf.h>
#include <linux/gfp.h>
+#include <linux/uprobes.h>

#include <asm/io.h>
#include <asm/pgalloc.h>
@@ -1337,6 +1338,9 @@ unsigned long unmap_vmas(struct mmu_gather *tlb,
if (unlikely(is_pfn_mapping(vma)))
untrack_pfn_vma(vma, 0, 0);

+ if (vma->vm_file)
+ munmap_uprobe(vma);
+
while (start != end) {
if (unlikely(is_vm_hugetlb_page(vma))) {
/*
diff --git a/mm/mmap.c b/mm/mmap.c
index a65efd4..f51d482 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -30,6 +30,7 @@
#include <linux/perf_event.h>
#include <linux/audit.h>
#include <linux/khugepaged.h>
+#include <linux/uprobes.h>

#include <asm/uaccess.h>
#include <asm/cacheflush.h>
@@ -1329,6 +1330,11 @@ unsigned long mmap_region(struct file *file, unsigned long addr,
mm->locked_vm += (len >> PAGE_SHIFT);
} else if ((flags & MAP_POPULATE) && !(flags & MAP_NONBLOCK))
make_pages_present(addr, addr + len);
+
+ if (file && mmap_uprobe(vma))
+ /* matching probes but cannot insert */
+ goto unmap_and_free_vma;
+
return addr;

unmap_and_free_vma:


\
 
 \ /
  Last update: 2011-09-20 14:19    [W:0.417 / U:0.084 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site