lkml.org 
[lkml]   [2002]   [Oct]   [22]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[patch 2/4] kprobes - debug register management for 2.5.44
Here is a patch to provide global debug registers support on ia32.
Debug registers are global resources and unless they are allocated
and freed when needed we will end up overwriting somebody else's
settings.

While this is not absolutely essential for kernel watchpoints,
it is needed for its correct functioning.

2.5 specific changes include:
- lots of code cleanup
- s/CONFIG_DR_ALLOC/CONFIG_DEBUGREG
- kill lots of #ifdefs in .c files
- consolidate all dr stuff in include/asm-i386/debugreg.h

This patches applies on top of kprobes base.
--
Vamsi Krishna S.
Linux Technology Center,
IBM Software Lab, Bangalore.
Ph: +91 80 5044959
Internet: vamsi@in.ibm.com
--
diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/config.in 44-kprobes/arch/i386/config.in
--- 44-pure/arch/i386/config.in 2002-10-22 12:52:07.000000000 +0530
+++ 44-kprobes/arch/i386/config.in 2002-10-22 12:38:18.000000000 +0530
@@ -457,6 +457,7 @@

bool 'Kernel debugging' CONFIG_DEBUG_KERNEL
if [ "$CONFIG_DEBUG_KERNEL" != "n" ]; then
+ bool ' Global Debug Registers' CONFIG_DEBUGREG
bool ' Check for stack overflows' CONFIG_DEBUG_STACKOVERFLOW
bool ' Debug memory allocations' CONFIG_DEBUG_SLAB
bool ' Memory mapped I/O debugging' CONFIG_DEBUG_IOVIRT
diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/kernel/debugreg.c 44-kprobes/arch/i386/kernel/debugreg.c
--- 44-pure/arch/i386/kernel/debugreg.c 1970-01-01 05:30:00.000000000 +0530
+++ 44-kprobes/arch/i386/kernel/debugreg.c 2002-10-22 12:37:52.000000000 +0530
@@ -0,0 +1,178 @@
+/*
+ * This provides a debug register allocation mechanism, to be
+ * used by all debuggers, which need debug registers.
+ *
+ * Author: vamsi_krishna@in.ibm.com
+ * bharata@in.ibm.com
+ */
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <asm/system.h>
+#include <asm/debugreg.h>
+
+struct debugreg dr_list[DR_MAX];
+unsigned long dr7_global_mask = 0;
+static spinlock_t dr_lock = SPIN_LOCK_UNLOCKED;
+
+static inline void set_dr7_global_mask(int regnum)
+{
+ switch (regnum) {
+ case 0: dr7_global_mask |= DR7_DR0_BITS; break;
+ case 1: dr7_global_mask |= DR7_DR1_BITS; break;
+ case 2: dr7_global_mask |= DR7_DR2_BITS; break;
+ case 3: dr7_global_mask |= DR7_DR3_BITS; break;
+ }
+ return;
+}
+
+static inline void clear_dr7_global_mask(int regnum)
+{
+ switch (regnum) {
+ case 0: dr7_global_mask &= ~DR7_DR0_BITS; break;
+ case 1: dr7_global_mask &= ~DR7_DR1_BITS; break;
+ case 2: dr7_global_mask &= ~DR7_DR2_BITS; break;
+ case 3: dr7_global_mask &= ~DR7_DR3_BITS; break;
+ }
+ return;
+}
+
+static int get_dr(int regnum, int flag)
+{
+ if ((flag == DR_ALLOC_GLOBAL) && (dr_list[regnum].flag == DR_UNUSED)) {
+ dr_list[regnum].flag = DR_GLOBAL;
+ set_dr7_global_mask(regnum);
+ return regnum;
+ }
+ else if ((dr_list[regnum].flag == DR_UNUSED) || (dr_list[regnum].flag == DR_LOCAL)) {
+ dr_list[regnum].use_count++;
+ dr_list[regnum].flag = DR_LOCAL;
+ return regnum;
+ }
+ return -1;
+}
+
+static int get_any_dr(int flag)
+{
+ int i;
+ if (flag == DR_ALLOC_LOCAL) {
+ for (i = 0; i < DR_MAX; i++) {
+ if (dr_list[i].flag == DR_LOCAL) {
+ dr_list[i].use_count++;
+ return i;
+ } else if (dr_list[i].flag == DR_UNUSED) {
+ dr_list[i].flag = DR_LOCAL;
+ dr_list[i].use_count = 1;
+ return i;
+ }
+ }
+ } else {
+ for (i = DR_MAX-1; i >= 0; i--) {
+ if (dr_list[i].flag == DR_UNUSED) {
+ dr_list[i].flag = DR_GLOBAL;
+ set_dr7_global_mask(i);
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+static inline void dr_free_local(int regnum)
+{
+ if (! (--dr_list[regnum].use_count))
+ dr_list[regnum].flag = DR_UNUSED;
+ return;
+}
+
+static inline void dr_free_global(int regnum)
+{
+ dr_list[regnum].flag = DR_UNUSED;
+ dr_list[regnum].use_count = 0;
+ clear_dr7_global_mask(regnum);
+ return;
+}
+
+int dr_alloc(int regnum, int flag)
+{
+ int ret;
+
+ spin_lock(&dr_lock);
+ if (regnum == DR_ANY)
+ ret = get_any_dr(flag);
+ else if (regnum >= DR_MAX)
+ ret = -1;
+ else
+ ret = get_dr(regnum, flag);
+ spin_unlock(&dr_lock);
+ return ret;
+}
+
+int dr_free(int regnum)
+{
+ spin_lock(&dr_lock);
+ if (regnum >= DR_MAX || dr_list[regnum].flag == DR_UNUSED) {
+ spin_unlock(&dr_lock);
+ return -1;
+ }
+ if (dr_list[regnum].flag == DR_LOCAL)
+ dr_free_local(regnum);
+ else
+ dr_free_global(regnum);
+ spin_unlock(&dr_lock);
+ return 0;
+}
+
+void dr_inc_use_count(unsigned long mask)
+{
+ int i;
+
+ spin_lock(&dr_lock);
+ for (i =0; i < DR_MAX; i++) {
+ if (DR_IS_LOCAL(mask, i))
+ dr_list[i].use_count++;
+ }
+ spin_unlock(&dr_lock);
+}
+
+void dr_dec_use_count(unsigned long mask)
+{
+ int i;
+
+ spin_lock(&dr_lock);
+ for (i =0; i < DR_MAX; i++) {
+ if (DR_IS_LOCAL(mask, i))
+ dr_free_local(i);
+ }
+ spin_unlock(&dr_lock);
+}
+
+/*
+ * This routine decides if the ptrace request is for enabling or disabling
+ * a debug reg, and accordingly calls dr_alloc() or dr_free().
+ *
+ * gdb uses ptrace to write to debug registers. It assumes that writing to
+ * debug register always succeds and it doesn't check the return value of
+ * ptrace. Now with this new global debug register allocation/freeing,
+ * ptrace request for a local debug register can fail, if the required debug
+ * register is already globally allocated. Since gdb fails to notice this
+ * failure, it sometimes tries to free a debug register, which is not
+ * allocated for it.
+ */
+int enable_debugreg(unsigned long old_dr7, unsigned long new_dr7)
+{
+ int i, dr_shift = 1UL;
+ for (i = 0; i < DR_MAX; i++, dr_shift <<= 2) {
+ if ((old_dr7 ^ new_dr7) & dr_shift) {
+ if (new_dr7 & dr_shift)
+ dr_alloc(i, DR_ALLOC_LOCAL);
+ else
+ dr_free(i);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+EXPORT_SYMBOL(dr_alloc);
+EXPORT_SYMBOL(dr_free);
diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/kernel/Makefile 44-kprobes/arch/i386/kernel/Makefile
--- 44-pure/arch/i386/kernel/Makefile 2002-10-22 12:52:07.000000000 +0530
+++ 44-kprobes/arch/i386/kernel/Makefile 2002-10-22 12:37:52.000000000 +0530
@@ -4,7 +4,7 @@

EXTRA_TARGETS := head.o init_task.o

-export-objs := mca.o i386_ksyms.o time.o
+export-objs := mca.o i386_ksyms.o time.o debugreg.o

obj-y := process.o semaphore.o signal.o entry.o traps.o irq.o vm86.o \
ptrace.o i8259.o ioport.o ldt.o setup.o time.o sys_i386.o \
@@ -30,6 +30,7 @@
obj-$(CONFIG_PROFILING) += profile.o
obj-$(CONFIG_EDD) += edd.o
obj-$(CONFIG_KPROBES) += kprobes.o
+obj-$(CONFIG_DEBUGREG) += debugreg.o

EXTRA_AFLAGS := -traditional

diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/kernel/process.c 44-kprobes/arch/i386/kernel/process.c
--- 44-pure/arch/i386/kernel/process.c 2002-10-19 09:30:42.000000000 +0530
+++ 44-kprobes/arch/i386/kernel/process.c 2002-10-22 12:37:52.000000000 +0530
@@ -46,6 +46,7 @@
#ifdef CONFIG_MATH_EMULATION
#include <asm/math_emu.h>
#endif
+#include <asm/debugreg.h>

#include <linux/irq.h>
#include <linux/err.h>
@@ -240,12 +241,16 @@
kfree(tsk->thread.ts_io_bitmap);
tsk->thread.ts_io_bitmap = NULL;
}
+ if (tsk->thread.debugreg[7])
+ dr_dec_use_count(tsk->thread.debugreg[7]);
}

void flush_thread(void)
{
struct task_struct *tsk = current;

+ if (tsk->thread.debugreg[7])
+ dr_dec_use_count(tsk->thread.debugreg[7]);
memset(tsk->thread.debugreg, 0, sizeof(unsigned long)*8);
/*
* Forget coprocessor state..
@@ -328,6 +333,9 @@
desc->a = LDT_entry_a(&info);
desc->b = LDT_entry_b(&info);
}
+
+ if (current->thread.debugreg[7])
+ dr_inc_use_count(current->thread.debugreg[7]);
return 0;
}

@@ -443,6 +451,24 @@
/*
* Now maybe reload the debug registers
*/
+#ifdef CONFIG_DEBUGREG
+{
+ /*
+ * Don't reload global debug registers. Don't touch the global debug
+ * register settings in dr7.
+ */
+ unsigned long next_dr7 = next->debugreg[7];
+ if (unlikely(next_dr7)) {
+ if (DR7_L0(next_dr7)) loaddebug(next, 0);
+ if (DR7_L1(next_dr7)) loaddebug(next, 1);
+ if (DR7_L2(next_dr7)) loaddebug(next, 2);
+ if (DR7_L3(next_dr7)) loaddebug(next, 3);
+ /* no 4 and 5 */
+ loaddebug(next, 6);
+ load_process_dr7(next_dr7);
+ }
+}
+#else
if (unlikely(next->debugreg[7])) {
loaddebug(next, 0);
loaddebug(next, 1);
@@ -452,7 +478,7 @@
loaddebug(next, 6);
loaddebug(next, 7);
}
-
+#endif
if (unlikely(prev->ts_io_bitmap || next->ts_io_bitmap)) {
if (next->ts_io_bitmap) {
/*
diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/kernel/ptrace.c 44-kprobes/arch/i386/kernel/ptrace.c
--- 44-pure/arch/i386/kernel/ptrace.c 2002-10-19 09:31:19.000000000 +0530
+++ 44-kprobes/arch/i386/kernel/ptrace.c 2002-10-22 12:37:52.000000000 +0530
@@ -269,6 +269,11 @@

addr -= (long) &dummy->u_debugreg;
addr = addr >> 2;
+
+ if (addr == 7 && (enable_debugreg(child->thread.debugreg[addr], data)) < 0) {
+ ret = -EBUSY;
+ break;
+ }
child->thread.debugreg[addr] = data;
ret = 0;
}
diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/kernel/signal.c 44-kprobes/arch/i386/kernel/signal.c
--- 44-pure/arch/i386/kernel/signal.c 2002-10-19 09:31:52.000000000 +0530
+++ 44-kprobes/arch/i386/kernel/signal.c 2002-10-22 12:37:52.000000000 +0530
@@ -22,6 +22,7 @@
#include <asm/ucontext.h>
#include <asm/uaccess.h>
#include <asm/i387.h>
+#include <asm/debugreg.h>

#define DEBUG_SIG 0

@@ -574,7 +575,7 @@
* have been cleared if the watchpoint triggered
* inside the kernel.
*/
- __asm__("movl %0,%%db7" : : "r" (current->thread.debugreg[7]));
+ load_process_dr7(current->thread.debugreg[7]);

/* Whee! Actually deliver the signal. */
handle_signal(signr, &info, oldset, regs);
diff -urN -X /home/vamsi/.dontdiff 44-pure/arch/i386/kernel/traps.c 44-kprobes/arch/i386/kernel/traps.c
--- 44-pure/arch/i386/kernel/traps.c 2002-10-22 12:52:07.000000000 +0530
+++ 44-kprobes/arch/i386/kernel/traps.c 2002-10-22 12:37:52.000000000 +0530
@@ -641,9 +641,7 @@
* the signal is delivered.
*/
clear_dr7:
- __asm__("movl %0,%%db7"
- : /* no output */
- : "r" (0));
+ load_process_dr7(0);
return 0;

debug_vm86:
diff -urN -X /home/vamsi/.dontdiff 44-pure/include/asm-i386/debugreg.h 44-kprobes/include/asm-i386/debugreg.h
--- 44-pure/include/asm-i386/debugreg.h 2002-10-19 09:32:27.000000000 +0530
+++ 44-kprobes/include/asm-i386/debugreg.h 2002-10-22 12:37:52.000000000 +0530
@@ -61,4 +61,166 @@
#define DR_LOCAL_SLOWDOWN (0x100) /* Local slow the pipeline */
#define DR_GLOBAL_SLOWDOWN (0x200) /* Global slow the pipeline */

+struct debugreg {
+ unsigned long flag;
+ unsigned long use_count;
+};
+
+/* debugreg flags */
+#define DR_UNUSED 0
+#define DR_LOCAL 1
+#define DR_GLOBAL 2
+
+#define DR_MAX 4
+#define DR_ANY DR_MAX + 1
+
+/* global or local allocation requests */
+#define DR_ALLOC_GLOBAL 0
+#define DR_ALLOC_LOCAL 1
+
+#define DR7_RW_SET(dr, regnum, rw) do { \
+ (dr) &= ~(0x3 << (16 + (4 * (regnum)))); \
+ (dr) |= (((rw) & 0x3) << (16 + (4 * (regnum)))); \
+ } while (0)
+
+#define DR7_RW_VAL(dr, regnum) \
+ (((dr) >> (16 + (4 * (regnum)))) & 0x3)
+
+#define DR7_LEN_SET(dr, regnum, len) do { \
+ (dr) &= ~(0x3 << (18 + (4 * (regnum)))); \
+ (dr) |= (((len-1) & 0x3) << (18 + (4 * (regnum)))); \
+ } while (0)
+
+#define DR7_LEN_VAL(dr, regnum) \
+ (((dr) >> (18 + (4 * (regnum)))) & 0x3)
+
+#define DR7_L0(dr) (((dr))&0x1)
+#define DR7_L1(dr) (((dr)>>2)&0x1)
+#define DR7_L2(dr) (((dr)>>4)&0x1)
+#define DR7_L3(dr) (((dr)>>6)&0x1)
+
+#define DR_IS_LOCAL(dr, num) ((dr) & (1UL << (num <<1)))
+
+/* Set the rw, len and global flag in dr7 for a debug register */
+#define SET_DR7(dr, regnum, access, len) do { \
+ DR7_RW_SET(dr, regnum, access); \
+ DR7_LEN_SET(dr, regnum, len); \
+ dr |= (2UL << regnum*2); \
+ } while (0)
+
+/* Disable a debug register by clearing the global/local flag in dr7 */
+#define RESET_DR7(dr, regnum) dr &= ~(3UL << regnum*2)
+
+#define DR7_DR0_BITS 0x000F0003
+#define DR7_DR1_BITS 0x00F0000C
+#define DR7_DR2_BITS 0x0F000030
+#define DR7_DR3_BITS 0xF00000C0
+
+#define DR_TRAP_MASK 0xF
+
+#define DR_TYPE_EXECUTE 0x0
+#define DR_TYPE_WRITE 0x1
+#define DR_TYPE_IO 0x2
+#define DR_TYPE_RW 0x3
+
+#define get_dr(regnum, val) \
+ __asm__("movl %%db" #regnum ", %0" \
+ :"=r" (val))
+static inline unsigned long read_dr(int regnum)
+{
+ unsigned long val = 0;
+ switch (regnum) {
+ case 0: get_dr(0, val); break;
+ case 1: get_dr(1, val); break;
+ case 2: get_dr(2, val); break;
+ case 3: get_dr(3, val); break;
+ case 6: get_dr(6, val); break;
+ case 7: get_dr(7, val); break;
+ }
+ return val;
+}
+#undef get_dr
+
+#define set_dr(regnum, val) \
+ __asm__("movl %0,%%db" #regnum \
+ : /* no output */ \
+ :"r" (val))
+static inline void write_dr(int regnum, unsigned long val)
+{
+ switch (regnum) {
+ case 0: set_dr(0, val); break;
+ case 1: set_dr(1, val); break;
+ case 2: set_dr(2, val); break;
+ case 3: set_dr(3, val); break;
+ case 7: set_dr(7, val); break;
+ }
+ return;
+}
+#undef set_dr
+
+/*
+ * Given the debug status register, returns the debug register number
+ * which caused the debug trap.
+ */
+static inline int dr_trap(unsigned int condition)
+{
+ int i, reg_shift = 1UL;
+ for (i = 0; i < DR_MAX; i++, reg_shift <<= 1)
+ if ((condition & reg_shift))
+ return i;
+ return -1;
+}
+
+/*
+ * Given the debug status register, returns the address due to which
+ * the debug trap occured.
+ */
+static inline unsigned long dr_trap_addr(unsigned int condition)
+{
+ int regnum = dr_trap(condition);
+
+ if (regnum == -1)
+ return -1;
+ return read_dr(regnum);
+}
+
+/*
+ * Given the debug status register, returns the type of debug trap:
+ * execute, read/write, write or io.
+ */
+static inline int dr_trap_type(unsigned int condition)
+{
+ int regnum = dr_trap(condition);
+
+ if (regnum == -1)
+ return -1;
+ return DR7_RW_VAL(read_dr(7), regnum);
+}
+
+/* Function declarations */
+
+#ifdef CONFIG_DEBUGREG
+extern int dr_alloc(int regnum, int flag);
+extern int dr_free(int regnum);
+extern void dr_inc_use_count(unsigned long mask);
+extern void dr_dec_use_count(unsigned long mask);
+extern struct debugreg dr_list[DR_MAX];
+extern unsigned long dr7_global_mask;
+extern int enable_debugreg(unsigned long old_dr7, unsigned long new_dr7);
+
+static inline void load_process_dr7(unsigned long curr_dr7)
+{
+ write_dr(7, (read_dr(7) & dr7_global_mask) | curr_dr7);
+}
+#else
+static inline int enable_debugreg(unsigned long old_dr7, unsigned long new_dr7) { return 0; }
+static inline void void load_process_dr7(unsigned long curr_dr7)
+{
+ write_dr(7, curr_dr7);
+}
+
+static void dr_inc_use_count(unsigned long mask) { }
+static void dr_dec_use_count(unsigned long mask) { }
+
+#endif /* CONFIG_DEBUGREG */
#endif
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
\
 
 \ /
  Last update: 2005-03-22 13:30    [W:0.036 / U:2.712 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site