aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gdb/ChangeLog8
-rw-r--r--gdb/amd64-linux-nat.c69
-rw-r--r--gdb/gdbserver/ChangeLog5
-rw-r--r--gdb/gdbserver/linux-x86-low.c13
4 files changed, 93 insertions, 2 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index f0797b6..cc6762d 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,11 @@
+2019-04-10 Kevin Buettner <kevinb@redhat.com>
+
+ * amd64-linux-nat.c (amd64_linux_collect_native_gregset): New
+ function.
+ (fill_gregset): Call amd64_linux_collect_native_gregset instead
+ of amd64_collect_native_gregset.
+ (amd64_linux_nat_target::store_registers): Likewise.
+
2019-04-10 Tom Tromey <tom@tromey.com>
* symtab.c (lookup_global_symbol_from_objfile)
diff --git a/gdb/amd64-linux-nat.c b/gdb/amd64-linux-nat.c
index a0bb105..8d0e8eb 100644
--- a/gdb/amd64-linux-nat.c
+++ b/gdb/amd64-linux-nat.c
@@ -92,6 +92,71 @@ static int amd64_linux_gregset32_reg_offset[] =
/* Transfering the general-purpose registers between GDB, inferiors
and core files. */
+/* See amd64_collect_native_gregset. This linux specific version handles
+ issues with negative EAX values not being restored correctly upon syscall
+ return when debugging 32-bit targets. It has no effect on 64-bit
+ targets. */
+
+static void
+amd64_linux_collect_native_gregset (const struct regcache *regcache,
+ void *gregs, int regnum)
+{
+ amd64_collect_native_gregset (regcache, gregs, regnum);
+
+ struct gdbarch *gdbarch = regcache->arch ();
+ if (gdbarch_bfd_arch_info (gdbarch)->bits_per_word == 32)
+ {
+ /* Sign extend EAX value to avoid potential syscall restart
+ problems.
+
+ On Linux, when a syscall is interrupted by a signal, the
+ (kernel function implementing the) syscall may return
+ -ERESTARTSYS when a signal occurs. Doing so indicates that
+ the syscall is restartable. Then, depending on settings
+ associated with the signal handler, and after the signal
+ handler is called, the kernel can then either return -EINTR
+ or it can cause the syscall to be restarted. We are
+ concerned with the latter case here.
+
+ On (32-bit) i386, the status (-ERESTARTSYS) is placed in the
+ EAX register. When debugging a 32-bit process from a 64-bit
+ (amd64) GDB, the debugger fetches 64-bit registers even
+ though the process being debugged is only 32-bit. The
+ register cache is only 32 bits wide though; GDB discards the
+ high 32 bits when placing 64-bit values in the 32-bit
+ regcache. Normally, this is not a problem since the 32-bit
+ process should only care about the lower 32-bit portions of
+ these registers. That said, it can happen that the 64-bit
+ value being restored will be different from the 64-bit value
+ that was originally retrieved from the kernel. The one place
+ (that we know of) where it does matter is in the kernel's
+ syscall restart code. The kernel's code for restarting a
+ syscall after a signal expects to see a negative value
+ (specifically -ERESTARTSYS) in the 64-bit RAX register in
+ order to correctly cause a syscall to be restarted.
+
+ The call to amd64_collect_native_gregset, above, is setting
+ the high 32 bits of RAX (and other registers too) to 0. For
+ syscall restart, we need to sign extend EAX so that RAX will
+ appear as a negative value when EAX is set to -ERESTARTSYS.
+ This in turn will cause the signal handling code in the
+ kernel to recognize -ERESTARTSYS which will in turn cause the
+ syscall to be restarted.
+
+ The test case gdb.base/interrupt.exp tests for this problem.
+ Without this sign extension code in place, it'll show
+ a number of failures when testing against unix/-m32. */
+
+ if (regnum == -1 || regnum == I386_EAX_REGNUM)
+ {
+ void *ptr = ((gdb_byte *) gregs
+ + amd64_linux_gregset32_reg_offset[I386_EAX_REGNUM]);
+
+ *(int64_t *) ptr = *(int32_t *) ptr;
+ }
+ }
+}
+
/* Fill GDB's register cache with the general-purpose register values
in *GREGSETP. */
@@ -109,7 +174,7 @@ void
fill_gregset (const struct regcache *regcache,
elf_gregset_t *gregsetp, int regnum)
{
- amd64_collect_native_gregset (regcache, gregsetp, regnum);
+ amd64_linux_collect_native_gregset (regcache, gregsetp, regnum);
}
/* Transfering floating-point registers between GDB, inferiors and cores. */
@@ -237,7 +302,7 @@ amd64_linux_nat_target::store_registers (struct regcache *regcache, int regnum)
if (ptrace (PTRACE_GETREGS, tid, 0, (long) &regs) < 0)
perror_with_name (_("Couldn't get registers"));
- amd64_collect_native_gregset (regcache, &regs, regnum);
+ amd64_linux_collect_native_gregset (regcache, &regs, regnum);
if (ptrace (PTRACE_SETREGS, tid, 0, (long) &regs) < 0)
perror_with_name (_("Couldn't write registers"));
diff --git a/gdb/gdbserver/ChangeLog b/gdb/gdbserver/ChangeLog
index 1618e2c..2a5233b 100644
--- a/gdb/gdbserver/ChangeLog
+++ b/gdb/gdbserver/ChangeLog
@@ -1,3 +1,8 @@
+2019-04-10 Kevin Buettner <kevinb@redhat.com>
+
+ * linux-x86-low.c (x86_fill_gregset): Sign extend EAX value
+ when using a 64-bit gdbserver.
+
2019-04-09 Tom Tromey <tromey@adacore.com>
* linux-low.c (select_event_lwp): Use find_thread_in_random.
diff --git a/gdb/gdbserver/linux-x86-low.c b/gdb/gdbserver/linux-x86-low.c
index 029796e..dd76731 100644
--- a/gdb/gdbserver/linux-x86-low.c
+++ b/gdb/gdbserver/linux-x86-low.c
@@ -338,6 +338,19 @@ x86_fill_gregset (struct regcache *regcache, void *buf)
collect_register_by_name (regcache, "orig_eax",
((char *) buf) + ORIG_EAX * REGSIZE);
+
+ /* Sign extend EAX value to avoid potential syscall restart
+ problems.
+
+ See amd64_linux_collect_native_gregset() in gdb/amd64-linux-nat.c
+ for a detailed explanation. */
+ if (register_size (regcache->tdesc, 0) == 4)
+ {
+ void *ptr = ((gdb_byte *) buf
+ + i386_regmap[find_regno (regcache->tdesc, "eax")]);
+
+ *(int64_t *) ptr = *(int32_t *) ptr;
+ }
}
static void