Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+51 −5
Original line number Diff line number Diff line
@@ -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;
+130 −91
Original line number Diff line number Diff line
@@ -32,12 +32,6 @@
#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)
{
	if (!child->thread.ds_area_msr)
		return -ENXIO;

	return ds_clear((void *)child->thread.ds_area_msr);
}

static int ptrace_bts_drain(struct task_struct *child,
			    struct bts_struct __user *out)
{
	unsigned long debugctl_mask = ds_debugctl_mask();
	int end, i;
	void *ds = (void *)child->thread.ds_area_msr;

	if (!ds)
		return -ENXIO;

	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 = ptrace_bts_get_buffer_size(child);
		retval = ds_read_bts(ds, i, &ret);
		if (retval < 0)
			return retval;
	if (retval == 0)
		return -ENXIO;

	if (options & PTRACE_BTS_O_TRACE_TASK) {
		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;
	void *ds = (void *)child->thread.ds_area_msr;
	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) &&
	    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;
}

static int ptrace_bts_allocate_bts(struct task_struct *child,
				   int size_in_records)
{
	int retval = 0;
	void *ds;
	if (ds) {
		cfg.size = ds_get_bts_size(ds);

	if (size_in_records < 0)
		return -EINVAL;
		if (ds_get_overflow(ds) == DS_O_SIGNAL)
			cfg.flags |= PTRACE_BTS_O_SIGNAL;

	if (size_in_records > ptrace_bts_max_buffer_size())
		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 == 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:
+7 −0
Original line number Diff line number Diff line
@@ -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);
+48 −46
Original line number Diff line number Diff line
@@ -80,51 +80,53 @@

#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
+1 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@

#ifdef __KERNEL__

/* the DS BTS struct is used for ptrace as well */
#include <asm/ds.h>

struct task_struct;