]>
Commit | Line | Data |
---|---|---|
a64afc22 | 1 | /* Smoke testing GDB process attach with thread-local variable access. |
6d7e8eda | 2 | Copyright (C) 2021-2023 Free Software Foundation, Inc. |
a64afc22 FW |
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 | ||
f553dc06 | 23 | #include <elf.h> |
a64afc22 | 24 | #include <errno.h> |
f553dc06 | 25 | #include <fcntl.h> |
390c43ba | 26 | #include <signal.h> |
f553dc06 | 27 | #include <stdbool.h> |
a64afc22 | 28 | #include <stdlib.h> |
f553dc06 | 29 | #include <string.h> |
390c43ba | 30 | #include <support/capture_subprocess.h> |
a64afc22 | 31 | #include <support/check.h> |
390c43ba AZ |
32 | #include <support/xptrace.h> |
33 | #include <support/subprocess.h> | |
a64afc22 FW |
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 | ||
f553dc06 FW |
46 | /* Common prefix between 32-bit and 64-bit ELF. */ |
47 | struct 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. */ | |
59 | static bool | |
60 | read_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. */ | |
69 | static char * | |
70 | find_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 (path, ':'); | |
78 | char *candidate = xasprintf ("%.*s/gdb", (int) (colon - path), path); | |
79 | if (access (candidate, X_OK) == 0) | |
80 | return candidate; | |
81 | free (candidate); | |
82 | if (*colon == '\0') | |
83 | break; | |
84 | path = colon + 1; | |
85 | } | |
86 | return NULL; | |
87 | } | |
88 | ||
a64afc22 FW |
89 | /* Writes the GDB script to run the test to PATH. */ |
90 | static void | |
91 | write_gdbscript (const char *path, int tested_pid) | |
92 | { | |
93 | FILE *fp = xfopen (path, "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. */ | |
124 | void __attribute__ ((weak)) | |
125 | debugger_inspection_point (void) | |
126 | { | |
127 | } | |
128 | ||
129 | /* Thread function for the test thread in the subprocess. */ | |
130 | static void * | |
131 | subprocess_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 (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. */ | |
147 | static void | |
390c43ba | 148 | in_subprocess (void *arg) |
a64afc22 FW |
149 | { |
150 | pthread_t thr = xpthread_create (NULL, subprocess_thread, NULL); | |
151 | TEST_VERIFY (xpthread_join (thr) == NULL); | |
152 | TEST_COMPARE (altered_by_debugger, 1); | |
153 | _exit (0); | |
154 | } | |
155 | ||
390c43ba AZ |
156 | static void |
157 | gdb_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 (in_subprocess, NULL); | |
164 | ||
165 | write_gdbscript (gdbscript, 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 | ||
a64afc22 FW |
176 | static int |
177 | do_test (void) | |
178 | { | |
f553dc06 FW |
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 ("%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 (gdb_path, &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 (threaddb_path); | |
204 | } | |
205 | ||
390c43ba AZ |
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 | } | |
a64afc22 FW |
212 | |
213 | char *gdbscript; | |
214 | xclose (create_temp_file ("tst-pthread-gdb-attach-", &gdbscript)); | |
390c43ba AZ |
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 (sizeof (pid_t)); | |
a64afc22 FW |
219 | |
220 | pid_t gdb_pid = xfork (); | |
221 | if (gdb_pid == 0) | |
390c43ba | 222 | gdb_process (gdb_path, gdbscript, tested_pid); |
a64afc22 FW |
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); | |
a64afc22 | 230 | |
390c43ba AZ |
231 | kill (*tested_pid, SIGKILL); |
232 | ||
233 | support_shared_free (tested_pid); | |
a64afc22 | 234 | free (gdbscript); |
f553dc06 | 235 | free (gdb_path); |
a64afc22 FW |
236 | return 0; |
237 | } | |
238 | ||
239 | #include <support/test-driver.c> |