x86, ptrace: new ptrace BTS API
authorMarkus Metzger <markus.t.metzger@intel.com>
Wed, 30 Jan 2008 12:31:20 +0000 (13:31 +0100)
committerIngo Molnar <mingo@elte.hu>
Wed, 30 Jan 2008 12:31:20 +0000 (13:31 +0100)
Here's the new ptrace BTS API that supports two different overflow handling mechanisms (wrap-around and buffer-full-signal) to support two different use cases (debugging and profiling).

It further combines buffer allocation and configuration.

Opens:
- memory rlimit
- overflow signal

What would be the right signal to use?

Signed-off-by: Markus Metzger <markus.t.metzger@intel.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
arch/x86/kernel/ds.c
arch/x86/kernel/ptrace.c
include/asm-x86/ds.h
include/asm-x86/ptrace-abi.h
include/asm-x86/ptrace.h

index e7855de..6eb5d49 100644 (file)
@@ -177,18 +177,20 @@ static inline void set_info_data(char *base, unsigned long value)
 }
 
 
-int ds_allocate(void **dsp, size_t bts_size_in_records)
+int ds_allocate(void **dsp, size_t bts_size_in_bytes)
 {
-       size_t bts_size_in_bytes = 0;
-       void *bts = 0;
-       void *ds = 0;
+       size_t bts_size_in_records;
+       void *bts;
+       void *ds;
 
        if (!ds_cfg.sizeof_ds || !ds_cfg.sizeof_bts)
                return -EOPNOTSUPP;
 
-       if (bts_size_in_records < 0)
+       if (bts_size_in_bytes < 0)
                return -EINVAL;
 
+       bts_size_in_records =
+               bts_size_in_bytes / ds_cfg.sizeof_bts;
        bts_size_in_bytes =
                bts_size_in_records * ds_cfg.sizeof_bts;
 
@@ -233,9 +235,21 @@ int ds_get_bts_size(void *ds)
        if (!ds_cfg.sizeof_ds || !ds_cfg.sizeof_bts)
                return -EOPNOTSUPP;
 
+       if (!ds)
+               return 0;
+
        size_in_bytes =
                get_bts_absolute_maximum(ds) -
                get_bts_buffer_base(ds);
+       return size_in_bytes;
+}
+
+int ds_get_bts_end(void *ds)
+{
+       size_t size_in_bytes = ds_get_bts_size(ds);
+
+       if (size_in_bytes <= 0)
+               return size_in_bytes;
 
        return size_in_bytes / ds_cfg.sizeof_bts;
 }
@@ -254,6 +268,38 @@ int ds_get_bts_index(void *ds)
        return index_offset_in_bytes / ds_cfg.sizeof_bts;
 }
 
+int ds_set_overflow(void *ds, int method)
+{
+       switch (method) {
+       case DS_O_SIGNAL:
+               return -EOPNOTSUPP;
+       case DS_O_WRAP:
+               return 0;
+       default:
+               return -EINVAL;
+       }
+}
+
+int ds_get_overflow(void *ds)
+{
+       return DS_O_WRAP;
+}
+
+int ds_clear(void *ds)
+{
+       int bts_size = ds_get_bts_size(ds);
+       void *bts_base;
+
+       if (bts_size <= 0)
+               return bts_size;
+
+       bts_base = get_bts_buffer_base(ds);
+       memset(bts_base, 0, bts_size);
+
+       set_bts_index(ds, bts_base);
+       return 0;
+}
+
 int ds_read_bts(void *ds, size_t index, struct bts_struct *out)
 {
        void *bts;
index 3e78c12..18972a3 100644 (file)
 #include <asm/ds.h>
 
 
-/*
- * The maximal size of a BTS buffer per traced task in number of BTS
- * records.
- */
-#define PTRACE_BTS_BUFFER_MAX 4000
-
 /*
  * does not yet catch signals sent when the child dies.
  * in exit.c or in signal.c.
@@ -466,17 +460,12 @@ static int ptrace_set_debugreg(struct task_struct *child,
        return 0;
 }
 
-static int ptrace_bts_max_buffer_size(void)
-{
-       return PTRACE_BTS_BUFFER_MAX;
-}
-
-static int ptrace_bts_get_buffer_size(struct task_struct *child)
+static int ptrace_bts_get_size(struct task_struct *child)
 {
        if (!child->thread.ds_area_msr)
                return -ENXIO;
 
-       return ds_get_bts_size((void *)child->thread.ds_area_msr);
+       return ds_get_bts_index((void *)child->thread.ds_area_msr);
 }
 
 static int ptrace_bts_read_record(struct task_struct *child,
@@ -485,7 +474,7 @@ static int ptrace_bts_read_record(struct task_struct *child,
 {
        struct bts_struct ret;
        int retval;
-       int bts_size;
+       int bts_end;
        int bts_index;
 
        if (!child->thread.ds_area_msr)
@@ -494,15 +483,15 @@ static int ptrace_bts_read_record(struct task_struct *child,
        if (index < 0)
                return -EINVAL;
 
-       bts_size = ds_get_bts_size((void *)child->thread.ds_area_msr);
-       if (bts_size <= index)
+       bts_end = ds_get_bts_end((void *)child->thread.ds_area_msr);
+       if (bts_end <= index)
                return -EINVAL;
 
        /* translate the ptrace bts index into the ds bts index */
        bts_index = ds_get_bts_index((void *)child->thread.ds_area_msr);
        bts_index -= (index + 1);
        if (bts_index < 0)
-               bts_index += bts_size;
+               bts_index += bts_end;
 
        retval = ds_read_bts((void *)child->thread.ds_area_msr,
                             bts_index, &ret);
@@ -530,19 +519,97 @@ static int ptrace_bts_write_record(struct task_struct *child,
        return sizeof(*in);
 }
 
-static int ptrace_bts_config(struct task_struct *child,
-                            unsigned long options)
+static int ptrace_bts_clear(struct task_struct *child)
 {
-       unsigned long debugctl_mask = ds_debugctl_mask();
-       int retval;
+       if (!child->thread.ds_area_msr)
+               return -ENXIO;
 
-       retval = ptrace_bts_get_buffer_size(child);
-       if (retval < 0)
-               return retval;
-       if (retval == 0)
+       return ds_clear((void *)child->thread.ds_area_msr);
+}
+
+static int ptrace_bts_drain(struct task_struct *child,
+                           struct bts_struct __user *out)
+{
+       int end, i;
+       void *ds = (void *)child->thread.ds_area_msr;
+
+       if (!ds)
                return -ENXIO;
 
-       if (options & PTRACE_BTS_O_TRACE_TASK) {
+       end = ds_get_bts_index(ds);
+       if (end <= 0)
+               return end;
+
+       for (i = 0; i < end; i++, out++) {
+               struct bts_struct ret;
+               int retval;
+
+               retval = ds_read_bts(ds, i, &ret);
+               if (retval < 0)
+                       return retval;
+
+               if (copy_to_user(out, &ret, sizeof(ret)))
+                       return -EFAULT;
+       }
+
+       ds_clear(ds);
+
+       return i;
+}
+
+static int ptrace_bts_config(struct task_struct *child,
+                            const struct ptrace_bts_config __user *ucfg)
+{
+       struct ptrace_bts_config cfg;
+       unsigned long debugctl_mask;
+       int bts_size, ret;
+       void *ds;
+
+       if (copy_from_user(&cfg, ucfg, sizeof(cfg)))
+               return -EFAULT;
+
+       bts_size = 0;
+       ds = (void *)child->thread.ds_area_msr;
+       if (ds) {
+               bts_size = ds_get_bts_size(ds);
+               if (bts_size < 0)
+                       return bts_size;
+       }
+
+       if (bts_size != cfg.size) {
+               ret = ds_free((void **)&child->thread.ds_area_msr);
+               if (ret < 0)
+                       return ret;
+
+               if (cfg.size > 0)
+                       ret = ds_allocate((void **)&child->thread.ds_area_msr,
+                                         cfg.size);
+               ds = (void *)child->thread.ds_area_msr;
+               if (ds)
+                       set_tsk_thread_flag(child, TIF_DS_AREA_MSR);
+               else
+                       clear_tsk_thread_flag(child, TIF_DS_AREA_MSR);
+
+               if (ret < 0)
+                       return ret;
+
+               bts_size = ds_get_bts_size(ds);
+               if (bts_size <= 0)
+                       return bts_size;
+       }
+
+       if (ds) {
+               if (cfg.flags & PTRACE_BTS_O_SIGNAL) {
+                       ret = ds_set_overflow(ds, DS_O_SIGNAL);
+               } else {
+                       ret = ds_set_overflow(ds, DS_O_WRAP);
+               }
+               if (ret < 0)
+                       return ret;
+       }
+
+       debugctl_mask = ds_debugctl_mask();
+       if (ds && (cfg.flags & PTRACE_BTS_O_TRACE)) {
                child->thread.debugctlmsr |= debugctl_mask;
                set_tsk_thread_flag(child, TIF_DEBUGCTLMSR);
        } else {
@@ -555,7 +622,7 @@ static int ptrace_bts_config(struct task_struct *child,
                        clear_tsk_thread_flag(child, TIF_DEBUGCTLMSR);
        }
 
-       if (options & PTRACE_BTS_O_TIMESTAMPS)
+       if (ds && (cfg.flags & PTRACE_BTS_O_SCHED))
                set_tsk_thread_flag(child, TIF_BTS_TRACE_TS);
        else
                clear_tsk_thread_flag(child, TIF_BTS_TRACE_TS);
@@ -563,59 +630,32 @@ static int ptrace_bts_config(struct task_struct *child,
        return 0;
 }
 
-static int ptrace_bts_status(struct task_struct *child)
+static int ptrace_bts_status(struct task_struct *child,
+                            struct ptrace_bts_config __user *ucfg)
 {
-       unsigned long debugctl_mask = ds_debugctl_mask();
-       int retval, status = 0;
-
-       retval = ptrace_bts_get_buffer_size(child);
-       if (retval < 0)
-               return retval;
-       if (retval == 0)
-               return -ENXIO;
-
-       if (ptrace_bts_get_buffer_size(child) <= 0)
-               return -ENXIO;
+       void *ds = (void *)child->thread.ds_area_msr;
+       struct ptrace_bts_config cfg;
 
-       if (test_tsk_thread_flag(child, TIF_DEBUGCTLMSR) &&
-           child->thread.debugctlmsr & debugctl_mask)
-               status |= PTRACE_BTS_O_TRACE_TASK;
-       if (test_tsk_thread_flag(child, TIF_BTS_TRACE_TS))
-               status |= PTRACE_BTS_O_TIMESTAMPS;
+       memset(&cfg, 0, sizeof(cfg));
 
-       return status;
-}
+       if (ds) {
+               cfg.size = ds_get_bts_size(ds);
 
-static int ptrace_bts_allocate_bts(struct task_struct *child,
-                                  int size_in_records)
-{
-       int retval = 0;
-       void *ds;
+               if (ds_get_overflow(ds) == DS_O_SIGNAL)
+                       cfg.flags |= PTRACE_BTS_O_SIGNAL;
 
-       if (size_in_records < 0)
-               return -EINVAL;
+               if (test_tsk_thread_flag(child, TIF_DEBUGCTLMSR) &&
+                   child->thread.debugctlmsr & ds_debugctl_mask())
+                       cfg.flags |= PTRACE_BTS_O_TRACE;
 
-       if (size_in_records > ptrace_bts_max_buffer_size())
-               return -EINVAL;
-
-       if (size_in_records == 0) {
-               ptrace_bts_config(child, /* options = */ 0);
-       } else {
-               retval = ds_allocate(&ds, size_in_records);
-               if (retval)
-                       return retval;
+               if (test_tsk_thread_flag(child, TIF_BTS_TRACE_TS))
+                       cfg.flags |= PTRACE_BTS_O_SCHED;
        }
 
-       if (child->thread.ds_area_msr)
-               ds_free((void **)&child->thread.ds_area_msr);
-
-       child->thread.ds_area_msr = (unsigned long)ds;
-       if (child->thread.ds_area_msr)
-               set_tsk_thread_flag(child, TIF_DS_AREA_MSR);
-       else
-               clear_tsk_thread_flag(child, TIF_DS_AREA_MSR);
+       if (copy_to_user(ucfg, &cfg, sizeof(cfg)))
+               return -EFAULT;
 
-       return retval;
+       return sizeof(cfg);
 }
 
 void ptrace_bts_take_timestamp(struct task_struct *tsk,
@@ -626,9 +666,6 @@ void ptrace_bts_take_timestamp(struct task_struct *tsk,
                .variant.jiffies = jiffies
        };
 
-       if (ptrace_bts_get_buffer_size(tsk) <= 0)
-               return;
-
        ptrace_bts_write_record(tsk, &rec);
 }
 
@@ -808,30 +845,32 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
                break;
 #endif
 
-       case PTRACE_BTS_MAX_BUFFER_SIZE:
-               ret = ptrace_bts_max_buffer_size();
+       case PTRACE_BTS_CONFIG:
+               ret = ptrace_bts_config
+                       (child, (struct ptrace_bts_config __user *)addr);
                break;
 
-       case PTRACE_BTS_ALLOCATE_BUFFER:
-               ret = ptrace_bts_allocate_bts(child, data);
+       case PTRACE_BTS_STATUS:
+               ret = ptrace_bts_status
+                       (child, (struct ptrace_bts_config __user *)addr);
                break;
 
-       case PTRACE_BTS_GET_BUFFER_SIZE:
-               ret = ptrace_bts_get_buffer_size(child);
+       case PTRACE_BTS_SIZE:
+               ret = ptrace_bts_get_size(child);
                break;
 
-       case PTRACE_BTS_READ_RECORD:
+       case PTRACE_BTS_GET:
                ret = ptrace_bts_read_record
-                       (child, data,
-                        (struct bts_struct __user *) addr);
+                       (child, data, (struct bts_struct __user *) addr);
                break;
 
-       case PTRACE_BTS_CONFIG:
-               ret = ptrace_bts_config(child, data);
+       case PTRACE_BTS_CLEAR:
+               ret = ptrace_bts_clear(child);
                break;
 
-       case PTRACE_BTS_STATUS:
-               ret = ptrace_bts_status(child);
+       case PTRACE_BTS_DRAIN:
+               ret = ptrace_bts_drain
+                       (child, (struct bts_struct __user *) addr);
                break;
 
        default:
@@ -1017,12 +1056,12 @@ asmlinkage long sys32_ptrace(long request, u32 pid, u32 addr, u32 data)
        case PTRACE_SETOPTIONS:
        case PTRACE_SET_THREAD_AREA:
        case PTRACE_GET_THREAD_AREA:
-       case PTRACE_BTS_MAX_BUFFER_SIZE:
-       case PTRACE_BTS_ALLOCATE_BUFFER:
-       case PTRACE_BTS_GET_BUFFER_SIZE:
-       case PTRACE_BTS_READ_RECORD:
        case PTRACE_BTS_CONFIG:
        case PTRACE_BTS_STATUS:
+       case PTRACE_BTS_SIZE:
+       case PTRACE_BTS_GET:
+       case PTRACE_BTS_CLEAR:
+       case PTRACE_BTS_DRAIN:
                return sys_ptrace(request, pid, addr, data);
 
        default:
index c9e1538..b84040a 100644 (file)
@@ -52,11 +52,18 @@ struct bts_struct {
        } variant;
 };
 
+/* Overflow handling mechanisms */
+#define DS_O_SIGNAL    1 /* send overflow signal */
+#define DS_O_WRAP      2 /* wrap around */
 
 extern int ds_allocate(void **, size_t);
 extern int ds_free(void **);
 extern int ds_get_bts_size(void *);
+extern int ds_get_bts_end(void *);
 extern int ds_get_bts_index(void *);
+extern int ds_set_overflow(void *, int);
+extern int ds_get_overflow(void *);
+extern int ds_clear(void *);
 extern int ds_read_bts(void *, size_t, struct bts_struct *);
 extern int ds_write_bts(void *, const struct bts_struct *);
 extern unsigned long ds_debugctl_mask(void);
index b473ad4..cf2fe46 100644 (file)
 
 #define PTRACE_SINGLEBLOCK     33      /* resume execution until next branch */
 
-/* Return maximal BTS buffer size in number of records,
-   if successuf; -1, otherwise.
-   EOPNOTSUPP...processor does not support bts tracing */
-#define PTRACE_BTS_MAX_BUFFER_SIZE 40
-
-/* Allocate new bts buffer (free old one, if exists) of size DATA bts records;
-   parameter ADDR is ignored.
-   Return 0, if successful; -1, otherwise.
-   EOPNOTSUPP...processor does not support bts tracing
-   EINVAL.......invalid size in records
-   ENOMEM.......out of memory */
-#define PTRACE_BTS_ALLOCATE_BUFFER 41
-
-/* Return the size of the bts buffer in number of bts records,
-   if successful; -1, otherwise.
-   EOPNOTSUPP...processor does not support bts tracing
-   ENXIO........no buffer allocated */
-#define PTRACE_BTS_GET_BUFFER_SIZE 42
-
-/* Read the DATA'th bts record into a ptrace_bts_record buffer
-   provided in ADDR.
-   Records are ordered from newest to oldest.
-   Return 0, if successful; -1, otherwise
-   EOPNOTSUPP...processor does not support bts tracing
-   ENXIO........no buffer allocated
-   EINVAL.......invalid index */
-#define PTRACE_BTS_READ_RECORD 43
-
-/* Configure last branch trace; the configuration is given as a bit-mask of
-   PTRACE_BTS_O_* options in DATA; parameter ADDR is ignored.
-   Return 0, if successful; -1, otherwise
-   EOPNOTSUPP...processor does not support bts tracing
-   ENXIO........no buffer allocated */
-#define PTRACE_BTS_CONFIG 44
-
-/* Return the configuration as bit-mask of PTRACE_BTS_O_* options
-   if successful; -1, otherwise.
-   EOPNOTSUPP...processor does not support bts tracing
-   ENXIO........no buffer allocated */
-#define PTRACE_BTS_STATUS 45
-
-/* Trace configuration options */
-/* Collect last branch trace */
-#define PTRACE_BTS_O_TRACE_TASK 0x1
-/* Take timestamps when the task arrives and departs */
-#define PTRACE_BTS_O_TIMESTAMPS 0x2
+/* configuration/status structure used in PTRACE_BTS_CONFIG and
+   PTRACE_BTS_STATUS commands.
+*/
+struct ptrace_bts_config {
+       /* requested or actual size of BTS buffer in bytes */
+       unsigned long size;
+       /* bitmask of below flags */
+       unsigned long flags;
+};
+
+#define PTRACE_BTS_O_TRACE     0x1 /* branch trace */
+#define PTRACE_BTS_O_SCHED     0x2 /* scheduling events w/ jiffies */
+#define PTRACE_BTS_O_SIGNAL     0x4 /* send SIG? on buffer overflow
+                                      instead of wrapping around */
+#define PTRACE_BTS_O_CUT_SIZE  0x8 /* cut requested size to max available
+                                      instead of failing */
+
+#define PTRACE_BTS_CONFIG      40
+/* Configure branch trace recording.
+   DATA is ignored, ADDR points to a struct ptrace_bts_config.
+   A new buffer is allocated, iff the size changes.
+*/
+#define PTRACE_BTS_STATUS      41
+/* Return the current configuration.
+   DATA is ignored, ADDR points to a struct ptrace_bts_config
+   that will contain the result.
+*/
+#define PTRACE_BTS_SIZE                42
+/* Return the number of available BTS records.
+   DATA and ADDR are ignored.
+*/
+#define PTRACE_BTS_GET         43
+/* Get a single BTS record.
+   DATA defines the index into the BTS array, where 0 is the newest
+   entry, and higher indices refer to older entries.
+   ADDR is pointing to struct bts_struct (see asm/ds.h).
+*/
+#define PTRACE_BTS_CLEAR       44
+/* Clear the BTS buffer.
+   DATA and ADDR are ignored.
+*/
+#define PTRACE_BTS_DRAIN       45
+/* Read all available BTS records and clear the buffer.
+   DATA is ignored. ADDR points to an array of struct bts_struct of
+   suitable size.
+   BTS records are read from oldest to newest.
+   Returns number of BTS records drained.
+*/
 
 #endif
index a9a1bab..61946fe 100644 (file)
@@ -9,6 +9,7 @@
 
 #ifdef __KERNEL__
 
+/* the DS BTS struct is used for ptrace as well */
 #include <asm/ds.h>
 
 struct task_struct;