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