On Fri, 25 Jun 2021 at 11:27:10 +0100, Simon McVittie wrote:
> This seems to be a known bug, fixed in
> <https://sourceware.org/git/?p=binutils-gdb.git;a=commitdiff_plain;h=037e8112b9794a633248e5aa5943f3be273e0a20>
I confirm that the attached cherry-picks resolve this. Patch 0001
(whitespace change, originally part of a much larger commit) is just to
make patch 0002 apply cleanly; they could be turned into a single patch
if preferred.
smcv
From: Simon Marchi <[email protected]>
Date: Mon, 2 Nov 2020 10:26:14 -0500
Subject: gdbserver: fix a leading space vs tabs issue
[Originally part of a larger commit]
Origin: upstream, 11.1, part of commit:dda83cd783075941aabe9b0292b004b11f00c831
---
gdbserver/linux-x86-low.cc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
index 79284473f14..bbf6c302e10 100644
--- a/gdbserver/linux-x86-low.cc
+++ b/gdbserver/linux-x86-low.cc
@@ -432,7 +432,7 @@ x86_fill_gregset (struct regcache *regcache, void *buf)
if (register_size (regcache->tdesc, 0) == 4)
{
void *ptr = ((gdb_byte *) buf
- + i386_regmap[find_regno (regcache->tdesc, "eax")]);
+ + i386_regmap[find_regno (regcache->tdesc, "eax")]);
*(int64_t *) ptr = *(int32_t *) ptr;
}
From: Tom de Vries <[email protected]>
Date: Wed, 20 Jan 2021 16:29:30 +0100
Subject: [gdb/server] Don't overwrite fs/gs_base with -m32
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 <[email protected]>
* linux-x86-low.cc (collect_register_i386): New function.
(x86_fill_gregset): Remove memset. Use collect_register_i386.
Origin: upstream, 11.1, commit:037e8112b9794a633248e5aa5943f3be273e0a20
---
gdbserver/linux-x86-low.cc | 55 +++++++++++++++++++++++---------------
1 file changed, 33 insertions(+), 22 deletions(-)
diff --git a/gdbserver/linux-x86-low.cc b/gdbserver/linux-x86-low.cc
index bbf6c302e10..ea95f3ea2ae 100644
--- 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