binutils-gdb/gdb/nat/x86-linux-tdesc.h

51 lines
2.0 KiB
C
Raw Normal View History

gdb/gdbserver: share some code relating to target description creation This commit is part of a series to share more of the x86 target description creation code between GDB and gdbserver. Unlike previous commits which were mostly refactoring, this commit is the first that makes a real change, though that change should mostly be for gdbserver; I've largely adopted the "GDB" way of doing things for gdbserver, and this fixes a real gdbserver bug. On a x86-64 Linux target, running the test: gdb.server/connect-with-no-symbol-file.exp results in two core files being created. Both of these core files are from the inferior process, created after gdbserver has detached. In this test a gdbserver process is started and then, after gdbserver has started, but before GDB attaches, we either delete the inferior executable, or change its permissions so it can't be read. Only after doing this do we attempt to connect with GDB. As GDB connects to gdbserver, gdbserver attempts to figure out the target description so that it can send the description to GDB, this involves a call to x86_linux_read_description. In x86_linux_read_description one of the first things we do is try to figure out if the process is 32-bit or 64-bit. To do this we look up the executable via the thread-id, and then attempt to read the architecture size from the executable. This isn't going to work if the executable has been deleted, or is no longer readable. And so, as we can't read the executable, we default to an i386 target and use an i386 target description. A consequence of using an i386 target description is that addresses are assumed to be 32-bits. Here's an example session that shows the problems this causes. This is run on an x86-64 machine, and the test binary (xx.x) is a standard 64-bit x86-64 binary: shell_1$ gdbserver --once localhost :54321 /tmp/xx.x shell_2$ gdb -q (gdb) set sysroot (gdb) shell chmod 000 /tmp/xx.x (gdb) target remote :54321 Remote debugging using :54321 warning: /tmp/xx.x: Permission denied. 0xf7fd3110 in ?? () (gdb) show architecture The target architecture is set to "auto" (currently "i386"). (gdb) p/x $pc $1 = 0xf7fd3110 (gdb) info proc mappings process 2412639 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x400000 0x401000 0x1000 0x0 r--p /tmp/xx.x 0x401000 0x402000 0x1000 0x1000 r-xp /tmp/xx.x 0x402000 0x403000 0x1000 0x2000 r--p /tmp/xx.x 0x403000 0x405000 0x2000 0x2000 rw-p /tmp/xx.x 0xf7fcb000 0xf7fcf000 0x4000 0x0 r--p [vvar] 0xf7fcf000 0xf7fd1000 0x2000 0x0 r-xp [vdso] 0xf7fd1000 0xf7fd3000 0x2000 0x0 r--p /usr/lib64/ld-2.30.so 0xf7fd3000 0xf7ff3000 0x20000 0x2000 r-xp /usr/lib64/ld-2.30.so 0xf7ff3000 0xf7ffb000 0x8000 0x22000 r--p /usr/lib64/ld-2.30.so 0xf7ffc000 0xf7ffe000 0x2000 0x2a000 rw-p /usr/lib64/ld-2.30.so 0xf7ffe000 0xf7fff000 0x1000 0x0 rw-p 0xfffda000 0xfffff000 0x25000 0x0 rw-p [stack] 0xff600000 0xff601000 0x1000 0x0 r-xp [vsyscall] (gdb) info inferiors Num Description Connection Executable * 1 process 2412639 1 (remote :54321) (gdb) shell cat /proc/2412639/maps 00400000-00401000 r--p 00000000 fd:03 45907133 /tmp/xx.x 00401000-00402000 r-xp 00001000 fd:03 45907133 /tmp/xx.x 00402000-00403000 r--p 00002000 fd:03 45907133 /tmp/xx.x 00403000-00405000 rw-p 00002000 fd:03 45907133 /tmp/xx.x 7ffff7fcb000-7ffff7fcf000 r--p 00000000 00:00 0 [vvar] 7ffff7fcf000-7ffff7fd1000 r-xp 00000000 00:00 0 [vdso] 7ffff7fd1000-7ffff7fd3000 r--p 00000000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7fd3000-7ffff7ff3000 r-xp 00002000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7ff3000-7ffff7ffb000 r--p 00022000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7ffc000-7ffff7ffe000 rw-p 0002a000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffda000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] (gdb) Notice the difference between the mappings reported via GDB and those reported directly from the kernel via /proc/PID/maps, the addresses of every mapping is clamped to 32-bits for GDB, while the kernel reports real 64-bit addresses. Notice also that the $pc value is a 32-bit value. It appears to be within one of the mappings reported by GDB, but is outside any of the mappings reported from the kernel. And this is where the problem arises. When gdbserver detaches from the inferior we pass the inferior the address from which it should resume. Due to the 32/64 bit confusion we tell the inferior to resume from the 32-bit $pc value, which is not within any valid mapping, and so, as soon as the inferior resumes, it segfaults. If we look at how GDB (not gdbserver) figures out its target description then we see an interesting difference. GDB doesn't try to read the executable. Instead GDB uses ptrace to query the thread's state, and uses this to figure out the if the thread is 32 or 64 bit. If we update gdbserver to do it the "GDB" way then the above problem is resolved, gdbserver now sees the process as 64-bit, and when we detach from the inferior we give it the correct 64-bit address, and the inferior no longer segfaults. Now, I could just update the gdbserver code, but better, I think, to share one copy of the code between GDB and gdbserver in gdb/nat/. That is what this commit does. The cores of x86_linux_read_description from gdbserver and x86_linux_nat_target::read_description from GDB are moved into a new file gdb/nat/x86-linux-tdesc.c and combined into a single function x86_linux_tdesc_for_tid which is called from each location. This new function does things mostly the GDB way, some changes are needed to allow for the sharing; we now take some pointers for where the shared code can cache the xcr0 and xsave layout values. Another thing to note about this commit is how the functions i386_linux_read_description and amd64_linux_read_description are handled. For now I've left these function as implemented separately in GDB and gdbserver. I've moved the declarations of these functions into gdb/arch/{i386,amd64}-linux-tdesc.h, but the implementations are left where they are. A later commit in this series will make these functions shared too, but doing this is not trivial, so I've left that for a separate commit. Merging the declarations as I've done here ensures that everyone implements the function to the same API, and once these functions are shared (in a later commit) we'll want a shared declaration anyway. Reviewed-By: Felix Willgerodt <felix.willgerodt@intel.com> Acked-By: John Baldwin <jhb@FreeBSD.org>
2024-01-25 22:25:57 +08:00
/* Target description related code for GNU/Linux x86 (i386 and x86-64).
Copyright (C) 2024 Free Software Foundation, Inc.
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#ifndef NAT_X86_LINUX_TDESC_H
#define NAT_X86_LINUX_TDESC_H
#include "gdbsupport/function-view.h"
struct target_desc;
struct x86_xsave_layout;
/* Return the target description for Linux thread TID.
The storage pointed to by XCR0_STORAGE and XSAVE_LAYOUT_STORAGE must
exist until the program (GDB or gdbserver) terminates, this storage is
used to cache the xcr0 and xsave layout values. The values pointed to
by these arguments are only updated at most once, the first time this
function is called if the have_ptrace_getregset global is set to
TRIBOOL_UNKNOWN.
This function returns a target description based on the extracted xcr0
value along with other characteristics of the thread identified by TID.
This function can return nullptr if we encounter a machine configuration
for which a target_desc cannot be created. Ideally this would not be
the case, we should be able to create a target description for every
possible machine configuration. See amd64_linux_read_description and
i386_linux_read_description for cases when nullptr might be
returned. */
gdb/i386: fix tdesc rejection issue for targets without PTRACE_GETREGSET After the x86 target description changes that I committed recently, the first commit in the series being: commit 8a29222b85f28a2201db50a34ac4144f961311db Date: Sat Jan 27 10:40:35 2024 +0000 gdb/gdbserver: share I386_LINUX_XSAVE_XCR0_OFFSET definition and the last commit in the series being: commit 646d754d14c2fe70a492a893506a74b0463b6ae8 Author: Andrew Burgess <aburgess@redhat.com> Date: Tue Jan 30 15:37:23 2024 +0000 gdb/gdbserver: share x86/linux tdesc caching The sourceware buildbot highlighted a regression on i386. On the GDB side we'd see this: Remote debugging using :54321 warning: Architecture rejected target-supplied description Remote connection closed (gdb) while on the gdbserver side we'd see this: $ ./gdbserver/gdbserver --once :54321 ~/empty Process /srv/aburgess/empty created; pid = 31406 Listening on port 54321 Remote debugging from host ::1, port 39488 ../../src/gdbserver/regcache.cc:272: A problem internal to GDBserver has been detected. Unknown register st0 requested Aborted (core dumped) When I tried to reproduce this regression on my local i386 VM the issue would not reproduce. I eventually tracked the problem down to x86_linux_tdesc_for_tid in gdb/nat/x86-linux-tdesc.c. In this function we have this line: /* Check if PTRACE_GETREGSET works. */ if (ptrace (PTRACE_GETREGSET, tid, (unsigned int) NT_X86_XSTATE, &iov) < 0) { ... handle failure ... } else { ... handle success ... } The problem is that on my VM the PTRACE_GETREGSET feature is supported, while on sourceware's buildbot machine this feature is not supported. I did a quick search and it seems like the 'xsave' feature in /proc/cpuinfo might be the indicator for whether PTRACE_GETREGSET is supported or not, and indeed my machine has the 'xsave' feature while the sourceware machine does not. The point of divergence then is this ptrace call, on my machine the call succeeds and we extract the xcr0 value from the iov vector, while on the sourceware machine the ptrace call fails and we use a default xcr0 value of 0. This xcr0 value is then passed to i386_linux_read_description at the end of x86_linux_tdesc_for_tid. In gdb/arch/i386-linux-tdesc.c we find i386_linux_read_description which does some caching but calls i386_create_target_description to actually create the target descriptions when needed. The xcr0 value is masked to only the bits that are interesting, but given a value of 0 we'll just pass 0 through to i386_create_target_description. In gdb/arch/i386.c we find i386_create_target_description which checks the xcr0 bits and builds the target description. What we can see is that if no bits are set in the xcr0 value then no features will be added to the created target description. This featureless target description is then transmitted back to GDB, which is then rejected due to lack of essential core registers. So, how did things work prior to the above commit series? There are three places of interest, on the GDB side there is x86_linux_nat_target::read_description and i386_linux_core_read_description. Then on the gdbserver side there is x86_linux_read_description. All of these locations have a call to i386_linux_read_description followed by a check if the return value was nullptr. If we do get back nullptr then we perform another call to i386_linux_read_description with a default xcr0 value. Looking in i386_linux_read_description we see a specific check for xcr0 being 0 in which case we return nullptr. And so, prior to the above series, if xcr0 was 0 due to PTRACE_GETREGSET being unavailable we'd use a default xcr0 value. After the above series this is no longer the case, the 'xcr0 == 0' check has been removed from i386_linux_read_description and the calling code is streamlined to remove the use of default xcr0 values. The fix I propose here is to setup the default xcr0 value at the point where we find that PTRACE_GETREGSET is unavailable. The default value used is X86_XSTATE_SSE_MASK. This is the default used in x86_linux_nat_target::read_description (for GDB) and in x86_linux_read_description (for gdbserver). The above commit series already fixed i386_linux_core_read_description to ensure that the correct default xcr0 value was used, this case is a little special in that it uses different defaults depending on which sections are present in the core file, so that case always needed to be handled differently. The choice of X86_XSTATE_SSE_MASK corresponds to the default used for i386 before the above series was committed. This mask includes the X87 and SSE bits only, neither of these bits are checked for on amd64 or x32, so this default doesn't change the behaviour on these targets. By setting the default xcr0 value at this early stage we ensure that the cached xcr0 value on the gdbserver side is correct. This is critical as this cached xcr0 value is passed through to the in process agent (IPA). If we leave the cached xcr0 value as 0 and apply the defaults later in the series we also have to encode the knowledge of the default into the IPA, this just means we have the default encoded in multiple locations, which seems like a bad idea. The approach used in this patch means the default is present in just one location. This commit should fix the i386 regressions seen on the sourceware buildbot. In addition to the fix in nat/x86-linux-tdesc.c I've also fixed the layout of the declaration of x86_linux_tdesc_for_tid in the header file. Approved-By: Felix Willgerodt <felix.willgerodt@intel.com>
2024-06-21 18:43:58 +08:00
extern const target_desc *x86_linux_tdesc_for_tid
(int tid, uint64_t *xcr0_storage, x86_xsave_layout *xsave_layout_storage);
gdb/gdbserver: share some code relating to target description creation This commit is part of a series to share more of the x86 target description creation code between GDB and gdbserver. Unlike previous commits which were mostly refactoring, this commit is the first that makes a real change, though that change should mostly be for gdbserver; I've largely adopted the "GDB" way of doing things for gdbserver, and this fixes a real gdbserver bug. On a x86-64 Linux target, running the test: gdb.server/connect-with-no-symbol-file.exp results in two core files being created. Both of these core files are from the inferior process, created after gdbserver has detached. In this test a gdbserver process is started and then, after gdbserver has started, but before GDB attaches, we either delete the inferior executable, or change its permissions so it can't be read. Only after doing this do we attempt to connect with GDB. As GDB connects to gdbserver, gdbserver attempts to figure out the target description so that it can send the description to GDB, this involves a call to x86_linux_read_description. In x86_linux_read_description one of the first things we do is try to figure out if the process is 32-bit or 64-bit. To do this we look up the executable via the thread-id, and then attempt to read the architecture size from the executable. This isn't going to work if the executable has been deleted, or is no longer readable. And so, as we can't read the executable, we default to an i386 target and use an i386 target description. A consequence of using an i386 target description is that addresses are assumed to be 32-bits. Here's an example session that shows the problems this causes. This is run on an x86-64 machine, and the test binary (xx.x) is a standard 64-bit x86-64 binary: shell_1$ gdbserver --once localhost :54321 /tmp/xx.x shell_2$ gdb -q (gdb) set sysroot (gdb) shell chmod 000 /tmp/xx.x (gdb) target remote :54321 Remote debugging using :54321 warning: /tmp/xx.x: Permission denied. 0xf7fd3110 in ?? () (gdb) show architecture The target architecture is set to "auto" (currently "i386"). (gdb) p/x $pc $1 = 0xf7fd3110 (gdb) info proc mappings process 2412639 Mapped address spaces: Start Addr End Addr Size Offset Perms objfile 0x400000 0x401000 0x1000 0x0 r--p /tmp/xx.x 0x401000 0x402000 0x1000 0x1000 r-xp /tmp/xx.x 0x402000 0x403000 0x1000 0x2000 r--p /tmp/xx.x 0x403000 0x405000 0x2000 0x2000 rw-p /tmp/xx.x 0xf7fcb000 0xf7fcf000 0x4000 0x0 r--p [vvar] 0xf7fcf000 0xf7fd1000 0x2000 0x0 r-xp [vdso] 0xf7fd1000 0xf7fd3000 0x2000 0x0 r--p /usr/lib64/ld-2.30.so 0xf7fd3000 0xf7ff3000 0x20000 0x2000 r-xp /usr/lib64/ld-2.30.so 0xf7ff3000 0xf7ffb000 0x8000 0x22000 r--p /usr/lib64/ld-2.30.so 0xf7ffc000 0xf7ffe000 0x2000 0x2a000 rw-p /usr/lib64/ld-2.30.so 0xf7ffe000 0xf7fff000 0x1000 0x0 rw-p 0xfffda000 0xfffff000 0x25000 0x0 rw-p [stack] 0xff600000 0xff601000 0x1000 0x0 r-xp [vsyscall] (gdb) info inferiors Num Description Connection Executable * 1 process 2412639 1 (remote :54321) (gdb) shell cat /proc/2412639/maps 00400000-00401000 r--p 00000000 fd:03 45907133 /tmp/xx.x 00401000-00402000 r-xp 00001000 fd:03 45907133 /tmp/xx.x 00402000-00403000 r--p 00002000 fd:03 45907133 /tmp/xx.x 00403000-00405000 rw-p 00002000 fd:03 45907133 /tmp/xx.x 7ffff7fcb000-7ffff7fcf000 r--p 00000000 00:00 0 [vvar] 7ffff7fcf000-7ffff7fd1000 r-xp 00000000 00:00 0 [vdso] 7ffff7fd1000-7ffff7fd3000 r--p 00000000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7fd3000-7ffff7ff3000 r-xp 00002000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7ff3000-7ffff7ffb000 r--p 00022000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7ffc000-7ffff7ffe000 rw-p 0002a000 fd:00 143904 /usr/lib64/ld-2.30.so 7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0 7ffffffda000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] (gdb) Notice the difference between the mappings reported via GDB and those reported directly from the kernel via /proc/PID/maps, the addresses of every mapping is clamped to 32-bits for GDB, while the kernel reports real 64-bit addresses. Notice also that the $pc value is a 32-bit value. It appears to be within one of the mappings reported by GDB, but is outside any of the mappings reported from the kernel. And this is where the problem arises. When gdbserver detaches from the inferior we pass the inferior the address from which it should resume. Due to the 32/64 bit confusion we tell the inferior to resume from the 32-bit $pc value, which is not within any valid mapping, and so, as soon as the inferior resumes, it segfaults. If we look at how GDB (not gdbserver) figures out its target description then we see an interesting difference. GDB doesn't try to read the executable. Instead GDB uses ptrace to query the thread's state, and uses this to figure out the if the thread is 32 or 64 bit. If we update gdbserver to do it the "GDB" way then the above problem is resolved, gdbserver now sees the process as 64-bit, and when we detach from the inferior we give it the correct 64-bit address, and the inferior no longer segfaults. Now, I could just update the gdbserver code, but better, I think, to share one copy of the code between GDB and gdbserver in gdb/nat/. That is what this commit does. The cores of x86_linux_read_description from gdbserver and x86_linux_nat_target::read_description from GDB are moved into a new file gdb/nat/x86-linux-tdesc.c and combined into a single function x86_linux_tdesc_for_tid which is called from each location. This new function does things mostly the GDB way, some changes are needed to allow for the sharing; we now take some pointers for where the shared code can cache the xcr0 and xsave layout values. Another thing to note about this commit is how the functions i386_linux_read_description and amd64_linux_read_description are handled. For now I've left these function as implemented separately in GDB and gdbserver. I've moved the declarations of these functions into gdb/arch/{i386,amd64}-linux-tdesc.h, but the implementations are left where they are. A later commit in this series will make these functions shared too, but doing this is not trivial, so I've left that for a separate commit. Merging the declarations as I've done here ensures that everyone implements the function to the same API, and once these functions are shared (in a later commit) we'll want a shared declaration anyway. Reviewed-By: Felix Willgerodt <felix.willgerodt@intel.com> Acked-By: John Baldwin <jhb@FreeBSD.org>
2024-01-25 22:25:57 +08:00
#endif /* NAT_X86_LINUX_TDESC_H */