1/* Smoke testing GDB process attach with thread-local variable access.
2 Copyright (C) 2021-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19/* This test runs GDB against a forked copy of itself, to check
20 whether libthread_db can be loaded, and that access to thread-local
21 variables works. */
22
23#include <elf.h>
24#include <errno.h>
25#include <fcntl.h>
26#include <signal.h>
27#include <stdbool.h>
28#include <stdlib.h>
29#include <string.h>
30#include <support/capture_subprocess.h>
31#include <support/check.h>
32#include <support/xptrace.h>
33#include <support/subprocess.h>
34#include <support/support.h>
35#include <support/temp_file.h>
36#include <support/test-driver.h>
37#include <support/xstdio.h>
38#include <support/xthread.h>
39#include <support/xunistd.h>
40#include <unistd.h>
41
42/* Starts out as zero, changed to 1 or 2 by the debugger, depending on
43 the thread. */
44__thread volatile int altered_by_debugger;
45
46/* Common prefix between 32-bit and 64-bit ELF. */
47struct elf_prefix
48{
49 unsigned char e_ident[EI_NIDENT];
50 uint16_t e_type;
51 uint16_t e_machine;
52 uint32_t e_version;
53};
54_Static_assert (sizeof (struct elf_prefix) == EI_NIDENT + 8,
55 "padding in struct elf_prefix");
56
57/* Reads the ELF header from PATH. Returns true if the header can be
58 read, false if the file is too short. */
59static bool
60read_elf_header (const char *path, struct elf_prefix *elf)
61{
62 int fd = xopen (path, O_RDONLY, 0);
63 bool result = read (fd, elf, sizeof (*elf)) == sizeof (*elf);
64 xclose (fd);
65 return result;
66}
67
68/* Searches for "gdb" alongside the path variable. See execvpe. */
69static char *
70find_gdb (void)
71{
72 const char *path = getenv ("PATH");
73 if (path == NULL)
74 return NULL;
75 while (true)
76 {
77 const char *colon = strchrnul (s: path, c: ':');
78 char *candidate = xasprintf (format: "%.*s/gdb", (int) (colon - path), path);
79 if (access (name: candidate, X_OK) == 0)
80 return candidate;
81 free (ptr: candidate);
82 if (*colon == '\0')
83 break;
84 path = colon + 1;
85 }
86 return NULL;
87}
88
89/* Writes the GDB script to run the test to PATH. */
90static void
91write_gdbscript (const char *path, int tested_pid)
92{
93 FILE *fp = xfopen (path, mode: "w");
94 fprintf (fp,
95 "set trace-commands on\n"
96 "set debug libthread-db 1\n"
97#if DO_ADD_SYMBOL_FILE
98 /* Do not do this unconditionally to work around a GDB
99 assertion failure: ../../gdb/symtab.c:6404:
100 internal-error: CORE_ADDR get_msymbol_address(objfile*,
101 const minimal_symbol*): Assertion `(objf->flags &
102 OBJF_MAINLINE) == 0' failed. */
103 "add-symbol-file %1$s/nptl/tst-pthread-gdb-attach\n"
104#endif
105 "set auto-load safe-path %1$s/nptl_db\n"
106 "set libthread-db-search-path %1$s/nptl_db\n"
107 "attach %2$d\n",
108 support_objdir_root, tested_pid);
109 fputs ("break debugger_inspection_point\n"
110 "continue\n"
111 "thread 1\n"
112 "print altered_by_debugger\n"
113 "print altered_by_debugger = 1\n"
114 "thread 2\n"
115 "print altered_by_debugger\n"
116 "print altered_by_debugger = 2\n"
117 "continue\n",
118 fp);
119 xfclose (fp);
120}
121
122/* The test sets a breakpoint on this function and alters the
123 altered_by_debugger thread-local variable. */
124void __attribute__ ((weak))
125debugger_inspection_point (void)
126{
127}
128
129/* Thread function for the test thread in the subprocess. */
130static void *
131subprocess_thread (void *closure)
132{
133 /* Wait until altered_by_debugger changes the value away from 0. */
134 while (altered_by_debugger == 0)
135 {
136 usleep (useconds: 100 * 1000);
137 debugger_inspection_point ();
138 }
139
140 TEST_COMPARE (altered_by_debugger, 2);
141 return NULL;
142}
143
144/* This function implements the subprocess under test. It creates a
145 second thread, waiting for its value to change to 2, and checks
146 that the main thread also changed its value to 1. */
147static void
148in_subprocess (void *arg)
149{
150 pthread_t thr = xpthread_create (NULL, thread_func: subprocess_thread, NULL);
151 TEST_VERIFY (xpthread_join (thr) == NULL);
152 TEST_COMPARE (altered_by_debugger, 1);
153 _exit (0);
154}
155
156static void
157gdb_process (const char *gdb_path, const char *gdbscript, pid_t *tested_pid)
158{
159 /* Create a copy of current test to check with gdb. As the
160 target_process is a child of this gdb_process, gdb is also able
161 to attach to target_process if YAMA is configured to 1 =
162 "restricted ptrace". */
163 struct support_subprocess target = support_subprocess (callback: in_subprocess, NULL);
164
165 write_gdbscript (path: gdbscript, tested_pid: target.pid);
166 *tested_pid = target.pid;
167
168 xdup2 (STDOUT_FILENO, STDERR_FILENO);
169 execl (gdb_path, "gdb", "-nx", "-batch", "-x", gdbscript, NULL);
170 if (errno == ENOENT)
171 _exit (EXIT_UNSUPPORTED);
172 else
173 _exit (1);
174}
175
176static int
177do_test (void)
178{
179 char *gdb_path = find_gdb ();
180 if (gdb_path == NULL)
181 FAIL_UNSUPPORTED ("gdb command not found in PATH: %s", getenv ("PATH"));
182
183 /* Check that libthread_db is compatible with the gdb architecture
184 because gdb loads it via dlopen. */
185 {
186 char *threaddb_path = xasprintf (format: "%s/nptl_db/libthread_db.so",
187 support_objdir_root);
188 struct elf_prefix elf_threaddb;
189 TEST_VERIFY_EXIT (read_elf_header (threaddb_path, &elf_threaddb));
190 struct elf_prefix elf_gdb;
191 /* If the ELF header cannot be read or "gdb" is not an ELF file,
192 assume this is a wrapper script that can run. */
193 if (read_elf_header (path: gdb_path, elf: &elf_gdb)
194 && memcmp (&elf_gdb, ELFMAG, SELFMAG) == 0)
195 {
196 if (elf_gdb.e_ident[EI_CLASS] != elf_threaddb.e_ident[EI_CLASS])
197 FAIL_UNSUPPORTED ("GDB at %s has wrong class", gdb_path);
198 if (elf_gdb.e_ident[EI_DATA] != elf_threaddb.e_ident[EI_DATA])
199 FAIL_UNSUPPORTED ("GDB at %s has wrong data", gdb_path);
200 if (elf_gdb.e_machine != elf_threaddb.e_machine)
201 FAIL_UNSUPPORTED ("GDB at %s has wrong machine", gdb_path);
202 }
203 free (ptr: threaddb_path);
204 }
205
206 /* Check if our subprocess can be debugged with ptrace. */
207 {
208 int ptrace_scope = support_ptrace_scope ();
209 if (ptrace_scope >= 2)
210 FAIL_UNSUPPORTED ("/proc/sys/kernel/yama/ptrace_scope >= 2");
211 }
212
213 char *gdbscript;
214 xclose (create_temp_file (base: "tst-pthread-gdb-attach-", filename: &gdbscript));
215
216 /* Run 'gdb' on test subprocess which will be created in gdb_process.
217 The pid of the subprocess will be written to 'tested_pid'. */
218 pid_t *tested_pid = support_shared_allocate (size: sizeof (pid_t));
219
220 pid_t gdb_pid = xfork ();
221 if (gdb_pid == 0)
222 gdb_process (gdb_path, gdbscript, tested_pid);
223
224 int status;
225 TEST_COMPARE (xwaitpid (gdb_pid, &status, 0), gdb_pid);
226 if (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_UNSUPPORTED)
227 /* gdb is not installed. */
228 return EXIT_UNSUPPORTED;
229 TEST_COMPARE (status, 0);
230
231 kill (pid: *tested_pid, SIGKILL);
232
233 support_shared_free (tested_pid);
234 free (ptr: gdbscript);
235 free (ptr: gdb_path);
236 return 0;
237}
238
239#include <support/test-driver.c>
240

source code of glibc/nptl/tst-pthread-gdb-attach.c