#ifdef BAD_FTS
#include <sys/types.h>
#include <fts.h>
#endif
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "printversion.h"
#include <dwarf.h>
#include <argp.h>
#include <cstring>
#include <set>
#include <string>
#include <cassert>
#include <gelf.h>
#include <memory>
#ifdef ENABLE_LIBDEBUGINFOD
#include "debuginfod.h"
#endif
#include <libdwfl.h>
#include <fcntl.h>
#include <iostream>
#include <libdw.h>
#include <sstream>
#include <vector>
#ifdef HAVE_LIBARCHIVE
#include <archive.h>
#include <archive_entry.h>
#endif
#ifdef BAD_FTS
#ifdef _FILE_OFFSET_BITS
#define open open64
#define fopen fopen64
#endif
#else
#include <sys/types.h>
#include <fts.h>
#endif
using namespace std;
ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
constexpr size_t BUFFER_SIZE = 8192;
static const struct argp_option options[] =
{
{ NULL, 0, NULL, OPTION_DOC, N_("Output options:"), 1 },
{ "null", '0', NULL, 0,
N_ ("Separate items by a null instead of a newline."), 0 },
{ "verbose", 'v', NULL, 0,
N_ ("Increase verbosity of logging messages."), 0 },
{ "cu-only", 'c', NULL, 0, N_("Only list the CU names."), 0 },
#ifdef HAVE_LIBARCHIVE
{ "zip", 'z', NULL, 0, N_("Zip all the source files and send to stdout. "
"Cannot be used with the null option"), 0 },
#ifdef ENABLE_LIBDEBUGINFOD
{ "no-backup", 'b', NULL, 0, N_("Disables local source file search when "
"debuginfod fails to fetch files. This option is only applicable"
"when fetching and zipping files."), 0 },
#endif
#endif
{ NULL, 0, NULL, 0, NULL, 0 }
};
static const char doc[] = N_("Lists the source files of a DWARF/ELF file. The default input is the file 'a.out'.");
static const char args_doc[] = N_("INPUT");
static error_t parse_opt (int key, char *arg, struct argp_state *state);
static struct argp_child argp_children[2];
static const struct argp argp =
{
options, parse_opt, args_doc, doc, argp_children, NULL, NULL
};
static bool verbose;
static bool null_arg;
static bool CU_only;
#ifdef HAVE_LIBARCHIVE
static bool zip;
#ifdef ENABLE_LIBDEBUGINFOD
static bool no_backup;
#endif
#endif
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
(void)arg;
switch (key)
{
case ARGP_KEY_INIT:
state->child_inputs[0] = state->input;
break;
case '0':
null_arg = true;
break;
case 'v':
verbose = true;
break;
case 'c':
CU_only = true;
break;
#ifdef HAVE_LIBARCHIVE
case 'z':
zip = true;
break;
#ifdef ENABLE_LIBDEBUGINFOD
case 'b':
no_backup = true;
break;
#endif
#endif
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
string canonicalize_path(string path)
{
stringstream ss(path);
string token;
vector<string> tokens;
while (getline(ss, token, '/')) {
if (token == "" || token == ".")
continue;
else if (token == "..") {
if (!tokens.empty())
tokens.pop_back();
} else
tokens.push_back(token);
}
stringstream result;
if (tokens.empty())
return "/";
for (const string &t : tokens) {
result << '/' << t;
}
return result.str();
}
set<pair<string, Dwfl_Module*>> debug_sourcefiles;
static int
collect_sourcefiles (Dwfl_Module *dwflmod,
void **userdata __attribute__ ((unused)),
const char *name __attribute__ ((unused)),
Dwarf_Addr base __attribute__ ((unused)),
void *arg __attribute__ ((unused)))
{
Dwarf *dbg;
Dwarf_Addr bias;
dbg = dwfl_module_getdwarf (dwflmod, &bias);
Dwarf_Off offset = 0;
Dwarf_Off old_offset;
size_t hsize;
while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
{
Dwarf_Die cudie_mem;
Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
if (cudie == NULL)
continue;
const char *cuname = dwarf_diename (cudie) ?: "<unknown>";
Dwarf_Files *files;
size_t nfiles;
if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
continue;
const char *comp_dir = "";
const char *const *dirs;
size_t ndirs;
if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 && dirs[0] != NULL)
comp_dir = dirs[0];
if (comp_dir == NULL)
comp_dir = "";
if (verbose)
clog << "searching for sources for cu=" << cuname
<< " comp_dir=" << comp_dir << " #files=" << nfiles
<< " #dirs=" << ndirs << endl;
if (comp_dir[0] == '\0' && cuname[0] != '/')
{
if (verbose)
clog << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
continue;
}
for (size_t f = 1; f < nfiles; ++f)
{
const char *hat;
if (CU_only)
{
if (strcmp(cuname, "<unknown>") == 0 || strcmp(cuname, "<artificial>") == 0 )
continue;
hat = cuname;
}
else
hat = dwarf_filesrc (files, f, NULL, NULL);
if (hat == NULL)
continue;
if (string(hat).find("<built-in>")
!= string::npos)
continue;
string waldo;
if (hat[0] == '/')
waldo = (string (hat));
else if (comp_dir[0] != '\0')
waldo = (string (comp_dir) + string ("/") + string (hat));
else
{
if (verbose)
clog << "skipping file=" << hat << " due to empty comp_dir" << endl;
continue;
}
waldo = canonicalize_path (waldo);
debug_sourcefiles.insert (make_pair(waldo, dwflmod));
}
}
return DWARF_CB_OK;
}
#ifdef HAVE_LIBARCHIVE
void zip_files()
{
struct archive *a = archive_write_new();
struct stat st;
char buff[BUFFER_SIZE];
int len;
int fd;
#ifdef ENABLE_LIBDEBUGINFOD
static unique_ptr <debuginfod_client, void (*)(debuginfod_client*)>
client (debuginfod_begin(), &debuginfod_end);
#endif
archive_write_set_format_zip(a);
archive_write_open_fd(a, STDOUT_FILENO);
int missing_files = 0;
for (const auto &pair : debug_sourcefiles)
{
fd = -1;
const std::string &file_path = pair.first;
#ifdef ENABLE_LIBDEBUGINFOD
Dwfl_Module* dwflmod = pair.second;
const unsigned char *bits;
GElf_Addr vaddr;
int bits_length = dwfl_module_build_id(dwflmod, &bits, &vaddr);
if (client.get() != NULL && bits_length > 0)
{
fd = debuginfod_find_source(client.get(),
bits, bits_length,
file_path.c_str(), NULL);
}
else
{
if (client.get() == NULL)
cerr << "Error: Failed to initialize debuginfod client." << endl;
else
cerr << "Error: Invalid build ID length (" << bits_length << ")." << endl;
}
#endif
if (!no_backup)
if (fd < 0)
fd = open(file_path.c_str(), O_RDONLY);
if (fd < 0)
{
if (verbose)
cerr << file_path << endl;
missing_files++;
continue;
}
if (fstat(fd, &st) == -1)
{
if (verbose)
cerr << file_path << endl;
missing_files++;
if (verbose)
cerr << "Error: Failed to get file status for " << file_path << ": " << strerror(errno) << endl;
continue;
}
struct archive_entry *entry = archive_entry_new();
string entry_name = file_path.substr(file_path.find_first_of('/') + 1);
archive_entry_set_pathname(entry, entry_name.c_str());
archive_entry_copy_stat(entry, &st);
if (archive_write_header(a, entry) != ARCHIVE_OK)
{
if (verbose)
cerr << file_path << endl;
missing_files++;
if (verbose)
cerr << "Error: failed to write header for " << file_path << ": " << archive_error_string(a) << endl;
continue;
}
len = read(fd, buff, sizeof(buff));
if (len == -1)
{
if (verbose)
cerr << file_path << endl;
missing_files++;
if (verbose)
cerr << "Error: Failed to open file: " << file_path << ": " << strerror(errno) <<endl;
continue;
}
while (len > 0)
{
if (archive_write_data(a, buff, len) < ARCHIVE_OK)
{
if (verbose)
cerr << "Error: Failed to read from the file: " << file_path << ": " << strerror(errno) << endl;
break;
}
len = read(fd, buff, sizeof(buff));
}
close(fd);
archive_entry_free(entry);
}
if (verbose && missing_files > 0 )
cerr << missing_files << " file(s) listed above could not be found. " << endl;
archive_write_close(a);
archive_write_free(a);
}
#endif
int
main (int argc, char *argv[])
{
int remaining;
argp_children[0].argp = dwfl_standard_argp ();
argp_children[0].group = 1;
Dwfl *dwfl = NULL;
(void) argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
assert (dwfl != NULL);
(void) dwfl_getmodules (dwfl, &collect_sourcefiles, NULL, 0);
if (!debug_sourcefiles.empty ())
{
#ifdef HAVE_LIBARCHIVE
if (zip)
zip_files();
else
#endif
{
for (const auto &pair : debug_sourcefiles)
{
cout << pair.first;
if (null_arg)
cout << '\0';
else
cout << '\n';
}
}
}
dwfl_end (dwfl);
return 0;
}