This is the mail archive of the systemtap@sourceware.org mailing list for the systemtap project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

PATCH: vm area for uprobes SSOL


On Thu, 2007-03-22 at 18:00 -0700, Roland McGrath wrote [elsewhere]:
[Jim said:]
> > We'll post the patch as soon as we've done that testing and we get (and
> > address, if necessary) Dave's review.  If you wanna see it before then,
> > we can post what we have.
> 
> Please do.  Posting something with a "still buggy or unfinished" disclaimer
> is always fine, and the more often and earlier the sharing the better.
> This work in particular has several components that can be reviewed and
> discussed independently.
> Thanks,
> Roland
> 

This patch, by Prasanna Panchamukhi, replaces the out-of-line
single-stepping mechanism for the i386 version of uprobes.  The
single-stepped instruction must reside in the probed process's address
space.  To store this instruction-copy, we used to allocate space in the
thread's stack vm area, beyond TOS, but that introduced a security
problem (temporarily executable stack) and wasn't portable.  Now we
allocate a separate 1-page vm area and put the instruction-copies there.

Per Roland's request, we are posting this patch even though it has some
rough edges.  It seems to work as expected, but it needs some cleanup.
Here are Dave Hansen's first-pass comments on the vma-allocation code:
-----
I'd probably like to see a few things get cleaned up, like the calling
conventions for arch_setup_special_page() (should use ERR_PTR()) and the
storage of stuff in the mm_context which is pretty redundant.  The
completely separate code path when the VDSO is configured off also isn't
good, but I"m sure you can clean that up.  But, as a basic approach, it
should work.
-----
This patch applies atop the 3 patches I posted here:
http://sources.redhat.com/ml/systemtap/2007-q1/msg00587.html

Comments welcome.
Jim
This patches replaces the out-of-line single stepping mechanism for
the i386 architecture. This patch provides a mechanism to allocate
the space for instructions to be single stepped out-of-line. The
solution adopted is to allocate, during exec time, a page to hold
the instruction copies. This page gets allocated for every process.
We call this area the "SSOL area". The SSOL area is divided up into
instruction slots, each of which is large enough to hold the longest
instruction in i386 architecture. To support an unlimited number of
probes with the limited SSOL area, this patch uses a
least-recently-used algorithm. As probepoint gets hit, we assign one
instruction slot per probepoint in the SSOL area until the number of
probepoints ever hit exceeds the capacity of the SSOL area. When a
probepoint gets hit and there is no room for its instruction copy in
the SSOL area, we steal a slot where no single stepping is in progress.



 arch/i386/kernel/sysenter.c |   77 ++++++++++++++---
 arch/i386/kernel/uprobes.c  |   83 ++++++-------------
 include/asm-i386/mmu.h      |    2 
 include/linux/interp.h      |    3 
 include/linux/uprobes.h     |   89 ++++++++++++++++++++
 kernel/uprobes.c            |  191 ++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 370 insertions(+), 75 deletions(-)

diff -puN arch/i386/kernel/sysenter.c~uprobes-singlestep-out-of-line-lru arch/i386/kernel/sysenter.c
--- linux-2.6.21-rc3-mm2/arch/i386/kernel/sysenter.c~uprobes-singlestep-out-of-line-lru	2007-03-21 11:52:25.000000000 +0530
+++ linux-2.6.21-rc3-mm2-prasanna/arch/i386/kernel/sysenter.c	2007-03-22 13:22:26.000000000 +0530
@@ -17,6 +17,7 @@
 #include <linux/elf.h>
 #include <linux/mm.h>
 #include <linux/module.h>
+#include <linux/uprobes.h>

 #include <asm/cpufeature.h>
 #include <asm/msr.h>
@@ -96,23 +97,50 @@ int __init sysenter_setup(void)
 	return 0;
 }

+/*
+ * Setup a special page at program startup for uprobes and syscall
+ * entry/exit.
+ */
+int arch_setup_special_page(unsigned long *page_addr,
+				 unsigned long flags, struct page **pages)
+{
+	struct mm_struct *mm = current->mm;
+	unsigned long addr;
+	int ret;
+
+	/*
+	 * Allocate additional pages, one for syscall entry/exit and
+	 * another for uprobes to single step out-of-line.
+	 */
+	addr = get_unmapped_area(NULL, 0, PAGE_SIZE , 0, 0);
+	if (IS_ERR_VALUE(addr))
+		return addr;
+
+	ret = install_special_mapping(mm, addr, PAGE_SIZE, flags, pages);
+	if (ret)
+		return ret;
+
+	*page_addr = addr;
+
+	return 0;
+}
+
 #ifndef CONFIG_COMPAT_VDSO
 /* Defined in vsyscall-sysenter.S */
 extern void SYSENTER_RETURN;

-/* Setup a VMA at program startup for the vsyscall page */
+/*
+ * Setup a VMA at program startup for the vsyscall page  and uprobes ssol
+ * area
+ */
 int arch_setup_additional_pages(struct linux_binprm *bprm, int exstack)
 {
 	struct mm_struct *mm = current->mm;
 	unsigned long addr;
-	int ret;
+	unsigned long flags;
+	int ret = 0;

 	down_write(&mm->mmap_sem);
-	addr = get_unmapped_area(NULL, 0, PAGE_SIZE, 0, 0);
-	if (IS_ERR_VALUE(addr)) {
-		ret = addr;
-		goto up_fail;
-	}

 	/*
 	 * MAYWRITE to allow gdb to COW and set breakpoints
@@ -122,22 +150,23 @@ int arch_setup_additional_pages(struct l
 	 * without matching up the same kernel and hardware config to see
 	 * what PC values meant.
 	 */
-	ret = install_special_mapping(mm, addr, PAGE_SIZE,
-				      VM_READ|VM_EXEC|
-				      VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC|
-				      VM_ALWAYSDUMP,
-				      syscall_pages);
+	flags = VM_READ| VM_EXEC | VM_MAYREAD
+		| VM_MAYWRITE | VM_MAYEXEC | VM_ALWAYSDUMP;
+	ret = arch_setup_special_page(&addr, flags, syscall_pages);
 	if (ret)
 		goto up_fail;

-	current->mm->context.vdso = (void *)addr;
+	mm->context.vdso = (void *)addr;
 	current_thread_info()->sysenter_return =
-				    (void *)VDSO_SYM(&SYSENTER_RETURN);
+				(void *)VDSO_SYM(&SYSENTER_RETURN);
+
+	ret = arch_setup_uprobes_page();
+
 up_fail:
 	up_write(&mm->mmap_sem);
+
 	return ret;
 }
-EXPORT_SYMBOL_GPL(arch_setup_additional_pages);

 const char *arch_vma_name(struct vm_area_struct *vma)
 {
@@ -160,4 +189,22 @@ int in_gate_area_no_task(unsigned long a
 {
 	return 0;
 }
+#else
+/*
+ * Setup a VMA at program startup for uprobes ssol area.
+ */
+int arch_setup_additional_pages(struct linux_binprm *bprm, int exstack)
+{
+	struct mm_struct *mm = current->mm;
+	int ret = 0;
+
+	down_write(&mm->mmap_sem);
+
+	ret = arch_setup_uprobes_page();
+
+	up_write(&mm->mmap_sem);
+
+	return ret;
+}
 #endif
+EXPORT_SYMBOL_GPL(arch_setup_additional_pages);
diff -puN arch/i386/kernel/uprobes.c~uprobes-singlestep-out-of-line-lru arch/i386/kernel/uprobes.c
--- linux-2.6.21-rc3-mm2/arch/i386/kernel/uprobes.c~uprobes-singlestep-out-of-line-lru	2007-03-21 11:52:25.000000000 +0530
+++ linux-2.6.21-rc3-mm2-prasanna/arch/i386/kernel/uprobes.c	2007-03-21 15:18:14.000000000 +0530
@@ -23,67 +23,52 @@
 #include <linux/dcache.h>
 #include <linux/namei.h>
 #include <linux/pagemap.h>
+#include <linux/interp.h>
 #include <asm/kdebug.h>
 #include <asm/uprobes.h>

 #ifdef SS_OUT_OF_LINE
-/*
- * This routine finds an appropriate address for the instruction-copy that's
- * to be single-stepped out of line.  Between that address and the top of stack,
- * there must be room for the instruction (size bytes) and potential
- * growth (and overwriting) of the stack due to an instruction such as push.
- * If there's not enough room on the page containing the top of stack,
- * select the adjacent page.  (access_process_vm() will expand the stack
- * vma as necessary.)
- */
-static __always_inline long find_stack_space(long tos, int size)
-{
-	long page_addr = tos & PAGE_MASK;
-
-	if ((page_addr + size + SLOT_BEFORE_ORIGINSN) > tos)
-		page_addr -= PAGE_SIZE;
-
-	return page_addr;
-}

 /*
  * This routine provides the functionality of single stepping
- * out-of-line. FIXME: if single stepping out-of-line cannot be
- * achieved.
+ * out-of-line.
  */
 int uprobe_prepare_singlestep(struct uprobe_kimg *uk,
 		struct uprobe_task *utask, struct pt_regs *regs)
 {
-	long addr = 0, stack_addr = regs->esp;
-	int ret, size = sizeof(uprobe_opcode_t) * MAX_UINSN_SIZE;
-	long *source = (long *)uk->insn;
-	struct vm_area_struct *vma;
-	struct task_struct *tsk = utask->tsk;
+	struct uprobe_ssol_slot *slot;

 	/*
-	 * Get free stack space to copy original instruction, so as to
-	 * single step out-of-line.
-	 */
-	addr = find_stack_space(stack_addr, size);
-	if (!addr)
-		return 1;
-	/*
-	 * Copy original instruction on this per process stack
-	 * page so as to single step out-of-line.
+	 * Get an instruction slot from the process's
+	 * SSOL area, containing the instruction we're probing.
+	 * Change the eip to point to that slot, so as to single
+	 * step out-of-line
 	 */
-	ret = access_process_vm(tsk, addr, source, size, 1);
-	if (ret < size)
-		return 1;
-
-	regs->eip = addr;
+	slot = uprobe_get_insn_slot(uk);
+	BUG_ON(!slot);
+	regs->eip = (long)slot->insn;
 	utask->singlestep_addr = regs->eip;

-	down_write(&tsk->mm->mmap_sem);
-	vma = find_vma(tsk->mm, addr);
-	BUG_ON(!vma);
-	utask->orig_vm_flags = vma->vm_flags;
-	vma->vm_flags |=  (VM_EXEC | VM_EXECUTABLE);
-	up_write(&tsk->mm->mmap_sem);
+	return 0;
+}
+
+/* Setup the VMA for uprobes page */
+int arch_setup_uprobes_page(void)
+{
+	unsigned long addr, flags;
+	int ret;
+	void *page = (void *)get_zeroed_page(GFP_ATOMIC);
+
+	current->mm->context.uprobes_page[0] = virt_to_page(page);
+
+	flags = VM_READ| VM_EXEC | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
+
+	ret = arch_setup_special_page(&addr, flags,
+					current->mm->context.uprobes_page);
+	if (ret)
+		return ret;
+
+	current->mm->context.uprobes_ssol_area = (void *)addr;

 	return 0;
 }
@@ -139,8 +124,6 @@ void uprobe_resume_execution(struct upro
 	long next_eip = 0;
 	long copy_eip = utask->singlestep_addr;
 	long orig_eip = uk->vaddr;
-	struct vm_area_struct *vma;
-	struct task_struct *tsk = utask->tsk;

 	switch (uk->insn[0]) {
 	case 0xc3:		/* ret/lret */
@@ -177,12 +160,6 @@ void uprobe_resume_execution(struct upro
 		regs->eip = next_eip;
 	else
 		regs->eip = orig_eip + (regs->eip - copy_eip);
-
-	down_write(&tsk->mm->mmap_sem);
-	vma = find_vma(tsk->mm, copy_eip);
-	BUG_ON(!vma);
-	vma->vm_flags = utask->orig_vm_flags;
-	up_write(&tsk->mm->mmap_sem);
 }
 #endif	/* SSTEP_OUT_OF_LINE */

diff -puN include/asm-i386/mmu.h~uprobes-singlestep-out-of-line-lru include/asm-i386/mmu.h
--- linux-2.6.21-rc3-mm2/include/asm-i386/mmu.h~uprobes-singlestep-out-of-line-lru	2007-03-21 11:52:25.000000000 +0530
+++ linux-2.6.21-rc3-mm2-prasanna/include/asm-i386/mmu.h	2007-03-21 15:09:25.000000000 +0530
@@ -13,6 +13,8 @@ typedef struct { 
 	struct semaphore sem;
 	void *ldt;
 	void *vdso;
+	void *uprobes_ssol_area;
+	struct page *uprobes_page[1];
 } mm_context_t;

 #endif
diff -puN include/linux/interp.h~uprobes-singlestep-out-of-line-lru include/linux/interp.h
--- linux-2.6.21-rc3-mm2/include/linux/interp.h~uprobes-singlestep-out-of-line-lru	2007-03-21 11:52:25.000000000 +0530
+++ linux-2.6.21-rc3-mm2-prasanna/include/linux/interp.h	2007-03-21 11:52:26.000000000 +0530
@@ -5,4 +5,7 @@ struct linux_binprm;

 extern int __attribute__((weak))
 	arch_setup_additional_pages(struct linux_binprm*, int);
+extern int arch_setup_special_page(unsigned long *page_addr,
+                                 unsigned long flags, struct page **pages);
+
 #endif /* _LINUX_INTERP_H */
diff -puN include/linux/uprobes.h~uprobes-singlestep-out-of-line-lru include/linux/uprobes.h
--- linux-2.6.21-rc3-mm2/include/linux/uprobes.h~uprobes-singlestep-out-of-line-lru	2007-03-21 11:52:25.000000000 +0530
+++ linux-2.6.21-rc3-mm2-prasanna/include/linux/uprobes.h	2007-03-21 11:52:26.000000000 +0530
@@ -31,6 +31,8 @@ struct task_struct;
 struct utrace_attached_engine;
 struct uprobe_kimg;
 struct uprobe;
+struct uprobe_ssol_slot;
+struct uprobe_ssol_area;

 enum uprobe_type {
 	UPTY_UPROBE,
@@ -116,6 +118,61 @@ enum uprobe_task_state {

 #define UPROBE_HASH_BITS 5
 #define UPROBE_TABLE_SIZE (1 << UPROBE_HASH_BITS)
+#define MAX_UINSN_BYTES (MAX_UINSN_SIZE * sizeof(uprobe_opcode_t))
+#define UINSNS_PER_PAGE  (PAGE_SIZE/MAX_UINSN_BYTES)
+
+/*
+ * The below slot states are just advisory, used
+ * when deciding which slot to steal.
+ */
+
+enum uprobe_ssol_slot_state {
+	SSOL_FREE,
+	SSOL_ASSIGNED,
+	SSOL_BEING_STOLEN
+};
+
+/*
+ * For uprobe_process that uses an SSOL area, there's an array of these
+ * objects matching the array of instruction slots in the SSOL area.
+ */
+struct uprobe_ssol_slot {
+	/* The slot in the SSOL area that holds the instruction-copy */
+	__user uprobe_opcode_t	*insn;
+
+	enum uprobe_ssol_slot_state state;
+
+	/* The probepoint that currently owns this slot */
+	struct uprobe_kimg *owner;
+
+	/*
+	 * Read-locked when slot is in use during single-stepping.
+	 * Write-locked by stealing task.
+	 */
+	struct rw_semaphore rwsem;
+
+	/* Used for LRU heuristics.  If this overflows, it's OK. */
+	unsigned long last_used;
+};
+
+/*
+ * For each uprobe_process that uses an SSOL area, there's one of these.
+ */
+struct uprobe_ssol_area {
+	/* Array of instruction slots */
+	__user uprobe_opcode_t *insn_area;
+
+	int nslots;
+
+	/* Array of slot objects, one per instruction slot */
+	struct uprobe_ssol_slot *slots;
+
+	/* lock held while finding a free slot */
+	 spinlock_t lock;
+
+	/* Next slot to steal */
+	int next_slot;
+};

 /*
  * uprobe_process -- not a user-visible struct.
@@ -168,6 +225,12 @@ struct uprobe_process {
 	 */
 	struct uprobe *uretprobe_trampoline;
 	unsigned long uretprobe_trampoline_addr;
+
+	/*
+	 * Manages slots for instruction-copies to be single-stepped
+	 * out of line.
+	 */
+	struct uprobe_ssol_area ssol_area;
 };

 /*
@@ -181,6 +244,13 @@ struct uprobe_kimg {
 	 * in a process can run handlers for same probepoint simultaneously.
 	 */
 	struct rw_semaphore rwsem;
+	/*
+	 * Hold this while stealing an insn slot to ensure that no
+	 * other thread, having also hit this probepoint, simultaneously
+	 * steals a slot for it.
+	 */
+	struct mutex slot_mutex;	// or larceny_mutex or some such
+

 	/* vaddr copied from (first) uprobe */
 	unsigned long vaddr;
@@ -194,7 +264,7 @@ struct uprobe_kimg {
 	uprobe_opcode_t opcode;

 	/* Saved original instruction */
-	uprobe_opcode_t insn[MAX_UINSN_SIZE];
+	uprobe_opcode_t insn[MAX_UINSN_BYTES / sizeof(uprobe_opcode_t)];

 	/* The corresponding struct uprobe_process */
 	struct uprobe_process *uproc;
@@ -213,6 +283,12 @@ struct uprobe_kimg {

 	/* [un]register_uprobe() waits 'til bkpt inserted/removed. */
 	wait_queue_head_t waitq;
+
+	/*
+	 * We put the instruction-copy here to single-step it.
+	 * We don't own it unless slot->owner points back to us.
+	 */
+	struct uprobe_ssol_slot *slot;
 };

 /*
@@ -278,11 +354,18 @@ extern void uprobe_resume_execution(stru
 			struct uprobe_task *utask, struct pt_regs *regs);
 extern int uprobe_prepare_singlestep(struct uprobe_kimg *uk,
 			struct uprobe_task *utask, struct pt_regs *regs);
+extern int arch_setup_uprobes_page(void);
+#else
+static inline int arch_setup_uprobes_page(void)
+{
+	return 0;
+}
 #endif

 int register_uretprobe(struct uretprobe *rp);
 void unregister_uretprobe(struct uretprobe *rp);
 int init_uretprobes(pid_t pid, unsigned long vaddr);
+struct uprobe_ssol_slot *uprobe_get_insn_slot(struct uprobe_kimg *uk);

 #else	/* CONFIG_UPROBES */
 static inline int register_uprobe(struct uprobe *u)
@@ -303,5 +386,9 @@ static int init_uretprobes(pid_t pid, un
 {
 	return -ENOSYS;
 }
+static inline int arch_setup_uprobes_page(void)
+{
+	return 0;
+}
 #endif	/* CONFIG_UPROBES */
 #endif	/* _LINUX_UPROBES_H */
diff -puN kernel/uprobes.c~uprobes-singlestep-out-of-line-lru kernel/uprobes.c
--- linux-2.6.21-rc3-mm2/kernel/uprobes.c~uprobes-singlestep-out-of-line-lru	2007-03-21 11:52:25.000000000 +0530
+++ linux-2.6.21-rc3-mm2-prasanna/kernel/uprobes.c	2007-03-21 12:05:59.000000000 +0530
@@ -219,9 +219,8 @@ static void insert_bkpt(struct uprobe_ki
 	 * than the max and near the end of the last page of instructions.
 	 * But there must be room at least for a breakpoint-size instruction.
 	 */
-	len = access_process_vm(tsk, uk->vaddr, uk->insn,
-		       MAX_UINSN_SIZE * sizeof(uprobe_opcode_t), 0);
-	if (len < MAX_UINSN_SIZE) {
+	len = access_process_vm(tsk, uk->vaddr, uk->insn, MAX_UINSN_BYTES, 0);
+	if (len < sizeof(uprobe_opcode_t)) {
 		bkpt_insertion_failed(uk, "error reading original instruction");
 		result = -EIO;
 		goto out;
@@ -419,7 +418,6 @@ static void uprobe_free_task(struct upro
 		list_del(&dr->list);
 		kfree(dr);
 	}
-
 	/*
 	 * Trampoline uprobe stays around 'til task exits, so assume
 	 * task is exiting if any uretprobe_instances remain.
@@ -437,6 +435,7 @@ static void uprobe_free_task(struct upro
 static void uprobe_free_process(struct uprobe_process *uproc)
 {
 	struct uprobe_task *utask, *tmp;
+	struct uprobe_ssol_area *area = &uproc->ssol_area;

 	if (!hlist_unhashed(&uproc->hlist))
 		hlist_del(&uproc->hlist);
@@ -457,6 +456,9 @@ static void uprobe_free_process(struct u
 		kfree(uproc->uretprobe_trampoline);

 	mutex_unlock(&uproc->mutex);	// So kfree doesn't complain
+	/*free the ssol slots */
+	if (area->slots)
+		kfree(area->slots);
 	kfree(uproc);
 }

@@ -557,6 +559,35 @@ static struct uprobe_task *uprobe_add_ta
 	return utask;
 }

+/*
+ * Initialize per-process ssol_area for single stepping out-of-line.
+ */
+static void uprobe_init_ssol(struct uprobe_ssol_area *area,
+						struct task_struct *task)
+{
+	struct uprobe_ssol_slot *slot;
+	int i;
+
+	area->insn_area = (uprobe_opcode_t *)
+			 task->mm->context.uprobes_ssol_area;
+	area->slots = (struct uprobe_ssol_slot *)
+		kzalloc(sizeof(struct uprobe_ssol_slot) *
+					UINSNS_PER_PAGE, GFP_USER);
+	area->nslots = UINSNS_PER_PAGE;
+	spin_lock_init(&area->lock);
+	area->next_slot = 0;
+	for (i = 0; i < UINSNS_PER_PAGE; i++) {
+		slot = &area->slots[i];
+		init_rwsem(&slot->rwsem);
+		slot->state = SSOL_FREE;
+		slot->owner = NULL;
+		slot->last_used = 0;
+		slot->insn = (uprobe_opcode_t *)
+			((unsigned long)area->insn_area
+			+ (i * MAX_UINSN_BYTES));
+	}
+}
+
 /* See comment in uprobe_mk_process(). */
 static struct task_struct *find_next_thread_to_add(struct uprobe_process *uproc,		struct task_struct *start)
 {
@@ -611,6 +642,9 @@ static struct uprobe_process *uprobe_mk_
 	uproc->uretprobe_trampoline = NULL;
 	uproc->uretprobe_trampoline_addr = 0;

+	/* initialize per-process ssol area */
+	uprobe_init_ssol(&uproc->ssol_area, p);
+
 	/*
 	 * Create and populate one utask per thread in this process.  We
 	 * can't call uprobe_add_task() while holding tasklist_lock, so we:
@@ -659,6 +693,7 @@ static struct uprobe_kimg *uprobe_add_ki
 	if (unlikely(uk == NULL))
 		return ERR_PTR(-ENOMEM);
 	init_rwsem(&uk->rwsem);
+	mutex_init(&uk->slot_mutex);
 	down_write(&uk->rwsem);
 	init_waitqueue_head(&uk->waitq);

@@ -698,6 +733,18 @@ static void uprobe_free_kimg(struct upro
 	mutex_lock(&uproc->mutex);
 	down_write(&uk->rwsem);	// So other CPUs have time to see UPROBE_FREEING
 	hlist_del(&uk->ut_node);
+
+	/* Mark the slot free; with write lock held*/
+
+	if (uk->slot) {
+		down_write(&uk->slot->rwsem);
+		if (uk->slot->owner == uk) {
+			uk->slot->state = SSOL_FREE;
+			uk->slot->owner = NULL;
+		}
+		up_write(&uk->slot->rwsem);
+	}
+
 	up_write(&uk->rwsem);	// So kfree doesn't complain
 	kfree(uk);
 	uproc->nuk--;
@@ -996,6 +1043,132 @@ void unregister_uprobe(struct uprobe *u)
 }

 /*
+ * Management of instruction slots for single-stepping out of line
+ */
+
+static inline int advance_slot(int slot, struct uprobe_ssol_area *area)
+{
+	return (slot + 1) % area->nslots;
+}
+
+/*
+ * Choose an instruction slot and take it.  Choose a free slot if there is one.
+ * Otherwise choose the least-recently-used slot.  Returns with slot read-locked.
+ * Runs with uk write-locked.
+ */
+static struct uprobe_ssol_slot *uprobe_lru_insn_slot(struct uprobe_kimg *uk)
+{
+	struct uprobe_process *uproc = uk->uproc;
+	struct uprobe_ssol_area *area = &uproc->ssol_area;
+	struct uprobe_ssol_slot *s;
+	int slot, best_slot, len;
+	unsigned long lru_time, flags;
+
+	spin_lock_irqsave(&area->lock, flags);
+
+	/*
+	 * Find a slot to take.  Start looking at next_slot.  Don't bother
+	 * locking individual slots while we decide.
+	 */
+	best_slot = -1;
+	lru_time = ULONG_MAX;
+	slot = area->next_slot;
+	do {
+		s = &area->slots[slot];
+		if (s->state == SSOL_FREE) {
+			best_slot = slot;
+			break;
+		}
+		if (s->state == SSOL_ASSIGNED && lru_time > s->last_used) {
+			lru_time = s->last_used;
+			best_slot = slot;
+		}
+		slot = advance_slot(slot, area);
+	} while (slot != area->next_slot);
+
+	if (unlikely(best_slot < 0))
+		/* All slots are in the act of being stolen.  Join the melee. */
+		slot = area->next_slot;
+	else
+		slot = best_slot;
+	area->next_slot = advance_slot(slot, area);
+	s = &area->slots[slot];
+	s->state = SSOL_BEING_STOLEN;
+
+	spin_unlock_irqrestore(&area->lock, flags);
+
+
+	down_write(&s->rwsem);
+	uk->slot = s;
+	s->owner = uk;
+	s->last_used = jiffies;
+	s->state = SSOL_ASSIGNED;
+	/*
+	 * Copy the original instruction to the uprobe ssol area, so
+	 * as to single step out-of-line.
+	 */
+	len = access_process_vm(current,(unsigned long)s->insn,
+					 uk->insn, MAX_UINSN_BYTES, 1);
+        if (unlikely(len < MAX_UINSN_BYTES)) {
+		up_write(&s->rwsem);
+		printk("cound not copy the insn to slot area");
+		return NULL;
+	}
+
+	downgrade_write(&s->rwsem);
+
+	return s;
+}
+
+/*
+ * uk doesn't own a slot.  Get one for uk, and return it read-locked.
+ */
+static struct uprobe_ssol_slot *uprobe_find_insn_slot(struct uprobe_kimg *uk)
+{
+	struct uprobe_ssol_slot *slot;
+
+	mutex_lock(&uk->slot_mutex);
+	slot = uk->slot;
+	if (unlikely(slot && slot->owner == uk)) {
+		down_read(&slot->rwsem);
+		/* Looks like another thread snuck in and got a slot for us. */
+		if (likely(slot->owner == uk)) {
+			slot->last_used = jiffies;
+			mutex_unlock(&uk->slot_mutex);
+			return slot;
+		}
+		/* ... but then somebody stole it. */
+		up_read(&slot->rwsem);
+	}
+	slot = uprobe_lru_insn_slot(uk);
+	mutex_unlock(&uk->slot_mutex);
+
+
+	return slot;
+}
+
+/*
+ * Ensure that uk owns an instruction slot for single-stepping.
+ * Returns with the slot read-locked and uk->slot pointing at it.
+ */
+struct uprobe_ssol_slot *uprobe_get_insn_slot(struct uprobe_kimg *uk)
+{
+	struct uprobe_ssol_slot *slot;
+
+	if (!uk->slot)
+		return uprobe_find_insn_slot(uk);
+
+	slot = uk->slot;
+	down_read(&slot->rwsem);
+	if (slot->owner != uk) {
+		up_read(&slot->rwsem);
+		return uprobe_find_insn_slot(uk);
+	}
+	slot->last_used = jiffies;
+	return slot;
+}
+
+/*
  * utrace engine report callbacks
  */

@@ -1106,6 +1279,7 @@ static u32 uprobe_report_signal(struct u
 	struct uprobe_kimg *uk;
 	struct uprobe_process *uproc;
 	struct uprobe *u;
+	struct uprobe_ssol_slot *slot;
 #ifndef SS_OUT_OF_LINE
 	int len;
 #endif
@@ -1170,6 +1344,8 @@ static u32 uprobe_report_signal(struct u
 		if (hit_uretprobe_trampoline)
 			goto bp_done;

+		utask->state = UPTASK_SSTEP_AFTER_BP;
+		mutex_unlock(&utask->mutex);
 #ifdef SS_OUT_OF_LINE
 		ret = uprobe_prepare_singlestep(uk, utask, regs);
 		BUG_ON(ret);
@@ -1185,8 +1361,6 @@ static u32 uprobe_report_signal(struct u
 			do_exit(SIGSEGV);
 		}
 #endif
-		utask->state = UPTASK_SSTEP_AFTER_BP;
-		mutex_unlock(&utask->mutex);
 		/*
 		 * No other engines must see this signal, and the
 		 * signal shouldn't be passed on either.
@@ -1205,7 +1379,12 @@ static u32 uprobe_report_signal(struct u
 			uk->state = UPROBE_DISABLED;
 		}
 #else
+		mutex_unlock(&utask->mutex);
+		slot = uk->slot;
+		up_read(&slot->rwsem);
 		uprobe_resume_execution(uk, utask, regs);
+		mutex_lock(&utask->mutex);
+
 #endif

 bp_done:

_

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]