diff --git a/arch/i386/oprofile/Makefile b/arch/i386/oprofile/Makefile
index a6945077e804a963c8e6b3f92b2278d16ef1ef81..30f3eb3666675e4b8a90bff8f0edfd43cfe99f6f 100644
--- a/arch/i386/oprofile/Makefile
+++ b/arch/i386/oprofile/Makefile
@@ -6,7 +6,7 @@ DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 		oprofilefs.o oprofile_stats.o  \
 		timer_int.o )
 
-oprofile-y				:= $(DRIVER_OBJS) init.o
+oprofile-y				:= $(DRIVER_OBJS) init.o backtrace.o
 oprofile-$(CONFIG_X86_LOCAL_APIC) 	+= nmi_int.o op_model_athlon.o \
 					   op_model_ppro.o op_model_p4.o
 oprofile-$(CONFIG_X86_IO_APIC)		+= nmi_timer_int.o
diff --git a/arch/i386/oprofile/backtrace.c b/arch/i386/oprofile/backtrace.c
new file mode 100644
index 0000000000000000000000000000000000000000..49a06053f70f0b5bc894d8276ca4ab7e133e3430
--- /dev/null
+++ b/arch/i386/oprofile/backtrace.c
@@ -0,0 +1,124 @@
+/**
+ * @file backtrace.c
+ *
+ * @remark Copyright 2002 OProfile authors
+ * @remark Read the file COPYING
+ *
+ * @author John Levon
+ * @author David Smith
+ */
+
+#include <linux/oprofile.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <asm/ptrace.h>
+
+struct frame_head {
+	struct frame_head * ebp;
+	unsigned long ret;
+} __attribute__((packed));
+
+
+static struct frame_head *
+dump_backtrace(struct frame_head * head)
+{
+	oprofile_add_trace(head->ret);
+
+	/* frame pointers should strictly progress back up the stack
+	 * (towards higher addresses) */
+	if (head >= head->ebp)
+		return 0;
+
+	return head->ebp;
+}
+
+
+#ifdef CONFIG_X86_4G
+/* With a 4G kernel/user split, user pages are not directly
+ * accessible from the kernel, so don't try
+ */
+static int pages_present(struct frame_head * head)
+{
+	return 0;
+}
+#else
+/* check that the page(s) containing the frame head are present */
+static int pages_present(struct frame_head * head)
+{
+	struct mm_struct * mm = current->mm;
+
+	/* FIXME: only necessary once per page */
+	if (!check_user_page_readable(mm, (unsigned long)head))
+		return 0;
+
+	return check_user_page_readable(mm, (unsigned long)(head + 1));
+}
+#endif /* CONFIG_X86_4G */
+
+
+/*
+ * |             | /\ Higher addresses
+ * |             |
+ * --------------- stack base (address of current_thread_info)
+ * | thread info |
+ * .             .
+ * |    stack    |
+ * --------------- saved regs->ebp value if valid (frame_head address)
+ * .             .
+ * --------------- struct pt_regs stored on stack (struct pt_regs *)
+ * |             |
+ * .             .
+ * |             |
+ * --------------- %esp
+ * |             |
+ * |             | \/ Lower addresses
+ *
+ * Thus, &pt_regs <-> stack base restricts the valid(ish) ebp values
+ */
+#ifdef CONFIG_FRAME_POINTER
+static int valid_kernel_stack(struct frame_head * head, struct pt_regs * regs)
+{
+	unsigned long headaddr = (unsigned long)head;
+	unsigned long stack = (unsigned long)regs;
+	unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE;
+
+	return headaddr > stack && headaddr < stack_base;
+}
+#else
+/* without fp, it's just junk */
+static int valid_kernel_stack(struct frame_head * head, struct pt_regs * regs)
+{
+	return 0;
+}
+#endif
+
+
+void
+x86_backtrace(struct pt_regs * const regs, unsigned int depth)
+{
+	struct frame_head *head;
+
+#ifdef CONFIG_X86_64
+	head = (struct frame_head *)regs->rbp;
+#else
+	head = (struct frame_head *)regs->ebp;
+#endif
+
+	if (!user_mode(regs)) {
+		while (depth-- && valid_kernel_stack(head, regs))
+			head = dump_backtrace(head);
+		return;
+	}
+
+#ifdef CONFIG_SMP
+	if (!spin_trylock(&current->mm->page_table_lock))
+		return;
+#endif
+
+	while (depth-- && head && pages_present(head))
+		head = dump_backtrace(head);
+
+#ifdef CONFIG_SMP
+	spin_unlock(&current->mm->page_table_lock);
+#endif
+}
diff --git a/arch/i386/oprofile/init.c b/arch/i386/oprofile/init.c
index 1b7747ca726a676097ea0c23ba91619e96db221b..672eed39d53e3c2ab8f64ea9b6811023ad28a3a6 100644
--- a/arch/i386/oprofile/init.c
+++ b/arch/i386/oprofile/init.c
@@ -15,22 +15,26 @@
  * with the NMI mode driver.
  */
  
-extern int nmi_init(struct oprofile_operations ** ops);
-extern int nmi_timer_init(struct oprofile_operations **ops);
+extern int nmi_init(struct oprofile_operations * ops);
+extern int nmi_timer_init(struct oprofile_operations * ops);
 extern void nmi_exit(void);
+extern void x86_backtrace(struct pt_regs * const regs, unsigned int depth);
 
-int __init oprofile_arch_init(struct oprofile_operations ** ops)
+
+void __init oprofile_arch_init(struct oprofile_operations * ops)
 {
-	int ret = -ENODEV;
+	int ret;
+
+	ret = -ENODEV;
+
 #ifdef CONFIG_X86_LOCAL_APIC
 	ret = nmi_init(ops);
 #endif
-
 #ifdef CONFIG_X86_IO_APIC
 	if (ret < 0)
 		ret = nmi_timer_init(ops);
 #endif
-	return ret;
+	ops->backtrace = x86_backtrace;
 }
 
 
diff --git a/arch/i386/oprofile/nmi_int.c b/arch/i386/oprofile/nmi_int.c
index d3450dcc53e0eeb3b425d8f3ada63ad6c5087d4b..d4ffd3f9c33bae8aab5be49183bd96604beb3d30 100644
--- a/arch/i386/oprofile/nmi_int.c
+++ b/arch/i386/oprofile/nmi_int.c
@@ -84,7 +84,7 @@ static void __exit exit_driverfs(void)
 
 static int nmi_callback(struct pt_regs * regs, int cpu)
 {
-	return model->check_ctrs(cpu, &cpu_msrs[cpu], regs);
+	return model->check_ctrs(regs, &cpu_msrs[cpu]);
 }
  
  
@@ -300,16 +300,7 @@ static int nmi_create_files(struct super_block * sb, struct dentry * root)
 }
  
  
-struct oprofile_operations nmi_ops = {
-	.create_files 	= nmi_create_files,
-	.setup 		= nmi_setup,
-	.shutdown	= nmi_shutdown,
-	.start		= nmi_start,
-	.stop		= nmi_stop
-};
- 
-
-static int __init p4_init(void)
+static int __init p4_init(char ** cpu_type)
 {
 	__u8 cpu_model = current_cpu_data.x86_model;
 
@@ -317,18 +308,18 @@ static int __init p4_init(void)
 		return 0;
 
 #ifndef CONFIG_SMP
-	nmi_ops.cpu_type = "i386/p4";
+	*cpu_type = "i386/p4";
 	model = &op_p4_spec;
 	return 1;
 #else
 	switch (smp_num_siblings) {
 		case 1:
-			nmi_ops.cpu_type = "i386/p4";
+			*cpu_type = "i386/p4";
 			model = &op_p4_spec;
 			return 1;
 
 		case 2:
-			nmi_ops.cpu_type = "i386/p4-ht";
+			*cpu_type = "i386/p4-ht";
 			model = &op_p4_ht2_spec;
 			return 1;
 	}
@@ -340,7 +331,7 @@ static int __init p4_init(void)
 }
 
 
-static int __init ppro_init(void)
+static int __init ppro_init(char ** cpu_type)
 {
 	__u8 cpu_model = current_cpu_data.x86_model;
 
@@ -348,13 +339,13 @@ static int __init ppro_init(void)
 		return 0;
 
 	if (cpu_model == 9) {
-		nmi_ops.cpu_type = "i386/p6_mobile";
+		*cpu_type = "i386/p6_mobile";
 	} else if (cpu_model > 5) {
-		nmi_ops.cpu_type = "i386/piii";
+		*cpu_type = "i386/piii";
 	} else if (cpu_model > 2) {
-		nmi_ops.cpu_type = "i386/pii";
+		*cpu_type = "i386/pii";
 	} else {
-		nmi_ops.cpu_type = "i386/ppro";
+		*cpu_type = "i386/ppro";
 	}
 
 	model = &op_ppro_spec;
@@ -364,11 +355,12 @@ static int __init ppro_init(void)
 /* in order to get driverfs right */
 static int using_nmi;
 
-int __init nmi_init(struct oprofile_operations ** ops)
+int __init nmi_init(struct oprofile_operations *ops)
 {
 	__u8 vendor = current_cpu_data.x86_vendor;
 	__u8 family = current_cpu_data.x86;
- 
+	char *cpu_type;
+
 	if (!cpu_has_apic)
 		return -ENODEV;
  
@@ -381,13 +373,13 @@ int __init nmi_init(struct oprofile_operations ** ops)
 				return -ENODEV;
 			case 6:
 				model = &op_athlon_spec;
-				nmi_ops.cpu_type = "i386/athlon";
+				cpu_type = "i386/athlon";
 				break;
 			case 0xf:
 				model = &op_athlon_spec;
 				/* Actually it could be i386/hammer too, but give
 				   user space an consistent name. */
-				nmi_ops.cpu_type = "x86-64/hammer";
+				cpu_type = "x86-64/hammer";
 				break;
 			}
 			break;
@@ -396,13 +388,13 @@ int __init nmi_init(struct oprofile_operations ** ops)
 			switch (family) {
 				/* Pentium IV */
 				case 0xf:
-					if (!p4_init())
+					if (!p4_init(&cpu_type))
 						return -ENODEV;
 					break;
 
 				/* A P6-class processor */
 				case 6:
-					if (!ppro_init())
+					if (!ppro_init(&cpu_type))
 						return -ENODEV;
 					break;
 
@@ -417,7 +409,12 @@ int __init nmi_init(struct oprofile_operations ** ops)
 
 	init_driverfs();
 	using_nmi = 1;
-	*ops = &nmi_ops;
+	ops->create_files = nmi_create_files;
+	ops->setup = nmi_setup;
+	ops->shutdown = nmi_shutdown;
+	ops->start = nmi_start;
+	ops->stop = nmi_stop;
+	ops->cpu_type = cpu_type;
 	printk(KERN_INFO "oprofile: using NMI interrupt.\n");
 	return 0;
 }
diff --git a/arch/i386/oprofile/nmi_timer_int.c b/arch/i386/oprofile/nmi_timer_int.c
index c3351b824187427499501195e53f4f80bddfaf83..b2e462abf337e44ffe7f2417b26b865542f193b9 100644
--- a/arch/i386/oprofile/nmi_timer_int.c
+++ b/arch/i386/oprofile/nmi_timer_int.c
@@ -20,9 +20,7 @@
  
 static int nmi_timer_callback(struct pt_regs * regs, int cpu)
 {
-	unsigned long eip = instruction_pointer(regs);
- 
-	oprofile_add_sample(eip, !user_mode(regs), 0, cpu);
+	oprofile_add_sample(regs, 0);
 	return 1;
 }
 
@@ -42,20 +40,16 @@ static void timer_stop(void)
 }
 
 
-static struct oprofile_operations nmi_timer_ops = {
-	.start	= timer_start,
-	.stop	= timer_stop,
-	.cpu_type = "timer"
-};
-
-int __init nmi_timer_init(struct oprofile_operations ** ops)
+int __init nmi_timer_init(struct oprofile_operations * ops)
 {
 	extern int nmi_active;
 
 	if (nmi_active <= 0)
 		return -ENODEV;
 
-	*ops = &nmi_timer_ops;
+	ops->start = timer_start;
+	ops->stop = timer_stop;
+	ops->cpu_type = "timer";
 	printk(KERN_INFO "oprofile: using NMI timer interrupt.\n");
 	return 0;
 }
diff --git a/arch/i386/oprofile/op_model_athlon.c b/arch/i386/oprofile/op_model_athlon.c
index 0d4e00f42995ff2a9ed5836637177a7be3f4c764..3ad9a72a5036b324d32a976b034dedd03fbc09f0 100644
--- a/arch/i386/oprofile/op_model_athlon.c
+++ b/arch/i386/oprofile/op_model_athlon.c
@@ -90,19 +90,16 @@ static void athlon_setup_ctrs(struct op_msrs const * const msrs)
 }
 
  
-static int athlon_check_ctrs(unsigned int const cpu, 
-			      struct op_msrs const * const msrs, 
-			      struct pt_regs * const regs)
+static int athlon_check_ctrs(struct pt_regs * const regs,
+			     struct op_msrs const * const msrs)
 {
 	unsigned int low, high;
 	int i;
-	unsigned long eip = profile_pc(regs);
-	int is_kernel = !user_mode(regs);
 
 	for (i = 0 ; i < NUM_COUNTERS; ++i) {
 		CTR_READ(low, high, msrs, i);
 		if (CTR_OVERFLOWED(low)) {
-			oprofile_add_sample(eip, is_kernel, i, cpu);
+			oprofile_add_sample(regs, i);
 			CTR_WRITE(reset_value[i], msrs, i);
 		}
 	}
diff --git a/arch/i386/oprofile/op_model_p4.c b/arch/i386/oprofile/op_model_p4.c
index 1b3626df5dd227cbfafaaf6814d2d6f48f47d551..ac8a066035c24a1f9a9cc3f3d63e57ce29eee33d 100644
--- a/arch/i386/oprofile/op_model_p4.c
+++ b/arch/i386/oprofile/op_model_p4.c
@@ -619,14 +619,11 @@ static void p4_setup_ctrs(struct op_msrs const * const msrs)
 }
 
 
-static int p4_check_ctrs(unsigned int const cpu, 
-			  struct op_msrs const * const msrs,
-			  struct pt_regs * const regs)
+static int p4_check_ctrs(struct pt_regs * const regs,
+			 struct op_msrs const * const msrs)
 {
 	unsigned long ctr, low, high, stag, real;
 	int i;
-	unsigned long eip = profile_pc(regs);
-	int is_kernel = !user_mode(regs);
 
 	stag = get_stagger();
 
@@ -657,7 +654,7 @@ static int p4_check_ctrs(unsigned int const cpu,
 		CCCR_READ(low, high, real);
  		CTR_READ(ctr, high, real);
 		if (CCCR_OVF_P(low) || CTR_OVERFLOW_P(ctr)) {
-			oprofile_add_sample(eip, is_kernel, i, cpu);
+			oprofile_add_sample(regs, i);
  			CTR_WRITE(reset_value[i], real);
 			CCCR_CLEAR_OVF(low);
 			CCCR_WRITE(low, high, real);
diff --git a/arch/i386/oprofile/op_model_ppro.c b/arch/i386/oprofile/op_model_ppro.c
index 0cc80654db626cbc2f374880e395dbadcefd3594..d719015fc0444a8e229448654f16d7351bb41798 100644
--- a/arch/i386/oprofile/op_model_ppro.c
+++ b/arch/i386/oprofile/op_model_ppro.c
@@ -85,19 +85,16 @@ static void ppro_setup_ctrs(struct op_msrs const * const msrs)
 }
 
  
-static int ppro_check_ctrs(unsigned int const cpu, 
-			    struct op_msrs const * const msrs,
-			    struct pt_regs * const regs)
+static int ppro_check_ctrs(struct pt_regs * const regs,
+			   struct op_msrs const * const msrs)
 {
 	unsigned int low, high;
 	int i;
-	unsigned long eip = profile_pc(regs);
-	int is_kernel = !user_mode(regs);
  
 	for (i = 0 ; i < NUM_COUNTERS; ++i) {
 		CTR_READ(low, high, msrs, i);
 		if (CTR_OVERFLOWED(low)) {
-			oprofile_add_sample(eip, is_kernel, i, cpu);
+			oprofile_add_sample(regs, i);
 			CTR_WRITE(reset_value[i], msrs, i);
 		}
 	}
diff --git a/arch/i386/oprofile/op_x86_model.h b/arch/i386/oprofile/op_x86_model.h
index 5cc2514670f3fecedf969297aab1097203cd238f..123b7e90a9eed803699470e6eb44010f8f2428db 100644
--- a/arch/i386/oprofile/op_x86_model.h
+++ b/arch/i386/oprofile/op_x86_model.h
@@ -36,9 +36,8 @@ struct op_x86_model_spec {
 	unsigned int const num_controls;
 	void (*fill_in_addresses)(struct op_msrs * const msrs);
 	void (*setup_ctrs)(struct op_msrs const * const msrs);
-	int (*check_ctrs)(unsigned int const cpu, 
-		struct op_msrs const * const msrs,
-		struct pt_regs * const regs);
+	int (*check_ctrs)(struct pt_regs * const regs,
+		struct op_msrs const * const msrs);
 	void (*start)(struct op_msrs const * const msrs);
 	void (*stop)(struct op_msrs const * const msrs);
 };
diff --git a/arch/x86_64/oprofile/Makefile b/arch/x86_64/oprofile/Makefile
index f4e3ccb8b7258ba161cf9526340f6a5538429976..6be32683e1bc71081fc8b8a888a38f2e2b58a884 100644
--- a/arch/x86_64/oprofile/Makefile
+++ b/arch/x86_64/oprofile/Makefile
@@ -11,7 +11,7 @@ DRIVER_OBJS = $(addprefix ../../../drivers/oprofile/, \
 	oprofilefs.o oprofile_stats.o \
 	timer_int.o )
 
-OPROFILE-y := init.o
+OPROFILE-y := init.o backtrace.o
 OPROFILE-$(CONFIG_X86_LOCAL_APIC) += nmi_int.o op_model_athlon.o op_model_p4.o \
 				     op_model_ppro.o
 OPROFILE-$(CONFIG_X86_IO_APIC)    += nmi_timer_int.o