[Patch 08/11] Modify Ptrace routines to access breakpoint registers

From: K.Prasad
Date: Tue Mar 24 2009 - 11:28:19 EST


From: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>

This patch modifies the ptrace code to use the new wrapper routines around the
debug/breakpoint registers.

[K.Prasad: Adapted the ptrace routines and to changes post x86/x86_64 merger,
split the minor patch from bigger patch. Re-wrote ptrace_write_dr7()
and ptrace_set_debugreg() functions to use new data-structures]

[K.Prasad: Changed code to suit the simplified HW breakpoint implementation]

Signed-off-by: K.Prasad <prasad@xxxxxxxxxxxxxxxxxx>
Signed-off-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
---
arch/x86/kernel/ptrace.c | 229 ++++++++++++++++++++++++++++-------------------
1 file changed, 138 insertions(+), 91 deletions(-)

Index: linux-2.6-tip/arch/x86/kernel/ptrace.c
===================================================================
--- linux-2.6-tip.orig/arch/x86/kernel/ptrace.c
+++ linux-2.6-tip/arch/x86/kernel/ptrace.c
@@ -34,6 +34,7 @@
#include <asm/prctl.h>
#include <asm/proto.h>
#include <asm/ds.h>
+#include <asm/hw_breakpoint.h>

#include "tls.h"

@@ -134,11 +135,6 @@ static int set_segment_reg(struct task_s
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)
@@ -263,15 +259,6 @@ static int set_segment_reg(struct task_s
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)
@@ -462,95 +449,155 @@ static int genregs_set(struct task_struc
}

/*
- * 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.debugreg0;
- case 1: return child->thread.debugreg1;
- case 2: return child->thread.debugreg2;
- case 3: return child->thread.debugreg3;
- case 6: return child->thread.debugreg6;
- case 7: return child->thread.debugreg7;
- }
- return 0;
+ int temp = dr7 >> (DR_CONTROL_SHIFT + bpnum * DR_CONTROL_SIZE);
+
+ *len = (temp & 0xc) | 0x40;
+ *type = (temp & 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; hbp_user_refcount[i]; i++)
+ if (bp->info.address == thread->hbp[i]->info.address)
+ break;

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

- switch (n) {
- case 0: child->thread.debugreg0 = data; break;
- case 1: child->thread.debugreg1 = data; break;
- case 2: child->thread.debugreg2 = data; break;
- case 3: child->thread.debugreg3 = data; break;
+/*
+ * Handle ptrace writes to debug register 7.
+ */
+static int ptrace_write_dr7(struct task_struct *tsk, unsigned long data)
+{
+ struct hw_breakpoint *bp;
+ struct thread_struct *thread = &(tsk->thread);
+ int i;
+ int rc = 0;
+ unsigned long old_dr7 = thread->dr7;

- case 6:
- if ((data & ~0xffffffffUL) != 0)
- return -EIO;
- child->thread.debugreg6 = data;
- break;
+ data &= ~DR_CONTROL_RESERVED;
+ /* Loop through all the hardware breakpoints, making the
+ * appropriate changes to each.
+ */
+ thread->dr7 = data;
+ for (i = 0; i < HB_NUM; i++) {
+ int enabled;
+ unsigned len, type;
+
+ bp = thread->hbp[i];
+ if (!bp)
+ continue;
+
+ enabled = decode_dr7(data, i, &len, &type);
+ if (!enabled) {
+ if (bp->triggered)
+ __unregister_user_hw_breakpoint(i, tsk, bp);
+ continue;
+ }

- 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);
+ if (bp->triggered)
+ rc = __modify_user_hw_breakpoint(i, tsk, bp);
+ else {
+ bp->triggered = ptrace_triggered;
+ bp->info.len = len;
+ bp->info.type = type;
+ rc = __register_user_hw_breakpoint(i, tsk, bp);
+ }
+ if (rc < 0)
+ break;
else
- clear_tsk_thread_flag(child, TIF_DEBUG);
- break;
+ set_tsk_thread_flag(tsk, TIF_DEBUG);
}
+ /* If anything above failed, restore the original settings.
+ * The original settings will take effect the next time the thread
+ * is scheduled
+ */
+ if (rc < 0)
+ data = old_dr7;
+ return rc;
+}

- return 0;
+/*
+ * 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;
+
+ mutex_lock(&hw_breakpoint_mutex);
+ if (n < HB_NUM) {
+ if (thread->hbp[n])
+ val = thread->hbp[n]->info.address;
+ } else if (n == 6) {
+ val = thread->dr6;
+ } else if (n == 7) {
+ val = thread->dr7;
+ }
+ mutex_unlock(&hw_breakpoint_mutex);
+ return val;
+}
+
+/*
+ * 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 = -EIO;
+
+ mutex_lock(&hw_breakpoint_mutex);
+
+ /* There are no DR4 or DR5 registers */
+ if (n == 4 || n == 5)
+ goto ret_path;
+
+ /* Writes to DR6 modify the virtualized value */
+ if (n == 6) {
+ tsk->thread.dr6 = val;
+ rc = 0;
+ goto ret_path;
+ }
+
+ /* Writes to DR0 - DR3 change a breakpoint address */
+ rc = 0;
+ if (n < HB_NUM) {
+ if (!val)
+ goto ret_path;
+ if (thread->hbp[n]) {
+ thread->hbp[n]->info.address = val;
+ rc = __modify_user_hw_breakpoint(n, tsk,
+ thread->hbp[n]);
+ goto ret_path;
+ }
+ thread->hbp[n] = kzalloc(sizeof(struct hw_breakpoint),
+ GFP_KERNEL);
+ if (!thread->hbp[n]) {
+ rc = -ENOMEM;
+ goto ret_path;
+ } else
+ thread->hbp[n]->info.address = val;
+ }
+ /* All that's left is DR7 */
+ if (n == 7)
+ rc = ptrace_write_dr7(tsk, val);
+
+ret_path:
+ mutex_unlock(&hw_breakpoint_mutex);
+ return rc;
}

/*
@@ -871,7 +918,7 @@ long arch_ptrace(struct task_struct *chi
else if (addr >= offsetof(struct user, u_debugreg[0]) &&
addr <= offsetof(struct user, u_debugreg[7])) {
addr -= offsetof(struct user, u_debugreg[0]);
- tmp = ptrace_get_debugreg(child, addr / sizeof(data));
+ tmp = ptrace_get_debugreg(child, addr/sizeof(data));
}
ret = put_user(tmp, datap);
break;
@@ -889,7 +936,7 @@ long arch_ptrace(struct task_struct *chi
addr <= offsetof(struct user, u_debugreg[7])) {
addr -= offsetof(struct user, u_debugreg[0]);
ret = ptrace_set_debugreg(child,
- addr / sizeof(data), data);
+ addr/sizeof(data), data);
}
break;


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/