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. */ |
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 | (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 (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. */ |
90 | static void |
91 | write_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. */ |
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 (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. */ |
147 | static void |
148 | in_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 | |
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 (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 | |
176 | static int |
177 | do_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 | |