Branch data Line data Source code
1 : : /* Print the source files of a given ELF file.
2 : : Copyright (C) 2023 Red Hat, Inc.
3 : : This file is part of elfutils.
4 : : Written by Housam Alamour <alamourh@redhat.com>.
5 : :
6 : : This file is free software; you can redistribute it and/or modify
7 : : it under the terms of the GNU General Public License as published by
8 : : the Free Software Foundation; either version 3 of the License, or
9 : : (at your option) any later version.
10 : :
11 : : elfutils is distributed in the hope that it will be useful, but
12 : : WITHOUT ANY WARRANTY; without even the implied warranty of
13 : : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : : GNU General Public License for more details.
15 : :
16 : : You should have received a copy of the GNU General Public License
17 : : along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 : :
19 : :
20 : : /* In case we have a bad fts we include this before config.h because it
21 : : can't handle _FILE_OFFSET_BITS.
22 : : Everything we need here is fine if its declarations just come first.
23 : : Also, include sys/types.h before fts. On some systems fts.h is not self
24 : : contained. */
25 : : #ifdef BAD_FTS
26 : : #include <sys/types.h>
27 : : #include <fts.h>
28 : : #endif
29 : :
30 : : #ifdef HAVE_CONFIG_H
31 : : # include <config.h>
32 : : #endif
33 : :
34 : : #include "printversion.h"
35 : : #include <dwarf.h>
36 : : #include <argp.h>
37 : : #include <cstring>
38 : : #include <set>
39 : : #include <string>
40 : : #include <cassert>
41 : : #include <gelf.h>
42 : : #include <memory>
43 : :
44 : : #ifdef ENABLE_LIBDEBUGINFOD
45 : : #include "debuginfod.h"
46 : : #endif
47 : :
48 : : #include <libdwfl.h>
49 : : #include <fcntl.h>
50 : : #include <iostream>
51 : : #include <libdw.h>
52 : : #include <sstream>
53 : : #include <vector>
54 : :
55 : : /* Libraries for use by the --zip option */
56 : : #ifdef HAVE_LIBARCHIVE
57 : : #include <archive.h>
58 : : #include <archive_entry.h>
59 : : #endif
60 : :
61 : : /* If fts.h is included before config.h, its indirect inclusions may not
62 : : give us the right LFS aliases of these functions, so map them manually. */
63 : : #ifdef BAD_FTS
64 : : #ifdef _FILE_OFFSET_BITS
65 : : #define open open64
66 : : #define fopen fopen64
67 : : #endif
68 : : #else
69 : : #include <sys/types.h>
70 : : #include <fts.h>
71 : : #endif
72 : :
73 : : using namespace std;
74 : :
75 : : /* Name and version of program. */
76 : : ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
77 : :
78 : : /* Bug report address. */
79 : : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
80 : :
81 : : #ifdef HAVE_LIBARCHIVE
82 : : constexpr size_t BUFFER_SIZE = 8192;
83 : : #endif
84 : :
85 : : /* Definitions of arguments for argp functions. */
86 : : static const struct argp_option options[] =
87 : : {
88 : : { NULL, 0, NULL, OPTION_DOC, N_("Output options:"), 1 },
89 : : { "null", '0', NULL, 0,
90 : : N_ ("Separate items by a null instead of a newline."), 0 },
91 : : { "verbose", 'v', NULL, 0,
92 : : N_ ("Increase verbosity of logging messages."), 0 },
93 : : { "cu-only", 'c', NULL, 0, N_("Only list the CU names."), 0 },
94 : : #ifdef HAVE_LIBARCHIVE
95 : : { "zip", 'z', NULL, 0, N_("Zip all the source files and send to stdout. "
96 : : "Cannot be used with the null option"), 0 },
97 : : #ifdef ENABLE_LIBDEBUGINFOD
98 : : { "no-backup", 'b', NULL, 0, N_("Disables local source file search when "
99 : : "debuginfod fails to fetch files. This option is only applicable"
100 : : "when fetching and zipping files."), 0 },
101 : : #endif
102 : : #endif
103 : : { NULL, 0, NULL, 0, NULL, 0 }
104 : : };
105 : :
106 : : /* Short description of program. */
107 : : static const char doc[] = N_("Lists the source files of a DWARF/ELF file. The default input is the file 'a.out'.");
108 : :
109 : : /* Strings for arguments in help texts. */
110 : : static const char args_doc[] = N_("INPUT");
111 : :
112 : : /* Prototype for option handler. */
113 : : static error_t parse_opt (int key, char *arg, struct argp_state *state);
114 : :
115 : : static struct argp_child argp_children[2]; /* [0] is set in main. */
116 : :
117 : : /* Data structure to communicate with argp functions. */
118 : : static const struct argp argp =
119 : : {
120 : : options, parse_opt, args_doc, doc, argp_children, NULL, NULL
121 : : };
122 : :
123 : : /* Verbose message printing. */
124 : : static bool verbose;
125 : : /* Delimit the output with nulls. */
126 : : static bool null_arg;
127 : : /* Only print compilation unit names. */
128 : : static bool CU_only;
129 : : #ifdef HAVE_LIBARCHIVE
130 : : /* Zip all the source files and send to stdout. */
131 : : static bool zip;
132 : :
133 : : #ifdef ENABLE_LIBDEBUGINFOD
134 : : /* Disables local source file search when debuginfod fails to fetch them.
135 : : This option is only applicable when fetching and zipping files.*/
136 : : static bool no_backup;
137 : : #endif
138 : : #endif
139 : :
140 : : /* Handle program arguments. Note null arg and zip
141 : : cannot be combined due to warnings raised when unzipping. */
142 : : static error_t
143 : 230 : parse_opt (int key, char *arg, struct argp_state *state)
144 : : {
145 : : /* Suppress "unused parameter" warning. */
146 : 230 : (void)arg;
147 [ + + + + : 230 : switch (key)
+ + + ]
148 : : {
149 : 38 : case ARGP_KEY_INIT:
150 : 38 : state->child_inputs[0] = state->input;
151 : 38 : break;
152 : :
153 : 12 : case '0':
154 : 12 : null_arg = true;
155 : 12 : break;
156 : :
157 : 16 : case 'v':
158 : 16 : verbose = true;
159 : 16 : break;
160 : :
161 : 8 : case 'c':
162 : 8 : CU_only = true;
163 : 8 : break;
164 : :
165 : : #ifdef HAVE_LIBARCHIVE
166 : 10 : case 'z':
167 : 10 : zip = true;
168 : 10 : break;
169 : :
170 : : #ifdef ENABLE_LIBDEBUGINFOD
171 : 2 : case 'b':
172 : 2 : no_backup = true;
173 : 2 : break;
174 : : #endif
175 : : #endif
176 : :
177 : : default:
178 : : return ARGP_ERR_UNKNOWN;
179 : : }
180 : : return 0;
181 : : }
182 : :
183 : : /* Remove the "/./" , "../" and the preceding directory
184 : : that some paths include which raise errors during unzip. */
185 : 364896 : string canonicalize_path(string path)
186 : : {
187 : 364896 : stringstream ss(path);
188 : 364896 : string token;
189 : 364896 : vector<string> tokens;
190 : : /* Extract each directory of the path and place into a vector. */
191 [ + - + + ]: 3013268 : while (getline(ss, token, '/')) {
192 : : /* Ignore any empty //, or /./ dirs. */
193 [ + + + + ]: 5143056 : if (token == "" || token == ".")
194 : 393956 : continue;
195 : : /* When /.. is encountered, remove the most recent directory from the vector. */
196 [ + + ]: 2254416 : else if (token == "..") {
197 [ + - ]: 229688 : if (!tokens.empty())
198 : 229688 : tokens.pop_back();
199 : : } else
200 [ + - ]: 2024728 : tokens.push_back(token);
201 : : }
202 [ + - ]: 364896 : stringstream result;
203 [ + - ]: 364896 : if (tokens.empty())
204 [ # # ]: 0 : return "/";
205 : : /* Reconstruct the path from the extracted directories. */
206 [ + + ]: 2159936 : for (const string &t : tokens) {
207 [ + - + - ]: 1795040 : result << '/' << t;
208 : : }
209 [ + - ]: 364896 : return result.str();
210 [ + + ]: 521480 : }
211 : :
212 : : /* Global list of collected source files and their respective module.
213 : : Normally, it'll contain the sources of just one named binary, but
214 : : the '-K' option can cause multiple dwfl modules to be loaded, thus
215 : : listed. */
216 : : set<pair<string, Dwfl_Module*>> debug_sourcefiles;
217 : :
218 : : static int
219 : 68 : collect_sourcefiles (Dwfl_Module *dwflmod,
220 : : void **userdata __attribute__ ((unused)),
221 : : const char *name __attribute__ ((unused)),
222 : : Dwarf_Addr base __attribute__ ((unused)),
223 : : void *arg __attribute__ ((unused)))
224 : : {
225 : 68 : Dwarf *dbg;
226 : 68 : Dwarf_Addr bias; /* ignored - for addressing purposes only. */
227 : :
228 : 68 : dbg = dwfl_module_getdwarf (dwflmod, &bias);
229 : :
230 : 68 : Dwarf_Off offset = 0;
231 : 68 : Dwarf_Off old_offset;
232 : 68 : size_t hsize;
233 : : /* Traverse all CUs of this module. */
234 [ + + ]: 26084 : while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
235 : : {
236 : 26016 : Dwarf_Die cudie_mem;
237 : 26016 : Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
238 : :
239 [ - + ]: 26016 : if (cudie == NULL)
240 : 0 : continue;
241 : :
242 [ - + ]: 26016 : const char *cuname = dwarf_diename (cudie) ?: "<unknown>";
243 : 26016 : Dwarf_Files *files;
244 : 26016 : size_t nfiles;
245 [ - + ]: 26016 : if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
246 : 0 : continue;
247 : :
248 : : /* extract DW_AT_comp_dir to resolve relative file names. */
249 : 26016 : const char *comp_dir = "";
250 : 26016 : const char *const *dirs;
251 : 26016 : size_t ndirs;
252 : :
253 [ + - - + ]: 26016 : if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 && dirs[0] != NULL)
254 : : comp_dir = dirs[0];
255 : 0 : if (comp_dir == NULL)
256 : : comp_dir = "";
257 : :
258 [ + + ]: 26016 : if (verbose)
259 : 12392 : clog << "searching for sources for cu=" << cuname
260 : 12392 : << " comp_dir=" << comp_dir << " #files=" << nfiles
261 : 12392 : << " #dirs=" << ndirs << endl;
262 : :
263 [ + - - - ]: 26016 : if (comp_dir[0] == '\0' && cuname[0] != '/')
264 : : {
265 : : /* This is a common symptom for dwz-compressed debug files,
266 : : where the altdebug file cannot be resolved. */
267 [ # # ]: 0 : if (verbose)
268 : 0 : clog << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
269 : 0 : continue;
270 : : }
271 [ + + ]: 394696 : for (size_t f = 1; f < nfiles; ++f)
272 : : {
273 : 368680 : const char *hat;
274 [ + + ]: 368680 : if (CU_only)
275 : : {
276 [ + - - + ]: 40336 : if (strcmp(cuname, "<unknown>") == 0 || strcmp(cuname, "<artificial>") == 0 )
277 : 0 : continue;
278 : : hat = cuname;
279 : : }
280 : : else
281 : 328344 : hat = dwarf_filesrc (files, f, NULL, NULL);
282 : :
283 [ - + ]: 328344 : if (hat == NULL)
284 : 0 : continue;
285 : :
286 [ + + ]: 737360 : if (string(hat).find("<built-in>")
287 [ + + ]: 368680 : != string::npos) /* gcc intrinsics, don't bother recording */
288 : 3784 : continue;
289 : :
290 [ + + ]: 364896 : string waldo;
291 [ + + ]: 364896 : if (hat[0] == '/') /* absolute */
292 [ + - - + ]: 76732 : waldo = (string (hat));
293 [ + - ]: 288164 : else if (comp_dir[0] != '\0') /* comp_dir relative */
294 [ + - + - : 534112 : waldo = (string (comp_dir) + string ("/") + string (hat));
+ - + - +
- - + - +
- + - + +
+ - - -
- ]
295 : : else
296 : : {
297 [ # # ]: 0 : if (verbose)
298 [ # # # # : 0 : clog << "skipping file=" << hat << " due to empty comp_dir" << endl;
# # # # ]
299 [ # # ]: 0 : continue;
300 : : }
301 [ + - + - : 711904 : waldo = canonicalize_path (waldo);
+ + + - ]
302 [ + - + - : 729792 : debug_sourcefiles.insert (make_pair(waldo, dwflmod));
+ - - - ]
303 : 368680 : }
304 : : }
305 : 68 : return DWARF_CB_OK;
306 : : }
307 : :
308 : : #ifdef HAVE_LIBARCHIVE
309 : 10 : void zip_files()
310 : : {
311 : 10 : struct archive *a = archive_write_new();
312 : 10 : struct stat st;
313 : 10 : char buff[BUFFER_SIZE];
314 : 10 : int len;
315 : 10 : int fd;
316 : : #ifdef ENABLE_LIBDEBUGINFOD
317 : : /* Initialize a debuginfod client. */
318 : 10 : static unique_ptr <debuginfod_client, void (*)(debuginfod_client*)>
319 [ + - + - : 10 : client (debuginfod_begin(), &debuginfod_end);
+ - ]
320 : : #endif
321 : :
322 : 10 : archive_write_set_format_zip(a);
323 : 10 : archive_write_open_fd(a, STDOUT_FILENO);
324 : :
325 : 10 : int missing_files = 0;
326 [ + + ]: 5220 : for (const auto &pair : debug_sourcefiles)
327 : : {
328 : 5210 : fd = -1;
329 : 5210 : const std::string &file_path = pair.first;
330 : :
331 : : /* Attempt to query debuginfod client to fetch source files. */
332 : : #ifdef ENABLE_LIBDEBUGINFOD
333 : 5210 : Dwfl_Module* dwflmod = pair.second;
334 : : /* Obtain source file's build ID. */
335 : 5210 : const unsigned char *bits;
336 : 5210 : GElf_Addr vaddr;
337 : 5210 : int bits_length = dwfl_module_build_id(dwflmod, &bits, &vaddr);
338 : : /* Ensure successful client and build ID acquisition. */
339 [ + - + - ]: 5210 : if (client.get() != NULL && bits_length > 0)
340 : : {
341 : 5210 : fd = debuginfod_find_source(client.get(),
342 : : bits, bits_length,
343 : : file_path.c_str(), NULL);
344 : : }
345 : : else
346 : : {
347 [ # # ]: 0 : if (client.get() == NULL)
348 : 0 : cerr << "Error: Failed to initialize debuginfod client." << endl;
349 : : else
350 : 0 : cerr << "Error: Invalid build ID length (" << bits_length << ")." << endl;
351 : : }
352 : : #endif
353 : :
354 [ + + ]: 5210 : if (!no_backup)
355 : : /* Files could not be located using debuginfod, search locally */
356 [ + - ]: 4168 : if (fd < 0)
357 : 4168 : fd = open(file_path.c_str(), O_RDONLY);
358 [ - + ]: 5210 : if (fd < 0)
359 : : {
360 [ # # ]: 0 : if (verbose)
361 : 0 : cerr << file_path << endl;
362 : 0 : missing_files++;
363 : 0 : continue;
364 : : }
365 : :
366 : : /* Create an entry for each file including file information to be placed in the zip. */
367 [ - + ]: 5210 : if (fstat(fd, &st) == -1)
368 : : {
369 [ # # ]: 0 : if (verbose)
370 : 0 : cerr << file_path << endl;
371 : 0 : missing_files++;
372 [ # # ]: 0 : if (verbose)
373 : 0 : cerr << "Error: Failed to get file status for " << file_path << ": " << strerror(errno) << endl;
374 : 0 : continue;
375 : : }
376 : 5210 : struct archive_entry *entry = archive_entry_new();
377 : : /* Removing first "/"" to make the path "relative" before zipping, otherwise warnings are raised when unzipping. */
378 : 5210 : string entry_name = file_path.substr(file_path.find_first_of('/') + 1);
379 [ + - ]: 5210 : archive_entry_set_pathname(entry, entry_name.c_str());
380 [ + - ]: 5210 : archive_entry_copy_stat(entry, &st);
381 [ + - - + ]: 5210 : if (archive_write_header(a, entry) != ARCHIVE_OK)
382 : : {
383 [ # # ]: 0 : if (verbose)
384 [ # # # # ]: 0 : cerr << file_path << endl;
385 : 0 : missing_files++;
386 [ # # ]: 0 : if (verbose)
387 [ # # # # : 0 : cerr << "Error: failed to write header for " << file_path << ": " << archive_error_string(a) << endl;
# # # # #
# # # ]
388 : 0 : continue;
389 : : }
390 : :
391 : : /* Write the file to the zip. */
392 [ + - ]: 5210 : len = read(fd, buff, sizeof(buff));
393 [ - + ]: 5210 : if (len == -1)
394 : : {
395 [ # # ]: 0 : if (verbose)
396 [ # # # # ]: 0 : cerr << file_path << endl;
397 : 0 : missing_files++;
398 [ # # ]: 0 : if (verbose)
399 [ # # # # : 0 : cerr << "Error: Failed to open file: " << file_path << ": " << strerror(errno) <<endl;
# # # # #
# ]
400 : 0 : continue;
401 : : }
402 [ + + ]: 15190 : while (len > 0)
403 : : {
404 [ + - - + ]: 9980 : if (archive_write_data(a, buff, len) < ARCHIVE_OK)
405 : : {
406 [ # # ]: 0 : if (verbose)
407 [ # # # # : 0 : cerr << "Error: Failed to read from the file: " << file_path << ": " << strerror(errno) << endl;
# # # # #
# ]
408 : : break;
409 : : }
410 [ + - ]: 9980 : len = read(fd, buff, sizeof(buff));
411 : : }
412 [ + - ]: 5210 : close(fd);
413 [ + - ]: 5210 : archive_entry_free(entry);
414 : 5210 : }
415 [ + + - + ]: 10 : if (verbose && missing_files > 0 )
416 : 0 : cerr << missing_files << " file(s) listed above could not be found. " << endl;
417 : :
418 : 10 : archive_write_close(a);
419 : 10 : archive_write_free(a);
420 : 10 : }
421 : : #endif
422 : :
423 : : int
424 : 38 : main (int argc, char *argv[])
425 : : {
426 : 38 : int remaining;
427 : :
428 : : /* Parse and process arguments. This includes opening the modules. */
429 : 38 : argp_children[0].argp = dwfl_standard_argp ();
430 : 38 : argp_children[0].group = 1;
431 : :
432 : 38 : Dwfl *dwfl = NULL;
433 : 38 : (void) argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
434 [ - + ]: 36 : assert (dwfl != NULL);
435 : : /* Process all loaded modules - probably just one, except if -K or -p is used. */
436 : 36 : (void) dwfl_getmodules (dwfl, &collect_sourcefiles, NULL, 0);
437 : :
438 [ + - ]: 36 : if (!debug_sourcefiles.empty ())
439 : : {
440 : : #ifdef HAVE_LIBARCHIVE
441 [ + + ]: 36 : if (zip)
442 : 10 : zip_files();
443 : : else
444 : : #endif
445 : : {
446 [ + + ]: 27604 : for (const auto &pair : debug_sourcefiles)
447 : : {
448 : 27578 : cout << pair.first;
449 [ + + ]: 27578 : if (null_arg)
450 : 13268 : cout << '\0';
451 : : else
452 : 14310 : cout << '\n';
453 : : }
454 : : }
455 : : }
456 : :
457 : 36 : dwfl_end (dwfl);
458 : 36 : return 0;
459 : : }
|