Index: gdb/amd64-linux-nat.c |
diff --git a/gdb/amd64-linux-nat.c b/gdb/amd64-linux-nat.c |
index 0df06fc85f82ca9bd70b112bb4767d6c8af90af3..01982acf17c26fd840eeafcc281db0ec9a429c5c 100644 |
--- a/gdb/amd64-linux-nat.c |
+++ b/gdb/amd64-linux-nat.c |
@@ -34,6 +34,7 @@ |
#include <sys/debugreg.h> |
#include <sys/syscall.h> |
#include <sys/procfs.h> |
+#include <sys/user.h> |
#include <asm/prctl.h> |
/* FIXME ezannoni-2003-07-09: we need <sys/reg.h> to be included after |
<asm/ptrace.h> because the latter redefines FS and GS for no apparent |
@@ -63,6 +64,14 @@ |
#define PTRACE_SETREGSET 0x4205 |
#endif |
+/* Per-thread arch-specific data we want to keep. */ |
+ |
+struct arch_lwp_info |
+{ |
+ /* Non-zero if our copy differs from what's recorded in the thread. */ |
+ int debug_registers_changed; |
+}; |
+ |
/* Does the current host support PTRACE_GETREGSET? */ |
static int have_ptrace_getregset = -1; |
@@ -264,8 +273,6 @@ amd64_linux_store_inferior_registers (struct target_ops *ops, |
/* Support for debug registers. */ |
-static unsigned long amd64_linux_dr[DR_CONTROL + 1]; |
- |
static unsigned long |
amd64_linux_dr_get (ptid_t ptid, int regnum) |
{ |
@@ -303,75 +310,129 @@ amd64_linux_dr_set (ptid_t ptid, int regnum, unsigned long value) |
perror_with_name (_("Couldn't write debug register")); |
} |
-/* Set DR_CONTROL to ADDR in all LWPs of LWP_LIST. */ |
+/* Return the inferior's debug register REGNUM. */ |
-static void |
-amd64_linux_dr_set_control (unsigned long control) |
+static CORE_ADDR |
+amd64_linux_dr_get_addr (int regnum) |
{ |
- struct lwp_info *lp; |
+ /* DR6 and DR7 are retrieved with some other way. */ |
+ gdb_assert (DR_FIRSTADDR <= regnum && regnum <= DR_LASTADDR); |
- amd64_linux_dr[DR_CONTROL] = control; |
- ALL_LWPS (lp) |
- amd64_linux_dr_set (lp->ptid, DR_CONTROL, control); |
+ return amd64_linux_dr_get (inferior_ptid, regnum); |
} |
-/* Set address REGNUM (zero based) to ADDR in all LWPs of LWP_LIST. */ |
+/* Return the inferior's DR7 debug control register. */ |
-static void |
-amd64_linux_dr_set_addr (int regnum, CORE_ADDR addr) |
+static unsigned long |
+amd64_linux_dr_get_control (void) |
{ |
- struct lwp_info *lp; |
+ return amd64_linux_dr_get (inferior_ptid, DR_CONTROL); |
+} |
- gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR); |
+/* Get DR_STATUS from only the one LWP of INFERIOR_PTID. */ |
- amd64_linux_dr[DR_FIRSTADDR + regnum] = addr; |
- ALL_LWPS (lp) |
- amd64_linux_dr_set (lp->ptid, DR_FIRSTADDR + regnum, addr); |
+static unsigned long |
+amd64_linux_dr_get_status (void) |
+{ |
+ return amd64_linux_dr_get (inferior_ptid, DR_STATUS); |
+} |
+ |
+/* Callback for linux_nat_iterate_watchpoint_lwps. Update the debug registers |
+ of LWP. */ |
+ |
+static int |
+update_debug_registers_callback (struct lwp_info *lwp, void *arg) |
+{ |
+ if (lwp->arch_private == NULL) |
+ lwp->arch_private = XCNEW (struct arch_lwp_info); |
+ |
+ /* The actual update is done later just before resuming the lwp, we |
+ just mark that the registers need updating. */ |
+ lwp->arch_private->debug_registers_changed = 1; |
+ |
+ /* If the lwp isn't stopped, force it to momentarily pause, so we |
+ can update its debug registers. */ |
+ if (!lwp->stopped) |
+ linux_stop_lwp (lwp); |
+ |
+ /* Continue the iteration. */ |
+ return 0; |
} |
-/* Set address REGNUM (zero based) to zero in all LWPs of LWP_LIST. */ |
+/* Set DR_CONTROL to CONTROL in all LWPs of the current inferior. */ |
static void |
-amd64_linux_dr_reset_addr (int regnum) |
+amd64_linux_dr_set_control (unsigned long control) |
{ |
- amd64_linux_dr_set_addr (regnum, 0); |
+ linux_nat_iterate_watchpoint_lwps (update_debug_registers_callback, NULL); |
} |
-/* Get DR_STATUS from only the one LWP of INFERIOR_PTID. */ |
+/* Set address REGNUM (zero based) to ADDR in all LWPs of the current |
+ inferior. */ |
-static unsigned long |
-amd64_linux_dr_get_status (void) |
+static void |
+amd64_linux_dr_set_addr (int regnum, CORE_ADDR addr) |
{ |
- return amd64_linux_dr_get (inferior_ptid, DR_STATUS); |
+ gdb_assert (regnum >= 0 && regnum <= DR_LASTADDR - DR_FIRSTADDR); |
+ |
+ linux_nat_iterate_watchpoint_lwps (update_debug_registers_callback, NULL); |
} |
-/* Unset MASK bits in DR_STATUS in all LWPs of LWP_LIST. */ |
+/* Called when resuming a thread. |
+ If the debug regs have changed, update the thread's copies. */ |
static void |
-amd64_linux_dr_unset_status (unsigned long mask) |
+amd64_linux_prepare_to_resume (struct lwp_info *lwp) |
{ |
- struct lwp_info *lp; |
+ int clear_status = 0; |
+ |
+ /* NULL means this is the main thread still going through the shell, |
+ or, no watchpoint has been set yet. In that case, there's |
+ nothing to do. */ |
+ if (lwp->arch_private == NULL) |
+ return; |
- ALL_LWPS (lp) |
+ if (lwp->arch_private->debug_registers_changed) |
{ |
- unsigned long value; |
- |
- value = amd64_linux_dr_get (lp->ptid, DR_STATUS); |
- value &= ~mask; |
- amd64_linux_dr_set (lp->ptid, DR_STATUS, value); |
+ struct i386_debug_reg_state *state = i386_debug_reg_state (); |
+ int i; |
+ |
+ /* On Linux kernel before 2.6.33 commit |
+ 72f674d203cd230426437cdcf7dd6f681dad8b0d |
+ if you enable a breakpoint by the DR_CONTROL bits you need to have |
+ already written the corresponding DR_FIRSTADDR...DR_LASTADDR registers. |
+ |
+ Ensure DR_CONTROL gets written as the very last register here. */ |
+ |
+ for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++) |
+ if (state->dr_ref_count[i] > 0) |
+ { |
+ amd64_linux_dr_set (lwp->ptid, i, state->dr_mirror[i]); |
+ |
+ /* If we're setting a watchpoint, any change the inferior |
+ had done itself to the debug registers needs to be |
+ discarded, otherwise, i386_stopped_data_address can get |
+ confused. */ |
+ clear_status = 1; |
+ } |
+ |
+ amd64_linux_dr_set (lwp->ptid, DR_CONTROL, state->dr_control_mirror); |
+ |
+ lwp->arch_private->debug_registers_changed = 0; |
} |
-} |
+ if (clear_status || lwp->stopped_by_watchpoint) |
+ amd64_linux_dr_set (lwp->ptid, DR_STATUS, 0); |
+} |
static void |
-amd64_linux_new_thread (ptid_t ptid) |
+amd64_linux_new_thread (struct lwp_info *lp) |
{ |
- int i; |
+ struct arch_lwp_info *info = XCNEW (struct arch_lwp_info); |
- for (i = DR_FIRSTADDR; i <= DR_LASTADDR; i++) |
- amd64_linux_dr_set (ptid, i, amd64_linux_dr[i]); |
+ info->debug_registers_changed = 1; |
- amd64_linux_dr_set (ptid, DR_CONTROL, amd64_linux_dr[DR_CONTROL]); |
+ lp->arch_private = info; |
} |
@@ -382,7 +443,7 @@ ps_err_e |
ps_get_thread_area (const struct ps_prochandle *ph, |
lwpid_t lwpid, int idx, void **base) |
{ |
- if (gdbarch_ptr_bit (target_gdbarch) == 32) |
+ if (gdbarch_bfd_arch_info (target_gdbarch)->bits_per_word == 32) |
{ |
/* The full structure is found in <asm-i386/ldt.h>. The second |
integer is the LDT's base_address and that is used to locate |
@@ -419,10 +480,39 @@ ps_get_thread_area (const struct ps_prochandle *ph, |
switch (idx) |
{ |
case FS: |
+#ifdef HAVE_STRUCT_USER_REGS_STRUCT_FS_BASE |
+ { |
+ /* PTRACE_ARCH_PRCTL is obsolete since 2.6.25, where the |
+ fs_base and gs_base fields of user_regs_struct can be |
+ used directly. */ |
+ unsigned long fs; |
+ errno = 0; |
+ fs = ptrace (PTRACE_PEEKUSER, lwpid, |
+ offsetof (struct user_regs_struct, fs_base), 0); |
+ if (errno == 0) |
+ { |
+ *base = (void *) fs; |
+ return PS_OK; |
+ } |
+ } |
+#endif |
if (ptrace (PTRACE_ARCH_PRCTL, lwpid, base, ARCH_GET_FS) == 0) |
return PS_OK; |
break; |
case GS: |
+#ifdef HAVE_STRUCT_USER_REGS_STRUCT_GS_BASE |
+ { |
+ unsigned long gs; |
+ errno = 0; |
+ gs = ptrace (PTRACE_PEEKUSER, lwpid, |
+ offsetof (struct user_regs_struct, gs_base), 0); |
+ if (errno == 0) |
+ { |
+ *base = (void *) gs; |
+ return PS_OK; |
+ } |
+ } |
+#endif |
if (ptrace (PTRACE_ARCH_PRCTL, lwpid, base, ARCH_GET_GS) == 0) |
return PS_OK; |
break; |
@@ -531,6 +621,71 @@ typedef struct compat_siginfo |
} _sifields; |
} compat_siginfo_t; |
+/* For x32, clock_t in _sigchld is 64bit aligned at 4 bytes. */ |
+typedef struct compat_x32_clock |
+{ |
+ int lower; |
+ int upper; |
+} compat_x32_clock_t; |
+ |
+typedef struct compat_x32_siginfo |
+{ |
+ int si_signo; |
+ int si_errno; |
+ int si_code; |
+ |
+ union |
+ { |
+ int _pad[((128 / sizeof (int)) - 3)]; |
+ |
+ /* kill() */ |
+ struct |
+ { |
+ unsigned int _pid; |
+ unsigned int _uid; |
+ } _kill; |
+ |
+ /* POSIX.1b timers */ |
+ struct |
+ { |
+ compat_timer_t _tid; |
+ int _overrun; |
+ compat_sigval_t _sigval; |
+ } _timer; |
+ |
+ /* POSIX.1b signals */ |
+ struct |
+ { |
+ unsigned int _pid; |
+ unsigned int _uid; |
+ compat_sigval_t _sigval; |
+ } _rt; |
+ |
+ /* SIGCHLD */ |
+ struct |
+ { |
+ unsigned int _pid; |
+ unsigned int _uid; |
+ int _status; |
+ compat_x32_clock_t _utime; |
+ compat_x32_clock_t _stime; |
+ } _sigchld; |
+ |
+ /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ |
+ struct |
+ { |
+ unsigned int _addr; |
+ } _sigfault; |
+ |
+ /* SIGPOLL */ |
+ struct |
+ { |
+ int _band; |
+ int _fd; |
+ } _sigpoll; |
+ } _sifields; |
+} compat_x32_siginfo_t; |
+ |
#define cpt_si_pid _sifields._kill._pid |
#define cpt_si_uid _sifields._kill._uid |
#define cpt_si_timerid _sifields._timer._tid |
@@ -664,6 +819,124 @@ siginfo_from_compat_siginfo (siginfo_t *to, compat_siginfo_t *from) |
} |
} |
+static void |
+compat_x32_siginfo_from_siginfo (compat_x32_siginfo_t *to, |
+ siginfo_t *from) |
+{ |
+ memset (to, 0, sizeof (*to)); |
+ |
+ to->si_signo = from->si_signo; |
+ to->si_errno = from->si_errno; |
+ to->si_code = from->si_code; |
+ |
+ if (to->si_code == SI_TIMER) |
+ { |
+ to->cpt_si_timerid = from->si_timerid; |
+ to->cpt_si_overrun = from->si_overrun; |
+ to->cpt_si_ptr = (intptr_t) from->si_ptr; |
+ } |
+ else if (to->si_code == SI_USER) |
+ { |
+ to->cpt_si_pid = from->si_pid; |
+ to->cpt_si_uid = from->si_uid; |
+ } |
+ else if (to->si_code < 0) |
+ { |
+ to->cpt_si_pid = from->si_pid; |
+ to->cpt_si_uid = from->si_uid; |
+ to->cpt_si_ptr = (intptr_t) from->si_ptr; |
+ } |
+ else |
+ { |
+ switch (to->si_signo) |
+ { |
+ case SIGCHLD: |
+ to->cpt_si_pid = from->si_pid; |
+ to->cpt_si_uid = from->si_uid; |
+ to->cpt_si_status = from->si_status; |
+ memcpy (&to->cpt_si_utime, &from->si_utime, |
+ sizeof (to->cpt_si_utime)); |
+ memcpy (&to->cpt_si_stime, &from->si_stime, |
+ sizeof (to->cpt_si_stime)); |
+ break; |
+ case SIGILL: |
+ case SIGFPE: |
+ case SIGSEGV: |
+ case SIGBUS: |
+ to->cpt_si_addr = (intptr_t) from->si_addr; |
+ break; |
+ case SIGPOLL: |
+ to->cpt_si_band = from->si_band; |
+ to->cpt_si_fd = from->si_fd; |
+ break; |
+ default: |
+ to->cpt_si_pid = from->si_pid; |
+ to->cpt_si_uid = from->si_uid; |
+ to->cpt_si_ptr = (intptr_t) from->si_ptr; |
+ break; |
+ } |
+ } |
+} |
+ |
+static void |
+siginfo_from_compat_x32_siginfo (siginfo_t *to, |
+ compat_x32_siginfo_t *from) |
+{ |
+ memset (to, 0, sizeof (*to)); |
+ |
+ to->si_signo = from->si_signo; |
+ to->si_errno = from->si_errno; |
+ to->si_code = from->si_code; |
+ |
+ if (to->si_code == SI_TIMER) |
+ { |
+ to->si_timerid = from->cpt_si_timerid; |
+ to->si_overrun = from->cpt_si_overrun; |
+ to->si_ptr = (void *) (intptr_t) from->cpt_si_ptr; |
+ } |
+ else if (to->si_code == SI_USER) |
+ { |
+ to->si_pid = from->cpt_si_pid; |
+ to->si_uid = from->cpt_si_uid; |
+ } |
+ if (to->si_code < 0) |
+ { |
+ to->si_pid = from->cpt_si_pid; |
+ to->si_uid = from->cpt_si_uid; |
+ to->si_ptr = (void *) (intptr_t) from->cpt_si_ptr; |
+ } |
+ else |
+ { |
+ switch (to->si_signo) |
+ { |
+ case SIGCHLD: |
+ to->si_pid = from->cpt_si_pid; |
+ to->si_uid = from->cpt_si_uid; |
+ to->si_status = from->cpt_si_status; |
+ memcpy (&to->si_utime, &from->cpt_si_utime, |
+ sizeof (to->si_utime)); |
+ memcpy (&to->si_stime, &from->cpt_si_stime, |
+ sizeof (to->si_stime)); |
+ break; |
+ case SIGILL: |
+ case SIGFPE: |
+ case SIGSEGV: |
+ case SIGBUS: |
+ to->si_addr = (void *) (intptr_t) from->cpt_si_addr; |
+ break; |
+ case SIGPOLL: |
+ to->si_band = from->cpt_si_band; |
+ to->si_fd = from->cpt_si_fd; |
+ break; |
+ default: |
+ to->si_pid = from->cpt_si_pid; |
+ to->si_uid = from->cpt_si_uid; |
+ to->si_ptr = (void* ) (intptr_t) from->cpt_si_ptr; |
+ break; |
+ } |
+ } |
+} |
+ |
/* Convert a native/host siginfo object, into/from the siginfo in the |
layout of the inferiors' architecture. Returns true if any |
conversion was done; false otherwise. If DIRECTION is 1, then copy |
@@ -671,13 +944,15 @@ siginfo_from_compat_siginfo (siginfo_t *to, compat_siginfo_t *from) |
INF. */ |
static int |
-amd64_linux_siginfo_fixup (struct siginfo *native, gdb_byte *inf, int direction) |
+amd64_linux_siginfo_fixup (siginfo_t *native, gdb_byte *inf, int direction) |
{ |
+ struct gdbarch *gdbarch = get_frame_arch (get_current_frame ()); |
+ |
/* Is the inferior 32-bit? If so, then do fixup the siginfo |
object. */ |
- if (gdbarch_addr_bit (get_frame_arch (get_current_frame ())) == 32) |
+ if (gdbarch_bfd_arch_info (gdbarch)->bits_per_word == 32) |
{ |
- gdb_assert (sizeof (struct siginfo) == sizeof (compat_siginfo_t)); |
+ gdb_assert (sizeof (siginfo_t) == sizeof (compat_siginfo_t)); |
if (direction == 0) |
compat_siginfo_from_siginfo ((struct compat_siginfo *) inf, native); |
@@ -686,6 +961,20 @@ amd64_linux_siginfo_fixup (struct siginfo *native, gdb_byte *inf, int direction) |
return 1; |
} |
+ /* No fixup for native x32 GDB. */ |
+ else if (gdbarch_addr_bit (gdbarch) == 32 && sizeof (void *) == 8) |
+ { |
+ gdb_assert (sizeof (siginfo_t) == sizeof (compat_x32_siginfo_t)); |
+ |
+ if (direction == 0) |
+ compat_x32_siginfo_from_siginfo ((struct compat_x32_siginfo *) inf, |
+ native); |
+ else |
+ siginfo_from_compat_x32_siginfo (native, |
+ (struct compat_x32_siginfo *) inf); |
+ |
+ return 1; |
+ } |
else |
return 0; |
} |
@@ -695,16 +984,23 @@ amd64_linux_siginfo_fixup (struct siginfo *native, gdb_byte *inf, int direction) |
Value of CS segment register: |
1. 64bit process: 0x33. |
2. 32bit process: 0x23. |
+ |
+ Value of DS segment register: |
+ 1. LP64 process: 0x0. |
+ 2. X32 process: 0x2b. |
*/ |
#define AMD64_LINUX_USER64_CS 0x33 |
+#define AMD64_LINUX_X32_DS 0x2b |
static const struct target_desc * |
amd64_linux_read_description (struct target_ops *ops) |
{ |
unsigned long cs; |
+ unsigned long ds; |
int tid; |
int is_64bit; |
+ int is_x32; |
static uint64_t xcr0; |
/* GNU/Linux LWP ID's are process ID's. */ |
@@ -721,6 +1017,18 @@ amd64_linux_read_description (struct target_ops *ops) |
is_64bit = cs == AMD64_LINUX_USER64_CS; |
+ /* Get DS register. */ |
+ errno = 0; |
+ ds = ptrace (PTRACE_PEEKUSER, tid, |
+ offsetof (struct user_regs_struct, ds), 0); |
+ if (errno != 0) |
+ perror_with_name (_("Couldn't get DS register")); |
+ |
+ is_x32 = ds == AMD64_LINUX_X32_DS; |
+ |
+ if (sizeof (void *) == 4 && is_64bit && !is_x32) |
+ error (_("Can't debug 64-bit process with 32-bit GDB")); |
+ |
if (have_ptrace_getregset == -1) |
{ |
uint64_t xstateregs[(I386_XSTATE_SSE_SIZE / sizeof (uint64_t))]; |
@@ -748,14 +1056,24 @@ amd64_linux_read_description (struct target_ops *ops) |
&& (xcr0 & I386_XSTATE_AVX_MASK) == I386_XSTATE_AVX_MASK) |
{ |
if (is_64bit) |
- return tdesc_amd64_avx_linux; |
+ { |
+ if (is_x32) |
+ return tdesc_x32_avx_linux; |
+ else |
+ return tdesc_amd64_avx_linux; |
+ } |
else |
return tdesc_i386_avx_linux; |
} |
else |
{ |
if (is_64bit) |
- return tdesc_amd64_linux; |
+ { |
+ if (is_x32) |
+ return tdesc_x32_linux; |
+ else |
+ return tdesc_amd64_linux; |
+ } |
else |
return tdesc_i386_linux; |
} |
@@ -784,9 +1102,9 @@ _initialize_amd64_linux_nat (void) |
i386_dr_low.set_control = amd64_linux_dr_set_control; |
i386_dr_low.set_addr = amd64_linux_dr_set_addr; |
- i386_dr_low.reset_addr = amd64_linux_dr_reset_addr; |
+ i386_dr_low.get_addr = amd64_linux_dr_get_addr; |
i386_dr_low.get_status = amd64_linux_dr_get_status; |
- i386_dr_low.unset_status = amd64_linux_dr_unset_status; |
+ i386_dr_low.get_control = amd64_linux_dr_get_control; |
i386_set_debug_register_length (8); |
/* Override the GNU/Linux inferior startup hook. */ |
@@ -803,4 +1121,5 @@ _initialize_amd64_linux_nat (void) |
linux_nat_add_target (t); |
linux_nat_set_new_thread (t, amd64_linux_new_thread); |
linux_nat_set_siginfo_fixup (t, amd64_linux_siginfo_fixup); |
+ linux_nat_set_prepare_to_resume (t, amd64_linux_prepare_to_resume); |
} |