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]

[RFC][PATCH] Kprobes robust fault handling for i386


Please find the patch for robust fault handling of kprobes for
i386 architecture. This patch requires kgdb-setjmp/longjmp
routines defined in kgdb-jmp.c file. If this approach
looks ok, we can ask kgdb maintainer to push setjmp/longjmp
routines to mainline.

Please let me know if you have any issues.

Thanks
Prasanna

If a user-specified pre/post handler generates a fault say, due to
access to user address space, through copy_from_user(), get_user() etc it
must be fixed. Kprobes handles this by calling fixup_exception(). If
fixup_exception() does not succeed, then the faulty handler must not be
allowed to continue further. This patch provides a simple mechanism
to save and restore the state in such cases by aborting the faulted
user-specified handler. Sufficient registers must be saved so that kprobes
can recover from such faults. This patch uses the kgdb_fault_setjmp() and
kgdb_fault_longjmp() routines which save the minimum set of registers
required to recover from faults.  The resgisters are saved and restored
from the per-probe kprobe_ctlblk structure. If there are multiple handlers
registered for a probepoint, then only the faulted user-specified handler is
aborted, the rest of the user-specified handlers in the aggregate probe list
get executed.

Signed-off-by: Prasanna S Panchamukhi <prasanna@in.ibm.com>


 arch/i386/kernel/Makefile  |    5 ++-
 arch/i386/kernel/kprobes.c |   60 ++++++++++++++++++++++++++++++++++++++---
 include/asm-i386/kprobes.h |    8 +++++
 kernel/kprobes.c           |   65 +++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 129 insertions(+), 9 deletions(-)

diff -puN include/asm-i386/kprobes.h~kprobes-robust-fault-hanlding-for-i386 include/asm-i386/kprobes.h
--- linux-2.6.17-rc1-mm2/include/asm-i386/kprobes.h~kprobes-robust-fault-hanlding-for-i386	2006-04-24 12:04:07.000000000 +0530
+++ linux-2.6.17-rc1-mm2-prasanna/include/asm-i386/kprobes.h	2006-04-24 12:04:08.000000000 +0530
@@ -44,6 +44,8 @@ typedef u8 kprobe_opcode_t;
 
 #define JPROBE_ENTRY(pentry)	(kprobe_opcode_t *)pentry
 #define ARCH_SUPPORTS_KRETPROBES
+#define ARCH_SUPPORTS_FAULT_HANDLING
+#define NUMCRITREG	6
 
 void arch_remove_kprobe(struct kprobe *p);
 void kretprobe_trampoline(void);
@@ -71,6 +73,7 @@ struct kprobe_ctlblk {
 	unsigned long kprobe_status;
 	unsigned long kprobe_old_eflags;
 	unsigned long kprobe_saved_eflags;
+	unsigned long kprobe_fault_jmp_regs[NUMCRITREG];
 	long *jprobe_saved_esp;
 	struct pt_regs jprobe_saved_regs;
 	kprobe_opcode_t jprobes_stack[MAX_STACK_SIZE];
@@ -88,4 +91,9 @@ static inline void restore_interrupts(st
 
 extern int kprobe_exceptions_notify(struct notifier_block *self,
 				    unsigned long val, void *data);
+extern int asmlinkage kgdb_fault_setjmp(unsigned long *);
+extern int asmlinkage kgdb_fault_longjmp(unsigned long *);
+extern int aggr_pre_handler(struct kprobe *p, struct pt_regs *regs);
+extern void aggr_post_handler(struct kprobe *p, struct pt_regs *regs,
+							unsigned long flags);
 #endif				/* _ASM_KPROBES_H */
diff -puN kernel/kprobes.c~kprobes-robust-fault-hanlding-for-i386 kernel/kprobes.c
--- linux-2.6.17-rc1-mm2/kernel/kprobes.c~kprobes-robust-fault-hanlding-for-i386	2006-04-24 12:04:07.000000000 +0530
+++ linux-2.6.17-rc1-mm2-prasanna/kernel/kprobes.c	2006-04-25 12:11:15.000000000 +0530
@@ -189,9 +189,40 @@ struct kprobe __kprobes *get_kprobe(void
  * Aggregate handlers for multiple kprobes support - these handlers
  * take care of invoking the individual kprobe handlers on p->list
  */
-static int __kprobes aggr_pre_handler(struct kprobe *p, struct pt_regs *regs)
+int __kprobes aggr_pre_handler(struct kprobe *p, struct pt_regs *regs)
 {
 	struct kprobe *kp;
+#ifdef ARCH_SUPPORTS_FAULT_HANDLING
+	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+	int skipped_faulty_handler;
+	struct kprobe *faulty_kprobe;
+	/*
+	 * Save enough registers before calling the user-specified
+	 * handlers, so that if a user-specified handler generates a
+	 * fault, it could be recovered to a sane state. Also since
+	 * mutiple handlers are registered for this kprobe, only the
+	 * faulted user-specified handler must be prevented from
+	 * further execution, allow other handler in the aggregate
+	 * kprobes list to execute.
+	 */
+	if ((kgdb_fault_setjmp(kcb->kprobe_fault_jmp_regs)) != 0) {
+		skipped_faulty_handler = 0;
+		faulty_kprobe = __get_cpu_var(kprobe_instance);
+		list_for_each_entry_rcu(kp, &p->list, list) {
+			if (!skipped_faulty_handler && (kp == faulty_kprobe))
+				skipped_faulty_handler = 1;
+			else if (skipped_faulty_handler == 1) {
+				if (kp->pre_handler) {
+					set_kprobe_instance(kp);
+					if (kp->pre_handler(kp, regs))
+						return 1;
+				}
+				reset_kprobe_instance();
+			}
+		}
+	return  0;
+	}
+#endif
 
 	list_for_each_entry_rcu(kp, &p->list, list) {
 		if (kp->pre_handler) {
@@ -204,10 +235,40 @@ static int __kprobes aggr_pre_handler(st
 	return 0;
 }
 
-static void __kprobes aggr_post_handler(struct kprobe *p, struct pt_regs *regs,
+void __kprobes aggr_post_handler(struct kprobe *p, struct pt_regs *regs,
 					unsigned long flags)
 {
 	struct kprobe *kp;
+#ifdef ARCH_SUPPORTS_FAULT_HANDLING
+	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+	int skipped_faulty_handler;
+	struct kprobe *faulty_kprobe;
+	/*
+	 * Save enough registers before calling the user-specified
+	 * handlers, so that if a user-specified handler generates a
+	 * fault, it could be recovered to a sane state. Also since
+	 * mutiple handlers are registered for this kprobe, only the
+	 * faulted user-specified handler must be prevented from
+	 * further execution, allow other handler in the aggregate
+	 * kprobes list to execute.
+	 */
+	if ((kgdb_fault_setjmp(kcb->kprobe_fault_jmp_regs)) != 0) {
+		skipped_faulty_handler = 0;
+		faulty_kprobe = __get_cpu_var(kprobe_instance);
+		list_for_each_entry_rcu(kp, &p->list, list) {
+			if (!skipped_faulty_handler && (kp == faulty_kprobe))
+				skipped_faulty_handler = 1;
+			else if (skipped_faulty_handler == 1) {
+				if (kp->post_handler) {
+					set_kprobe_instance(kp);
+					kp->post_handler(kp, regs, flags);
+					reset_kprobe_instance();
+				}
+			}
+		}
+	return;
+	}
+#endif
 
 	list_for_each_entry_rcu(kp, &p->list, list) {
 		if (kp->post_handler) {
diff -puN arch/i386/kernel/kprobes.c~kprobes-robust-fault-hanlding-for-i386 arch/i386/kernel/kprobes.c
--- linux-2.6.17-rc1-mm2/arch/i386/kernel/kprobes.c~kprobes-robust-fault-hanlding-for-i386	2006-04-24 12:04:07.000000000 +0530
+++ linux-2.6.17-rc1-mm2-prasanna/arch/i386/kernel/kprobes.c	2006-04-25 11:45:06.000000000 +0530
@@ -290,9 +290,26 @@ static int __kprobes kprobe_handler(stru
 	set_current_kprobe(p, regs, kcb);
 	kcb->kprobe_status = KPROBE_HIT_ACTIVE;
 
-	if (p->pre_handler && p->pre_handler(p, regs))
+	if (p->pre_handler) {
+		/*
+		 * If a user-specified handler faults, and the fault isn't
+		 * handled successfully, then the handler must be prevented
+		 * from continuing. Before executing the user-specified
+		 * handler, save enough registers in the kprobe_ctlblk
+		 * structure so that kprobes can recover from faults.
+		 * kgdb_fault_setjmp() saves enough registers to recover
+		 * from such faults.
+		 */
+		if ((p->pre_handler != aggr_pre_handler) &&
+			((kgdb_fault_setjmp(kcb->kprobe_fault_jmp_regs)) != 0))
+			 /* Uhhh! came here because of kgdb_fault_longjmp() */
+			goto boost_probe;
+
+		if (p->pre_handler(p, regs))
 		/* handler has already set things up, so skip ss setup */
-		return 1;
+			return 1;
+	}
+boost_probe:
 
 	if (p->ainsn.boostable == 1 &&
 #ifdef CONFIG_PREEMPT
@@ -320,6 +337,20 @@ no_kprobe:
 }
 
 /*
+ * This trampoline does a long jump, when a user-specified
+ * handler page faults, thereby restoring kprobes to a sane
+ * state.
+ */
+int trampoline_fault_handler(void)
+{
+	struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+
+	kgdb_fault_longjmp(kcb->kprobe_fault_jmp_regs);
+
+	return 0;
+}
+
+/*
  * For function-return probes, init_kprobes() establishes a probepoint
  * here. When a retprobed function returns, this probe is hit and
  * trampoline_probe_handler() runs, calling the kretprobe's handler.
@@ -517,9 +548,24 @@ static inline int post_kprobe_handler(st
 
 	if ((kcb->kprobe_status != KPROBE_REENTER) && cur->post_handler) {
 		kcb->kprobe_status = KPROBE_HIT_SSDONE;
+		/*
+		 * If a user-specified handler faults, and the fault isn't
+		 * handled successfully, then the handler must be prevented
+		 * from continuing. Before executing the user-specified
+		 * handler, save enough registers in the kprobe_ctlblk
+		 * structure so that kprobes can recover from faults.
+		 * kgdb_fault_setjmp() saves enough registers to recover
+		 * from such faults.
+		 */
+		if ((cur->post_handler != aggr_post_handler) &&
+			((kgdb_fault_setjmp(kcb->kprobe_fault_jmp_regs)) != 0))
+			/* Uhhh! came here because of kgdb_fault_longjmp() */
+			goto resume_exec;
+
 		cur->post_handler(cur, regs, 0);
 	}
 
+resume_exec:
 	resume_execution(cur, regs, kcb);
 	regs->eflags |= kcb->kprobe_saved_eflags;
 
@@ -587,15 +633,19 @@ static inline int kprobe_fault_handler(s
 
 		/*
 		 * In case the user-specified fault handler returned
-		 * zero, try to fix up.
+		 * zero, we try to fix it.
 		 */
 		if (fixup_exception(regs))
 			return 1;
 
 		/*
-		 * fixup_exception() could not handle it,
-		 * Let do_page_fault() fix it.
+		 * In case if fixup_exception() does not fix it, then
+		 * we return to a trampoline_fault_handler(), that
+		 * does a long jump to the kprobes handler.
 		 */
+		regs->eip = (unsigned long)trampoline_fault_handler;
+		return 1;
+
 		break;
 	default:
 		break;
diff -puN arch/i386/kernel/Makefile~kprobes-robust-fault-hanlding-for-i386 arch/i386/kernel/Makefile
--- linux-2.6.17-rc1-mm2/arch/i386/kernel/Makefile~kprobes-robust-fault-hanlding-for-i386	2006-04-24 12:04:07.000000000 +0530
+++ linux-2.6.17-rc1-mm2-prasanna/arch/i386/kernel/Makefile	2006-04-24 12:04:48.000000000 +0530
@@ -7,7 +7,8 @@ extra-y := head.o init_task.o vmlinux.ld
 obj-y	:= process.o semaphore.o signal.o entry.o traps.o irq.o \
 		ptrace.o time.o ioport.o ldt.o setup.o i8259.o sys_i386.o \
 		pci-dma.o i386_ksyms.o i387.o bootflag.o \
-		quirks.o i8237.o topology.o alternative.o i8253.o tsc.o
+		quirks.o i8237.o topology.o alternative.o i8253.o tsc.o \
+		kgdb-jmp.o
 
 obj-y				+= cpu/
 obj-y				+= acpi/
@@ -36,7 +37,7 @@ obj-$(CONFIG_EFI) 		+= efi.o efi_stub.o
 obj-$(CONFIG_DOUBLEFAULT) 	+= doublefault.o
 obj-$(CONFIG_VM86)		+= vm86.o
 obj-$(CONFIG_EARLY_PRINTK)	+= early_printk.o
-obj-$(CONFIG_KGDB)		+= kgdb.o kgdb-jmp.o
+obj-$(CONFIG_KGDB)		+= kgdb.o
 obj-$(CONFIG_HPET_TIMER) 	+= hpet.o
 
 EXTRA_AFLAGS   := -traditional

_
-- 
Prasanna S Panchamukhi
Linux Technology Center
India Software Labs, IBM Bangalore
Email: prasanna@in.ibm.com
Ph: 91-80-51776329


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