perf: Add ability to attach user level registers dump to sample
authorJiri Olsa <jolsa@redhat.com>
Tue, 7 Aug 2012 13:20:37 +0000 (15:20 +0200)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 10 Aug 2012 14:31:26 +0000 (11:31 -0300)
Introducing PERF_SAMPLE_REGS_USER sample type bit to trigger the dump of
user level registers on sample. Registers we want to dump are specified
by sample_regs_user bitmask.

Only user level registers are dumped at the moment. Meaning the register
values 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).

The layout of the sample_regs_user bitmap is described in
asm/perf_regs.h for archs that support register dump.

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

Original-patch-by: Frederic Weisbecker <fweisbec@gmail.com>
[ Dump registers ABI specification. ]
Signed-off-by: Jiri Olsa <jolsa@redhat.com>
Suggested-by: Stephane Eranian <eranian@google.com>
Cc: "Frank Ch. Eigler" <fche@redhat.com>
Cc: Arun Sharma <asharma@fb.com>
Cc: Benjamin Redelings <benjamin.redelings@nescent.org>
Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Frank Ch. Eigler <fche@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Stephane Eranian <eranian@google.com>
Cc: Tom Zanussi <tzanussi@gmail.com>
Cc: Ulrich Drepper <drepper@gmail.com>
Link: http://lkml.kernel.org/r/1344345647-11536-3-git-send-email-jolsa@redhat.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
arch/x86/kernel/perf_regs.c
include/linux/perf_event.h
include/linux/perf_regs.h
kernel/events/core.c

index 3d69235..c5a3e5c 100644 (file)
@@ -1,5 +1,7 @@
 #include <linux/errno.h>
 #include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/perf_event.h>
 #include <linux/bug.h>
 #include <linux/stddef.h>
 #include <asm/perf_regs.h>
@@ -71,6 +73,11 @@ int perf_reg_validate(u64 mask)
 
        return 0;
 }
+
+u64 perf_reg_abi(struct task_struct *task)
+{
+       return PERF_SAMPLE_REGS_ABI_32;
+}
 #else /* CONFIG_X86_64 */
 #define REG_NOSUPPORT ((1ULL << PERF_REG_X86_DS) | \
                       (1ULL << PERF_REG_X86_ES) | \
@@ -87,4 +94,12 @@ int perf_reg_validate(u64 mask)
 
        return 0;
 }
+
+u64 perf_reg_abi(struct task_struct *task)
+{
+       if (test_tsk_thread_flag(task, TIF_IA32))
+               return PERF_SAMPLE_REGS_ABI_32;
+       else
+               return PERF_SAMPLE_REGS_ABI_64;
+}
 #endif /* CONFIG_X86_32 */
index 7602ccb..3d4d847 100644 (file)
@@ -130,8 +130,9 @@ enum perf_event_sample_format {
        PERF_SAMPLE_STREAM_ID                   = 1U << 9,
        PERF_SAMPLE_RAW                         = 1U << 10,
        PERF_SAMPLE_BRANCH_STACK                = 1U << 11,
+       PERF_SAMPLE_REGS_USER                   = 1U << 12,
 
-       PERF_SAMPLE_MAX = 1U << 12,             /* non-ABI */
+       PERF_SAMPLE_MAX = 1U << 13,             /* non-ABI */
 };
 
 /*
@@ -163,6 +164,15 @@ enum perf_branch_sample_type {
         PERF_SAMPLE_BRANCH_HV)
 
 /*
+ * Values to determine ABI of the registers dump.
+ */
+enum perf_sample_regs_abi {
+       PERF_SAMPLE_REGS_ABI_NONE       = 0,
+       PERF_SAMPLE_REGS_ABI_32         = 1,
+       PERF_SAMPLE_REGS_ABI_64         = 2,
+};
+
+/*
  * The format of the data returned by read() on a perf event fd,
  * as specified by attr.read_format:
  *
@@ -194,6 +204,7 @@ enum perf_event_read_format {
 #define PERF_ATTR_SIZE_VER0    64      /* sizeof first published struct */
 #define PERF_ATTR_SIZE_VER1    72      /* add: config2 */
 #define PERF_ATTR_SIZE_VER2    80      /* add: branch_sample_type */
+#define PERF_ATTR_SIZE_VER3    88      /* add: sample_regs_user */
 
 /*
  * Hardware event_id to monitor via a performance monitoring event:
@@ -271,7 +282,13 @@ struct perf_event_attr {
                __u64           bp_len;
                __u64           config2; /* extension of config1 */
        };
-       __u64   branch_sample_type; /* enum branch_sample_type */
+       __u64   branch_sample_type; /* enum perf_branch_sample_type */
+
+       /*
+        * Defines set of user regs to dump on samples.
+        * See asm/perf_regs.h for details.
+        */
+       __u64   sample_regs_user;
 };
 
 /*
@@ -548,6 +565,9 @@ enum perf_event_type {
         *        char                  data[size];}&& PERF_SAMPLE_RAW
         *
         *      { u64 from, to, flags } lbr[nr];} && PERF_SAMPLE_BRANCH_STACK
+        *
+        *      { u64                   abi; # enum perf_sample_regs_abi
+        *        u64                   regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER
         * };
         */
        PERF_RECORD_SAMPLE                      = 9,
@@ -609,6 +629,7 @@ struct perf_guest_info_callbacks {
 #include <linux/static_key.h>
 #include <linux/atomic.h>
 #include <linux/sysfs.h>
+#include <linux/perf_regs.h>
 #include <asm/local.h>
 
 struct perf_callchain_entry {
@@ -654,6 +675,11 @@ struct perf_branch_stack {
        struct perf_branch_entry        entries[0];
 };
 
+struct perf_regs_user {
+       __u64           abi;
+       struct pt_regs  *regs;
+};
+
 struct task_struct;
 
 /*
@@ -1133,6 +1159,7 @@ struct perf_sample_data {
        struct perf_callchain_entry     *callchain;
        struct perf_raw_record          *raw;
        struct perf_branch_stack        *br_stack;
+       struct perf_regs_user           regs_user;
 };
 
 static inline void perf_sample_data_init(struct perf_sample_data *data,
@@ -1142,7 +1169,9 @@ static inline void perf_sample_data_init(struct perf_sample_data *data,
        data->addr = addr;
        data->raw  = NULL;
        data->br_stack = NULL;
-       data->period    = period;
+       data->period = period;
+       data->regs_user.abi = PERF_SAMPLE_REGS_ABI_NONE;
+       data->regs_user.regs = NULL;
 }
 
 extern void perf_output_sample(struct perf_output_handle *handle,
index a2f1a98..3c73d5f 100644 (file)
@@ -5,6 +5,7 @@
 #include <asm/perf_regs.h>
 u64 perf_reg_value(struct pt_regs *regs, int idx);
 int perf_reg_validate(u64 mask);
+u64 perf_reg_abi(struct task_struct *task);
 #else
 static inline u64 perf_reg_value(struct pt_regs *regs, int idx)
 {
@@ -15,5 +16,10 @@ static inline int perf_reg_validate(u64 mask)
 {
        return mask ? -ENOSYS : 0;
 }
+
+static inline u64 perf_reg_abi(struct task_struct *task)
+{
+       return PERF_SAMPLE_REGS_ABI_NONE;
+}
 #endif /* CONFIG_HAVE_PERF_REGS */
 #endif /* _LINUX_PERF_REGS_H */
index b7935fc..d3ce975 100644 (file)
@@ -3756,6 +3756,37 @@ int perf_unregister_guest_info_callbacks(struct perf_guest_info_callbacks *cbs)
 }
 EXPORT_SYMBOL_GPL(perf_unregister_guest_info_callbacks);
 
+static void
+perf_output_sample_regs(struct perf_output_handle *handle,
+                       struct pt_regs *regs, u64 mask)
+{
+       int bit;
+
+       for_each_set_bit(bit, (const unsigned long *) &mask,
+                        sizeof(mask) * BITS_PER_BYTE) {
+               u64 val;
+
+               val = perf_reg_value(regs, bit);
+               perf_output_put(handle, val);
+       }
+}
+
+static void perf_sample_regs_user(struct perf_regs_user *regs_user,
+                                 struct pt_regs *regs)
+{
+       if (!user_mode(regs)) {
+               if (current->mm)
+                       regs = task_pt_regs(current);
+               else
+                       regs = NULL;
+       }
+
+       if (regs) {
+               regs_user->regs = regs;
+               regs_user->abi  = perf_reg_abi(current);
+       }
+}
+
 static void __perf_event_header__init_id(struct perf_event_header *header,
                                         struct perf_sample_data *data,
                                         struct perf_event *event)
@@ -4016,6 +4047,23 @@ void perf_output_sample(struct perf_output_handle *handle,
                        perf_output_put(handle, nr);
                }
        }
+
+       if (sample_type & PERF_SAMPLE_REGS_USER) {
+               u64 abi = data->regs_user.abi;
+
+               /*
+                * If there are no regs to dump, notice it through
+                * first u64 being zero (PERF_SAMPLE_REGS_ABI_NONE).
+                */
+               perf_output_put(handle, abi);
+
+               if (abi) {
+                       u64 mask = event->attr.sample_regs_user;
+                       perf_output_sample_regs(handle,
+                                               data->regs_user.regs,
+                                               mask);
+               }
+       }
 }
 
 void perf_prepare_sample(struct perf_event_header *header,
@@ -4067,6 +4115,20 @@ void perf_prepare_sample(struct perf_event_header *header,
                }
                header->size += size;
        }
+
+       if (sample_type & PERF_SAMPLE_REGS_USER) {
+               /* regs dump ABI info */
+               int size = sizeof(u64);
+
+               perf_sample_regs_user(&data->regs_user, regs);
+
+               if (data->regs_user.regs) {
+                       u64 mask = event->attr.sample_regs_user;
+                       size += hweight64(mask) * sizeof(u64);
+               }
+
+               header->size += size;
+       }
 }
 
 static void perf_event_output(struct perf_event *event,
@@ -6142,6 +6204,10 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr,
                        attr->branch_sample_type = mask;
                }
        }
+
+       if (attr->sample_type & PERF_SAMPLE_REGS_USER)
+               ret = perf_reg_validate(attr->sample_regs_user);
+
 out:
        return ret;