blarsen / rpms / gdb

Forked from rpms/gdb 2 years ago
Clone
2f36ebc
From FEDORA_PATCHES Mon Sep 17 00:00:00 2001
2f36ebc
From: Tom de Vries <tdevries@suse.de>
2f36ebc
Date: Tue, 1 Jun 2021 10:14:31 -0700
2f36ebc
Subject: gdb-dont-overwrite-fsgsbase-m32.patch
2f36ebc
2f36ebc
;; Backport "[gdb/server] Don't overwrite fs/gs_base with -m32"
2f36ebc
;; (Tom de Vries)
2f36ebc
2f36ebc
Consider a minimal test-case test.c:
2f36ebc
...
2f36ebc
int main (void) { return 0; }
2f36ebc
...
2f36ebc
compiled with -m32:
2f36ebc
...
2f36ebc
$ gcc test.c -m32
2f36ebc
...
2f36ebc
2f36ebc
When running the exec using gdbserver on openSUSE Factory (currently running a
2f36ebc
linux kernel version 5.10.5):
2f36ebc
...
2f36ebc
$ gdbserver localhost:12345 a.out
2f36ebc
...
2f36ebc
to which we connect in a gdb session, we run into a segfault in the inferior:
2f36ebc
...
2f36ebc
$ gdb -batch -q -ex "target remote localhost:12345" -ex continue
2f36ebc
Program received signal SIGSEGV, Segmentation fault.
2f36ebc
0xf7dd8bd2 in init_cacheinfo () at ../sysdeps/x86/cacheinfo.c:761
2f36ebc
...
2f36ebc
2f36ebc
The segfault is caused by gdbserver overwriting $gs_base with 0 using
2f36ebc
PTRACE_SETREGS.  After it is overwritten, the next use of $gs in the inferior
2f36ebc
will trigger the segfault.
2f36ebc
2f36ebc
Before linux kernel version 5.9, the value used by PTRACE_SETREGS for $gs_base
2f36ebc
was ignored, but starting version 5.9, the linux kernel has support for
2f36ebc
intel architecture extension FSGSBASE, which allows users to modify $gs_base,
2f36ebc
and consequently PTRACE_SETREGS can no longer ignore the $gs_base value.
2f36ebc
2f36ebc
The overwrite of $gs_base with 0 is done by a memset in x86_fill_gregset,
2f36ebc
which was added in commit 9e0aa64f551 "Fix gdbserver qGetTLSAddr for
2f36ebc
x86_64 -m32".  The memset intends to zero-extend 32-bit registers that are
2f36ebc
tracked in the regcache to 64-bit when writing them into the PTRACE_SETREGS
2f36ebc
data argument.  But in addition, it overwrites other registers that are
2f36ebc
not tracked in the regcache, such as $gs_base.
2f36ebc
2f36ebc
Fix the segfault by redoing the fix from commit 9e0aa64f551 in minimal form.
2f36ebc
2f36ebc
Tested on x86_64-linux:
2f36ebc
- openSUSE Leap 15.2 (using kernel version 5.3.18):
2f36ebc
  - native
2f36ebc
  - gdbserver -m32
2f36ebc
  - -m32
2f36ebc
- openSUSE Factory (using kernel version 5.10.5):
2f36ebc
  - native
2f36ebc
  - m32
2f36ebc
2f36ebc
gdbserver/ChangeLog:
2f36ebc
2f36ebc
2021-01-20  Tom de Vries  <tdevries@suse.de>
2f36ebc
2f36ebc
	* linux-x86-low.cc (collect_register_i386): New function.
2f36ebc
	(x86_fill_gregset):  Remove memset.  Use collect_register_i386.
2f36ebc
2f36ebc
diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
2f36ebc
--- a/gdbserver/linux-x86-low.cc
2f36ebc
+++ b/gdbserver/linux-x86-low.cc
2f36ebc
@@ -397,6 +397,35 @@ x86_target::low_cannot_fetch_register (int regno)
2f36ebc
   return regno >= I386_NUM_REGS;
2f36ebc
 }
2f36ebc
 
2f36ebc
+static void
2f36ebc
+collect_register_i386 (struct regcache *regcache, int regno, void *buf)
2f36ebc
+{
2f36ebc
+  collect_register (regcache, regno, buf);
2f36ebc
+
2f36ebc
+#ifdef __x86_64__
2f36ebc
+  /* In case of x86_64 -m32, collect_register only writes 4 bytes, but the
2f36ebc
+     space reserved in buf for the register is 8 bytes.  Make sure the entire
2f36ebc
+     reserved space is initialized.  */
2f36ebc
+
2f36ebc
+  gdb_assert (register_size (regcache->tdesc, regno) == 4);
2f36ebc
+
2f36ebc
+  if (regno == RAX)
2f36ebc
+    {
2f36ebc
+      /* Sign extend EAX value to avoid potential syscall restart
2f36ebc
+	 problems.
2f36ebc
+
2f36ebc
+	 See amd64_linux_collect_native_gregset() in
2f36ebc
+	 gdb/amd64-linux-nat.c for a detailed explanation.  */
2f36ebc
+      *(int64_t *) buf = *(int32_t *) buf;
2f36ebc
+    }
2f36ebc
+  else
2f36ebc
+    {
2f36ebc
+      /* Zero-extend.  */
2f36ebc
+      *(uint64_t *) buf = *(uint32_t *) buf;
2f36ebc
+    }
2f36ebc
+#endif
2f36ebc
+}
2f36ebc
+
2f36ebc
 static void
2f36ebc
 x86_fill_gregset (struct regcache *regcache, void *buf)
2f36ebc
 {
2f36ebc
@@ -411,32 +440,14 @@ x86_fill_gregset (struct regcache *regcache, void *buf)
2f36ebc
 
2f36ebc
       return;
2f36ebc
     }
2f36ebc
-
2f36ebc
-  /* 32-bit inferior registers need to be zero-extended.
2f36ebc
-     Callers would read uninitialized memory otherwise.  */
2f36ebc
-  memset (buf, 0x00, X86_64_USER_REGS * 8);
2f36ebc
 #endif
2f36ebc
 
2f36ebc
   for (i = 0; i < I386_NUM_REGS; i++)
2f36ebc
-    collect_register (regcache, i, ((char *) buf) + i386_regmap[i]);
2f36ebc
-
2f36ebc
-  collect_register_by_name (regcache, "orig_eax",
2f36ebc
-			    ((char *) buf) + ORIG_EAX * REGSIZE);
2f36ebc
+    collect_register_i386 (regcache, i, ((char *) buf) + i386_regmap[i]);
2f36ebc
 
2f36ebc
-#ifdef __x86_64__
2f36ebc
-  /* Sign extend EAX value to avoid potential syscall restart
2f36ebc
-     problems. 
2f36ebc
-
2f36ebc
-     See amd64_linux_collect_native_gregset() in gdb/amd64-linux-nat.c
2f36ebc
-     for a detailed explanation.  */
2f36ebc
-  if (register_size (regcache->tdesc, 0) == 4)
2f36ebc
-    {
2f36ebc
-      void *ptr = ((gdb_byte *) buf
2f36ebc
-                   + i386_regmap[find_regno (regcache->tdesc, "eax")]);
2f36ebc
-
2f36ebc
-      *(int64_t *) ptr = *(int32_t *) ptr;
2f36ebc
-    }
2f36ebc
-#endif
2f36ebc
+  /* Handle ORIG_EAX, which is not in i386_regmap.  */
2f36ebc
+  collect_register_i386 (regcache, find_regno (regcache->tdesc, "orig_eax"),
2f36ebc
+			 ((char *) buf) + ORIG_EAX * REGSIZE);
2f36ebc
 }
2f36ebc
 
2f36ebc
 static void