diff options
Diffstat (limited to 'kernel/events/core.c')
| -rw-r--r-- | kernel/events/core.c | 71 | 
1 files changed, 64 insertions, 7 deletions
| diff --git a/kernel/events/core.c b/kernel/events/core.c index 5fa58e4cffac..6b17ac1b0c2a 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -40,6 +40,7 @@  #include <linux/mm_types.h>  #include <linux/cgroup.h>  #include <linux/module.h> +#include <linux/mman.h>  #include "internal.h" @@ -2319,7 +2320,7 @@ static void perf_event_context_sched_out(struct task_struct *task, int ctxn,  	next_parent = rcu_dereference(next_ctx->parent_ctx);  	/* If neither context have a parent context; they cannot be clones. */ -	if (!parent && !next_parent) +	if (!parent || !next_parent)  		goto unlock;  	if (next_parent == ctx || next_ctx == parent || next_parent == parent) { @@ -5128,6 +5129,7 @@ struct perf_mmap_event {  	int			maj, min;  	u64			ino;  	u64			ino_generation; +	u32			prot, flags;  	struct {  		struct perf_event_header	header; @@ -5169,6 +5171,8 @@ static void perf_event_mmap_output(struct perf_event *event,  		mmap_event->event_id.header.size += sizeof(mmap_event->min);  		mmap_event->event_id.header.size += sizeof(mmap_event->ino);  		mmap_event->event_id.header.size += sizeof(mmap_event->ino_generation); +		mmap_event->event_id.header.size += sizeof(mmap_event->prot); +		mmap_event->event_id.header.size += sizeof(mmap_event->flags);  	}  	perf_event_header__init_id(&mmap_event->event_id.header, &sample, event); @@ -5187,6 +5191,8 @@ static void perf_event_mmap_output(struct perf_event *event,  		perf_output_put(&handle, mmap_event->min);  		perf_output_put(&handle, mmap_event->ino);  		perf_output_put(&handle, mmap_event->ino_generation); +		perf_output_put(&handle, mmap_event->prot); +		perf_output_put(&handle, mmap_event->flags);  	}  	__output_copy(&handle, mmap_event->file_name, @@ -5205,6 +5211,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)  	struct file *file = vma->vm_file;  	int maj = 0, min = 0;  	u64 ino = 0, gen = 0; +	u32 prot = 0, flags = 0;  	unsigned int size;  	char tmp[16];  	char *buf = NULL; @@ -5235,6 +5242,28 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event)  		gen = inode->i_generation;  		maj = MAJOR(dev);  		min = MINOR(dev); + +		if (vma->vm_flags & VM_READ) +			prot |= PROT_READ; +		if (vma->vm_flags & VM_WRITE) +			prot |= PROT_WRITE; +		if (vma->vm_flags & VM_EXEC) +			prot |= PROT_EXEC; + +		if (vma->vm_flags & VM_MAYSHARE) +			flags = MAP_SHARED; +		else +			flags = MAP_PRIVATE; + +		if (vma->vm_flags & VM_DENYWRITE) +			flags |= MAP_DENYWRITE; +		if (vma->vm_flags & VM_MAYEXEC) +			flags |= MAP_EXECUTABLE; +		if (vma->vm_flags & VM_LOCKED) +			flags |= MAP_LOCKED; +		if (vma->vm_flags & VM_HUGETLB) +			flags |= MAP_HUGETLB; +  		goto got_name;  	} else {  		name = (char *)arch_vma_name(vma); @@ -5275,6 +5304,8 @@ got_name:  	mmap_event->min = min;  	mmap_event->ino = ino;  	mmap_event->ino_generation = gen; +	mmap_event->prot = prot; +	mmap_event->flags = flags;  	if (!(vma->vm_flags & VM_EXEC))  		mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_DATA; @@ -5315,6 +5346,8 @@ void perf_event_mmap(struct vm_area_struct *vma)  		/* .min (attr_mmap2 only) */  		/* .ino (attr_mmap2 only) */  		/* .ino_generation (attr_mmap2 only) */ +		/* .prot (attr_mmap2 only) */ +		/* .flags (attr_mmap2 only) */  	};  	perf_event_mmap_event(&mmap_event); @@ -6897,10 +6930,6 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr,  	if (ret)  		return -EFAULT; -	/* disabled for now */ -	if (attr->mmap2) -		return -EINVAL; -  	if (attr->__reserved_1)  		return -EINVAL; @@ -7429,7 +7458,19 @@ __perf_event_exit_task(struct perf_event *child_event,  			 struct perf_event_context *child_ctx,  			 struct task_struct *child)  { -	perf_remove_from_context(child_event, true); +	/* +	 * Do not destroy the 'original' grouping; because of the context +	 * switch optimization the original events could've ended up in a +	 * random child task. +	 * +	 * If we were to destroy the original group, all group related +	 * operations would cease to function properly after this random +	 * child dies. +	 * +	 * Do destroy all inherited groups, we don't care about those +	 * and being thorough is better. +	 */ +	perf_remove_from_context(child_event, !!child_event->parent);  	/*  	 * It can happen that the parent exits first, and has events @@ -7445,7 +7486,7 @@ __perf_event_exit_task(struct perf_event *child_event,  static void perf_event_exit_task_context(struct task_struct *child, int ctxn)  {  	struct perf_event *child_event, *next; -	struct perf_event_context *child_ctx; +	struct perf_event_context *child_ctx, *parent_ctx;  	unsigned long flags;  	if (likely(!child->perf_event_ctxp[ctxn])) { @@ -7470,6 +7511,15 @@ static void perf_event_exit_task_context(struct task_struct *child, int ctxn)  	raw_spin_lock(&child_ctx->lock);  	task_ctx_sched_out(child_ctx);  	child->perf_event_ctxp[ctxn] = NULL; + +	/* +	 * In order to avoid freeing: child_ctx->parent_ctx->task +	 * under perf_event_context::lock, grab another reference. +	 */ +	parent_ctx = child_ctx->parent_ctx; +	if (parent_ctx) +		get_ctx(parent_ctx); +  	/*  	 * If this context is a clone; unclone it so it can't get  	 * swapped to another process while we're removing all @@ -7480,6 +7530,13 @@ static void perf_event_exit_task_context(struct task_struct *child, int ctxn)  	raw_spin_unlock_irqrestore(&child_ctx->lock, flags);  	/* +	 * Now that we no longer hold perf_event_context::lock, drop +	 * our extra child_ctx->parent_ctx reference. +	 */ +	if (parent_ctx) +		put_ctx(parent_ctx); + +	/*  	 * Report the task dead after unscheduling the events so that we  	 * won't get any samples after PERF_RECORD_EXIT. We can however still  	 * get a few PERF_RECORD_READ events. |