Introducing PERF_SAMPLE_STACK_USER sample type bit to trigger
the dump of the user level stack on sample. The size of the
dump is specified by sample_stack_user value.

Being 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.

Signed-off-by: Jiri Olsa <[email protected]>
Signed-off-by: Frederic Weisbecker <[email protected]>
---
 include/linux/perf_event.h |   18 ++++++-
 kernel/events/core.c       |  121 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 137 insertions(+), 2 deletions(-)

diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index a4c53da..b4663b3 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -131,8 +131,9 @@ enum perf_event_sample_format {
        PERF_SAMPLE_RAW                         = 1U << 10,
        PERF_SAMPLE_BRANCH_STACK                = 1U << 11,
        PERF_SAMPLE_REGS_USER                   = 1U << 12,
+       PERF_SAMPLE_STACK_USER                  = 1U << 13,
 
-       PERF_SAMPLE_MAX = 1U << 13,             /* non-ABI */
+       PERF_SAMPLE_MAX = 1U << 14,             /* non-ABI */
 };
 
 /*
@@ -196,6 +197,7 @@ enum perf_event_read_format {
 #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 */
+#define PERF_ATTR_SIZE_VER4    96      /* add: sample_stack_user */
 
 /*
  * Hardware event_id to monitor via a performance monitoring event:
@@ -280,6 +282,14 @@ struct perf_event_attr {
         * See asm/perf_regs.h for details.
         */
        __u64   sample_regs_user;
+
+       /*
+        * Defines size of the user stack to dump on samples.
+        */
+       __u32   sample_stack_user;
+
+       /* Align to u64. */
+       __u32   __reserved_2;
 };
 
 /*
@@ -559,6 +569,10 @@ enum perf_event_type {
         *
         *      { u64                   available;
         *        u64                   regs[weight(mask)]; } && 
PERF_SAMPLE_REGS_USER
+        *
+        *      { u64                   size;
+        *        char                  data[size];
+        *        u64                   dyn_size; } && PERF_SAMPLE_STACK_USER
         * };
         */
        PERF_RECORD_SAMPLE                      = 9,
@@ -1146,6 +1160,7 @@ struct perf_sample_data {
        struct perf_raw_record          *raw;
        struct perf_branch_stack        *br_stack;
        struct pt_regs                  *regs_user;
+       u64                             stack_user_size;
 };
 
 static inline void perf_sample_data_init(struct perf_sample_data *data,
@@ -1157,6 +1172,7 @@ static inline void perf_sample_data_init(struct 
perf_sample_data *data,
        data->br_stack = NULL;
        data->period = period;
        data->regs_user = NULL;
+       data->stack_user_size = 0;
 }
 
 extern void perf_output_sample(struct perf_output_handle *handle,
diff --git a/kernel/events/core.c b/kernel/events/core.c
index e817e32..8a9cf80 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -3771,6 +3771,45 @@ perf_output_sample_regs(struct perf_output_handle 
*handle,
        }
 }
 
+static void
+perf_output_sample_ustack(struct perf_output_handle *handle, u64 dump_size,
+                         struct pt_regs *regs)
+{
+       /* Case of a kernel thread, nothing to dump */
+       if (!regs) {
+               u64 size = 0;
+               perf_output_put(handle, size);
+       } else {
+               unsigned long sp;
+               unsigned int rem;
+               u64 dyn_size;
+
+               /*
+                * We dump:
+                * static size
+                *   - the size requested by user or the best one we can fit
+                *     in to the sample max size
+                * data
+                *   - user stack dump data
+                * dynamic size
+                *   - the actual dumped size
+                */
+
+               /* Static size. */
+               perf_output_put(handle, dump_size);
+
+               /* Data. */
+               sp = user_stack_pointer(regs);
+               rem = __output_copy_user(handle, (void *) sp, dump_size);
+               dyn_size = dump_size - rem;
+
+               perf_output_skip(handle, rem);
+
+               /* Dynamic size. */
+               perf_output_put(handle, dyn_size);
+       }
+}
+
 static struct pt_regs *perf_sample_regs_user(struct pt_regs *regs)
 {
        if (!user_mode(regs)) {
@@ -4060,6 +4099,11 @@ void perf_output_sample(struct perf_output_handle 
*handle,
                                                mask);
                }
        }
+
+       if (sample_type & PERF_SAMPLE_STACK_USER)
+               perf_output_sample_ustack(handle,
+                                         data->stack_user_size,
+                                         data->regs_user);
 }
 
 void perf_prepare_sample(struct perf_event_header *header,
@@ -4124,6 +4168,66 @@ void perf_prepare_sample(struct perf_event_header 
*header,
 
                header->size += size;
        }
+
+       if (sample_type & PERF_SAMPLE_STACK_USER) {
+               /*
+                * 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.
+                */
+               u16 size = sizeof(u64);
+               u16 stack_size = 0;
+
+               if (!data->regs_user)
+                       data->regs_user = perf_sample_regs_user(regs);
+
+               /*
+                * 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.
+                */
+               if (data->regs_user) {
+                       /*
+                        * XXX We need to check if we fit in with the
+                        * requested stack size into the sample. If we
+                        * don't, we customize the stack size to fit in.
+                        *
+                        * Either we need PERF_SAMPLE_STACK_USER bit to
+                        * be allways processed as the last one or have
+                        * additional check added in case new sample type
+                        * is added, because we could eat up the rest of
+                        * the sample size.
+                        */
+
+                       u16 header_size = header->size;
+
+                       stack_size = (u16) event->attr.sample_stack_user;
+
+                       /*
+                        * Current header size plus static size and
+                        * dynamic size.
+                        */
+                       header_size += 2 * sizeof(u64);
+
+                       /* Do we fit in with the current stack dump size? */
+                       if ((u16) (header_size + stack_size) < header_size) {
+                               /*
+                                * If we overflow the maximum size for the
+                                * sample, we customize the stack dump size
+                                * to fit in.
+                                */
+                               stack_size = USHRT_MAX - header_size
+                                            - sizeof(u64);
+                               stack_size = round_up(stack_size, sizeof(u64));
+                       }
+
+                       size += sizeof(u64) + stack_size;
+               }
+
+               data->stack_user_size = stack_size;
+               header->size += size;
+       }
 }
 
 static void perf_event_output(struct perf_event *event,
@@ -6174,8 +6278,23 @@ static int perf_copy_attr(struct perf_event_attr __user 
*uattr,
                }
        }
 
-       if (attr->sample_type & PERF_SAMPLE_REGS_USER)
+       if (attr->sample_type & PERF_SAMPLE_REGS_USER) {
                ret = perf_reg_validate(attr->sample_regs_user);
+               if (ret)
+                       return ret;
+       }
+
+       if (attr->sample_type & PERF_SAMPLE_STACK_USER) {
+               /*
+                * We have __u32 type for the size, but so far
+                * we can only use __u16 as maximum due to the
+                * __u16 sample size limit.
+                */
+               if (attr->sample_stack_user >= USHRT_MAX)
+                       ret = -EINVAL;
+               else if (!IS_ALIGNED(attr->sample_stack_user, sizeof(u64)))
+                       ret = -EINVAL;
+       }
 
 out:
        return ret;
-- 
1.7.7.6

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to