diff options
Diffstat (limited to 'arch/x86/kernel/ptrace.c')
-rw-r--r-- | arch/x86/kernel/ptrace.c | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c index 3399c1be79b8..8d0dd8b5effe 100644 --- a/arch/x86/kernel/ptrace.c +++ b/arch/x86/kernel/ptrace.c @@ -2,6 +2,9 @@ /* * Pentium III FXSR, SSE support * Gareth Hughes <gareth@valinux.com>, May 2000 + * + * BTS tracing + * Markus Metzger <markus.t.metzger@intel.com>, Dec 2007 */ #include <linux/kernel.h> @@ -26,6 +29,14 @@ #include <asm/desc.h> #include <asm/prctl.h> #include <asm/proto.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. @@ -455,6 +466,165 @@ 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) +{ + if (!child->thread.ds_area_msr) + return -ENXIO; + + return ds_get_bts_size((void *)child->thread.ds_area_msr); +} + +static int ptrace_bts_get_index(struct task_struct *child) +{ + if (!child->thread.ds_area_msr) + return -ENXIO; + + return ds_get_bts_index((void *)child->thread.ds_area_msr); +} + +static int ptrace_bts_read_record(struct task_struct *child, + long index, + struct bts_struct __user *out) +{ + struct bts_struct ret; + int retval; + + if (!child->thread.ds_area_msr) + return -ENXIO; + + retval = ds_read_bts((void *)child->thread.ds_area_msr, + index, &ret); + if (retval) + return retval; + + if (copy_to_user(out, &ret, sizeof(ret))) + return -EFAULT; + + return sizeof(ret); +} + +static int ptrace_bts_write_record(struct task_struct *child, + const struct bts_struct *in) +{ + int retval; + + if (!child->thread.ds_area_msr) + return -ENXIO; + + retval = ds_write_bts((void *)child->thread.ds_area_msr, in); + if (retval) + return retval; + + return sizeof(*in); +} + +static int ptrace_bts_config(struct task_struct *child, + unsigned long options) +{ + unsigned long debugctl_mask = ds_debugctl_mask(); + int retval; + + retval = ptrace_bts_get_buffer_size(child); + if (retval < 0) + return retval; + if (retval == 0) + return -ENXIO; + + if (options & PTRACE_BTS_O_TRACE_TASK) { + child->thread.debugctlmsr |= debugctl_mask; + set_tsk_thread_flag(child, TIF_DEBUGCTLMSR); + } else { + /* there is no way for us to check whether we 'own' + * the respective bits in the DEBUGCTL MSR, we're + * about to clear */ + child->thread.debugctlmsr &= ~debugctl_mask; + + if (!child->thread.debugctlmsr) + clear_tsk_thread_flag(child, TIF_DEBUGCTLMSR); + } + + if (options & PTRACE_BTS_O_TIMESTAMPS) + set_tsk_thread_flag(child, TIF_BTS_TRACE_TS); + else + clear_tsk_thread_flag(child, TIF_BTS_TRACE_TS); + + return 0; +} + +static int ptrace_bts_status(struct task_struct *child) +{ + 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; + + 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; + + return status; +} + +static int ptrace_bts_allocate_bts(struct task_struct *child, + int size_in_records) +{ + int retval = 0; + void *ds; + + if (size_in_records < 0) + return -EINVAL; + + 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 (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); + + return retval; +} + +void ptrace_bts_take_timestamp(struct task_struct *tsk, + enum bts_qualifier qualifier) +{ + struct bts_struct rec = { + .qualifier = qualifier, + .variant.timestamp = sched_clock() + }; + + if (ptrace_bts_get_buffer_size(tsk) <= 0) + return; + + ptrace_bts_write_record(tsk, &rec); +} + /* * Called by kernel/ptrace.c when detaching.. * @@ -466,6 +636,11 @@ void ptrace_disable(struct task_struct *child) #ifdef TIF_SYSCALL_EMU clear_tsk_thread_flag(child, TIF_SYSCALL_EMU); #endif + ptrace_bts_config(child, /* options = */ 0); + if (child->thread.ds_area_msr) { + ds_free((void **)&child->thread.ds_area_msr); + clear_tsk_thread_flag(child, TIF_DS_AREA_MSR); + } } long arch_ptrace(struct task_struct *child, long request, long addr, long data) @@ -626,6 +801,36 @@ 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(); + break; + + case PTRACE_BTS_ALLOCATE_BUFFER: + ret = ptrace_bts_allocate_bts(child, data); + break; + + case PTRACE_BTS_GET_BUFFER_SIZE: + ret = ptrace_bts_get_buffer_size(child); + break; + + case PTRACE_BTS_GET_INDEX: + ret = ptrace_bts_get_index(child); + break; + + case PTRACE_BTS_READ_RECORD: + ret = ptrace_bts_read_record + (child, data, + (struct bts_struct __user *) addr); + break; + + case PTRACE_BTS_CONFIG: + ret = ptrace_bts_config(child, data); + break; + + case PTRACE_BTS_STATUS: + ret = ptrace_bts_status(child); + break; + default: ret = ptrace_request(child, request, addr, data); break; @@ -809,6 +1014,13 @@ 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_GET_INDEX: + case PTRACE_BTS_READ_RECORD: + case PTRACE_BTS_CONFIG: + case PTRACE_BTS_STATUS: return sys_ptrace(request, pid, addr, data); default: |