mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC v2] perf: Dwarf cfi based user callchains
@ 2010-10-22 19:13 Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 01/11] uaccess: New copy_from_user_gup() API Frederic Weisbecker
                   ` (10 more replies)
  0 siblings, 11 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch . Eigler

Various things have changed in this set.

- copy_from_user_nmi() renamed into copy_from_user_gup()

- regs dumping uses a version now and the user can select
  its own subset flavour of the regs proposed by the arch.
  But look: there is an open question in the 3rd patch to handle
  the compat stuff.

- new exclude_user_callchain attribute, instead of dropping fp based
  user callchains if we dump regs and stack.

- etc...

Important things to be done:

- Still no dwarf cfi from vdso. But I've read somewhere that vdso has
  dwarf cfi info. I need to handle that somewhow. Until then, it is
  only usable if you profile only userspace (use the :u flag in your event,
  as in the example below).

- Split generic/arch code in tools/perf/util/unwind.c

- Use .eh_frame_hdr for binary searching

- Make it much faster, it's too slow right now. Perhaps that needs some
  cfi info caching.

- handle compat regs, look at the open question in 3rd patch.

How to test:

	perf record -g dwarf -e cycles:u ./hackbench 10
	perf report

You can increase the stack dump by playing with the size, it is about 4000 by
default, if you want 10000 bytes:

	perf record -g dwarf,10000 -e cycles:u ./hackbench 10

You can pull from:

git://git.kernel.org/pub/scm/linux/kernel/git/frederic/random-tracing.git
	perf/unwind-v2

Thanks.

---

Frederic Weisbecker (11):
      uaccess: New copy_from_user_gup() API
      perf: Unified API to record selective sets of arch registers
      perf: Add ability to dump user regs
      perf: Add ability to dump part of the user stack
      perf: New attribute to filter out user callchains
      perf: Support for dwarf mode callchain on perf record
      perf: Build with dwarf cfi
      perf: Support for error passed over pointers
      perf: Add libunwind dependency for dwarf cfi unwinding
      perf: Support user regs and stack in sample parsing
      perf: Support for dwarf cfi unwinding on post processing


 arch/Kconfig                              |    7 +
 arch/x86/Kconfig                          |    1 +
 arch/x86/include/asm/perf_regs.h          |   10 +
 arch/x86/include/asm/perf_regs_32.h       |   85 +++
 arch/x86/include/asm/uaccess.h            |    5 +
 arch/x86/kernel/cpu/perf_event.c          |    8 +-
 arch/x86/kernel/cpu/perf_event_intel_ds.c |    2 +-
 include/asm-generic/uaccess.h             |    4 +
 include/linux/perf_event.h                |   40 +-
 kernel/perf_event.c                       |  194 +++++-
 tools/perf/Makefile                       |   30 +-
 tools/perf/arch/x86/include/perf_regs.h   |   11 +
 tools/perf/builtin-kmem.c                 |    2 +-
 tools/perf/builtin-lock.c                 |    2 +-
 tools/perf/builtin-record.c               |   78 ++-
 tools/perf/builtin-report.c               |    9 +-
 tools/perf/builtin-sched.c                |    2 +-
 tools/perf/builtin-timechart.c            |    2 +-
 tools/perf/builtin-trace.c                |    2 +-
 tools/perf/feature-tests.mak              |   14 +
 tools/perf/util/callchain.c               |   35 +-
 tools/perf/util/callchain.h               |   19 +-
 tools/perf/util/event.c                   |   28 +-
 tools/perf/util/event.h                   |   15 +-
 tools/perf/util/header.c                  |   33 +-
 tools/perf/util/header.h                  |    3 +-
 tools/perf/util/include/linux/err.h       |   24 +
 tools/perf/util/perf_regs.h               |   11 +
 tools/perf/util/session.c                 |    4 +-
 tools/perf/util/session.h                 |    6 +
 tools/perf/util/unwind.c                  | 1112 +++++++++++++++++++++++++++++
 31 files changed, 1739 insertions(+), 59 deletions(-)

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 01/11] uaccess: New copy_from_user_gup() API
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 02/11] perf: Unified API to record selective sets of arch registers Frederic Weisbecker
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

This brings a get_user_page_fast() based copy_from_user() that can
do a best effort copy from any context.

In order to support user stack dump safely in perf samples from
generic code, rename x86 copy_from_user_nmi to copy_from_user_gup
and make it generally available. If the arch doesn't provide an
implementation it will map to copy_from_user_inatomic.

Since perf is the first user for now, let the overriden x86
implementation in the perf source file.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 arch/x86/include/asm/uaccess.h            |    5 +++++
 arch/x86/kernel/cpu/perf_event.c          |    8 ++++----
 arch/x86/kernel/cpu/perf_event_intel_ds.c |    2 +-
 include/asm-generic/uaccess.h             |    4 ++++
 4 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h
index abd3e0e..d29f3ab 100644
--- a/arch/x86/include/asm/uaccess.h
+++ b/arch/x86/include/asm/uaccess.h
@@ -229,6 +229,11 @@ extern void __put_user_2(void);
 extern void __put_user_4(void);
 extern void __put_user_8(void);
 
+extern unsigned long
+__copy_from_user_gup(void *to, const void __user *from, unsigned long n);
+
+#define copy_from_user_gup(to, from, n)	__copy_from_user_gup(to, from, n)
+
 #ifdef CONFIG_X86_WP_WORKS_OK
 
 /**
diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c
index fe73c18..8747570 100644
--- a/arch/x86/kernel/cpu/perf_event.c
+++ b/arch/x86/kernel/cpu/perf_event.c
@@ -45,8 +45,8 @@ do {								\
 /*
  * best effort, GUP based copy_from_user() that assumes IRQ or NMI context
  */
-static unsigned long
-copy_from_user_nmi(void *to, const void __user *from, unsigned long n)
+unsigned long
+__copy_from_user_gup(void *to, const void __user *from, unsigned long n)
 {
 	unsigned long offset, addr = (unsigned long)from;
 	int type = in_nmi() ? KM_NMI : KM_IRQ0;
@@ -1688,7 +1688,7 @@ perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry *entry)
 		frame.next_frame     = 0;
 		frame.return_address = 0;
 
-		bytes = copy_from_user_nmi(&frame, fp, sizeof(frame));
+		bytes = copy_from_user_gup(&frame, fp, sizeof(frame));
 		if (bytes != sizeof(frame))
 			break;
 
@@ -1731,7 +1731,7 @@ perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs)
 		frame.next_frame	     = NULL;
 		frame.return_address = 0;
 
-		bytes = copy_from_user_nmi(&frame, fp, sizeof(frame));
+		bytes = copy_from_user_gup(&frame, fp, sizeof(frame));
 		if (bytes != sizeof(frame))
 			break;
 
diff --git a/arch/x86/kernel/cpu/perf_event_intel_ds.c b/arch/x86/kernel/cpu/perf_event_intel_ds.c
index 4977f9c..1642dee 100644
--- a/arch/x86/kernel/cpu/perf_event_intel_ds.c
+++ b/arch/x86/kernel/cpu/perf_event_intel_ds.c
@@ -426,7 +426,7 @@ static int intel_pmu_pebs_fixup_ip(struct pt_regs *regs)
 		if (!kernel_ip(ip)) {
 			int bytes, size = MAX_INSN_SIZE;
 
-			bytes = copy_from_user_nmi(buf, (void __user *)to, size);
+			bytes = copy_from_user_gup(buf, (void __user *)to, size);
 			if (bytes != size)
 				return 0;
 
diff --git a/include/asm-generic/uaccess.h b/include/asm-generic/uaccess.h
index b218b85..00bc7d3 100644
--- a/include/asm-generic/uaccess.h
+++ b/include/asm-generic/uaccess.h
@@ -240,6 +240,10 @@ extern int __get_user_bad(void) __attribute__((noreturn));
 #define __copy_to_user_inatomic __copy_to_user
 #endif
 
+#ifndef copy_from_user_gup
+#define copy_from_user_gup __copy_from_user_inatomic
+#endif
+
 static inline long copy_from_user(void *to,
 		const void __user * from, unsigned long n)
 {
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 02/11] perf: Unified API to record selective sets of arch registers
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 01/11] uaccess: New copy_from_user_gup() API Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 03/11] perf: Add ability to dump user regs Frederic Weisbecker
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

This brings a new API to help the selective dump of registers on
event sampling, and its implementation in x86-32.

- The informations about the desired registers will be passed
  to a single u64 mask. It's up to the architecture to map the
  registers into the mask bits.

- The architecture must provide a non-zero and unique id to
  identify the origin of a register set because interpreting a
  register dump requires to know from which architecture it comes.
  The achitecture is considered different between the 32 and 64 bits
  version. x86-32 has the id 1.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 arch/Kconfig                        |    7 +++
 arch/x86/Kconfig                    |    1 +
 arch/x86/include/asm/perf_regs.h    |   10 ++++
 arch/x86/include/asm/perf_regs_32.h |   85 +++++++++++++++++++++++++++++++++++
 4 files changed, 103 insertions(+), 0 deletions(-)
 create mode 100644 arch/x86/include/asm/perf_regs.h
 create mode 100644 arch/x86/include/asm/perf_regs_32.h

diff --git a/arch/Kconfig b/arch/Kconfig
index 53d7f61..a2bcd04 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -158,6 +158,13 @@ config HAVE_PERF_EVENTS_NMI
 	  subsystem.  Also has support for calculating CPU cycle events
 	  to determine how many clock cycles in a given period.
 
+config HAVE_PERF_REGS_DEFS
+	bool
+	help
+	  Support selective register dumps for perf events. This includes
+	  bit-mapping of each registers and a unique architecture version,
+	  also different between 32 and 64 bits.
+
 config HAVE_ARCH_JUMP_LABEL
 	bool
 
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index fd227d6..2b0ef54 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -58,6 +58,7 @@ config X86
 	select HAVE_MIXED_BREAKPOINTS_REGS
 	select PERF_EVENTS
 	select HAVE_PERF_EVENTS_NMI
+	select HAVE_PERF_REGS_DEFS if X86_32
 	select ANON_INODES
 	select HAVE_ARCH_KMEMCHECK
 	select HAVE_USER_RETURN_NOTIFIER
diff --git a/arch/x86/include/asm/perf_regs.h b/arch/x86/include/asm/perf_regs.h
new file mode 100644
index 0000000..fdf5df5
--- /dev/null
+++ b/arch/x86/include/asm/perf_regs.h
@@ -0,0 +1,10 @@
+#ifndef _ASM_X86_PERF_REGS_H
+#define _ASM_X86_PERF_REGS_H
+
+#ifdef CONFIG_X86_32
+#include "perf_regs_32.h"
+#endif
+
+/* TODO: x86-64 and compat */
+
+#endif /* _ASM_X86_PERF_REGS_H */
diff --git a/arch/x86/include/asm/perf_regs_32.h b/arch/x86/include/asm/perf_regs_32.h
new file mode 100644
index 0000000..660808b
--- /dev/null
+++ b/arch/x86/include/asm/perf_regs_32.h
@@ -0,0 +1,85 @@
+#ifndef _ASM_X86_PERF_REGS_32_H
+#define _ASM_X86_PERF_REGS_32_H
+
+#define PERF_X86_32_REG_VERSION		1ULL
+
+enum perf_event_x86_32_regs {
+	PERF_X86_32_REG_EAX,
+	PERF_X86_32_REG_EBX,
+	PERF_X86_32_REG_ECX,
+	PERF_X86_32_REG_EDX,
+	PERF_X86_32_REG_ESI,
+	PERF_X86_32_REG_EDI,
+	PERF_X86_32_REG_EBP,
+	PERF_X86_32_REG_ESP,
+	PERF_X86_32_REG_EIP,
+	PERF_X86_32_REG_FLAGS,
+	PERF_X86_32_REG_CS,
+	PERF_X86_32_REG_DS,
+	PERF_X86_32_REG_ES,
+	PERF_X86_32_REG_FS,
+	PERF_X86_32_REG_GS,
+
+	/* Non ABI */
+	PERF_X86_32_REG_MAX,
+};
+
+#ifdef __KERNEL__
+
+#define PERF_X86_32_REG_RESERVED (~((1ULL << PERF_X86_32_REG_MAX) - 1ULL))
+
+static inline u64 perf_reg_version(void)
+{
+	return PERF_X86_32_REG_VERSION;
+}
+
+static inline int perf_reg_validate(u64 mask)
+{
+	if (mask & PERF_X86_32_REG_RESERVED)
+		return -EINVAL;
+
+	return 0;
+}
+
+static inline u64 perf_reg_value(struct pt_regs *regs, int idx)
+{
+	switch (idx) {
+	case PERF_X86_32_REG_EAX:
+		return regs->ax;
+	case PERF_X86_32_REG_EBX:
+		return regs->bx;
+	case PERF_X86_32_REG_ECX:
+		return regs->cx;
+	case PERF_X86_32_REG_EDX:
+		return regs->dx;
+	case PERF_X86_32_REG_ESI:
+		return regs->si;
+	case PERF_X86_32_REG_EDI:
+		return regs->di;
+	case PERF_X86_32_REG_EBP:
+		return regs->bp;
+	case PERF_X86_32_REG_ESP:
+		return regs->sp;
+	case PERF_X86_32_REG_EIP:
+		return regs->ip;
+	case PERF_X86_32_REG_FLAGS:
+		return regs->flags;
+	case PERF_X86_32_REG_CS:
+		return regs->cs;
+	case PERF_X86_32_REG_DS:
+		return regs->ds;
+	case PERF_X86_32_REG_ES:
+		return regs->es;
+	case PERF_X86_32_REG_FS:
+		return regs->fs;
+	case PERF_X86_32_REG_GS:
+		return regs->gs;
+	}
+
+	/* Well... */
+	return 0;
+}
+
+#endif /* __KERNEL__ */
+
+#endif /* _ASM_X86_PERF_REGS_32_H */
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 03/11] perf: Add ability to dump user regs
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 01/11] uaccess: New copy_from_user_gup() API Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 02/11] perf: Unified API to record selective sets of arch registers Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 04/11] perf: Add ability to dump part of the user stack Frederic Weisbecker
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

Add new attr->user_regs bitmap that lets a user choose a set
of user registers to dump to the sample. The layout of this
bitmap is described in asm/perf_regs.h for archs that
support CONFIG_HAVE_PERF_REGS_DEFS, otherwise the perf
syscall will fail if attr->user_regs is non zero.

The register value here are those of the user space context as
it was before the user entered the kernel for whatever reason
(syscall, irq, exception, or a PMI happening in userspace).

This is going to be useful to bring Dwarf CFI based stack unwinding
on top of samples.

FIXME: the issue of compat regs has yet to be solved. I think we
need to split the regs bitmap in:

	attr->user_regs32
	attr->user_regs64

So that userspace doesn't need to care about beeing a compat task or
not, running on a 32 bits kernel or not, it can provide both bitmaps
and let the kernel handle that, ignore user_regs64 if it is a 32 bits
kernel, handle it otherwise and also user_regs32 for compat tasks,
etc...

Hmm?

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 include/linux/perf_event.h |   28 +++++++++++++++++++
 kernel/perf_event.c        |   63 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 90 insertions(+), 1 deletions(-)

diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index 057bf22..28210d4 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -226,6 +226,16 @@ struct perf_event_attr {
 	__u32			bp_type;
 	__u64			bp_addr;
 	__u64			bp_len;
+
+	/* Future breakpoint fields extension */
+	__u64			__reserved_2;
+	__u64			__reserved_3;
+
+	/*
+	 * Arch specific mask that defines a set of user regs to dump on
+	 * samples. See asm/perf_regs.h for details.
+	 */
+	__u64			user_regs;
 };
 
 /*
@@ -475,6 +485,7 @@ struct perf_guest_info_callbacks {
 #include <asm/hw_breakpoint.h>
 #endif
 
+
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/rculist.h>
@@ -891,6 +902,22 @@ struct perf_output_handle {
 
 #ifdef CONFIG_PERF_EVENTS
 
+#ifdef CONFIG_HAVE_PERF_REGS_DEFS
+#include <asm/perf_regs.h>
+#else
+static inline int perf_reg_value(struct pt_regs *regs, int idx) { return 0; }
+
+static inline int perf_reg_version(struct pt_regs *regs, int idx)
+{
+	return -EINVAL;
+}
+
+static inline int perf_reg_validate(u64 mask)
+{
+	return -ENOSYS;
+}
+#endif /*CONFIG_HAVE_PERF_REGS_DUMP */
+
 extern int perf_pmu_register(struct pmu *pmu);
 extern void perf_pmu_unregister(struct pmu *pmu);
 
@@ -950,6 +977,7 @@ struct perf_sample_data {
 	u64				period;
 	struct perf_callchain_entry	*callchain;
 	struct perf_raw_record		*raw;
+	struct pt_regs			*uregs;
 };
 
 static inline
diff --git a/kernel/perf_event.c b/kernel/perf_event.c
index 05ecf6f..0e4ab11 100644
--- a/kernel/perf_event.c
+++ b/kernel/perf_event.c
@@ -2051,6 +2051,19 @@ exit_put:
 	return entry;
 }
 
+static struct pt_regs *perf_sample_uregs(struct pt_regs *regs)
+{
+	if (!user_mode(regs)) {
+		if (current->mm)
+			regs = task_pt_regs(current);
+		else
+			regs = NULL;
+	}
+
+	return regs;
+}
+
+
 /*
  * Initialize the perf_event context in a task_struct:
  */
@@ -3502,6 +3515,25 @@ static void perf_output_read(struct perf_output_handle *handle,
 		perf_output_read_one(handle, event);
 }
 
+static void
+perf_output_sample_regs(struct perf_output_handle *handle,
+			struct pt_regs *regs, u64 mask)
+{
+	int i = 0;
+
+	do {
+		u64 val;
+
+		if (mask & 1) {
+			val = perf_reg_value(regs, i);
+			perf_output_put(handle, val);
+		}
+
+		mask >>= 1;
+		i++;
+	} while (mask);
+}
+
 void perf_output_sample(struct perf_output_handle *handle,
 			struct perf_event_header *header,
 			struct perf_sample_data *data,
@@ -3570,6 +3602,22 @@ void perf_output_sample(struct perf_output_handle *handle,
 			perf_output_put(handle, raw);
 		}
 	}
+
+	if (event->attr.user_regs) {
+		u64 id;
+
+		/* If there is no regs to dump, notice it through a 0 version */
+		if (!data->uregs) {
+			id = 0;
+			perf_output_put(handle, id);
+		} else {
+
+			id = perf_reg_version();
+			perf_output_put(handle, id);
+			perf_output_sample_regs(handle, data->uregs,
+						event->attr.user_regs);
+		}
+	}
 }
 
 void perf_prepare_sample(struct perf_event_header *header,
@@ -3657,6 +3705,17 @@ void perf_prepare_sample(struct perf_event_header *header,
 		WARN_ON_ONCE(size & (sizeof(u64)-1));
 		header->size += size;
 	}
+
+	if (event->attr.user_regs) {
+		int size = sizeof(u64); /* the version size */
+
+		data->uregs = perf_sample_uregs(regs);
+		if (data->uregs)
+			/* Regs values */
+			size += hweight64(event->attr.user_regs) * sizeof(u64);
+
+		header->size += size;
+	}
 }
 
 static void perf_event_output(struct perf_event *event, int nmi,
@@ -5429,7 +5488,7 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr,
 	if (attr->type >= PERF_TYPE_MAX)
 		return -EINVAL;
 
-	if (attr->__reserved_1)
+	if (attr->__reserved_1 || attr->__reserved_2 || attr->__reserved_3)
 		return -EINVAL;
 
 	if (attr->sample_type & ~(PERF_SAMPLE_MAX-1))
@@ -5438,6 +5497,8 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr,
 	if (attr->read_format & ~(PERF_FORMAT_MAX-1))
 		return -EINVAL;
 
+	ret = perf_reg_validate(attr->user_regs);
+
 out:
 	return ret;
 
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 04/11] perf: Add ability to dump part of the user stack
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (2 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 03/11] perf: Add ability to dump user regs Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 05/11] perf: New attribute to filter out user callchains Frederic Weisbecker
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

Beeing able to dump parts of the user stack, starting from the
stack pointer, will be useful to make a post mortem dwarf CFI based
stack unwinding.

This is done through the new ustack_dump_size perf attribute. If it
is non zero, the user stack will dumped in samples following the
requested size in bytes.

The longer is the dump, the deeper will be the resulting retrieved
callchain.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 include/linux/perf_event.h |    9 +++-
 kernel/perf_event.c        |  123 ++++++++++++++++++++++++++++++++++++--------
 2 files changed, 108 insertions(+), 24 deletions(-)

diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index 28210d4..87441b5 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -236,6 +236,10 @@ struct perf_event_attr {
 	 * samples. See asm/perf_regs.h for details.
 	 */
 	__u64			user_regs;
+	__u32			ustack_dump_size;
+
+	/* Future extension */
+	__u32			__reserved_4;
 };
 
 /*
@@ -1107,8 +1111,9 @@ extern int perf_output_begin(struct perf_output_handle *handle,
 			     struct perf_event *event, unsigned int size,
 			     int nmi, int sample);
 extern void perf_output_end(struct perf_output_handle *handle);
-extern void perf_output_copy(struct perf_output_handle *handle,
-			     const void *buf, unsigned int len);
+extern unsigned int
+perf_output_copy(struct perf_output_handle *handle,
+		 const void *buf, unsigned int len);
 extern int perf_swevent_get_recursion_context(void);
 extern void perf_swevent_put_recursion_context(int rctx);
 extern void perf_event_enable(struct perf_event *event);
diff --git a/kernel/perf_event.c b/kernel/perf_event.c
index 0e4ab11..674ed25 100644
--- a/kernel/perf_event.c
+++ b/kernel/perf_event.c
@@ -3295,28 +3295,43 @@ out:
 	preempt_enable();
 }
 
-__always_inline void perf_output_copy(struct perf_output_handle *handle,
-		      const void *buf, unsigned int len)
-{
-	do {
-		unsigned long size = min_t(unsigned long, handle->size, len);
-
-		memcpy(handle->addr, buf, size);
-
-		len -= size;
-		handle->addr += size;
-		buf += size;
-		handle->size -= size;
-		if (!handle->size) {
-			struct perf_buffer *buffer = handle->buffer;
-
-			handle->page++;
-			handle->page &= buffer->nr_pages - 1;
-			handle->addr = buffer->data_pages[handle->page];
-			handle->size = PAGE_SIZE << page_order(buffer);
-		}
-	} while (len);
-}
+static int memcpy_common(void *dst, const void *src, size_t n)
+{
+	memcpy(dst, src, n);
+
+	return n;
+}
+
+#define DEFINE_PERF_OUTPUT_COPY(func_name, memcpy_func)				\
+__always_inline unsigned int func_name(struct perf_output_handle *handle,	\
+				       const void *buf, unsigned int len)	\
+{										\
+	unsigned long size, written;						\
+										\
+	do {									\
+		size = min_t(unsigned long, handle->size, len);			\
+										\
+		written = memcpy_func(handle->addr, buf, size);			\
+										\
+		len -= written;							\
+		handle->addr += written;					\
+		buf += written;							\
+		handle->size -= written;					\
+		if (!handle->size) {						\
+			struct perf_buffer *buffer = handle->buffer;		\
+										\
+			handle->page++;						\
+			handle->page &= buffer->nr_pages - 1;			\
+			handle->addr = buffer->data_pages[handle->page];	\
+			handle->size = PAGE_SIZE << page_order(buffer);		\
+		}								\
+	} while (len && written == size);					\
+										\
+	return len;								\
+}
+
+DEFINE_PERF_OUTPUT_COPY(perf_output_copy, memcpy_common)
+DEFINE_PERF_OUTPUT_COPY(perf_output_copy_user_gup, copy_from_user_gup)
 
 int perf_output_begin(struct perf_output_handle *handle,
 		      struct perf_event *event, unsigned int size,
@@ -3618,6 +3633,44 @@ void perf_output_sample(struct perf_output_handle *handle,
 						event->attr.user_regs);
 		}
 	}
+
+	if (event->attr.ustack_dump_size) {
+		unsigned long sp;
+		unsigned int rem;
+		u64 size, dyn_size;
+
+		/* Case of a kernel thread, nothing to dump */
+		if (!data->uregs) {
+			size = 0;
+			perf_output_put(handle, size);
+
+			return;
+		}
+
+		/*
+		 * Static size: we always dump the size requested by the user
+		 * because most of the time, the top of the user stack is not
+		 * paged out. Perhaps we should force ustack_dump_size
+		 * to be % 8.
+		 */
+		size = event->attr.ustack_dump_size;
+		size = round_up(size, sizeof(u64));
+		perf_output_put(handle, size);
+
+		/* CHECKME: might me missing on some archs */
+		sp = user_stack_pointer(data->uregs);
+		rem = perf_output_copy_user_gup(handle, (void *)sp, size);
+		dyn_size = size - rem;
+
+		/* What couldn't be dumped is zero padded */
+		while (rem--) {
+			char zero = 0;
+			perf_output_put(handle, zero);
+		}
+
+		/* Dynamic size: whole dump - padding */
+		perf_output_put(handle, dyn_size);
+	}
 }
 
 void perf_prepare_sample(struct perf_event_header *header,
@@ -3716,6 +3769,32 @@ void perf_prepare_sample(struct perf_event_header *header,
 
 		header->size += size;
 	}
+
+	if (event->attr.ustack_dump_size) {
+		if (!event->attr.user_regs)
+			data->uregs = perf_sample_uregs(regs);
+
+		/*
+		 * A first field that tells the _static_ size of the dump. 0 if
+		 * there is nothing to dump (ie: we are in a kernel thread)
+		 * otherwise the requested size.
+		 */
+		header->size += sizeof(u64);
+
+		/*
+		 * If there is something to dump, add space for the dump itself
+		 * and for the field that tells the _dynamic_ size, which is
+		 * how many have been actually dumped. What couldn't be dumped
+		 * will be zero-padded.
+		 */
+		if (data->uregs) {
+			u64 size = event->attr.ustack_dump_size;
+
+			size = round_up(size, sizeof(u64));
+			header->size += size;
+			header->size += sizeof(u64);
+		}
+	}
 }
 
 static void perf_event_output(struct perf_event *event, int nmi,
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 05/11] perf: New attribute to filter out user callchains
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (3 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 04/11] perf: Add ability to dump part of the user stack Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 06/11] perf: Support for dwarf mode callchain on perf record Frederic Weisbecker
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

Add the new exclude_user_callchain attribute to filter out
frame pointer based user callchains. This is something we
want to select when we use the dwarf cfi callchain mode,
because frame pointer based user callchains are useless in
this mode.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 include/linux/perf_event.h |    3 ++-
 kernel/perf_event.c        |    8 +++++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index 87441b5..86c988e 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -215,8 +215,9 @@ struct perf_event_attr {
 				 */
 				precise_ip     :  2, /* skid constraint       */
 				mmap_data      :  1, /* non-exec mmap data    */
+				exclude_user_callchain : 1, /* only record kernel callchains */
 
-				__reserved_1   : 46;
+				__reserved_1   : 45;
 
 	union {
 		__u32		wakeup_events;	  /* wakeup every n events */
diff --git a/kernel/perf_event.c b/kernel/perf_event.c
index 674ed25..e76623a 100644
--- a/kernel/perf_event.c
+++ b/kernel/perf_event.c
@@ -2016,7 +2016,8 @@ put_callchain_entry(int rctx)
 	put_recursion_context(__get_cpu_var(callchain_recursion), rctx);
 }
 
-static struct perf_callchain_entry *perf_callchain(struct pt_regs *regs)
+static struct perf_callchain_entry *
+perf_callchain(struct pt_regs *regs, int user)
 {
 	int rctx;
 	struct perf_callchain_entry *entry;
@@ -2040,7 +2041,7 @@ static struct perf_callchain_entry *perf_callchain(struct pt_regs *regs)
 			regs = NULL;
 	}
 
-	if (regs) {
+	if (user && regs) {
 		perf_callchain_store(entry, PERF_CONTEXT_USER);
 		perf_callchain_user(entry, regs);
 	}
@@ -3738,8 +3739,9 @@ void perf_prepare_sample(struct perf_event_header *header,
 
 	if (sample_type & PERF_SAMPLE_CALLCHAIN) {
 		int size = 1;
+		int user = !event->attr.exclude_user_callchain;
 
-		data->callchain = perf_callchain(regs);
+		data->callchain = perf_callchain(regs, user);
 
 		if (data->callchain)
 			size += data->callchain->nr;
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 06/11] perf: Support for dwarf mode callchain on perf record
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (4 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 05/11] perf: New attribute to filter out user callchains Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 07/11] perf: Build with dwarf cfi Frederic Weisbecker
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

"perf record -g" is the command to record frame pointer based
callchains. This patch extends the "-g" option to support the
dwarf cfi mode.

The new behaviour is:

- "perf record -g" will record frame pointer based callchains
as it did before.

- "perf record -g fp" is the same as "-g" alone

- "perf record -g dwarf" will record frame pointer based kernel
callchains but will ignore the user part. Instead it will dump
user regs and 4000 bytes of stack by default to each samples.

- "perf record -g dwarf,x" does the same but overrides the
default 4000 bytes of user stack dumped to x bytes instead.
The higher is the size, the deeper will be the callchain but
the higher will be the overhead of the profiling and the size
of the output file.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 tools/perf/Makefile                     |    7 +++
 tools/perf/arch/x86/include/perf_regs.h |   11 ++++
 tools/perf/builtin-record.c             |   78 +++++++++++++++++++++++++++++--
 tools/perf/util/perf_regs.h             |   11 ++++
 4 files changed, 103 insertions(+), 4 deletions(-)
 create mode 100644 tools/perf/arch/x86/include/perf_regs.h
 create mode 100644 tools/perf/util/perf_regs.h

diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index d1db0f6..e22016d 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -179,10 +179,12 @@ ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
 				  -e s/s390x/s390/ -e s/parisc64/parisc/ \
 				  -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
 				  -e s/sh[234].*/sh/ )
+NO_PERF_REGS_DEFS := 1
 
 # Additional ARCH settings for x86
 ifeq ($(ARCH),i386)
         ARCH := x86
+	NO_PERF_REGS_DEFS := 0
 endif
 ifeq ($(ARCH),x86_64)
         ARCH := x86
@@ -417,6 +419,7 @@ LIB_H += util/probe-finder.h
 LIB_H += util/probe-event.h
 LIB_H += util/pstack.h
 LIB_H += util/cpumap.h
+LIB_H += util/perf_regs.h
 
 LIB_OBJS += $(OUTPUT)util/abspath.o
 LIB_OBJS += $(OUTPUT)util/alias.o
@@ -662,6 +665,10 @@ else
 	endif
 endif
 
+ifeq ($(NO_PERF_REGS_DEFS),1)
+	BASIC_CFLAGS += -DNO_PERF_REGS_DEFS
+endif
+
 
 ifdef NO_STRLCPY
 	BASIC_CFLAGS += -DNO_STRLCPY
diff --git a/tools/perf/arch/x86/include/perf_regs.h b/tools/perf/arch/x86/include/perf_regs.h
new file mode 100644
index 0000000..4f6d65d
--- /dev/null
+++ b/tools/perf/arch/x86/include/perf_regs.h
@@ -0,0 +1,11 @@
+#include "../../../../../arch/x86/include/asm/perf_regs_32.h"
+
+
+#define BIT(x)	(1 << (x))
+
+#define PERF_UNWIND_REGS_MASK	\
+	(BIT(PERF_X86_32_REG_EAX) | BIT(PERF_X86_32_REG_EBX) |	\
+	 BIT(PERF_X86_32_REG_ECX) | BIT(PERF_X86_32_REG_EDX) |	\
+	 BIT(PERF_X86_32_REG_ESI) | BIT(PERF_X86_32_REG_EDI) |	\
+	 BIT(PERF_X86_32_REG_EBP) | BIT(PERF_X86_32_REG_ESP) |	\
+	 BIT(PERF_X86_32_REG_EIP))
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index ff77b80..e3d835c 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -22,6 +22,7 @@
 #include "util/session.h"
 #include "util/symbol.h"
 #include "util/cpumap.h"
+#include "util/perf_regs.h"
 
 #include <unistd.h>
 #include <sched.h>
@@ -32,6 +33,12 @@ enum write_mode_t {
 	WRITE_APPEND
 };
 
+enum call_graph_mode {
+	CALLCHAIN_NONE,
+	CALLCHAIN_FP,
+	CALLCHAIN_DWARF
+};
+
 static int			*fd[MAX_NR_CPUS][MAX_COUNTERS];
 
 static u64			user_interval			= ULLONG_MAX;
@@ -56,7 +63,6 @@ static int			thread_num			=      0;
 static pid_t			child_pid			=     -1;
 static bool			no_inherit			=  false;
 static enum write_mode_t	write_mode			= WRITE_FORCE;
-static bool			call_graph			=  false;
 static bool			inherit_stat			=  false;
 static bool			no_samples			=  false;
 static bool			sample_address			=  false;
@@ -76,6 +82,10 @@ static off_t			post_processing_offset;
 static struct perf_session	*session;
 static const char		*cpu_list;
 
+static char			callchain_default_opt[]		=      "fp";
+static unsigned long		stack_dump_size			=	4000;
+static enum call_graph_mode	call_graph			= CALLCHAIN_NONE;
+
 struct mmap_data {
 	int			counter;
 	void			*base;
@@ -274,9 +284,16 @@ static void create_counter(int counter, int cpu)
 		attr->mmap_data = track;
 	}
 
-	if (call_graph)
+	if (call_graph) {
 		attr->sample_type	|= PERF_SAMPLE_CALLCHAIN;
 
+		if (call_graph == CALLCHAIN_DWARF) {
+			attr->user_regs = PERF_UNWIND_REGS_MASK;
+			attr->ustack_dump_size = stack_dump_size;
+			attr->exclude_user_callchain = 1;
+		}
+	}
+
 	if (system_wide)
 		attr->sample_type	|= PERF_SAMPLE_CPU;
 
@@ -779,6 +796,58 @@ out_delete_session:
 	return err;
 }
 
+static int
+parse_callchain_opt(const struct option *opt __used, const char *arg,
+		    int unset)
+{
+	char *tok;
+
+	/*
+	 * --no-call-graph
+	 */
+	if (unset)
+		return 0;
+
+	if (!arg)
+		return 0;
+
+	tok = strtok((char *)arg, ",");
+	if (!tok)
+		return -1;
+
+	/* get the output mode */
+	if (!strncmp(tok, "fp", strlen(arg)))
+		call_graph = CALLCHAIN_FP;
+
+	else if (!strncmp(tok, "dwarf", strlen(arg)))
+		call_graph = CALLCHAIN_DWARF;
+
+	else if (!strncmp(tok, "none", strlen(arg)))
+		return 0;
+
+	else
+		return -1;
+
+	/* get the stack dump size */
+	tok = strtok(NULL, ",");
+	if (!tok)
+		return 0;
+
+	/* No stack dump size if we record using frame pointers */
+	if (call_graph == CALLCHAIN_FP) {
+		fprintf(stderr, "Stack dump size is only necessary for -g dwarf\n");
+		return -1;
+	}
+
+	stack_dump_size = strtoul(tok, NULL, 0);
+	if (stack_dump_size == ULONG_MAX) {
+		perror("Incorrect stack dump size\n");
+		return -1;
+	}
+
+	return 0;
+}
+
 static const char * const record_usage[] = {
 	"perf record [<options>] [<command>]",
 	"perf record [<options>] -- <command> [<options>]",
@@ -816,8 +885,9 @@ static const struct option options[] = {
 		    "child tasks do not inherit counters"),
 	OPT_UINTEGER('F', "freq", &user_freq, "profile at this frequency"),
 	OPT_UINTEGER('m', "mmap-pages", &mmap_pages, "number of mmap data pages"),
-	OPT_BOOLEAN('g', "call-graph", &call_graph,
-		    "do call-graph (stack chain/backtrace) recording"),
+	OPT_CALLBACK_DEFAULT('g', "call-graph", NULL, "mode,dump_size",
+		     "do call-graph (stack chain/backtrace) recording"
+		     "Default: fp", &parse_callchain_opt, callchain_default_opt),
 	OPT_INCR('v', "verbose", &verbose,
 		    "be more verbose (show counter open errors, etc)"),
 	OPT_BOOLEAN('s', "stat", &inherit_stat,
diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h
new file mode 100644
index 0000000..f553a7a
--- /dev/null
+++ b/tools/perf/util/perf_regs.h
@@ -0,0 +1,11 @@
+#ifndef __PERF_REGS_H
+#define __PERF_REGS_H
+
+#ifndef NO_PERF_REGS_DEFS
+#include <perf_regs.h>
+#else
+
+#define PERF_UNWIND_REGS_MASK	0
+#endif /* NO_PERF_REGS_DEFS */
+
+#endif
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 07/11] perf: Build with dwarf cfi
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (5 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 06/11] perf: Support for dwarf mode callchain on perf record Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 08/11] perf: Support for error passed over pointers Frederic Weisbecker
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

Build perf with the dwarf call frame informations so that we can
unwind callchains in perf itself.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 tools/perf/Makefile |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index e22016d..49432e0 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -226,7 +226,7 @@ ifndef PERF_DEBUG
   CFLAGS_OPTIMIZE = -O6
 endif
 
-CFLAGS = -ggdb3 -Wall -Wextra -std=gnu99 -Werror $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS)
+CFLAGS = -ggdb3 -funwind-tables -Wall -Wextra -std=gnu99 -Werror $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS)
 EXTLIBS = -lpthread -lrt -lelf -lm
 ALL_CFLAGS = $(CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
 ALL_LDFLAGS = $(LDFLAGS)
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 08/11] perf: Support for error passed over pointers
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (6 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 07/11] perf: Build with dwarf cfi Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 09/11] perf: Add libunwind dependency for dwarf cfi unwinding Frederic Weisbecker
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

Export the linux/err.h that carries the ERR_PTR/PTR_ERR/IS_ERR
macros so that they also become usable by perf.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 tools/perf/Makefile                 |    1 +
 tools/perf/util/include/linux/err.h |   24 ++++++++++++++++++++++++
 2 files changed, 25 insertions(+), 0 deletions(-)
 create mode 100644 tools/perf/util/include/linux/err.h

diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 49432e0..addef2f 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -377,6 +377,7 @@ LIB_H += util/include/linux/prefetch.h
 LIB_H += util/include/linux/rbtree.h
 LIB_H += util/include/linux/string.h
 LIB_H += util/include/linux/types.h
+LIB_H += util/include/linux/err.h
 LIB_H += util/include/asm/asm-offsets.h
 LIB_H += util/include/asm/bug.h
 LIB_H += util/include/asm/byteorder.h
diff --git a/tools/perf/util/include/linux/err.h b/tools/perf/util/include/linux/err.h
new file mode 100644
index 0000000..4e6dc36
--- /dev/null
+++ b/tools/perf/util/include/linux/err.h
@@ -0,0 +1,24 @@
+#ifndef PERF_ERR_H
+#define PERF_ERR_H
+
+
+#define MAX_ERRNO	4095
+
+#define IS_ERR_VALUE(x) ((x) >= (unsigned long)-MAX_ERRNO)
+
+static inline void *ERR_PTR(long err)
+{
+	return (void *) err;
+}
+
+static inline long PTR_ERR(const void *ptr)
+{
+	return (long) ptr;
+}
+
+static inline long IS_ERR(const void *ptr)
+{
+	return IS_ERR_VALUE((unsigned long)ptr);
+}
+
+#endif /* PERF_ERR_H */
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 09/11] perf: Add libunwind dependency for dwarf cfi unwinding
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (7 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 08/11] perf: Support for error passed over pointers Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 10/11] perf: Support user regs and stack in sample parsing Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 11/11] perf: Support for dwarf cfi unwinding on post processing Frederic Weisbecker
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

This is not mandatory, but required to get dwarf cfi unwinding
support.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 tools/perf/Makefile          |   19 +++++++++++++++++++
 tools/perf/feature-tests.mak |   14 ++++++++++++++
 2 files changed, 33 insertions(+), 0 deletions(-)

diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index addef2f..0c46ac7 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -518,6 +518,20 @@ ifneq ($(call try-cc,$(SOURCE_DWARF),$(FLAGS_DWARF)),y)
 endif # Dwarf support
 endif # NO_DWARF
 
+
+# Only x86-32 is supported for now
+ifneq ($(ARCH),x86)
+	NO_LIBUNWIND := 1
+endif
+
+ifndef NO_LIBUNWIND
+FLAGS_UNWIND=$(ALL_CFLAGS) -lunwind-x86 -lunwind-ptrace $(ALL_LDFLAGS) $(EXTLIBS)
+ifneq ($(call try-cc,$(SOURCE_LIBUNWIND),$(FLAGS_UNWIND)),y)
+	msg := $(warning No libunwind found. Please install libunwind >= 0.99);
+	NO_LIBUNWIND := 1
+endif # Libunwind support
+endif # NO_LIBUNWIND
+
 -include arch/$(ARCH)/Makefile
 
 ifeq ($(uname_S),Darwin)
@@ -564,6 +578,11 @@ else
 endif # PERF_HAVE_DWARF_REGS
 endif # NO_DWARF
 
+ifndef NO_LIBUNWIND
+	BASIC_CFLAGS += -DLIBUNWIND_SUPPORT
+	EXTLIBS += -lunwind-ptrace -lunwind-x86
+endif
+
 ifdef NO_NEWT
 	BASIC_CFLAGS += -DNO_NEWT_SUPPORT
 else
diff --git a/tools/perf/feature-tests.mak b/tools/perf/feature-tests.mak
index b253db6..367b213 100644
--- a/tools/perf/feature-tests.mak
+++ b/tools/perf/feature-tests.mak
@@ -90,6 +90,20 @@ int main(void)
 endef
 endif
 
+ifndef NO_LIBUNWIND
+define SOURCE_LIBUNWIND
+#include <libunwind.h>
+#include <stdlib.h>
+
+int main(void)
+{
+	unw_addr_space_t addr_space;
+	addr_space = unw_create_addr_space(NULL, 0);
+	return 0;
+}
+endef
+endif
+
 define SOURCE_BFD
 #include <bfd.h>
 
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 10/11] perf: Support user regs and stack in sample parsing
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (8 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 09/11] perf: Add libunwind dependency for dwarf cfi unwinding Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  2010-10-22 19:13 ` [RFC PATCH 11/11] perf: Support for dwarf cfi unwinding on post processing Frederic Weisbecker
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

This will be needed for stack unwinding.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 tools/perf/builtin-kmem.c      |    2 +-
 tools/perf/builtin-lock.c      |    2 +-
 tools/perf/builtin-sched.c     |    2 +-
 tools/perf/builtin-timechart.c |    2 +-
 tools/perf/builtin-trace.c     |    2 +-
 tools/perf/util/event.c        |   28 ++++++++++++++++++++++++++--
 tools/perf/util/event.h        |   15 ++++++++++++++-
 tools/perf/util/header.c       |   33 +++++++++++++++++++++++++++++++--
 tools/perf/util/header.h       |    3 ++-
 tools/perf/util/session.c      |    4 ++--
 tools/perf/util/session.h      |    6 ++++++
 11 files changed, 86 insertions(+), 13 deletions(-)

diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c
index 31f60a2..2fee906 100644
--- a/tools/perf/builtin-kmem.c
+++ b/tools/perf/builtin-kmem.c
@@ -314,7 +314,7 @@ static int process_sample_event(event_t *event, struct perf_session *session)
 	data.cpu = -1;
 	data.period = 1;
 
-	event__parse_sample(event, session->sample_type, &data);
+	event__parse_sample(event, session, &data);
 
 	dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc,
 		    data.pid, data.tid, data.ip, data.period);
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
index 821c158..5791a25 100644
--- a/tools/perf/builtin-lock.c
+++ b/tools/perf/builtin-lock.c
@@ -840,7 +840,7 @@ static int process_sample_event(event_t *self, struct perf_session *s)
 	struct thread *thread;
 
 	bzero(&data, sizeof(data));
-	event__parse_sample(self, s->sample_type, &data);
+	event__parse_sample(self, s, &data);
 
 	thread = perf_session__findnew(s, data.tid);
 	if (thread == NULL) {
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 55f3b5d..5ef6075 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -1619,7 +1619,7 @@ static int process_sample_event(event_t *event, struct perf_session *session)
 	data.cpu = -1;
 	data.period = -1;
 
-	event__parse_sample(event, session->sample_type, &data);
+	event__parse_sample(event, session, &data);
 
 	dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc,
 		    data.pid, data.tid, data.ip, data.period);
diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
index 9bcc38f..7d752d5 100644
--- a/tools/perf/builtin-timechart.c
+++ b/tools/perf/builtin-timechart.c
@@ -477,7 +477,7 @@ static int process_sample_event(event_t *event, struct perf_session *session)
 
 	memset(&data, 0, sizeof(data));
 
-	event__parse_sample(event, session->sample_type, &data);
+	event__parse_sample(event, session, &data);
 
 	if (session->sample_type & PERF_SAMPLE_TIME) {
 		if (!first_time || first_time > data.time)
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 40a6a29..224c586 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -74,7 +74,7 @@ static int process_sample_event(event_t *event, struct perf_session *session)
 	data.cpu = -1;
 	data.period = 1;
 
-	event__parse_sample(event, session->sample_type, &data);
+	event__parse_sample(event, session, &data);
 
 	dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc,
 		    data.pid, data.tid, data.ip, data.period);
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
index dab9e75..567f97a 100644
--- a/tools/perf/util/event.c
+++ b/tools/perf/util/event.c
@@ -676,7 +676,7 @@ int event__preprocess_sample(const event_t *self, struct perf_session *session,
 	u8 cpumode = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
 	struct thread *thread;
 
-	event__parse_sample(self, session->sample_type, data);
+	event__parse_sample(self, session, data);
 
 	dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld cpu:%d\n",
 		    self->header.misc, data->pid, data->tid, data->ip,
@@ -766,9 +766,11 @@ out_filtered:
 	return 0;
 }
 
-int event__parse_sample(const event_t *event, u64 type, struct sample_data *data)
+int event__parse_sample(const event_t *event, struct perf_session *session,
+			struct sample_data *data)
 {
 	const u64 *array = event->sample.array;
+	u64 type = session->sample_type;
 
 	if (type & PERF_SAMPLE_IP) {
 		data->ip = event->ip.ip;
@@ -830,6 +832,28 @@ int event__parse_sample(const event_t *event, u64 type, struct sample_data *data
 		data->raw_size = *p;
 		p++;
 		data->raw_data = p;
+		array += 1 + (data->raw_size * sizeof(u64));
+	}
+
+	if (session->sample_uregs_nr) {
+		data->uregs.version = *array++;
+
+		if (data->uregs.version) {
+			data->uregs.regs = (u64 *)array;
+			array += session->sample_uregs_nr;
+		}
+	}
+
+	if (session->sample_ustack) {
+		u64 size = *array++;
+
+		if (!size) {
+			data->stack.size = 0;
+		} else {
+			data->stack.data = (char *)array;
+			array += size / sizeof(*array);
+			data->stack.size = *array;
+		}
 	}
 
 	return 0;
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
index 8e790da..5a05396 100644
--- a/tools/perf/util/event.h
+++ b/tools/perf/util/event.h
@@ -61,6 +61,16 @@ struct sample_event {
 	u64 array[];
 };
 
+struct user_regs {
+	u64 version;
+	u64 *regs;
+};
+
+struct user_stack_dump {
+	u64 size;
+	char *data;
+};
+
 struct sample_data {
 	u64 ip;
 	u32 pid, tid;
@@ -73,6 +83,8 @@ struct sample_data {
 	u32 raw_size;
 	void *raw_data;
 	struct ip_callchain *callchain;
+	struct user_regs uregs;
+	struct user_stack_dump stack;
 };
 
 #define BUILD_ID_SIZE 20
@@ -160,7 +172,8 @@ struct addr_location;
 int event__preprocess_sample(const event_t *self, struct perf_session *session,
 			     struct addr_location *al, struct sample_data *data,
 			     symbol_filter_t filter);
-int event__parse_sample(const event_t *event, u64 type, struct sample_data *data);
+int event__parse_sample(const event_t *event, struct perf_session *session,
+			struct sample_data *data);
 
 extern const char *event__name[];
 
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index d7e67b1..b73f481 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -922,9 +922,26 @@ out_errno:
 	return -errno;
 }
 
-u64 perf_header__sample_type(struct perf_header *header)
+static int regs_weight(u64 user_regs)
+{
+	int i, bits = 0;
+
+	for (i = 0; i < (int)sizeof(u64) * 8 && user_regs; i++) {
+		if (user_regs & 1)
+			bits++;
+
+		user_regs >>= 1;
+	}
+
+	return bits;
+}
+
+void perf_header__sample_type(struct perf_header *header,
+			      struct perf_session *session)
 {
 	u64 type = 0;
+	u64 uregs = 0;
+	int uregs_nr = 0, ustack = false;
 	int i;
 
 	for (i = 0; i < header->attrs; i++) {
@@ -934,9 +951,21 @@ u64 perf_header__sample_type(struct perf_header *header)
 			type = attr->attr.sample_type;
 		else if (type != attr->attr.sample_type)
 			die("non matching sample_type");
+
+		if (i == 0) {
+			uregs = attr->attr.user_regs;
+			uregs_nr = regs_weight(uregs);
+			ustack = !!attr->attr.ustack_dump_size;
+		} else if (uregs != attr->attr.user_regs ||
+			   ustack != !!attr->attr.ustack_dump_size) {
+			die("non matching sample_type");
+		}
 	}
 
-	return type;
+	session->sample_type = type;
+	session->sample_uregs = uregs;
+	session->sample_uregs_nr = uregs_nr;
+	session->sample_ustack = ustack;
 }
 
 struct perf_event_attr *
diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h
index 402ac24..6ec6af2 100644
--- a/tools/perf/util/header.h
+++ b/tools/perf/util/header.h
@@ -80,7 +80,8 @@ void perf_header_attr__delete(struct perf_header_attr *self);
 
 int perf_header_attr__add_id(struct perf_header_attr *self, u64 id);
 
-u64 perf_header__sample_type(struct perf_header *header);
+void perf_header__sample_type(struct perf_header *header,
+			      struct perf_session *session);
 struct perf_event_attr *
 perf_header__find_attr(u64 id, struct perf_header *header);
 void perf_header__set_feat(struct perf_header *self, int feat);
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index fa9d652..f0e427e 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -67,7 +67,7 @@ out_close:
 
 void perf_session__update_sample_type(struct perf_session *self)
 {
-	self->sample_type = perf_header__sample_type(&self->header);
+	perf_header__sample_type(&self->header, self);
 }
 
 int perf_session__create_kernel_maps(struct perf_session *self)
@@ -576,7 +576,7 @@ static int perf_session__process_sample(event_t *event, struct perf_session *s,
 		return ops->sample(event, s);
 
 	bzero(&data, sizeof(struct sample_data));
-	event__parse_sample(event, s->sample_type, &data);
+	event__parse_sample(event, s, &data);
 
 	queue_sample_event(event, &data, s);
 
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
index 9fa0fc2..ab9ebbc 100644
--- a/tools/perf/util/session.h
+++ b/tools/perf/util/session.h
@@ -39,6 +39,12 @@ struct perf_session {
 	 */
 	struct hists		hists;
 	u64			sample_type;
+	u64			sample_uregs;
+	/* We should probably use a quick hweight64() in sample_uregs to get
+	 * this instead of storing it.
+	 */
+	int			sample_uregs_nr;
+	bool			sample_ustack;
 	int			fd;
 	bool			fd_pipe;
 	bool			repipe;
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [RFC PATCH 11/11] perf: Support for dwarf cfi unwinding on post processing
  2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
                   ` (9 preceding siblings ...)
  2010-10-22 19:13 ` [RFC PATCH 10/11] perf: Support user regs and stack in sample parsing Frederic Weisbecker
@ 2010-10-22 19:13 ` Frederic Weisbecker
  10 siblings, 0 replies; 12+ messages in thread
From: Frederic Weisbecker @ 2010-10-22 19:13 UTC (permalink / raw)
  To: LKML
  Cc: LKML, Frederic Weisbecker, Ingo Molnar, Peter Zijlstra,
	Arnaldo Carvalho de Melo, Paul Mackerras, Stephane Eranian,
	Cyrill Gorcunov, Tom Zanussi, Masami Hiramatsu, Steven Rostedt,
	Robert Richter, Frank Ch. Eigler

This brings the support for dwarf cfi unwinding on perf
post processing. Call frame informations are retrieved and
then passed to libunwind that requests memory and register content
from the applications. We use the stack and regs dumped with
samples to retrieve the context state necessary for this unwinding.

No specific options are needed for perf report to handle this,
just run it as usual.

Only x86-32 is supported for now.

Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Frank Ch. Eigler <fche@redhat.com>
---
 tools/perf/Makefile         |    1 +
 tools/perf/builtin-report.c |    9 +-
 tools/perf/util/callchain.c |   35 ++-
 tools/perf/util/callchain.h |   19 +-
 tools/perf/util/unwind.c    | 1112 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1169 insertions(+), 7 deletions(-)
 create mode 100644 tools/perf/util/unwind.c

diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 0c46ac7..601c69a 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -468,6 +468,7 @@ LIB_OBJS += $(OUTPUT)util/hist.o
 LIB_OBJS += $(OUTPUT)util/probe-event.o
 LIB_OBJS += $(OUTPUT)util/util.o
 LIB_OBJS += $(OUTPUT)util/cpumap.o
+LIB_OBJS += $(OUTPUT)util/unwind.o
 
 BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o
 
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
index 5de405d..b48e218 100644
--- a/tools/perf/builtin-report.c
+++ b/tools/perf/builtin-report.c
@@ -30,6 +30,8 @@
 #include "util/sort.h"
 #include "util/hist.h"
 
+#include <linux/err.h>
+
 static char		const *input_name = "perf.data";
 
 static bool		force, use_tui, use_stdio;
@@ -87,6 +89,7 @@ static int perf_session__add_hist_entry(struct perf_session *self,
 	struct hist_entry *he;
 	struct hists *hists;
 	struct perf_event_attr *attr;
+	struct dwarf_callchain *dc;
 
 	if ((sort__has_parent || symbol_conf.use_callchain) && data->callchain) {
 		syms = perf_session__resolve_callchain(self, al->thread,
@@ -95,6 +98,10 @@ static int perf_session__add_hist_entry(struct perf_session *self,
 			return -ENOMEM;
 	}
 
+	dc = callchain_unwind(self, al->thread, data);
+	if (IS_ERR(dc))
+		return PTR_ERR(dc);
+
 	attr = perf_header__find_attr(data->id, &self->header);
 	if (attr)
 		hists = perf_session__hists_findnew(self, data->id, attr->type, attr->config);
@@ -108,7 +115,7 @@ static int perf_session__add_hist_entry(struct perf_session *self,
 	err = 0;
 	if (symbol_conf.use_callchain) {
 		err = callchain_append(he->callchain, data->callchain, syms,
-				       data->period);
+				       dc, data->period);
 		if (err)
 			goto out_free_syms;
 	}
diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c
index e12d539..54ca387 100644
--- a/tools/perf/util/callchain.c
+++ b/tools/perf/util/callchain.c
@@ -365,8 +365,10 @@ append_chain(struct callchain_node *root, struct resolved_chain *chain,
 }
 
 static void filter_context(struct ip_callchain *old, struct resolved_chain *new,
-			   struct map_symbol *syms)
+			   struct map_symbol *syms,
+			   struct dwarf_callchain *dwarf_chain)
 {
+	struct dwarf_callchain_entry *entry, *tmp;
 	int i, j = 0;
 
 	for (i = 0; i < (int)old->nr; i++) {
@@ -379,23 +381,46 @@ static void filter_context(struct ip_callchain *old, struct resolved_chain *new,
 	}
 
 	new->nr = j;
+
+	if (!dwarf_chain)
+		return;
+
+	list_for_each_entry_safe(entry, tmp, &dwarf_chain->chain_head, list) {
+		new->ips[j].ip = entry->ip;
+		new->ips[j].ms = entry->ms;
+		j++;
+
+		list_del(&entry->list);
+		free(entry);
+	}
+
+	new->nr += dwarf_chain->nb;
+
+	free(dwarf_chain);
 }
 
 
 int callchain_append(struct callchain_root *root, struct ip_callchain *chain,
-		     struct map_symbol *syms, u64 period)
+		     struct map_symbol *syms,
+		     struct dwarf_callchain *dwarf_chain, u64 period)
 {
 	struct resolved_chain *filtered;
+	int entries;
+
+	entries = chain->nr;
+
+	if (dwarf_chain)
+		entries += dwarf_chain->nb;
 
-	if (!chain->nr)
+	if (!entries)
 		return 0;
 
 	filtered = zalloc(sizeof(*filtered) +
-			  chain->nr * sizeof(struct resolved_ip));
+			  entries * sizeof(struct resolved_ip));
 	if (!filtered)
 		return -ENOMEM;
 
-	filter_context(chain, filtered, syms);
+	filter_context(chain, filtered, syms, dwarf_chain);
 
 	if (!filtered->nr)
 		goto end;
diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h
index c15fb8c..9fb55b4 100644
--- a/tools/perf/util/callchain.h
+++ b/tools/perf/util/callchain.h
@@ -31,6 +31,17 @@ struct callchain_root {
 	struct callchain_node	node;
 };
 
+struct dwarf_callchain_entry {
+	struct list_head	list;
+	u64			ip;
+	struct map_symbol	ms;
+};
+
+struct dwarf_callchain {
+	int			nb;
+	struct list_head	chain_head;
+};
+
 struct callchain_param;
 
 typedef void (*sort_chain_func_t)(struct rb_root *, struct callchain_root *,
@@ -68,8 +79,14 @@ static inline u64 cumul_hits(struct callchain_node *node)
 
 int register_callchain_param(struct callchain_param *param);
 int callchain_append(struct callchain_root *root, struct ip_callchain *chain,
-		     struct map_symbol *syms, u64 period);
+		     struct map_symbol *syms,
+		     struct dwarf_callchain *dwarf_chain, u64 period);
+
 int callchain_merge(struct callchain_root *dst, struct callchain_root *src);
 
 bool ip_callchain__valid(struct ip_callchain *chain, const event_t *event);
+
+struct dwarf_callchain *callchain_unwind(struct perf_session *session,
+					 struct thread *thread,
+					 struct sample_data *data);
 #endif	/* __PERF_CALLCHAIN_H */
diff --git a/tools/perf/util/unwind.c b/tools/perf/util/unwind.c
new file mode 100644
index 0000000..b8dc23f
--- /dev/null
+++ b/tools/perf/util/unwind.c
@@ -0,0 +1,1112 @@
+/*
+ * Post mortem Dwarf CFI based unwinding on top of regs and stack dumps.
+ *
+ * Lots of this code have been borrowed or heavily inspired from parts of
+ * the libunwind 0.99 code which are (amongst other contributors I may have
+ * forgotten):
+ *
+ * Copyright (C) 2002-2007 Hewlett-Packard Co
+ *	Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
+ *
+ * And the bugs have been added by:
+ *
+ * Copyright (C) 2010, Frederic Weisbecker <fweisbec@gmail.com>
+ *
+ */
+
+#include "util.h"
+#include <elf.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <linux/list.h>
+#include <linux/err.h>
+#include "thread.h"
+#include "session.h"
+#include "perf_regs.h"
+
+
+#ifdef LIBUNWIND_SUPPORT
+
+#include <libunwind-ptrace.h>
+#include <libunwind.h>
+
+struct pt_regs {
+	unsigned long ebx;
+	unsigned long ecx;
+	unsigned long edx;
+	unsigned long esi;
+	unsigned long edi;
+	unsigned long ebp;
+	unsigned long eax;
+	unsigned long ds;
+	unsigned long es;
+	unsigned long fs;
+	unsigned long gs;
+	unsigned long orig_ax;
+	unsigned long eip;
+	unsigned long cs;
+	unsigned long flags;
+	unsigned long esp;
+	unsigned long ss;
+};
+
+#define DW_EH_PE_FORMAT_MASK	0x0f	/* format of the encoded value */
+#define DW_EH_PE_APPL_MASK	0x70	/* how the value is to be applied */
+/*
+ * Flag bit.  If set, the resulting pointer is the address of the word
+ * that contains the final address.
+ */
+#define DW_EH_PE_indirect	0x80
+
+/* Pointer-encoding formats: */
+#define DW_EH_PE_omit		0xff
+#define DW_EH_PE_ptr		0x00	/* pointer-sized unsigned value */
+#define DW_EH_PE_uleb128	0x01	/* unsigned LE base-128 value */
+#define DW_EH_PE_udata2		0x02	/* unsigned 16-bit value */
+#define DW_EH_PE_udata4		0x03	/* unsigned 32-bit value */
+#define DW_EH_PE_udata8		0x04	/* unsigned 64-bit value */
+#define DW_EH_PE_sleb128	0x09	/* signed LE base-128 value */
+#define DW_EH_PE_sdata2		0x0a	/* signed 16-bit value */
+#define DW_EH_PE_sdata4		0x0b	/* signed 32-bit value */
+#define DW_EH_PE_sdata8		0x0c	/* signed 64-bit value */
+
+/* Pointer-encoding application: */
+#define DW_EH_PE_absptr		0x00	/* absolute value */
+#define DW_EH_PE_pcrel		0x10	/* rel. to addr. of encoded value */
+#define DW_EH_PE_textrel	0x20	/* text-relative (GCC-specific???) */
+#define DW_EH_PE_datarel	0x30	/* data-relative */
+/*
+ * The following are not documented by LSB v1.3, yet they are used by
+ * GCC, presumably they aren't documented by LSB since they aren't
+ * used on Linux:
+ */
+#define DW_EH_PE_funcrel	0x40	/* start-of-procedure-relative */
+#define DW_EH_PE_aligned	0x50	/* aligned pointer */
+
+struct dwarf_instr_addr {
+	u64			start;
+	u64			end;
+	struct dso		*dso;
+	struct list_head	list;
+};
+
+struct unwind_info {
+	struct sample_data	*sample;
+	struct perf_session	*session;
+	struct thread		*thread;
+	struct list_head	dia_head;
+};
+
+static int
+resolve_section_name(int fd, Elf32_Ehdr *ehdr, int idx, char *buf, int size)
+{
+	Elf32_Shdr shdr;
+	int offset;
+	int old;
+	int i;
+	char c;
+
+	old = lseek(fd, 0, SEEK_CUR);
+	offset = ehdr->e_shoff + (ehdr->e_shstrndx * ehdr->e_shentsize);
+	lseek(fd, offset, SEEK_SET);
+	if (read(fd, &shdr, ehdr->e_shentsize) == -1)
+		return -errno;
+
+	offset = shdr.sh_offset + idx;
+	lseek(fd, offset, SEEK_SET);
+
+	for (i = 0; i < size - 1; i++) {
+		if (read(fd, &c, 1) == -1)
+			return -errno;
+		if (!c)
+			break;
+		buf[i] = c;
+	}
+
+	buf[i] = 0;
+	lseek(fd, old, SEEK_SET);
+
+	return 0;
+}
+
+
+static int eh_frame_section(int fd, Elf32_Ehdr *ehdr, Elf32_Shdr *shdr)
+{
+	int i, err;
+
+	lseek(fd, ehdr->e_shoff + ehdr->e_shentsize, SEEK_SET);
+
+	for (i = 1; i < ehdr->e_shnum; i++) {
+		char buf[128];
+
+		if (read(fd, shdr, ehdr->e_shentsize) == -1)
+			return -errno;
+
+		err = resolve_section_name(fd, ehdr, shdr->sh_name, buf, sizeof(buf));
+		if (err)
+			return err;
+
+		if (!strcmp(buf, ".eh_frame"))
+			return 0;
+	}
+
+	return -ENOENT;
+}
+
+static int parse_elf_headers(int fd, Elf32_Ehdr *ehdr)
+{
+	if (read(fd, ehdr, sizeof(*ehdr)) == -1)
+		return -errno;
+
+	if (ehdr->e_ident[EI_MAG0] != ELFMAG0 ||
+		ehdr->e_ident[EI_MAG1] != ELFMAG1 ||
+		ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
+		ehdr->e_ident[EI_MAG3] != ELFMAG3) {
+
+		return -EINVAL;
+	}
+
+	if (!ehdr->e_shoff)
+		return -ENOENT;
+
+	return 0;
+}
+
+static u64 dwarf_read_uleb128(int fd, u64 *val)
+{
+	u64 shift = 0;
+	unsigned char byte;
+
+	*val = 0;
+
+	do {
+		if (read(fd, &byte, sizeof(byte)) == -1)
+			return -errno;
+
+		*val |= (byte & 0x7f) << shift;
+		shift += 7;
+	} while (byte & 0x80);
+
+	return 0;
+}
+
+static s64 dwarf_read_sleb128(int fd, s64 *val)
+{
+	s64 shift = 0;
+	unsigned char byte;
+
+	*val = 0;
+
+	do {
+		if (read(fd, &byte, sizeof(byte)) == -1)
+			return -errno;
+		*val |= (byte & 0x7f) << shift;
+		shift += 7;
+	} while (byte & 0x80);
+
+	if (shift < 8 * sizeof(*val) && (byte & 0x40) != 0)
+		/* sign-extend negative value */
+		*val |= -1LL << shift;
+
+	return 0;
+}
+
+static int dwarf_read_encoded_pointer(int fd, unsigned char encoding,
+				      u64 drop __used, u64 *val)
+{
+	u64 base = lseek(fd, 0, SEEK_CUR);
+
+	if (encoding == DW_EH_PE_omit || encoding == DW_EH_PE_aligned) {
+		pr_err("Unsupported dwarf encoding\n");
+		return -ENOSYS;
+	}
+
+	switch (encoding & DW_EH_PE_FORMAT_MASK) {
+	case DW_EH_PE_ptr: {
+		unsigned long lval;
+
+		if (read(fd, &lval, sizeof(lval)) == -1)
+			return -errno;
+		*val = lval;
+		break;
+	}
+	case DW_EH_PE_sdata4: {
+		s32 s32val;
+		s64 s64val;
+
+		if (read(fd, &s32val, sizeof(s32val)) == -1)
+			return -errno;
+		s64val = s32val;
+		*val = *(u64 *)&s64val;
+		break;
+	}
+	default:
+		pr_err("Unsupported encoded pointer: %d\n", encoding & DW_EH_PE_FORMAT_MASK);
+		return -EINVAL;
+	}
+
+	switch (encoding & DW_EH_PE_APPL_MASK) {
+	case DW_EH_PE_absptr:
+		break;
+	case DW_EH_PE_pcrel:
+		*val += base;
+		break;
+	default:
+		pr_err("Unsupported DW_EH_PE_APPL_MASK: %d\n", encoding & DW_EH_PE_APPL_MASK);
+		return -EINVAL;
+	}
+
+	if (encoding & DW_EH_PE_indirect) {
+		int prev_offset = lseek(fd, 0, SEEK_CUR);
+		unsigned long lval;
+
+		lseek(fd, *val, SEEK_SET);
+		if (read(fd, &lval, sizeof(unsigned long)) == -1)
+			return -errno;
+		*val = lval;
+		lseek(fd, prev_offset, SEEK_SET);
+	}
+
+	return 0;
+}
+
+struct cie {
+	u32		length;
+	u64		ext_length;
+	union {
+			u32 id32;
+			u64 id64;
+	};
+	u8		version;
+	u64		code_align;
+	s64		data_align;
+	u8		ret_column;
+	u64		aug_length;
+	u8		lsda_encoding;
+	u8		fde_encoding; /* Should have defaults */
+	u8		handler_encoding;
+	u8		have_abi_marker;
+	u64		handler;
+	u64		instr;
+	u64		end;
+};
+
+static int parse_cie(int fd, struct cie *cie)
+{
+	char aug_str[10];
+	int size, end, base;
+	int err;
+	int i;
+
+	memset(cie, 0, sizeof(*cie));
+
+	if (read(fd, &cie->length, sizeof(cie->length)) == -1)
+		return -errno;
+
+	if (cie->length == 0xffffffff) {
+		if (read(fd, &cie->ext_length, sizeof(cie->ext_length)) == -1)
+			return -errno;
+
+		size = cie->ext_length;
+		base = lseek(fd, 0, SEEK_CUR);
+		if (read(fd, &cie->id64, sizeof(cie->id64)) == -1)
+			return -errno;
+
+		if (cie->id64)
+			return -EINVAL;
+	} else {
+		base = lseek(fd, 0, SEEK_CUR);
+		if (read(fd, &cie->id32, sizeof(cie->id32)) == -1)
+			return -errno;
+		size = cie->length;
+		if (cie->id32)
+			return -EINVAL;
+	}
+	end = base + size;
+
+	if (read(fd, &cie->version, sizeof(cie->version)) == -1)
+		return -errno;
+
+	/* Should be else in 64 bits? */
+	if (cie->version != 1)
+		return -EINVAL;
+
+	memset(aug_str, 0, sizeof(aug_str));
+	for (i = 0; i < (int)sizeof(aug_str); i++) {
+		char c;
+
+		if (read(fd, &c, 1) == -1)
+			return -errno;
+
+		aug_str[i] = c;
+		if (!c)
+			break;
+	}
+
+	if (!strcmp("eh", aug_str))
+		lseek(fd, 4, SEEK_CUR);
+
+	err = dwarf_read_uleb128(fd, &cie->code_align);
+	if (err)
+		return err;
+
+	err = dwarf_read_sleb128(fd, &cie->data_align);
+	if (err)
+		return err;
+
+
+	if (read(fd, &cie->ret_column, sizeof(cie->ret_column)) == -1)
+		return -errno;
+
+	if (aug_str[0] == 'z') {
+		err = dwarf_read_uleb128(fd, &cie->aug_length);
+		if (err)
+			return err;
+	}
+
+
+	for (i = 1; aug_str[i]; i++) {
+		switch (aug_str[i]) {
+		case 'L':
+			if (read(fd, &cie->lsda_encoding, sizeof(cie->lsda_encoding)) == -1)
+				return -errno;
+			break;
+		case 'R':
+			if (read(fd, &cie->fde_encoding, sizeof(cie->fde_encoding)) == -1)
+				return -errno;
+			break;
+		case 'P':
+			if (read(fd, &cie->handler_encoding, sizeof(cie->handler_encoding)) == -1)
+				return -errno;
+
+			err = dwarf_read_encoded_pointer(fd, cie->handler_encoding, 0,
+								&cie->handler);
+			if (err)
+				return err;
+			break;
+		case 'S':
+			if (read(fd, &cie->have_abi_marker, sizeof(cie->have_abi_marker)) == -1)
+				return -errno;
+			break;
+		default:
+			break;
+		}
+	}
+	cie->instr = lseek(fd, 0, SEEK_CUR);
+	cie->end = end;
+
+	return 0;
+}
+
+struct fde {
+	u32	length;
+	u64	ext_length;
+	union {
+		u32	cie_offset32;
+		u64	cie_offset64;
+	};
+	u64	pc_begin;
+	u64	pc_range;
+	u64	aug_length;
+	u64	aug_end;
+	u64	lsda;
+	u16	abi;
+	u16	tag;
+	u64	end;
+};
+
+static int parse_fde(int fd, struct fde *fde, struct cie *cie, int fde_end)
+{
+	int ip_range_encoding;
+	int err;
+
+	memset(fde, 0, sizeof(*fde));
+	ip_range_encoding = cie->fde_encoding & DW_EH_PE_FORMAT_MASK;
+
+	err = dwarf_read_encoded_pointer(fd, cie->fde_encoding, 0, &fde->pc_begin);
+	if (err)
+		return err;
+	err = dwarf_read_encoded_pointer(fd, ip_range_encoding, 0, &fde->pc_range);
+	if (err)
+		return err;
+	fde->pc_range += fde->pc_begin;
+
+	if (cie->aug_length) {
+		err = dwarf_read_uleb128(fd, &fde->aug_length);
+		if (err)
+			return err;
+		fde->aug_end = lseek(fd, 0, SEEK_CUR) + fde->aug_length;
+	}
+
+	err = dwarf_read_encoded_pointer(fd, cie->lsda_encoding, 0, &fde->lsda);
+	if (err)
+		return err;
+
+	if (cie->have_abi_marker) {
+		if (read(fd, &fde->abi, sizeof(fde->abi)) == -1)
+			return -EINVAL;
+		if (read(fd, &fde->tag, sizeof(fde->tag)) == -1)
+			return -EINVAL;
+	}
+
+	if (!cie->aug_length)
+		fde->aug_end = lseek(fd, 0, SEEK_CUR);
+	fde->end = fde_end;
+
+	return 0;
+}
+
+struct dwarf_cie_info {
+	unw_word_t cie_instr_start;	/* start addr. of CIE "initial_instructions" */
+	unw_word_t cie_instr_end;	/* end addr. of CIE "initial_instructions" */
+	unw_word_t fde_instr_start;	/* start addr. of FDE "instructions" */
+	unw_word_t fde_instr_end;	/* end addr. of FDE "instructions" */
+	unw_word_t code_align;		/* code-alignment factor */
+	unw_word_t data_align;		/* data-alignment factor */
+	unw_word_t ret_addr_column;	/* column of return-address register */
+	unw_word_t handler;		/* address of personality-routine */
+	uint16_t abi;
+	uint16_t tag;
+	uint8_t fde_encoding;
+	uint8_t lsda_encoding;
+	unsigned int sized_augmentation : 1;
+	unsigned int have_abi_marker : 1;
+};
+
+static int
+cfi_match_fill(unw_word_t addr, struct cie *cie, struct fde *fde,
+		unw_proc_info_t *pi, int need_unwind_info)
+{
+	struct dwarf_cie_info *dci;
+
+	if (addr < fde->pc_begin || addr >= fde->pc_range)
+		return -1;
+
+	pi->start_ip = fde->pc_begin;
+	pi->end_ip = fde->pc_range;
+	pi->lsda = fde->lsda;
+	pi->handler = cie->handler;
+	pi->format = UNW_INFO_FORMAT_TABLE;
+
+	if (!need_unwind_info)
+		return 0;
+
+	dci = calloc(1, sizeof(*dci));
+	if (!dci)
+		return -ENOMEM;
+
+	dci->cie_instr_start = cie->instr;
+	dci->cie_instr_end = cie->end;
+	dci->fde_instr_start = fde->aug_end;
+	dci->fde_instr_end = fde->end;
+	dci->code_align = cie->code_align;
+	dci->data_align = cie->data_align;
+	dci->ret_addr_column = cie->ret_column;
+	dci->handler = pi->handler;
+	dci->abi = fde->abi;
+	dci->tag = fde->tag;
+	dci->fde_encoding = cie->fde_encoding;
+	dci->lsda_encoding = cie->lsda_encoding;
+	dci->sized_augmentation = !!cie->aug_length;
+	dci->have_abi_marker = cie->have_abi_marker;
+	pi->unwind_info_size = sizeof(*dci);
+	pi->unwind_info = dci;
+
+	return 0;
+}
+
+static int get_cfi_info(int fd, unw_word_t addr, Elf32_Shdr *eh_shdr,
+			unw_proc_info_t *pi, int need_unwind_info)
+{
+	int start, end;
+
+	start = eh_shdr->sh_offset;
+	end = start + eh_shdr->sh_size;
+
+	lseek(fd, start, SEEK_SET);
+
+	/*
+	 * For now, do a slow linear search of the fde matching that address.
+	 * Support for binary search across .eh_frame_hdr will come after.
+	 */
+	for (;;) {
+		int base, val_offset;
+		struct cie cie;
+		struct fde fde;
+		u32 size32, val32;
+		s64 size, val;
+
+		base = lseek(fd, 0, SEEK_CUR);
+		if (base >= end)
+			break;
+
+		if (read(fd, &size32, sizeof(size32)) == -1)
+			return -EINVAL;
+
+		if (size32 == 0xffffffff) {
+			if (read(fd, &size, sizeof(size)) == -1)
+				return -EINVAL;
+			val_offset = lseek(fd, 0, SEEK_CUR);
+			if (read(fd, &val, sizeof(val)) == -1)
+				return -EINVAL;
+		} else {
+			val_offset = lseek(fd, 0, SEEK_CUR);
+			if (read(fd, &val32, sizeof(val32)) == -1)
+				return -EINVAL;
+			val = val32;
+			size = size32;
+		}
+
+		if (val) {
+			int offset, err;
+
+			offset = lseek(fd, 0, SEEK_CUR);
+			lseek(fd, val_offset - val, SEEK_SET);
+			if (parse_cie(fd, &cie)) {
+				pr_debug("Incorrect cie %llx %llx\n", val_offset - val, val);
+				break;
+			}
+
+			lseek(fd, offset, SEEK_SET);
+			if (parse_fde(fd, &fde, &cie, size + val_offset)) {
+				pr_debug("Incorrect fde\n");
+				break;
+			}
+
+			err = cfi_match_fill(addr, &cie, &fde, pi, need_unwind_info);
+			if (err != -1)
+				return err;
+			if (!err)
+				return 0;
+		}
+
+		lseek(fd, val_offset + size, SEEK_SET);
+	}
+
+	return -ENOENT;
+}
+
+static void find_address_location(unw_word_t ip, struct unwind_info *ui,
+				  struct addr_location *al)
+{
+	thread__find_addr_map(ui->thread, ui->session, PERF_RECORD_MISC_USER,
+			   MAP__FUNCTION, ui->thread->pid, ip, al);
+}
+
+static int track_dwarf_instr_addr(unw_proc_info_t *pi, struct unwind_info *ui,
+				  struct addr_location *al)
+{
+	struct dwarf_instr_addr *dia_cie, *dia_fde;
+	struct dwarf_cie_info *dci;
+
+	dia_cie = malloc(sizeof(*dia_cie));
+	if (!dia_cie)
+		return -ENOMEM;
+
+	dci = (struct dwarf_cie_info *)pi->unwind_info;
+
+	dia_cie->start = dci->cie_instr_start - 1;
+	dia_cie->end = dci->cie_instr_end;
+	dia_cie->dso = al->map->dso;
+
+	list_add_tail(&dia_cie->list, &ui->dia_head);
+
+	dia_fde = malloc(sizeof(*dia_fde));
+	if (!dia_fde) {
+		list_del(&dia_cie->list);
+		free(dia_cie);
+		return -ENOMEM;
+	}
+
+	dia_fde->start = dci->fde_instr_start - 1;
+	dia_fde->end = dci->fde_instr_end;
+	dia_fde->dso = al->map->dso;
+
+	list_add_tail(&dia_fde->list, &ui->dia_head);
+
+	return 0;
+}
+
+static int
+find_proc_info(unw_addr_space_t as __used, unw_word_t ip, unw_proc_info_t *pi,
+	      int need_unwind_info, void *arg)
+{
+	int err;
+	int fd;
+	unw_word_t addr;
+	char *path;
+	Elf32_Ehdr ehdr;
+	Elf32_Shdr eh_shdr;
+	struct addr_location al;
+	struct unwind_info *ui = arg;
+
+	find_address_location(ip, ui, &al);
+
+	if (!al.map || !al.map->dso)
+		return -EINVAL;
+
+	path = al.map->dso->long_name;
+	if (!path)
+		return -ENOENT;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		close(fd);
+		return fd;
+	}
+
+	err = parse_elf_headers(fd, &ehdr);
+	if (err) {
+		close(fd);
+		return err;
+	}
+
+	err = eh_frame_section(fd, &ehdr, &eh_shdr);
+	if (err) {
+		close(fd);
+		return err;
+	}
+
+	if (ehdr.e_type == ET_DYN)
+		addr = al.map->map_ip(al.map, ip);
+	else
+		addr = ip;
+
+	err = get_cfi_info(fd, addr, &eh_shdr, pi, need_unwind_info);
+	if (err) {
+		close(fd);
+		return err;
+	}
+
+	if (need_unwind_info) {
+		err = track_dwarf_instr_addr(pi, ui, &al);
+		close(fd);
+		return err;
+	}
+
+	close(fd);
+
+	return 0;
+}
+
+static int access_fpreg(unw_addr_space_t __used as, unw_regnum_t __used num,
+			unw_fpreg_t __used *val, int __used __write,
+			void __used *arg)
+{
+	pr_warning("Unwind: fpreg unsupported yet\n");
+
+	return -1;
+}
+
+static int get_dyn_info_list_addr(unw_addr_space_t __used as,
+				  unw_word_t __used *dil_addr,
+				  void __used *arg)
+{
+	return -UNW_ENOINFO;
+}
+
+static int resume(unw_addr_space_t __used as, unw_cursor_t __used *cu,
+		  void __used *arg)
+{
+	pr_warning("Unwind: resume\n");
+
+	return 0;
+}
+
+static int
+get_proc_name(unw_addr_space_t __used as, unw_word_t __used addr,
+		char __used *bufp, size_t __used buf_len,
+		unw_word_t __used *offp, void __used *arg)
+{
+	*offp = 0;
+
+	return 0;
+}
+
+static int access_dso_mem(struct unwind_info *ui, unw_word_t addr,
+			  unw_word_t *valp)
+{
+	struct thread *thread = ui->thread;
+	struct perf_session *session = ui->session;
+	struct addr_location al;
+	int fd;
+	u64 offset;
+
+	thread__find_addr_map(thread, session, PERF_RECORD_MISC_USER,
+			   MAP__FUNCTION, thread->pid, addr, &al);
+	if (!al.map) {
+		pr_debug("unwind: not found map for %lx\n", (unsigned long)addr);
+		return -1;
+	}
+
+	offset = al.map->map_ip(al.map, addr);
+	fd = open(al.map->dso->long_name, O_RDONLY);
+	if (fd < 0) {
+		const char *name;
+
+		name = al.map ? al.map->dso->long_name : "Sais pas";
+		pr_debug("unwind: Can't open dso %s\n", name);
+
+		return -1;
+	}
+
+	if (lseek(fd, offset, SEEK_SET) == -1) {
+		close(fd);
+		pr_err("unwind: Can't seek\n");
+		return -1;
+	}
+	if (read(fd, valp, sizeof(*valp)) == -1) {
+		close(fd);
+		return -errno;
+	}
+	close(fd);
+
+	pr_debug("access mem offset: %llx va: %lx val: %lx\n",
+			offset, (unsigned long)addr, (unsigned long)*valp);
+
+	return 0;
+}
+
+static int access_dwarf_instr(struct unwind_info *ui, unw_word_t addr,
+			  unw_word_t *valp)
+{
+	struct dwarf_instr_addr *dia;
+	int found = 0;
+	int fd;
+
+	/*
+	 * This is quite crappy. There may be conflicts between dso adresses
+	 * here. Probably we only need to keep track of the last dso here.
+	 */
+	list_for_each_entry(dia, &ui->dia_head, list) {
+		if (addr >= dia->start && addr < dia->end) {
+			found = 1;
+			break;
+		}
+	}
+
+	if (!found)
+		return -ENOENT;
+
+	fd = open(dia->dso->long_name, O_RDONLY);
+	if (fd < 0)
+		return -EINVAL;
+
+	lseek(fd, addr, SEEK_SET);
+	if (read(fd, valp, sizeof(*valp)) == -1)
+		return -errno;
+
+	close(fd);
+
+	return 0;
+}
+
+static int reg_value(unw_word_t *valp, struct user_regs *regs, int id,
+		     u64 sample_regs)
+{
+	int i, idx = 0;
+
+	if (!(sample_regs & (1 << id)))
+		return -EINVAL;
+
+	for (i = 0; i < id; i++) {
+		if (sample_regs & (1 << i))
+			idx++;
+	}
+
+	*valp = regs->regs[idx];
+
+	return 0;
+}
+
+static int access_mem(unw_addr_space_t __used as,
+                      unw_word_t addr, unw_word_t *valp,
+                      int __write, void *arg)
+{
+	struct unwind_info *ui = arg;
+	struct user_stack_dump *stack = &ui->sample->stack;
+	unw_word_t start, end;
+	unw_word_t *val;
+	int offset;
+	int ret;
+
+	/* Don't support write, probably not needed */
+	if (__write || !stack || !ui->sample->uregs.version) {
+		*valp = 0;
+		return 0;
+	}
+
+	ret = reg_value(&start, &ui->sample->uregs, PERF_X86_32_REG_ESP,
+			ui->session->sample_uregs);
+	if (ret)
+		return ret;
+
+	end = start + stack->size;
+
+	ret = access_dwarf_instr(ui, addr, valp);
+	if (!ret)
+		return 0;
+
+	if (addr < start || addr + sizeof(unw_word_t) >= end) {
+		ret = access_dso_mem(ui, addr, valp);
+		if (ret) {
+			pr_debug("access_mem %p not inside range %p-%p\n",
+				(void *)addr, (void *)start, (void *)end);
+			*valp = 0;
+			return ret;
+		}
+		return 0;
+	}
+
+	offset = addr - start;
+	val = (void *)&stack->data[offset];
+	*valp = *val;
+
+	pr_debug("access_mem %p %lx\n", (void *)addr, (unsigned long)*valp);
+
+	return 0;
+}
+
+static int access_reg(unw_addr_space_t __used as,
+                      unw_regnum_t regnum, unw_word_t *valp,
+                      int __write, void *arg)
+{
+	struct unwind_info *ui = arg;
+	int id, ret;
+
+	/* Don't support write, I suspect we don't need it */
+	if (__write) {
+		pr_err("access_reg w %d\n", regnum);
+		return 0;
+	}
+
+	if (!ui->sample->uregs.version) {
+		*valp = 0;
+		return 0;
+	}
+
+	switch (regnum) {
+	case UNW_X86_EAX:
+		id = PERF_X86_32_REG_EAX;
+		break;
+	case UNW_X86_EDX:
+		id = PERF_X86_32_REG_EDX;
+		break;
+	case UNW_X86_ECX:
+		id = PERF_X86_32_REG_ECX;
+		break;
+	case UNW_X86_EBX:
+		id = PERF_X86_32_REG_EBX;
+		break;
+	case UNW_X86_ESI:
+		id = PERF_X86_32_REG_ESI;
+		break;
+	case UNW_X86_EDI:
+		id = PERF_X86_32_REG_EDI;
+		break;
+	case UNW_X86_EBP:
+		id = PERF_X86_32_REG_EBP;
+		break;
+	case UNW_X86_ESP:
+		id = PERF_X86_32_REG_ESP;
+		break;
+	case UNW_X86_EIP:
+		id = PERF_X86_32_REG_EIP;
+		break;
+	default:
+		pr_err("can't read reg %d\n", regnum);
+		return -EINVAL;
+	}
+
+	ret = reg_value(valp, &ui->sample->uregs, id, ui->session->sample_uregs);
+	if (ret) {
+		pr_err("can't read reg %d\n", regnum);
+		return ret;
+	}
+
+	pr_debug("reg: %d val: %lx\n", regnum, (unsigned long)*valp);
+
+	return 0;
+}
+
+static void put_unwind_info(unw_addr_space_t __used as, unw_proc_info_t *pi,
+			    void *arg)
+{
+	struct unwind_info *ui = arg;
+	struct dwarf_instr_addr *dia, *tmp;
+
+	if (pi->unwind_info) {
+		free(pi->unwind_info);
+		pi->unwind_info = NULL;
+	}
+
+	list_for_each_entry_safe(dia, tmp, &ui->dia_head, list) {
+		list_del(&dia->list);
+		free(dia);
+	}
+}
+
+static unw_accessors_t accessors = {
+	.find_proc_info		= find_proc_info,
+	.put_unwind_info	= put_unwind_info,
+	.get_dyn_info_list_addr	= get_dyn_info_list_addr,
+	.access_mem		= access_mem,
+	.access_reg		= access_reg,
+	.access_fpreg		= access_fpreg,
+	.resume			= resume,
+	.get_proc_name		= get_proc_name,
+};
+
+static int
+append_dwarf_chain(struct dwarf_callchain *callchain,
+		   struct addr_location *al)
+{
+	struct dwarf_callchain_entry *entry;
+
+	entry = calloc(sizeof(*entry), 1);
+	if (!entry)
+		return -ENOMEM;
+
+	entry->ip = al->addr;
+	entry->ms.map = al->map;
+	entry->ms.sym = al->sym;
+
+	list_add_tail(&entry->list, &callchain->chain_head);
+
+	callchain->nb++;
+
+	return 0;
+}
+
+
+static void callchain_unwind_release(struct dwarf_callchain *callchain)
+{
+	struct dwarf_callchain_entry *entry, *tmp;
+
+	list_for_each_entry_safe(entry, tmp, &callchain->chain_head, list) {
+		list_del(&entry->list);
+		free(entry);
+	}
+
+	free(callchain);
+}
+
+
+struct dwarf_callchain *callchain_unwind(struct perf_session *session,
+					 struct thread *thread,
+					 struct sample_data *data)
+{
+	struct dwarf_callchain *callchain;
+	unw_addr_space_t addr_space;
+	struct addr_location al;
+	struct unwind_info ui;
+	unw_cursor_t c;
+	unw_word_t ip;
+	int ret;
+
+	if (!data->uregs.version)
+		return NULL;
+
+	callchain = malloc(sizeof(*callchain));
+	if (!callchain)
+		return ERR_PTR(-ENOMEM);
+
+	callchain->nb = 0;
+	INIT_LIST_HEAD(&callchain->chain_head);
+
+	ret = reg_value(&ip, &data->uregs, PERF_X86_32_REG_EIP,
+			session->sample_uregs);
+	if (ret)
+		return ERR_PTR(ret);
+
+	thread__find_addr_location(thread, session,
+				   PERF_RECORD_MISC_USER,
+				   MAP__FUNCTION, thread->pid,
+				   ip, &al, NULL);
+
+	ret = append_dwarf_chain(callchain, &al);
+	if (ret)
+		goto fail;
+
+	memset(&ui, 0, sizeof(ui));
+	addr_space = unw_create_addr_space(&accessors, 0);
+	if (!addr_space) {
+		pr_err("Can't create unwind address space\n");
+		goto fail;
+	}
+
+	pr_debug("\n----- %s -----\n", al.map ? al.map->dso->long_name : "Unknown");
+	ui.sample = data;
+	ui.thread = thread;
+	ui.session = session;
+	INIT_LIST_HEAD(&ui.dia_head);
+
+	ret = unw_init_remote(&c, addr_space, &ui);
+	switch (ret) {
+	case UNW_EINVAL:
+		pr_err("Unwind: only supports local\n");
+		break;
+	case UNW_EUNSPEC:
+		pr_err("Unwind: unspecified error\n");
+		break;
+	case UNW_EBADREG:
+		pr_err("Unwind: register unavailable\n");
+		break;
+	default:
+		break;
+	}
+
+	if (ret)
+		goto fail_addrspace;
+
+	if (al.sym)
+		pr_debug("%s:ip = %llx\n", al.sym->name, al.addr);
+	else
+		pr_debug("ip = %llx\n", al.addr);
+
+	while (unw_step(&c) > 0) {
+		char name[250];
+		unsigned int offset;
+
+		unw_get_reg(&c, UNW_REG_IP, &ip);
+
+		thread__find_addr_location(thread, session,
+				   PERF_RECORD_MISC_USER,
+				   MAP__FUNCTION, thread->pid,
+				   ip, &al, NULL);
+
+		unw_get_proc_name(&c, name, sizeof(name), &offset);
+		if (al.sym)
+			pr_debug("%s:ip = %lx\n", al.sym->name, (unsigned long)ip);
+		else
+			pr_debug("ip = %lx (%llx)\n", (unsigned long)ip,
+					al.map ? al.map->map_ip(al.map, ip) : (u64)ip);
+
+		ret = append_dwarf_chain(callchain, &al);
+		if (ret)
+			goto fail_addrspace;
+	}
+
+	unw_destroy_addr_space(addr_space);
+
+	return callchain;
+
+fail_addrspace:
+	unw_destroy_addr_space(addr_space);
+fail:
+	callchain_unwind_release(callchain);
+	return ERR_PTR(ret);
+}
+
+#else /* LIBUNWIND_SUPPORT */
+
+struct dwarf_callchain *callchain_unwind(struct perf_session *session __used,
+					 struct thread *thread __used,
+					 struct sample_data *data __used)
+{
+	return NULL;
+}
+
+#endif
-- 
1.6.2.3


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2010-10-22 19:16 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-10-22 19:13 [RFC v2] perf: Dwarf cfi based user callchains Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 01/11] uaccess: New copy_from_user_gup() API Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 02/11] perf: Unified API to record selective sets of arch registers Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 03/11] perf: Add ability to dump user regs Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 04/11] perf: Add ability to dump part of the user stack Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 05/11] perf: New attribute to filter out user callchains Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 06/11] perf: Support for dwarf mode callchain on perf record Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 07/11] perf: Build with dwarf cfi Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 08/11] perf: Support for error passed over pointers Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 09/11] perf: Add libunwind dependency for dwarf cfi unwinding Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 10/11] perf: Support user regs and stack in sample parsing Frederic Weisbecker
2010-10-22 19:13 ` [RFC PATCH 11/11] perf: Support for dwarf cfi unwinding on post processing Frederic Weisbecker

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome