diff options
author | Eli Zaretskii <eliz@gnu.org> | 2001-03-21 11:36:58 +0000 |
---|---|---|
committer | Eli Zaretskii <eliz@gnu.org> | 2001-03-21 11:36:58 +0000 |
commit | 52b9821179d84d61852ac2ae2bd16fbb56ffe277 (patch) | |
tree | c1c3ee94b60327b31a987cf553d0de37de1bc2ad | |
parent | 28e9e0f00db3d6976aae188c204a66364b0857f9 (diff) | |
download | gdb-52b9821179d84d61852ac2ae2bd16fbb56ffe277.zip gdb-52b9821179d84d61852ac2ae2bd16fbb56ffe277.tar.gz gdb-52b9821179d84d61852ac2ae2bd16fbb56ffe277.tar.bz2 |
Unified support for hardware breakpoints and watchpoints on
x86 targets:
* config/i386/nm-i386.h: New file.
* config/i386/nm-i386.h: (i386_cleanup_dregs)
(i386_insert_watchpoint, i386_remove_watchpoint)
(i386_region_ok_for_watchpoint, i386_stopped_by_hwbp)
(i386_stopped_data_address, i386_insert_hw_breakpoint)
(i386_remove_hw_breakpoint): Declare prototypes.
[I386_USE_GENERIC_WATCHPOINTS] (TARGET_CAN_USE_HARDWARE_WATCHPOINT):
Define if not already defined.
(TARGET_REGION_OK_FOR_HW_WATCHPOINT, HAVE_CONTINUABLE_WATCHPOINT)
(STOPPED_BY_WATCHPOINT, target_stopped_data_address)
(target_insert_watchpoint, target_remove_watchpoint)
(target_insert_hw_breakpoint, target_remove_hw_breakpoint): Define
to call the appropriate i386_* functions.
* i386-nat.c: New file.
* i386-nat.c (I386_DR_CONTROL_MASK, I386_DR_LOCAL_ENABLE)
(I386_DR_GLOBAL_ENABLE, I386_DR_DISABLE, I386_DR_SET_RW_LEN)
(I386_DR_GET_RW_LEN, I386_DR_WATCH_HIT): New macros.
(dr_mirror, dr_status_mirror, dr_control_mirror, dr_ref_count)
(maint_show_dr): New variables.
(i386_cleanup_dregs, i386_show_dr, i386_length_and_rw_bits)
(i386_insert_aligned_watchpoint, i386_remove_aligned_watchpoint)
(i386_handle_nonaligned_watchpoint, i386_insert_watchpoint)
(i386_remove_watchpoint, i386_region_ok_for_watchpoint)
(i386_stopped_data_address, i386_stopped_by_hwbp)
(i386_insert_hw_breakpoint, i386_remove_hw_breakpoint): New
functions.
(_initialize_i386_nat): New function.
[I386_USE_GENERIC_WATCHPOINTS]: Add new maint command
`show-debug-regs', sets maint_show_dr to non-zero value and
activates debugging print-outs in functions which insert, remove,
and test watchpoints and hardware breakpoints.
* Makefile.in (i386-nat.o): New target.
(ALLDEPFILES): Add i386-nat.o.
-rw-r--r-- | gdb/ChangeLog | 43 | ||||
-rw-r--r-- | gdb/Makefile.in | 4 | ||||
-rw-r--r-- | gdb/config/i386/nm-i386.h | 122 | ||||
-rw-r--r-- | gdb/i386-nat.c | 618 |
4 files changed, 786 insertions, 1 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 201fea6..0d076f9 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,46 @@ +2001-03-21 Eli Zaretskii <eliz@is.elta.co.il> + + Unified support for hardware breakpoints and watchpoints on + x86 targets: + + * config/i386/nm-i386.h: New file. + + * config/i386/nm-i386.h: (i386_cleanup_dregs) + (i386_insert_watchpoint, i386_remove_watchpoint) + (i386_region_ok_for_watchpoint, i386_stopped_by_hwbp) + (i386_stopped_data_address, i386_insert_hw_breakpoint) + (i386_remove_hw_breakpoint): Declare prototypes. + [I386_USE_GENERIC_WATCHPOINTS] (TARGET_CAN_USE_HARDWARE_WATCHPOINT): + Define if not already defined. + (TARGET_REGION_OK_FOR_HW_WATCHPOINT, HAVE_CONTINUABLE_WATCHPOINT) + (STOPPED_BY_WATCHPOINT, target_stopped_data_address) + (target_insert_watchpoint, target_remove_watchpoint) + (target_insert_hw_breakpoint, target_remove_hw_breakpoint): Define + to call the appropriate i386_* functions. + + * i386-nat.c: New file. + + * i386-nat.c (I386_DR_CONTROL_MASK, I386_DR_LOCAL_ENABLE) + (I386_DR_GLOBAL_ENABLE, I386_DR_DISABLE, I386_DR_SET_RW_LEN) + (I386_DR_GET_RW_LEN, I386_DR_WATCH_HIT): New macros. + (dr_mirror, dr_status_mirror, dr_control_mirror, dr_ref_count) + (maint_show_dr): New variables. + (i386_cleanup_dregs, i386_show_dr, i386_length_and_rw_bits) + (i386_insert_aligned_watchpoint, i386_remove_aligned_watchpoint) + (i386_handle_nonaligned_watchpoint, i386_insert_watchpoint) + (i386_remove_watchpoint, i386_region_ok_for_watchpoint) + (i386_stopped_data_address, i386_stopped_by_hwbp) + (i386_insert_hw_breakpoint, i386_remove_hw_breakpoint): New + functions. + (_initialize_i386_nat): New function. + [I386_USE_GENERIC_WATCHPOINTS]: Add new maint command + `show-debug-regs', sets maint_show_dr to non-zero value and + activates debugging print-outs in functions which insert, remove, + and test watchpoints and hardware breakpoints. + + * Makefile.in (i386-nat.o): New target. + (ALLDEPFILES): Add i386-nat.o. + 2001-03-21 Mark Kettenis <kettenis@gnu.org> * i386-tdep.c: Add back _initialize_i386_tdep prototype with diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 0fa1874..91bb077 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1147,7 +1147,7 @@ ALLDEPFILES = 29k-share/udi/udip2soc.c 29k-share/udi/udr.c \ i386-tdep.c i386b-nat.c i386mach-nat.c i386v-nat.c i386-linux-nat.c \ i386aix-nat.c i386m3-nat.c i386v4-nat.c i386ly-tdep.c \ i387-tdep.c \ - i386-linux-tdep.c \ + i386-linux-tdep.c i386-nat.c \ i960-tdep.c \ ia64-linux-nat.c ia64-linux-tdep.c ia64-tdep.c \ infptrace.c inftarg.c irix4-nat.c irix5-nat.c isi-xdep.c \ @@ -1484,6 +1484,8 @@ i386-tdep.o: i386-tdep.c $(defs_h) gdb_string.h $(frame_h) \ $(inferior_h) $(gdbcore_h) target.h $(floatformat_h) \ $(symtab_h) $(gdbcmd_h) $(command_h) $(arch_utils_h) $(regcache_h) +i386-nat.o: i386-nat.c $(defs_h) $(breakpoint_h) $(command_h) $(gdbcmd_h) + i386aix-nat.o: i386aix-nat.c $(defs_h) $(frame_h) $(inferior_h) \ language.h $(gdbcore_h) $(floatformat_h) target.h $(regcache_h) diff --git a/gdb/config/i386/nm-i386.h b/gdb/config/i386/nm-i386.h new file mode 100644 index 0000000..4f9b157 --- /dev/null +++ b/gdb/config/i386/nm-i386.h @@ -0,0 +1,122 @@ +/* Native macro definitions for GDB on an Intel i[3456]86. + Copyright (C) 2001 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef NM_I386_H +#define NM_I386_H 1 + + +/* Hardware-assisted breakpoints and watchpoints. */ + +/* Targets should define this to use the generic x86 watchpoint support. */ +#ifdef I386_USE_GENERIC_WATCHPOINTS + + +#ifndef TARGET_HAS_HARDWARE_WATCHPOINTS +#define TARGET_HAS_HARDWARE_WATCHPOINTS +#endif + +/* Clear the reference counts and forget everything we knew about DRi. */ +extern void i386_cleanup_dregs (void); + +/* Insert a watchpoint to watch a memory region which starts at + address ADDR and whose length is LEN bytes. Watch memory accesses + of the type TYPE. Return 0 on success, -1 on failure. */ +extern int i386_insert_watchpoint (CORE_ADDR addr, int len, int type); + +/* Remove a watchpoint that watched the memory region which starts at + address ADDR, whose length is LEN bytes, and for accesses of the + type TYPE. Return 0 on success, -1 on failure. */ +extern int i386_remove_watchpoint (CORE_ADDR addr, int len, int type); + +/* Return non-zero if we can watch a memory region that starts at + address ADDR and whose length is LEN bytes. */ +extern int i386_region_ok_for_watchpoint (CORE_ADDR addr, int len); + +/* Return non-zero if the inferior has some break/watchpoint that + triggered. */ +extern int i386_stopped_by_hwbp (void); + +/* If the inferior has some break/watchpoint that triggered, return + the address associated with that break/watchpoint. Otherwise, + return zero. */ +extern CORE_ADDR i386_stopped_data_address (void); + +/* Insert a hardware-assisted breakpoint at address ADDR. SHADOW is + unused. Return 0 on success, EBUSY on failure. */ +extern int i386_insert_hw_breakpoint (CORE_ADDR addr, void *shadow); + +/* Remove a hardware-assisted breakpoint at address ADDR. SHADOW is + unused. Return 0 on success, -1 on failure. */ +extern int i386_remove_hw_breakpoint (CORE_ADDR addr, void *shadow); + +/* Returns the number of hardware watchpoints of type TYPE that we can + set. Value is positive if we can set CNT watchpoints, zero if + setting watchpoints of type TYPE is not supported, and negative if + CNT is more than the maximum number of watchpoints of type TYPE + that we can support. TYPE is one of bp_hardware_watchpoint, + bp_read_watchpoint, bp_write_watchpoint, or bp_hardware_breakpoint. + CNT is the number of such watchpoints used so far (including this + one). OTHERTYPE is non-zero if other types of watchpoints are + currently enabled. + + We always return 1 here because we don't have enough information + about possible overlap of addresses that they want to watch. As + an extreme example, consider the case where all the watchpoints + watch the same address and the same region length: then we can + handle a virtually unlimited number of watchpoints, due to debug + register sharing implemented via reference counts in i386-tdep.c. */ + +#define TARGET_CAN_USE_HARDWARE_WATCHPOINT(type, cnt, ot) 1 + +/* Returns non-zero if we can use hardware watchpoints to watch a region + whose address is ADDR and whose length is LEN. */ + +#define TARGET_REGION_OK_FOR_HW_WATCHPOINT(addr,len) \ + i386_region_ok_for_watchpoint(addr,len) + +/* After a watchpoint trap, the PC points to the instruction after the + one that caused the trap. Therefore we don't need to step over it. + But we do need to reset the status register to avoid another trap. */ + +#define HAVE_CONTINUABLE_WATCHPOINT + +#define STOPPED_BY_WATCHPOINT(W) (i386_stopped_data_address () != 0) + +#define target_stopped_data_address() i386_stopped_data_address () + +/* Use these macros for watchpoint insertion/removal. */ + +#define target_insert_watchpoint(addr, len, type) \ + i386_insert_watchpoint (addr, len, type) + +#define target_remove_watchpoint(addr, len, type) \ + i386_remove_watchpoint (addr, len, type) + +#define target_insert_hw_breakpoint(addr, shadow) \ + i386_insert_hw_breakpoint(addr, shadow) + +#define target_remove_hw_breakpoint(addr, shadow) \ + i386_remove_hw_breakpoint(addr, shadow) + +#define DECR_PC_AFTER_HW_BREAK 0 + +#endif /* I386_USE_GENERIC_WATCHPOINTS */ + +#endif /* NM_I386_H */ diff --git a/gdb/i386-nat.c b/gdb/i386-nat.c new file mode 100644 index 0000000..e250b1b --- /dev/null +++ b/gdb/i386-nat.c @@ -0,0 +1,618 @@ +/* Intel x86 (a.k.a. ia32) native-dependent code. + Copyright (C) 2001 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include "defs.h" +#include "breakpoint.h" +#include "command.h" +#include "gdbcmd.h" + +/* Support for hardware watchpoints and breakpoints using the x86 + debug registers. + + This provides several functions for inserting and removing + hardware-assisted breakpoints and watchpoints, testing if + one or more of the watchpoints triggerd and at what address, + checking whether a given region can be watched, etc. + + A target which wants to use these functions should define + several macros, such as `target_insert_watchpoint' and + `target_stopped_data_address', listed in target.h, to call + the appropriate functions below. It should also define + I386_USE_GENERIC_WATCHPOINTS in its tm.h file. + + In addition, each target should provide several low-level + macros that will be called to insert watchpoints and hardware + breakpoints into the inferior, remove them, and check their + status. These macros are: + + I386_DR_LOW_SET_CONTROL -- set the debug control (DR7) + register to a given value + + I386_DR_LOW_SET_ADDR -- put an address into one debug + register + + I386_DR_LOW_RESET_ADDR -- reset the address stored in + one debug register + + I386_DR_LOW_GET_STATUS -- return the value of the debug + status (DR6) register. + + The functions below implement debug registers sharing by + reference counts, and allow to watch regions up to 16 bytes + long. */ + +#ifdef I386_USE_GENERIC_WATCHPOINTS + +/* Debug registers' indices. */ +#define DR_NADDR 4 /* the number of debug address registers */ +#define DR_STATUS 6 /* index of debug status register (DR6) */ +#define DR_CONTROL 7 /* index of debug control register (DR7) */ + +/* DR7 Debug Control register fields. */ + +/* How many bits to skip in DR7 to get to R/W and LEN fields. */ +#define DR_CONTROL_SHIFT 16 +/* How many bits in DR7 per R/W and LEN field for each watchpoint. */ +#define DR_CONTROL_SIZE 4 + +/* Watchpoint/breakpoint read/write fields in DR7. */ +#define DR_RW_EXECUTE (0x0) /* break on instruction execution */ +#define DR_RW_WRITE (0x1) /* break on data writes */ +#define DR_RW_READ (0x3) /* break on data reads or writes */ + +/* This is here for completeness. No platform supports this + functionality yet (as of Mar-2001). Note that the DE flag in the + CR4 register needs to be set to support this. */ +#ifndef DR_RW_IORW +#define DR_RW_IORW (0x2) /* break on I/O reads or writes */ +#endif + +/* Watchpoint/breakpoint length fields in DR7. The 2-bit left shift + is so we could OR this with the read/write field defined above. */ +#define DR_LEN_1 (0x0 << 2) /* 1-byte region watch or breakpt */ +#define DR_LEN_2 (0x1 << 2) /* 2-byte region watch */ +#define DR_LEN_4 (0x3 << 2) /* 4-byte region watch */ + +/* Local and Global Enable flags in DR7. + + When the Local Enable flag is set, the breakpoint/watchpoint is + enabled only for the current task; the processor automatically + clears this flag on every task switch. When the Global Enable + flag is set, the breakpoint/watchpoint is enabled for all tasks; + the processor never clears this flag. + + Currently, all watchpoint are locally enabled. If you need to + enable them globally, read the comment which pertains to this in + i386_insert_aligned_watchpoint below. */ +#define DR_LOCAL_ENABLE_SHIFT 0 /* extra shift to the local enable bit */ +#define DR_GLOBAL_ENABLE_SHIFT 1 /* extra shift to the global enable bit */ +#define DR_ENABLE_SIZE 2 /* 2 enable bits per debug register */ + +/* Local and global exact breakpoint enable flags (a.k.a. slowdown + flags). These are only required on i386, to allow detection of the + exact instruction which caused a watchpoint to break; i486 and + later processors do that automatically. We set these flags for + back compatibility. */ +#define DR_LOCAL_SLOWDOWN (0x100) +#define DR_GLOBAL_SLOWDOWN (0x200) + +/* Fields reserved by Intel. This includes the GD (General Detect + Enable) flag, which causes a debug exception to be generated when a + MOV instruction accesses one of the debug registers. + + FIXME: My Intel manual says we should use 0xF800, not 0xFC00. */ +#define DR_CONTROL_RESERVED (0xFC00) + +/* Auxiliary helper macros. */ + +/* A value that masks all fields in DR7 that are reserved by Intel. */ +#define I386_DR_CONTROL_MASK (~DR_CONTROL_RESERVED) + +/* The I'th debug register is vacant if its Local and Global Enable + bits are reset in the Debug Control register. */ +#define I386_DR_VACANT(i) \ + ((dr_control_mirror & (3 << (DR_ENABLE_SIZE * (i)))) == 0) + +/* Locally enable the break/watchpoint in the I'th debug register. */ +#define I386_DR_LOCAL_ENABLE(i) \ + dr_control_mirror |= (1 << (DR_LOCAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i))) + +/* Globally enable the break/watchpoint in the I'th debug register. */ +#define I386_DR_GLOBAL_ENABLE(i) \ + dr_control_mirror |= (1 << (DR_GLOBAL_ENABLE_SHIFT + DR_ENABLE_SIZE * (i))) + +/* Disable the break/watchpoint in the I'th debug register. */ +#define I386_DR_DISABLE(i) \ + dr_control_mirror &= ~(3 << (DR_ENABLE_SIZE * (i))) + +/* Set in DR7 the RW and LEN fields for the I'th debug register. */ +#define I386_DR_SET_RW_LEN(i,rwlen) \ + do { \ + dr_control_mirror &= ~(0x0f << (DR_CONTROL_SHIFT+DR_CONTROL_SIZE*(i))); \ + dr_control_mirror |= ((rwlen) << (DR_CONTROL_SHIFT+DR_CONTROL_SIZE*(i))); \ + } while (0) + +/* Get from DR7 the RW and LEN fields for the I'th debug register. */ +#define I386_DR_GET_RW_LEN(i) \ + ((dr_control_mirror >> (DR_CONTROL_SHIFT + DR_CONTROL_SIZE * (i))) & 0x0f) + +/* Did the watchpoint whose address is in the I'th register break? */ +#define I386_DR_WATCH_HIT(i) (dr_status_mirror & (1 << (i))) + +/* A macro to loop over all debug registers. */ +#define ALL_DEBUG_REGISTERS(i) for (i = 0; i < DR_NADDR; i++) + +/* Mirror the inferior's DRi registers. We keep the status and + control registers separated because they don't hold addresses. */ +static CORE_ADDR dr_mirror[DR_NADDR]; +static unsigned dr_status_mirror, dr_control_mirror; + +/* Reference counts for each debug register. */ +static int dr_ref_count[DR_NADDR]; + +/* Whether or not to print the mirrored debug registers. */ +static int maint_show_dr; + +/* Types of operations supported by i386_handle_nonaligned_watchpoint. */ +typedef enum { WP_INSERT, WP_REMOVE, WP_COUNT } i386_wp_op_t; + +/* Internal functions. */ + +/* Return the value of a 4-bit field for DR7 suitable for watching a + region of LEN bytes for accesses of type TYPE. LEN is assumed + to have the value of 1, 2, or 4. */ +static unsigned i386_length_and_rw_bits (int len, enum target_hw_bp_type type); + +/* Insert a watchpoint at address ADDR, which is assumed to be aligned + according to the length of the region to watch. LEN_RW_BITS is the + value of the bit-field from DR7 which describes the length and + access type of the region to be watched by this watchpoint. Return + 0 on success, -1 on failure. */ +static int i386_insert_aligned_watchpoint (CORE_ADDR addr, + unsigned len_rw_bits); + +/* Remove a watchpoint at address ADDR, which is assumed to be aligned + according to the length of the region to watch. LEN_RW_BITS is the + value of the bits from DR7 which describes the length and access + type of the region watched by this watchpoint. Return 0 on + success, -1 on failure. */ +static int i386_remove_aligned_watchpoint (CORE_ADDR addr, + unsigned len_rw_bits); + +/* Insert or remove a (possibly non-aligned) watchpoint, or count the + number of debug registers required to watch a region at address + ADDR whose length is LEN for accesses of type TYPE. Return 0 on + successful insertion or removal, a positive number when queried + about the number of registers, or -1 on failure. If WHAT is not + a valid value, bombs through internal_error. */ +static int i386_handle_nonaligned_watchpoint (i386_wp_op_t what, + CORE_ADDR addr, int len, + enum target_hw_bp_type type); + +/* Implementation. */ + +/* Clear the reference counts and forget everything we knew about + the debug registers. */ +void +i386_cleanup_dregs (void) +{ + int i; + + ALL_DEBUG_REGISTERS(i) + { + dr_mirror[i] = 0; + dr_ref_count[i] = 0; + } + dr_control_mirror = 0; + dr_status_mirror = 0; +} + +/* Print the values of the mirrored debug registers. + This is called when maint_show_dr is non-zero. To set that + up, type "maint show-debug-regs" at GDB's prompt. */ +static void +i386_show_dr (const char *func, CORE_ADDR addr, + int len, enum target_hw_bp_type type) +{ + int i; + + puts_unfiltered (func); + if (addr || len) + printf_unfiltered (" (addr=%lx, len=%d, type=%s)", + /* This code is for ia32, so casting CORE_ADDR + to unsigned long should be okay. */ + (unsigned long)addr, len, + type == hw_write ? "data-write" + : (type == hw_read ? "data-read" + : (type == hw_access ? "data-read/write" + : (type == hw_execute ? "instruction-execute" + /* FIXME: if/when I/O read/write + watchpoints are supported, add them + here. */ + : "??unknown??")))); + puts_unfiltered (":\n"); + printf_unfiltered ("\tCONTROL (DR7): %08x STATUS (DR6): %08x\n", + dr_control_mirror, dr_status_mirror); + ALL_DEBUG_REGISTERS(i) + { + printf_unfiltered ("\tDR%d: addr=%08lx, ref.count=%d DR%d: addr=%08lx, ref.count=%d\n", + i, dr_mirror[i], dr_ref_count[i], + i+1, dr_mirror[i+1], dr_ref_count[i+1]); + i++; + } +} + +/* Return the value of a 4-bit field for DR7 suitable for watching a + region of LEN bytes for accesses of type TYPE. LEN is assumed + to have the value of 1, 2, or 4. */ +static unsigned +i386_length_and_rw_bits (int len, enum target_hw_bp_type type) +{ + unsigned rw; + + switch (type) + { + case hw_execute: + rw = DR_RW_EXECUTE; + break; + case hw_write: + rw = DR_RW_WRITE; + break; + case hw_read: /* x86 doesn't support data-read watchpoints */ + case hw_access: + rw = DR_RW_READ; + break; +#if 0 + case hw_io_access: /* not yet supported */ + rw = DR_RW_IORW; + break; +#endif + default: + internal_error (__FILE__, __LINE__, "\ +Invalid hw breakpoint type %d in i386_length_and_rw_bits.\n", (int)type); + } + + switch (len) + { + case 4: + return (DR_LEN_4 | rw); + case 2: + return (DR_LEN_2 | rw); + case 1: + return (DR_LEN_1 | rw); + default: + internal_error (__FILE__, __LINE__, "\ +Invalid hw breakpoint length %d in i386_length_and_rw_bits.\n", len); + } +} + +/* Insert a watchpoint at address ADDR, which is assumed to be aligned + according to the length of the region to watch. LEN_RW_BITS is the + value of the bits from DR7 which describes the length and access + type of the region to be watched by this watchpoint. Return 0 on + success, -1 on failure. */ +static int +i386_insert_aligned_watchpoint (CORE_ADDR addr, unsigned len_rw_bits) +{ + int i; + + /* First, look for an occupied debug register with the same address + and the same RW and LEN definitions. If we find one, we can + reuse it for this watchpoint as well (and save a register). */ + ALL_DEBUG_REGISTERS(i) + { + if (!I386_DR_VACANT (i) + && dr_mirror[i] == addr + && I386_DR_GET_RW_LEN (i) == len_rw_bits) + { + dr_ref_count[i]++; + return 0; + } + } + + /* Next, look for a vacant debug register. */ + ALL_DEBUG_REGISTERS(i) + { + if (I386_DR_VACANT (i)) + break; + } + + /* No more debug registers! */ + if (i >= DR_NADDR) + return -1; + + /* Now set up the register I to watch our region. */ + + /* Record the info in our local mirrored array. */ + dr_mirror[i] = addr; + dr_ref_count[i] = 1; + I386_DR_SET_RW_LEN (i, len_rw_bits); + /* Note: we only enable the watchpoint locally, i.e. in the current + task. Currently, no x86 target allows or supports global + watchpoints; however, if any target would want that in the + future, GDB should probably provide a command to control whether + to enable watchpoints globally or locally, and the code below + should use global or local enable and slow-down flags as + appropriate. */ + I386_DR_LOCAL_ENABLE (i); + dr_control_mirror |= DR_LOCAL_SLOWDOWN; + dr_control_mirror &= I386_DR_CONTROL_MASK; + + /* Finally, actually pass the info to the inferior. */ + I386_DR_LOW_SET_CONTROL (dr_control_mirror); + I386_DR_LOW_SET_ADDR (i, addr); + + return 0; +} + +/* Remove a watchpoint at address ADDR, which is assumed to be aligned + according to the length of the region to watch. LEN_RW_BITS is the + value of the bits from DR7 which describes the length and access + type of the region watched by this watchpoint. Return 0 on + success, -1 on failure. */ +static int +i386_remove_aligned_watchpoint (CORE_ADDR addr, unsigned len_rw_bits) +{ + int i, retval = -1; + + ALL_DEBUG_REGISTERS(i) + { + if (!I386_DR_VACANT (i) + && dr_mirror[i] == addr + && I386_DR_GET_RW_LEN (i) == len_rw_bits) + { + if (--dr_ref_count[i] == 0) /* no longer in use? */ + { + /* Reset our mirror. */ + dr_mirror[i] = 0; + I386_DR_DISABLE (i); + /* Reset it in the inferior. */ + I386_DR_LOW_RESET_ADDR (i); + I386_DR_LOW_SET_CONTROL (dr_control_mirror); + } + retval = 0; + } + } + + return retval; +} + +/* Insert or remove a (possibly non-aligned) watchpoint, or count the + number of debug registers required to watch a region at address + ADDR whose length is LEN for accesses of type TYPE. Return 0 on + successful insertion or removal, a positive number when queried + about the number of registers, or -1 on failure. If WHAT is not + a valid value, bombs through internal_error. */ +static int +i386_handle_nonaligned_watchpoint (i386_wp_op_t what, CORE_ADDR addr, int len, + enum target_hw_bp_type type) +{ + int align; + int size; + int rv = 0, status = 0; + + static int size_try_array[4][4] = + { + { 1, 1, 1, 1 }, /* trying size one */ + { 2, 1, 2, 1 }, /* trying size two */ + { 2, 1, 2, 1 }, /* trying size three */ + { 4, 1, 2, 1 } /* trying size four */ + }; + + while (len > 0) + { + align = addr % 4; + /* Four is the maximum length an x86 debug register can watch. */ + size = size_try_array[len > 4 ? 3 : len - 1][align]; + if (what == WP_COUNT) + /* size_try_array[] is defined so that each iteration through + the loop is guaranteed to produce an address and a size + that can be watched with a single debug register. Thus, + for counting the registers required to watch a region, we + simply need to increment the count on each iteration. */ + rv++; + else + { + unsigned len_rw = i386_length_and_rw_bits (size, type); + + if (what == WP_INSERT) + status = i386_insert_aligned_watchpoint (addr, len_rw); + else if (what == WP_REMOVE) + status = i386_remove_aligned_watchpoint (addr, len_rw); + else + internal_error (__FILE__, __LINE__, "\ +Invalid value %d of operation in i386_handle_nonaligned_watchpoint.\n", + (int)what); + /* We keep the loop going even after a failure, because some + of the other aligned watchpoints might still succeed + (e.g. if they watch addresses that are already watched, + in which case we just increment the reference counts of + occupied debug registers). If we break out of the loop + too early, we could cause those addresses watched by + other watchpoints to be disabled when breakpoint.c reacts + to our failure to insert this watchpoint and tries to + remove it. */ + if (status) + rv = status; + } + addr += size; + len -= size; + } + return rv; +} + +/* Insert a watchpoint to watch a memory region which starts at + address ADDR and whose length is LEN bytes. Watch memory accesses + of the type TYPE. Return 0 on success, -1 on failure. */ +int +i386_insert_watchpoint (CORE_ADDR addr, int len, int type) +{ + int retval; + + if (len == 3 || len > 4 || addr % len != 0) + retval = i386_handle_nonaligned_watchpoint (WP_INSERT, addr, len, type); + else + { + unsigned len_rw = i386_length_and_rw_bits (len, type); + + retval = i386_insert_aligned_watchpoint (addr, len_rw); + } + + if (maint_show_dr) + i386_show_dr ("insert_watchpoint", addr, len, type); + + return retval; +} + +/* Remove a watchpoint that watched the memory region which starts at + address ADDR, whose length is LEN bytes, and for accesses of the + type TYPE. Return 0 on success, -1 on failure. */ +int +i386_remove_watchpoint (CORE_ADDR addr, int len, int type) +{ + int retval; + + if (len == 3 || len > 4 || addr % len != 0) + retval = i386_handle_nonaligned_watchpoint (WP_REMOVE, addr, len, type); + else + { + unsigned len_rw = i386_length_and_rw_bits (len, type); + + retval = i386_remove_aligned_watchpoint (addr, len_rw); + } + + if (maint_show_dr) + i386_show_dr ("remove_watchpoint", addr, len, type); + + return retval; +} + +/* Return non-zero if we can watch a memory region that starts at + address ADDR and whose length is LEN bytes. */ +int +i386_region_ok_for_watchpoint (CORE_ADDR addr, int len) +{ + /* Compute how many aligned watchpoints we would need to cover this + region. */ + int nregs = i386_handle_nonaligned_watchpoint (WP_COUNT, addr, len, + hw_write); + + return nregs <= DR_NADDR ? 1 : 0; +} + +/* If the inferior has some watchpoint that triggered, return the + address associated with that watchpoint. Otherwise, return + zero. */ +CORE_ADDR +i386_stopped_data_address (void) +{ + int i; + CORE_ADDR ret = 0; + + dr_status_mirror = I386_DR_LOW_GET_STATUS (); + + ALL_DEBUG_REGISTERS(i) + { + if (I386_DR_WATCH_HIT (i) + /* This second condition makes sure DRi is set up for a data + watchpoint, not a hardware breakpoint. The reason is + that GDB doesn't call the target_stopped_data_address + method except for data watchpoints. In other words, I'm + being paranoiac. */ + && I386_DR_GET_RW_LEN (i) != 0) + { + ret = dr_mirror[i]; + if (maint_show_dr) + i386_show_dr ("watchpoint_hit", ret, -1, hw_write); + } + } + if (maint_show_dr && ret == 0) + i386_show_dr ("stopped_data_addr", 0, 0, hw_write); + + return ret; +} + +/* Return non-zero if the inferior has some break/watchpoint that + triggered. */ +int +i386_stopped_by_hwbp (void) +{ + int i; + + dr_status_mirror = I386_DR_LOW_GET_STATUS (); + if (maint_show_dr) + i386_show_dr ("stopped_by_hwbp", 0, 0, hw_execute); + + ALL_DEBUG_REGISTERS(i) + { + if (I386_DR_WATCH_HIT (i)) + return 1; + } + + return 0; +} + +/* Insert a hardware-assisted breakpoint at address ADDR. SHADOW is + unused. Return 0 on success, EBUSY on failure. */ +int +i386_insert_hw_breakpoint (CORE_ADDR addr, void *shadow) +{ + unsigned len_rw = i386_length_and_rw_bits (1, hw_execute); + int retval = i386_insert_aligned_watchpoint (addr, len_rw) ? EBUSY : 0; + + if (maint_show_dr) + i386_show_dr ("insert_hwbp", addr, 1, hw_execute); + + return retval; +} + +/* Remove a hardware-assisted breakpoint at address ADDR. SHADOW is + unused. Return 0 on success, -1 on failure. */ +int +i386_remove_hw_breakpoint (CORE_ADDR addr, void *shadow) +{ + unsigned len_rw = i386_length_and_rw_bits (1, hw_execute); + int retval = i386_remove_aligned_watchpoint (addr, len_rw); + + if (maint_show_dr) + i386_show_dr ("remove_hwbp", addr, 1, hw_execute); + + return retval; +} + +#endif /* I386_USE_GENERIC_WATCHPOINTS */ + + +void +_initialize_i386_nat (void) +{ +#ifdef I386_USE_GENERIC_WATCHPOINTS + /* A maintenance command to enable printing the internal DRi mirror + variables. */ + add_set_cmd ("show-debug-regs", class_maintenance, + var_boolean, (char *) &maint_show_dr, + "\ +Set whether to show variables that mirror the x86 debug registers.\n\ +Use \"on\" to enable, \"off\" to disable.\n\ +If enabled, the debug registers values are shown when GDB inserts\n\ +or removes a hardware breakpoint or watchpoint, and when the inferior\n\ +triggers a breakpoint or watchpoint.", &maintenancelist); +#endif +} |