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:4.239 / U:0.048 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site