Commit a95d67f8 authored by Markus Metzger's avatar Markus Metzger Committed by Ingo Molnar

x86, ptrace: new ptrace BTS API

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: default avatarMarkus Metzger <markus.t.metzger@intel.com>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
parent e4811f25
...@@ -177,18 +177,20 @@ static inline void set_info_data(char *base, unsigned long value) ...@@ -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; size_t bts_size_in_records;
void *bts = 0; void *bts;
void *ds = 0; void *ds;
if (!ds_cfg.sizeof_ds || !ds_cfg.sizeof_bts) if (!ds_cfg.sizeof_ds || !ds_cfg.sizeof_bts)
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (bts_size_in_records < 0) if (bts_size_in_bytes < 0)
return -EINVAL; return -EINVAL;
bts_size_in_records =
bts_size_in_bytes / ds_cfg.sizeof_bts;
bts_size_in_bytes = bts_size_in_bytes =
bts_size_in_records * ds_cfg.sizeof_bts; bts_size_in_records * ds_cfg.sizeof_bts;
...@@ -233,9 +235,21 @@ int ds_get_bts_size(void *ds) ...@@ -233,9 +235,21 @@ int ds_get_bts_size(void *ds)
if (!ds_cfg.sizeof_ds || !ds_cfg.sizeof_bts) if (!ds_cfg.sizeof_ds || !ds_cfg.sizeof_bts)
return -EOPNOTSUPP; return -EOPNOTSUPP;
if (!ds)
return 0;
size_in_bytes = size_in_bytes =
get_bts_absolute_maximum(ds) - get_bts_absolute_maximum(ds) -
get_bts_buffer_base(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; return size_in_bytes / ds_cfg.sizeof_bts;
} }
...@@ -254,6 +268,38 @@ int ds_get_bts_index(void *ds) ...@@ -254,6 +268,38 @@ int ds_get_bts_index(void *ds)
return index_offset_in_bytes / ds_cfg.sizeof_bts; 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) int ds_read_bts(void *ds, size_t index, struct bts_struct *out)
{ {
void *bts; void *bts;
......
...@@ -32,12 +32,6 @@ ...@@ -32,12 +32,6 @@
#include <asm/ds.h> #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. * does not yet catch signals sent when the child dies.
* in exit.c or in signal.c. * in exit.c or in signal.c.
...@@ -466,17 +460,12 @@ static int ptrace_set_debugreg(struct task_struct *child, ...@@ -466,17 +460,12 @@ static int ptrace_set_debugreg(struct task_struct *child,
return 0; return 0;
} }
static int ptrace_bts_max_buffer_size(void) static int ptrace_bts_get_size(struct task_struct *child)
{
return PTRACE_BTS_BUFFER_MAX;
}
static int ptrace_bts_get_buffer_size(struct task_struct *child)
{ {
if (!child->thread.ds_area_msr) if (!child->thread.ds_area_msr)
return -ENXIO; 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, static int ptrace_bts_read_record(struct task_struct *child,
...@@ -485,7 +474,7 @@ 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; struct bts_struct ret;
int retval; int retval;
int bts_size; int bts_end;
int bts_index; int bts_index;
if (!child->thread.ds_area_msr) if (!child->thread.ds_area_msr)
...@@ -494,15 +483,15 @@ static int ptrace_bts_read_record(struct task_struct *child, ...@@ -494,15 +483,15 @@ static int ptrace_bts_read_record(struct task_struct *child,
if (index < 0) if (index < 0)
return -EINVAL; return -EINVAL;
bts_size = ds_get_bts_size((void *)child->thread.ds_area_msr); bts_end = ds_get_bts_end((void *)child->thread.ds_area_msr);
if (bts_size <= index) if (bts_end <= index)
return -EINVAL; return -EINVAL;
/* translate the ptrace bts index into the ds bts index */ /* translate the ptrace bts index into the ds bts index */
bts_index = ds_get_bts_index((void *)child->thread.ds_area_msr); bts_index = ds_get_bts_index((void *)child->thread.ds_area_msr);
bts_index -= (index + 1); bts_index -= (index + 1);
if (bts_index < 0) if (bts_index < 0)
bts_index += bts_size; bts_index += bts_end;
retval = ds_read_bts((void *)child->thread.ds_area_msr, retval = ds_read_bts((void *)child->thread.ds_area_msr,
bts_index, &ret); bts_index, &ret);
...@@ -530,19 +519,97 @@ static int ptrace_bts_write_record(struct task_struct *child, ...@@ -530,19 +519,97 @@ static int ptrace_bts_write_record(struct task_struct *child,
return sizeof(*in); return sizeof(*in);
} }
static int ptrace_bts_config(struct task_struct *child, static int ptrace_bts_clear(struct task_struct *child)
unsigned long options)
{ {
unsigned long debugctl_mask = ds_debugctl_mask(); if (!child->thread.ds_area_msr)
int retval; return -ENXIO;
retval = ptrace_bts_get_buffer_size(child); return ds_clear((void *)child->thread.ds_area_msr);
if (retval < 0) }
return retval;
if (retval == 0) 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; 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; child->thread.debugctlmsr |= debugctl_mask;
set_tsk_thread_flag(child, TIF_DEBUGCTLMSR); set_tsk_thread_flag(child, TIF_DEBUGCTLMSR);
} else { } else {
...@@ -555,7 +622,7 @@ static int ptrace_bts_config(struct task_struct *child, ...@@ -555,7 +622,7 @@ static int ptrace_bts_config(struct task_struct *child,
clear_tsk_thread_flag(child, TIF_DEBUGCTLMSR); 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); set_tsk_thread_flag(child, TIF_BTS_TRACE_TS);
else else
clear_tsk_thread_flag(child, TIF_BTS_TRACE_TS); clear_tsk_thread_flag(child, TIF_BTS_TRACE_TS);
...@@ -563,59 +630,32 @@ static int ptrace_bts_config(struct task_struct *child, ...@@ -563,59 +630,32 @@ static int ptrace_bts_config(struct task_struct *child,
return 0; 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(); void *ds = (void *)child->thread.ds_area_msr;
int retval, status = 0; struct ptrace_bts_config cfg;
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;
if (test_tsk_thread_flag(child, TIF_DEBUGCTLMSR) && memset(&cfg, 0, sizeof(cfg));
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;
return status; if (ds) {
} cfg.size = ds_get_bts_size(ds);
static int ptrace_bts_allocate_bts(struct task_struct *child, if (ds_get_overflow(ds) == DS_O_SIGNAL)
int size_in_records) cfg.flags |= PTRACE_BTS_O_SIGNAL;
{
int retval = 0;
void *ds;
if (size_in_records < 0) if (test_tsk_thread_flag(child, TIF_DEBUGCTLMSR) &&
return -EINVAL; child->thread.debugctlmsr & ds_debugctl_mask())
cfg.flags |= PTRACE_BTS_O_TRACE;
if (size_in_records > ptrace_bts_max_buffer_size()) if (test_tsk_thread_flag(child, TIF_BTS_TRACE_TS))
return -EINVAL; cfg.flags |= PTRACE_BTS_O_SCHED;
if (size_in_records == 0) {
ptrace_bts_config(child, /* options = */ 0);
} else {
retval = ds_allocate(&ds, size_in_records);
if (retval)
return retval;
} }
if (child->thread.ds_area_msr) if (copy_to_user(ucfg, &cfg, sizeof(cfg)))
ds_free((void **)&child->thread.ds_area_msr); return -EFAULT;
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);
return retval; return sizeof(cfg);
} }
void ptrace_bts_take_timestamp(struct task_struct *tsk, void ptrace_bts_take_timestamp(struct task_struct *tsk,
...@@ -626,9 +666,6 @@ 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 .variant.jiffies = jiffies
}; };
if (ptrace_bts_get_buffer_size(tsk) <= 0)
return;
ptrace_bts_write_record(tsk, &rec); ptrace_bts_write_record(tsk, &rec);
} }
...@@ -808,30 +845,32 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) ...@@ -808,30 +845,32 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
break; break;
#endif #endif
case PTRACE_BTS_MAX_BUFFER_SIZE: case PTRACE_BTS_CONFIG:
ret = ptrace_bts_max_buffer_size(); ret = ptrace_bts_config
(child, (struct ptrace_bts_config __user *)addr);
break; break;
case PTRACE_BTS_ALLOCATE_BUFFER: case PTRACE_BTS_STATUS:
ret = ptrace_bts_allocate_bts(child, data); ret = ptrace_bts_status
(child, (struct ptrace_bts_config __user *)addr);
break; break;
case PTRACE_BTS_GET_BUFFER_SIZE: case PTRACE_BTS_SIZE:
ret = ptrace_bts_get_buffer_size(child); ret = ptrace_bts_get_size(child);
break; break;
case PTRACE_BTS_READ_RECORD: case PTRACE_BTS_GET:
ret = ptrace_bts_read_record ret = ptrace_bts_read_record
(child, data, (child, data, (struct bts_struct __user *) addr);
(struct bts_struct __user *) addr);
break; break;
case PTRACE_BTS_CONFIG: case PTRACE_BTS_CLEAR:
ret = ptrace_bts_config(child, data); ret = ptrace_bts_clear(child);
break; break;
case PTRACE_BTS_STATUS: case PTRACE_BTS_DRAIN:
ret = ptrace_bts_status(child); ret = ptrace_bts_drain
(child, (struct bts_struct __user *) addr);
break; break;
default: default:
...@@ -1017,12 +1056,12 @@ asmlinkage long sys32_ptrace(long request, u32 pid, u32 addr, u32 data) ...@@ -1017,12 +1056,12 @@ asmlinkage long sys32_ptrace(long request, u32 pid, u32 addr, u32 data)
case PTRACE_SETOPTIONS: case PTRACE_SETOPTIONS:
case PTRACE_SET_THREAD_AREA: case PTRACE_SET_THREAD_AREA:
case PTRACE_GET_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_CONFIG:
case PTRACE_BTS_STATUS: 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); return sys_ptrace(request, pid, addr, data);
default: default:
......
...@@ -52,11 +52,18 @@ struct bts_struct { ...@@ -52,11 +52,18 @@ struct bts_struct {
} variant; } 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_allocate(void **, size_t);
extern int ds_free(void **); extern int ds_free(void **);
extern int ds_get_bts_size(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_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_read_bts(void *, size_t, struct bts_struct *);
extern int ds_write_bts(void *, const struct bts_struct *); extern int ds_write_bts(void *, const struct bts_struct *);
extern unsigned long ds_debugctl_mask(void); extern unsigned long ds_debugctl_mask(void);
......
...@@ -80,51 +80,53 @@ ...@@ -80,51 +80,53 @@
#define PTRACE_SINGLEBLOCK 33 /* resume execution until next branch */ #define PTRACE_SINGLEBLOCK 33 /* resume execution until next branch */
/* Return maximal BTS buffer size in number of records, /* configuration/status structure used in PTRACE_BTS_CONFIG and
if successuf; -1, otherwise. PTRACE_BTS_STATUS commands.
EOPNOTSUPP...processor does not support bts tracing */ */
#define PTRACE_BTS_MAX_BUFFER_SIZE 40 struct ptrace_bts_config {
/* requested or actual size of BTS buffer in bytes */
/* Allocate new bts buffer (free old one, if exists) of size DATA bts records; unsigned long size;
parameter ADDR is ignored. /* bitmask of below flags */
Return 0, if successful; -1, otherwise. unsigned long flags;
EOPNOTSUPP...processor does not support bts tracing };
EINVAL.......invalid size in records
ENOMEM.......out of memory */ #define PTRACE_BTS_O_TRACE 0x1 /* branch trace */
#define PTRACE_BTS_ALLOCATE_BUFFER 41 #define PTRACE_BTS_O_SCHED 0x2 /* scheduling events w/ jiffies */
#define PTRACE_BTS_O_SIGNAL 0x4 /* send SIG? on buffer overflow
/* Return the size of the bts buffer in number of bts records, instead of wrapping around */
if successful; -1, otherwise. #define PTRACE_BTS_O_CUT_SIZE 0x8 /* cut requested size to max available
EOPNOTSUPP...processor does not support bts tracing instead of failing */
ENXIO........no buffer allocated */
#define PTRACE_BTS_GET_BUFFER_SIZE 42 #define PTRACE_BTS_CONFIG 40
/* Configure branch trace recording.
/* Read the DATA'th bts record into a ptrace_bts_record buffer DATA is ignored, ADDR points to a struct ptrace_bts_config.
provided in ADDR. A new buffer is allocated, iff the size changes.
Records are ordered from newest to oldest. */
Return 0, if successful; -1, otherwise #define PTRACE_BTS_STATUS 41
EOPNOTSUPP...processor does not support bts tracing /* Return the current configuration.
ENXIO........no buffer allocated DATA is ignored, ADDR points to a struct ptrace_bts_config
EINVAL.......invalid index */ that will contain the result.
#define PTRACE_BTS_READ_RECORD 43 */
#define PTRACE_BTS_SIZE 42
/* Configure last branch trace; the configuration is given as a bit-mask of /* Return the number of available BTS records.
PTRACE_BTS_O_* options in DATA; parameter ADDR is ignored. DATA and ADDR are ignored.
Return 0, if successful; -1, otherwise */
EOPNOTSUPP...processor does not support bts tracing #define PTRACE_BTS_GET 43
ENXIO........no buffer allocated */ /* Get a single BTS record.
#define PTRACE_BTS_CONFIG 44 DATA defines the index into the BTS array, where 0 is the newest
entry, and higher indices refer to older entries.
/* Return the configuration as bit-mask of PTRACE_BTS_O_* options ADDR is pointing to struct bts_struct (see asm/ds.h).
if successful; -1, otherwise. */
EOPNOTSUPP...processor does not support bts tracing #define PTRACE_BTS_CLEAR 44
ENXIO........no buffer allocated */ /* Clear the BTS buffer.
#define PTRACE_BTS_STATUS 45 DATA and ADDR are ignored.
*/
/* Trace configuration options */ #define PTRACE_BTS_DRAIN 45
/* Collect last branch trace */ /* Read all available BTS records and clear the buffer.
#define PTRACE_BTS_O_TRACE_TASK 0x1 DATA is ignored. ADDR points to an array of struct bts_struct of
/* Take timestamps when the task arrives and departs */ suitable size.
#define PTRACE_BTS_O_TIMESTAMPS 0x2 BTS records are read from oldest to newest.
Returns number of BTS records drained.
*/
#endif #endif
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#ifdef __KERNEL__ #ifdef __KERNEL__
/* the DS BTS struct is used for ptrace as well */
#include <asm/ds.h> #include <asm/ds.h>
struct task_struct; struct task_struct;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment