]>
Commit | Line | Data |
---|---|---|
11988f8f | 1 | /* List dynamic shared objects linked into given process. |
6d7e8eda | 2 | Copyright (C) 2011-2023 Free Software Foundation, Inc. |
11988f8f | 3 | This file is part of the GNU C Library. |
11988f8f UD |
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 | |
59ba27a6 | 16 | License along with the GNU C Library; if not, see |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
11988f8f | 18 | |
1a4c2735 AZ |
19 | #define _FILE_OFFSET_BITS 64 |
20 | ||
11988f8f | 21 | #include <argp.h> |
27724598 | 22 | #include <dirent.h> |
11988f8f UD |
23 | #include <error.h> |
24 | #include <fcntl.h> | |
25 | #include <libintl.h> | |
11988f8f UD |
26 | #include <stdio.h> |
27 | #include <stdlib.h> | |
11988f8f UD |
28 | #include <unistd.h> |
29 | #include <sys/ptrace.h> | |
b04acb26 | 30 | #include <sys/wait.h> |
7b8399f4 | 31 | #include <scratch_buffer.h> |
11988f8f UD |
32 | |
33 | #include <ldsodefs.h> | |
34 | #include <version.h> | |
35 | ||
36 | /* Global variables. */ | |
37 | extern char *program_invocation_short_name; | |
38 | #define PACKAGE _libc_intl_domainname | |
39 | ||
40 | /* External functions. */ | |
6ff444c4 | 41 | #include <programs/xmalloc.h> |
11988f8f UD |
42 | |
43 | /* Name and version of program. */ | |
44 | static void print_version (FILE *stream, struct argp_state *state); | |
45 | void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; | |
46 | ||
8b748aed JM |
47 | /* Function to print some extra text in the help message. */ |
48 | static char *more_help (int key, const char *text, void *input); | |
11988f8f UD |
49 | |
50 | /* Definitions of arguments for argp functions. */ | |
51 | static const struct argp_option options[] = | |
52 | { | |
53 | { NULL, 0, NULL, 0, NULL } | |
54 | }; | |
55 | ||
56 | /* Short description of program. */ | |
57 | static const char doc[] = N_("\ | |
58 | List dynamic shared objects loaded into process."); | |
59 | ||
60 | /* Strings for arguments in help texts. */ | |
61 | static const char args_doc[] = N_("PID"); | |
62 | ||
63 | /* Prototype for option handler. */ | |
64 | static error_t parse_opt (int key, char *arg, struct argp_state *state); | |
65 | ||
66 | /* Data structure to communicate with argp functions. */ | |
67 | static struct argp argp = | |
68 | { | |
8b748aed | 69 | options, parse_opt, args_doc, doc, NULL, more_help, NULL |
11988f8f UD |
70 | }; |
71 | ||
11988f8f UD |
72 | |
73 | /* Local functions. */ | |
1a4c2735 | 74 | static int get_process_info (const char *exe, int dfd, long int pid); |
b04acb26 | 75 | static void wait_for_ptrace_stop (long int pid); |
11988f8f UD |
76 | |
77 | ||
78 | int | |
79 | main (int argc, char *argv[]) | |
80 | { | |
81 | /* Parse and process arguments. */ | |
82 | int remaining; | |
83 | argp_parse (&argp, argc, argv, 0, &remaining, NULL); | |
84 | ||
85 | if (remaining != argc - 1) | |
86 | { | |
87 | fprintf (stderr, | |
88 | gettext ("Exactly one parameter with process ID required.\n")); | |
89 | argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name); | |
90 | return 1; | |
91 | } | |
92 | ||
1a4c2735 AZ |
93 | _Static_assert (sizeof (pid_t) == sizeof (int) |
94 | || sizeof (pid_t) == sizeof (long int), | |
95 | "sizeof (pid_t) != sizeof (int) or sizeof (long int)"); | |
96 | ||
11988f8f UD |
97 | char *endp; |
98 | errno = 0; | |
27724598 UD |
99 | long int pid = strtol (argv[remaining], &endp, 10); |
100 | if (pid < 0 || (pid == ULONG_MAX && errno == ERANGE) || *endp != '\0' | |
101 | || (sizeof (pid_t) < sizeof (pid) && pid > INT_MAX)) | |
11988f8f UD |
102 | error (EXIT_FAILURE, 0, gettext ("invalid process ID '%s'"), |
103 | argv[remaining]); | |
104 | ||
105 | /* Determine the program name. */ | |
27724598 UD |
106 | char buf[7 + 3 * sizeof (pid)]; |
107 | snprintf (buf, sizeof (buf), "/proc/%lu", pid); | |
108 | int dfd = open (buf, O_RDONLY | O_DIRECTORY); | |
109 | if (dfd == -1) | |
110 | error (EXIT_FAILURE, errno, gettext ("cannot open %s"), buf); | |
111 | ||
1a4c2735 AZ |
112 | /* Name of the executable */ |
113 | struct scratch_buffer exe; | |
114 | scratch_buffer_init (&exe); | |
11988f8f | 115 | ssize_t nexe; |
7b8399f4 | 116 | while ((nexe = readlinkat (dfd, "exe", |
1a4c2735 | 117 | exe.data, exe.length)) == exe.length) |
7b8399f4 | 118 | { |
1a4c2735 | 119 | if (!scratch_buffer_grow (&exe)) |
7b8399f4 FW |
120 | { |
121 | nexe = -1; | |
122 | break; | |
123 | } | |
124 | } | |
11988f8f | 125 | if (nexe == -1) |
1a4c2735 AZ |
126 | /* Default stack allocation is at least 1024. */ |
127 | snprintf (exe.data, exe.length, "<program name undetermined>"); | |
11988f8f | 128 | else |
1a4c2735 | 129 | ((char*)exe.data)[nexe] = '\0'; |
11988f8f | 130 | |
27724598 UD |
131 | /* Stop all threads since otherwise the list of loaded modules might |
132 | change while we are reading it. */ | |
133 | struct thread_list | |
134 | { | |
135 | pid_t tid; | |
136 | struct thread_list *next; | |
137 | } *thread_list = NULL; | |
138 | ||
139 | int taskfd = openat (dfd, "task", O_RDONLY | O_DIRECTORY | O_CLOEXEC); | |
140 | if (taskfd == 1) | |
141 | error (EXIT_FAILURE, errno, gettext ("cannot open %s/task"), buf); | |
142 | DIR *dir = fdopendir (taskfd); | |
143 | if (dir == NULL) | |
144 | error (EXIT_FAILURE, errno, gettext ("cannot prepare reading %s/task"), | |
145 | buf); | |
146 | ||
1a4c2735 AZ |
147 | struct dirent *d; |
148 | while ((d = readdir (dir)) != NULL) | |
27724598 UD |
149 | { |
150 | if (! isdigit (d->d_name[0])) | |
151 | continue; | |
152 | ||
153 | errno = 0; | |
154 | long int tid = strtol (d->d_name, &endp, 10); | |
155 | if (tid < 0 || (tid == ULONG_MAX && errno == ERANGE) || *endp != '\0' | |
156 | || (sizeof (pid_t) < sizeof (pid) && tid > INT_MAX)) | |
157 | error (EXIT_FAILURE, 0, gettext ("invalid thread ID '%s'"), | |
158 | d->d_name); | |
159 | ||
160 | if (ptrace (PTRACE_ATTACH, tid, NULL, NULL) != 0) | |
161 | { | |
162 | /* There might be a race between reading the directory and | |
163 | threads terminating. Ignore errors attaching to unknown | |
164 | threads unless this is the main thread. */ | |
165 | if (errno == ESRCH && tid != pid) | |
166 | continue; | |
167 | ||
168 | error (EXIT_FAILURE, errno, gettext ("cannot attach to process %lu"), | |
169 | tid); | |
170 | } | |
171 | ||
b04acb26 AS |
172 | wait_for_ptrace_stop (tid); |
173 | ||
1a4c2735 | 174 | struct thread_list *newp = xmalloc (sizeof (*newp)); |
27724598 UD |
175 | newp->tid = tid; |
176 | newp->next = thread_list; | |
177 | thread_list = newp; | |
178 | } | |
179 | ||
180 | closedir (dir); | |
11988f8f | 181 | |
1a4c2735 AZ |
182 | if (thread_list == NULL) |
183 | error (EXIT_FAILURE, 0, gettext ("no valid %s/task entries"), buf); | |
184 | ||
185 | int status = get_process_info (exe.data, dfd, pid); | |
27724598 | 186 | |
27724598 UD |
187 | do |
188 | { | |
189 | ptrace (PTRACE_DETACH, thread_list->tid, NULL, NULL); | |
1a4c2735 | 190 | struct thread_list *prev = thread_list; |
27724598 | 191 | thread_list = thread_list->next; |
1a4c2735 | 192 | free (prev); |
27724598 UD |
193 | } |
194 | while (thread_list != NULL); | |
11988f8f | 195 | |
27724598 | 196 | close (dfd); |
1a4c2735 | 197 | scratch_buffer_free (&exe); |
11988f8f UD |
198 | |
199 | return status; | |
200 | } | |
201 | ||
202 | ||
b04acb26 AS |
203 | /* Wait for PID to enter ptrace-stop state after being attached. */ |
204 | static void | |
205 | wait_for_ptrace_stop (long int pid) | |
206 | { | |
207 | int status; | |
208 | ||
209 | /* While waiting for SIGSTOP being delivered to the tracee we have to | |
210 | reinject any other pending signal. Ignore all other errors. */ | |
211 | while (waitpid (pid, &status, __WALL) == pid && WIFSTOPPED (status)) | |
212 | { | |
213 | /* The STOP signal should not be delivered to the tracee. */ | |
214 | if (WSTOPSIG (status) == SIGSTOP) | |
215 | return; | |
216 | if (ptrace (PTRACE_CONT, pid, NULL, | |
217 | (void *) (uintptr_t) WSTOPSIG (status))) | |
218 | /* The only possible error is that the process died. */ | |
219 | return; | |
220 | } | |
221 | } | |
222 | ||
223 | ||
11988f8f UD |
224 | /* Handle program arguments. */ |
225 | static error_t | |
226 | parse_opt (int key, char *arg, struct argp_state *state) | |
227 | { | |
228 | switch (key) | |
229 | { | |
230 | default: | |
231 | return ARGP_ERR_UNKNOWN; | |
232 | } | |
233 | return 0; | |
234 | } | |
235 | ||
236 | ||
8b748aed JM |
237 | /* Print bug-reporting information in the help message. */ |
238 | static char * | |
239 | more_help (int key, const char *text, void *input) | |
240 | { | |
241 | char *tp = NULL; | |
242 | switch (key) | |
243 | { | |
244 | case ARGP_KEY_HELP_EXTRA: | |
245 | /* We print some extra information. */ | |
246 | if (asprintf (&tp, gettext ("\ | |
247 | For bug reporting instructions, please see:\n\ | |
248 | %s.\n"), REPORT_BUGS_TO) < 0) | |
249 | return NULL; | |
250 | return tp; | |
251 | default: | |
252 | break; | |
253 | } | |
254 | return (char *) text; | |
255 | } | |
256 | ||
11988f8f UD |
257 | /* Print the version information. */ |
258 | static void | |
259 | print_version (FILE *stream, struct argp_state *state) | |
260 | { | |
8b748aed | 261 | fprintf (stream, "pldd %s%s\n", PKGVERSION, VERSION); |
11988f8f UD |
262 | fprintf (stream, gettext ("\ |
263 | Copyright (C) %s Free Software Foundation, Inc.\n\ | |
264 | This is free software; see the source for copying conditions. There is NO\n\ | |
265 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ | |
ae612c45 | 266 | "), "2023"); |
11988f8f UD |
267 | fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); |
268 | } | |
269 | ||
270 | ||
271 | #define CLASS 32 | |
272 | #include "pldd-xx.c" | |
273 | #define CLASS 64 | |
274 | #include "pldd-xx.c" | |
275 | ||
276 | ||
277 | static int | |
1a4c2735 | 278 | get_process_info (const char *exe, int dfd, long int pid) |
11988f8f | 279 | { |
1a4c2735 AZ |
280 | /* File descriptor of /proc/<pid>/mem file. */ |
281 | int memfd = openat (dfd, "mem", O_RDONLY); | |
11988f8f UD |
282 | if (memfd == -1) |
283 | goto no_info; | |
284 | ||
27724598 | 285 | int fd = openat (dfd, "exe", O_RDONLY); |
11988f8f UD |
286 | if (fd == -1) |
287 | { | |
288 | no_info: | |
289 | error (0, errno, gettext ("cannot get information about process %lu"), | |
27724598 | 290 | pid); |
11988f8f UD |
291 | return EXIT_FAILURE; |
292 | } | |
293 | ||
6585cb60 UD |
294 | char e_ident[EI_NIDENT]; |
295 | if (read (fd, e_ident, EI_NIDENT) != EI_NIDENT) | |
11988f8f UD |
296 | goto no_info; |
297 | ||
298 | close (fd); | |
299 | ||
6585cb60 | 300 | if (memcmp (e_ident, ELFMAG, SELFMAG) != 0) |
11988f8f | 301 | { |
27724598 | 302 | error (0, 0, gettext ("process %lu is no ELF program"), pid); |
11988f8f UD |
303 | return EXIT_FAILURE; |
304 | } | |
305 | ||
27724598 | 306 | fd = openat (dfd, "auxv", O_RDONLY); |
11988f8f UD |
307 | if (fd == -1) |
308 | goto no_info; | |
309 | ||
310 | size_t auxv_size = 0; | |
311 | void *auxv = NULL; | |
312 | while (1) | |
313 | { | |
314 | auxv_size += 512; | |
315 | auxv = xrealloc (auxv, auxv_size); | |
316 | ||
c5305d88 | 317 | ssize_t n = pread (fd, auxv, auxv_size, 0); |
11988f8f UD |
318 | if (n < 0) |
319 | goto no_info; | |
320 | if (n < auxv_size) | |
321 | { | |
322 | auxv_size = n; | |
323 | break; | |
324 | } | |
325 | } | |
326 | ||
327 | close (fd); | |
328 | ||
27724598 | 329 | int retval; |
6585cb60 | 330 | if (e_ident[EI_CLASS] == ELFCLASS32) |
1a4c2735 | 331 | retval = find_maps32 (exe, memfd, pid, auxv, auxv_size); |
11988f8f | 332 | else |
1a4c2735 | 333 | retval = find_maps64 (exe, memfd, pid, auxv, auxv_size); |
27724598 UD |
334 | |
335 | free (auxv); | |
336 | close (memfd); | |
337 | ||
338 | return retval; | |
11988f8f | 339 | } |