Skip to content

Commit

Permalink
hw-breakpoints: modify Ptrace routines to access breakpoint registers
Browse files Browse the repository at this point in the history
This patch modifies the ptrace code to use the new wrapper routines around the
debug/breakpoint registers.

[ Impact: adapt x86 ptrace to the new breakpoint Api ]

Original-patch-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: K.Prasad <prasad@linux.vnet.ibm.com>
Signed-off-by: Maneesh Soni <maneesh@linux.vnet.ibm.com>
Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
  • Loading branch information
K.Prasad authored and Frederic Weisbecker committed Jun 2, 2009
1 parent da0cdc1 commit 72f674d
Showing 1 changed file with 141 additions and 90 deletions.
231 changes: 141 additions & 90 deletions arch/x86/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <asm/prctl.h>
#include <asm/proto.h>
#include <asm/ds.h>
#include <asm/hw_breakpoint.h>

#include <trace/syscall.h>

Expand Down Expand Up @@ -136,11 +137,6 @@ static int set_segment_reg(struct task_struct *task,
return 0;
}

static unsigned long debugreg_addr_limit(struct task_struct *task)
{
return TASK_SIZE - 3;
}

#else /* CONFIG_X86_64 */

#define FLAG_MASK (FLAG_MASK_32 | X86_EFLAGS_NT)
Expand Down Expand Up @@ -265,15 +261,6 @@ static int set_segment_reg(struct task_struct *task,
return 0;
}

static unsigned long debugreg_addr_limit(struct task_struct *task)
{
#ifdef CONFIG_IA32_EMULATION
if (test_tsk_thread_flag(task, TIF_IA32))
return IA32_PAGE_OFFSET - 3;
#endif
return TASK_SIZE_MAX - 7;
}

#endif /* CONFIG_X86_32 */

static unsigned long get_flags(struct task_struct *task)
Expand Down Expand Up @@ -464,95 +451,159 @@ static int genregs_set(struct task_struct *target,
}

/*
* This function is trivial and will be inlined by the compiler.
* Having it separates the implementation details of debug
* registers from the interface details of ptrace.
* Decode the length and type bits for a particular breakpoint as
* stored in debug register 7. Return the "enabled" status.
*/
static unsigned long ptrace_get_debugreg(struct task_struct *child, int n)
static int decode_dr7(unsigned long dr7, int bpnum, unsigned *len,
unsigned *type)
{
switch (n) {
case 0: return child->thread.debugreg[0];
case 1: return child->thread.debugreg[1];
case 2: return child->thread.debugreg[2];
case 3: return child->thread.debugreg[3];
case 6: return child->thread.debugreg6;
case 7: return child->thread.debugreg7;
}
return 0;
int bp_info = dr7 >> (DR_CONTROL_SHIFT + bpnum * DR_CONTROL_SIZE);

*len = (bp_info & 0xc) | 0x40;
*type = (bp_info & 0x3) | 0x80;
return (dr7 >> (bpnum * DR_ENABLE_SIZE)) & 0x3;
}

static int ptrace_set_debugreg(struct task_struct *child,
int n, unsigned long data)
static void ptrace_triggered(struct hw_breakpoint *bp, struct pt_regs *regs)
{
struct thread_struct *thread = &(current->thread);
int i;

if (unlikely(n == 4 || n == 5))
return -EIO;
/*
* Store in the virtual DR6 register the fact that the breakpoint
* was hit so the thread's debugger will see it.
*/
for (i = 0; i < hbp_kernel_pos; i++)
/*
* We will check bp->info.address against the address stored in
* thread's hbp structure and not debugreg[i]. This is to ensure
* that the corresponding bit for 'i' in DR7 register is enabled
*/
if (bp->info.address == thread->hbp[i]->info.address)
break;

if (n < 4 && unlikely(data >= debugreg_addr_limit(child)))
return -EIO;
thread->debugreg6 |= (DR_TRAP0 << i);
}

switch (n) {
case 0: child->thread.debugreg[0] = data; break;
case 1: child->thread.debugreg[1] = data; break;
case 2: child->thread.debugreg[2] = data; break;
case 3: child->thread.debugreg[3] = data; break;
/*
* Handle ptrace writes to debug register 7.
*/
static int ptrace_write_dr7(struct task_struct *tsk, unsigned long data)
{
struct thread_struct *thread = &(tsk->thread);
unsigned long old_dr7 = thread->debugreg7;
int i, orig_ret = 0, rc = 0;
int enabled, second_pass = 0;
unsigned len, type;
struct hw_breakpoint *bp;

data &= ~DR_CONTROL_RESERVED;
restore:
/*
* Loop through all the hardware breakpoints, making the
* appropriate changes to each.
*/
for (i = 0; i < HBP_NUM; i++) {
enabled = decode_dr7(data, i, &len, &type);
bp = thread->hbp[i];

if (!enabled) {
if (bp) {
/* Don't unregister the breakpoints right-away,
* unless all register_user_hw_breakpoint()
* requests have succeeded. This prevents
* any window of opportunity for debug
* register grabbing by other users.
*/
if (!second_pass)
continue;
unregister_user_hw_breakpoint(tsk, bp);
kfree(bp);
}
continue;
}
if (!bp) {
rc = -ENOMEM;
bp = kzalloc(sizeof(struct hw_breakpoint), GFP_KERNEL);
if (bp) {
bp->info.address = thread->debugreg[i];
bp->triggered = ptrace_triggered;
bp->info.len = len;
bp->info.type = type;
rc = register_user_hw_breakpoint(tsk, bp);
if (rc)
kfree(bp);
}
} else
rc = modify_user_hw_breakpoint(tsk, bp);
if (rc)
break;
}
/*
* Make a second pass to free the remaining unused breakpoints
* or to restore the original breakpoints if an error occurred.
*/
if (!second_pass) {
second_pass = 1;
if (rc < 0) {
orig_ret = rc;
data = old_dr7;
}
goto restore;
}
return ((orig_ret < 0) ? orig_ret : rc);
}

case 6:
if ((data & ~0xffffffffUL) != 0)
return -EIO;
child->thread.debugreg6 = data;
break;
/*
* Handle PTRACE_PEEKUSR calls for the debug register area.
*/
unsigned long ptrace_get_debugreg(struct task_struct *tsk, int n)
{
struct thread_struct *thread = &(tsk->thread);
unsigned long val = 0;

if (n < HBP_NUM)
val = thread->debugreg[n];
else if (n == 6)
val = thread->debugreg6;
else if (n == 7)
val = thread->debugreg7;
return val;
}

case 7:
/*
* Sanity-check data. Take one half-byte at once with
* check = (val >> (16 + 4*i)) & 0xf. It contains the
* R/Wi and LENi bits; bits 0 and 1 are R/Wi, and bits
* 2 and 3 are LENi. Given a list of invalid values,
* we do mask |= 1 << invalid_value, so that
* (mask >> check) & 1 is a correct test for invalid
* values.
*
* R/Wi contains the type of the breakpoint /
* watchpoint, LENi contains the length of the watched
* data in the watchpoint case.
*
* The invalid values are:
* - LENi == 0x10 (undefined), so mask |= 0x0f00. [32-bit]
* - R/Wi == 0x10 (break on I/O reads or writes), so
* mask |= 0x4444.
* - R/Wi == 0x00 && LENi != 0x00, so we have mask |=
* 0x1110.
*
* Finally, mask = 0x0f00 | 0x4444 | 0x1110 == 0x5f54.
*
* See the Intel Manual "System Programming Guide",
* 15.2.4
*
* Note that LENi == 0x10 is defined on x86_64 in long
* mode (i.e. even for 32-bit userspace software, but
* 64-bit kernel), so the x86_64 mask value is 0x5454.
* See the AMD manual no. 24593 (AMD64 System Programming)
*/
#ifdef CONFIG_X86_32
#define DR7_MASK 0x5f54
#else
#define DR7_MASK 0x5554
#endif
data &= ~DR_CONTROL_RESERVED;
for (i = 0; i < 4; i++)
if ((DR7_MASK >> ((data >> (16 + 4*i)) & 0xf)) & 1)
return -EIO;
child->thread.debugreg7 = data;
if (data)
set_tsk_thread_flag(child, TIF_DEBUG);
else
clear_tsk_thread_flag(child, TIF_DEBUG);
break;
/*
* Handle PTRACE_POKEUSR calls for the debug register area.
*/
int ptrace_set_debugreg(struct task_struct *tsk, int n, unsigned long val)
{
struct thread_struct *thread = &(tsk->thread);
int rc = 0;

/* There are no DR4 or DR5 registers */
if (n == 4 || n == 5)
return -EIO;

if (n == 6) {
tsk->thread.debugreg6 = val;
goto ret_path;
}
if (n < HBP_NUM) {
if (thread->hbp[n]) {
if (arch_check_va_in_userspace(val,
thread->hbp[n]->info.len) == 0) {
rc = -EIO;
goto ret_path;
}
thread->hbp[n]->info.address = val;
}
thread->debugreg[n] = val;
}
/* All that's left is DR7 */
if (n == 7)
rc = ptrace_write_dr7(tsk, val);

return 0;
ret_path:
return rc;
}

/*
Expand Down

0 comments on commit 72f674d

Please sign in to comment.