aboutsummaryrefslogtreecommitdiff
path: root/gdb/nat/x86-linux-dregs.c
diff options
context:
space:
mode:
authorGary Benson <gbenson@redhat.com>2015-03-24 14:05:45 +0000
committerGary Benson <gbenson@redhat.com>2015-03-24 14:05:45 +0000
commit8e5d40700492597d103ebdb68eab733456b66fad (patch)
tree67577c5a91745a013a6e73493b64be0b874b0d76 /gdb/nat/x86-linux-dregs.c
parent2b95d4403883ba22687ecb81520855204348a7fb (diff)
downloadgdb-8e5d40700492597d103ebdb68eab733456b66fad.zip
gdb-8e5d40700492597d103ebdb68eab733456b66fad.tar.gz
gdb-8e5d40700492597d103ebdb68eab733456b66fad.tar.bz2
Move low-level Linux x86 debug register code to a shared file
This commit moves the now-identical low-level Linux x86 debug register code from gdb/x86-linux-nat.c and gdb/gdbserver/linux-x86-low.c into a new shared file gdb/nat/x86-linux-dregs.c. gdb/ChangeLog: * nat/x86-linux-dregs.h: New file. * nat/x86-linux-dregs.c: Likewise. * Makefile.in (HFILES_NO_SRCDIR): Add nat/x86-linux-dregs.h. (x86-linux-dregs.o): New rule. * config/i386/linux.mh (NATDEPFILES): Add x86-linux-dregs.o. * config/i386/linux64.mh (NATDEPFILES): Likewise. * x86-linux-nat.c: Include nat/x86-linux-dregs.h. (u_debugreg_offset): Moved to nat/x86-linux-dregs.c. (x86_linux_dr_get): Likewise. (x86_linux_dr_set): Likewise. (x86_linux_dr_get_addr): Likewise. (x86_linux_dr_get_control): Likewise. (x86_linux_dr_get_status): Likewise. (update_debug_registers_callback): Likewise. (x86_linux_dr_set_control): Likewise. (x86_linux_dr_set_addr): Likewise. (x86_linux_update_debug_registers): Likewise. gdb/gdbserver/ChangeLog: * Makefile.in (x86-linux-dregs.o): New rule. * configure.srv: Add x86-linux-dregs.o to relevant targets. * linux-x86-low.c: Include nat/x86-linux-dregs.h. (u_debugreg_offset): Moved to nat/x86-linux-dregs.c. (x86_linux_dr_get): Likewise. (x86_linux_dr_set): Likewise. (update_debug_registers_callback): Likewise. (x86_linux_dr_set_addr): Likewise. (x86_linux_dr_get_addr): Likewise. (x86_linux_dr_set_control): Likewise. (x86_linux_dr_get_control): Likewise. (x86_linux_dr_get_status): Likewise. (x86_linux_update_debug_registers): Likewise.
Diffstat (limited to 'gdb/nat/x86-linux-dregs.c')
-rw-r--r--gdb/nat/x86-linux-dregs.c183
1 files changed, 183 insertions, 0 deletions
diff --git a/gdb/nat/x86-linux-dregs.c b/gdb/nat/x86-linux-dregs.c
new file mode 100644
index 0000000..9389803
--- /dev/null
+++ b/gdb/nat/x86-linux-dregs.c
@@ -0,0 +1,183 @@
+/* Low-level debug register code for GNU/Linux x86 (i386 and x86-64).
+
+ Copyright (C) 1999-2015 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+#include "common-defs.h"
+#include <sys/ptrace.h>
+#include <sys/user.h>
+#include "target/waitstatus.h"
+#include "nat/x86-linux.h"
+#include "nat/x86-dregs.h"
+#include "nat/x86-linux-dregs.h"
+
+/* Return the offset of REGNUM in the u_debugreg field of struct
+ user. */
+
+static int
+u_debugreg_offset (int regnum)
+{
+ return (offsetof (struct user, u_debugreg)
+ + sizeof (((struct user *) 0)->u_debugreg[0]) * regnum);
+}
+
+/* Get debug register REGNUM value from the LWP specified by PTID. */
+
+static unsigned long
+x86_linux_dr_get (ptid_t ptid, int regnum)
+{
+ int tid;
+ unsigned long value;
+
+ gdb_assert (ptid_lwp_p (ptid));
+ tid = ptid_get_lwp (ptid);
+
+ errno = 0;
+ value = ptrace (PTRACE_PEEKUSER, tid, u_debugreg_offset (regnum), 0);
+ if (errno != 0)
+ perror_with_name (_("Couldn't read debug register"));
+
+ return value;
+}
+
+/* Set debug register REGNUM to VALUE in the LWP specified by PTID. */
+
+static void
+x86_linux_dr_set (ptid_t ptid, int regnum, unsigned long value)
+{
+ int tid;
+
+ gdb_assert (ptid_lwp_p (ptid));
+ tid = ptid_get_lwp (ptid);
+
+ errno = 0;
+ ptrace (PTRACE_POKEUSER, tid, u_debugreg_offset (regnum), value);
+ if (errno != 0)
+ perror_with_name (_("Couldn't write debug register"));
+}
+
+/* Callback for iterate_over_lwps. Mark that our local mirror of
+ LWP's debug registers has been changed, and cause LWP to stop if
+ it isn't already. Values are written from our local mirror to
+ the actual debug registers immediately prior to LWP resuming. */
+
+static int
+update_debug_registers_callback (struct lwp_info *lwp, void *arg)
+{
+ lwp_set_debug_registers_changed (lwp, 1);
+
+ if (!lwp_is_stopped (lwp))
+ linux_stop_lwp (lwp);
+
+ /* Continue the iteration. */
+ return 0;
+}
+
+/* See nat/x86-linux-dregs.h. */
+
+CORE_ADDR
+x86_linux_dr_get_addr (int regnum)
+{
+ gdb_assert (DR_FIRSTADDR <= regnum && regnum <= DR_LASTADDR);
+
+ return x86_linux_dr_get (current_lwp_ptid (), regnum);
+}
+
+/* See nat/x86-linux-dregs.h. */
+
+void
+x86_linux_dr_set_addr (int regnum, CORE_ADDR addr)
+{
+ ptid_t pid_ptid = pid_to_ptid (ptid_get_pid (current_lwp_ptid ()));
+
+ gdb_assert (DR_FIRSTADDR <= regnum && regnum <= DR_LASTADDR);
+
+ iterate_over_lwps (pid_ptid, update_debug_registers_callback, NULL);
+}
+
+/* See nat/x86-linux-dregs.h. */
+
+unsigned long
+x86_linux_dr_get_control (void)
+{
+ return x86_linux_dr_get (current_lwp_ptid (), DR_CONTROL);
+}
+
+/* See nat/x86-linux-dregs.h. */
+
+void
+x86_linux_dr_set_control (unsigned long control)
+{
+ ptid_t pid_ptid = pid_to_ptid (ptid_get_pid (current_lwp_ptid ()));
+
+ iterate_over_lwps (pid_ptid, update_debug_registers_callback, NULL);
+}
+
+/* See nat/x86-linux-dregs.h. */
+
+unsigned long
+x86_linux_dr_get_status (void)
+{
+ return x86_linux_dr_get (current_lwp_ptid (), DR_STATUS);
+}
+
+/* See nat/x86-linux-dregs.h. */
+
+void
+x86_linux_update_debug_registers (struct lwp_info *lwp)
+{
+ ptid_t ptid = ptid_of_lwp (lwp);
+ int clear_status = 0;
+
+ gdb_assert (lwp_is_stopped (lwp));
+
+ if (lwp_debug_registers_changed (lwp))
+ {
+ struct x86_debug_reg_state *state
+ = x86_debug_reg_state (ptid_get_pid (ptid));
+ int i;
+
+ /* Prior to Linux kernel 2.6.33 commit
+ 72f674d203cd230426437cdcf7dd6f681dad8b0d, setting DR0-3 to
+ a value that did not match what was enabled in DR_CONTROL
+ resulted in EINVAL. To avoid this we zero DR_CONTROL before
+ writing address registers, only writing DR_CONTROL's actual
+ value once all the addresses are in place. */
+ x86_linux_dr_set (ptid, DR_CONTROL, 0);
+
+ ALL_DEBUG_ADDRESS_REGISTERS (i)
+ if (state->dr_ref_count[i] > 0)
+ {
+ x86_linux_dr_set (ptid, i, state->dr_mirror[i]);
+
+ /* If we're setting a watchpoint, any change the inferior
+ has made to its debug registers needs to be discarded
+ to avoid x86_stopped_data_address getting confused. */
+ clear_status = 1;
+ }
+
+ /* If DR_CONTROL is supposed to be zero then it's already set. */
+ if (state->dr_control_mirror != 0)
+ x86_linux_dr_set (ptid, DR_CONTROL, state->dr_control_mirror);
+
+ lwp_set_debug_registers_changed (lwp, 0);
+ }
+
+ if (clear_status
+ || lwp_stop_reason (lwp) == TARGET_STOPPED_BY_WATCHPOINT)
+ x86_linux_dr_set (ptid, DR_STATUS, 0);
+}