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