You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* In case we have a bad fts we include this before config.h because it
+ can't handle _FILE_OFFSET_BITS.
+ Everything we need here is fine if its declarations just come first.
+ Also, include sys/types.h before fts. On some systems fts.h is not self
+ contained. */
+#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 <set>
#include <string>
#include <cassert>
-#include <config.h>
+#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>
+
+/* Libraries for use by the --zip option */
+#ifdef HAVE_LIBARCHIVE
+#include <archive.h>
+#include <archive_entry.h>
+#endif
+
+/* If fts.h is included before config.h, its indirect inclusions may not
+ give us the right LFS aliases of these functions, so map them manually. */
+#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;
/* Bug report address. */
ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+constexpr size_t BUFFER_SIZE = 8192;
+
/* Definitions of arguments for argp functions. */
static const struct argp_option options[] =
{
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 },
+ { "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 }
};
/* Short description of program. */
-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 doc[] = N_("Lists the source files of a DWARF/ELF file. The default input is the file 'a.out'.");
/* Strings for arguments in help texts. */
static const char args_doc[] = N_("INPUT");
options, parse_opt, args_doc, doc, argp_children, NULL, NULL
};
-/* Verbose message printing. */
+/* Verbose message printing. */
static bool verbose;
-/* Delimit the output with nulls. */
+/* Delimit the output with nulls. */
static bool null_arg;
-/* Only print compilation unit names. */
+/* Only print compilation unit names. */
static bool CU_only;
-
-/* Handle program arguments. */
+#ifdef HAVE_LIBARCHIVE
+ /* Zip all the source files and send to stdout. */
+ static bool zip;
+
+ #ifdef ENABLE_LIBDEBUGINFOD
+ /* Disables local source file search when debuginfod fails to fetch them.
+ This option is only applicable when fetching and zipping files.*/
+ static bool no_backup;
+ #endif
+#endif
+
+/* Handle program arguments. Note null arg and zip
+ cannot be combined due to warnings raised when unzipping. */
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
- /* Suppress "unused parameter" warning. */
+ /* Suppress "unused parameter" warning. */
(void)arg;
switch (key)
{
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;
}
+/* Remove the "/./" , "../" and the preceding directory
+ that some paths include which raise errors during unzip. */
+string canonicalize_path(string path)
+{
+ stringstream ss(path);
+ string token;
+ vector<string> tokens;
+ /* Extract each directory of the path and place into a vector. */
+ while (getline(ss, token, '/')) {
+ /* Ignore any empty //, or /./ dirs. */
+ if (token == "" || token == ".")
+ continue;
+ /* When /.. is encountered, remove the most recent directory from the vector. */
+ else if (token == "..") {
+ if (!tokens.empty())
+ tokens.pop_back();
+ } else
+ tokens.push_back(token);
+ }
+ stringstream result;
+ if (tokens.empty())
+ return "/";
+ /* Reconstruct the path from the extracted directories. */
+ for (const string &t : tokens) {
+ result << '/' << t;
+ }
+ return result.str();
+}
-/* Global list of collected source files. Normally, it'll contain
- the sources of just one named binary, but the '-K' option can cause
- multiple dwfl modules to be loaded, thus listed. */
- set<string> debug_sourcefiles;
+/* Global list of collected source files and their respective module.
+ Normally, it'll contain the sources of just one named binary, but
+ the '-K' option can cause multiple dwfl modules to be loaded, thus
+ listed. */
+set<pair<string, Dwfl_Module*>> debug_sourcefiles;
static int
collect_sourcefiles (Dwfl_Module *dwflmod,
void *arg __attribute__ ((unused)))
{
Dwarf *dbg;
- Dwarf_Addr bias; /* ignored - for addressing purposes only */
+ Dwarf_Addr bias; /* ignored - for addressing purposes only. */
dbg = dwfl_module_getdwarf (dwflmod, &bias);
Dwarf_Off offset = 0;
Dwarf_Off old_offset;
size_t hsize;
-
/* Traverse all CUs of this module. */
while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
{
if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
continue;
- /* extract DW_AT_comp_dir to resolve relative file names */
+ /* extract DW_AT_comp_dir to resolve relative file names. */
const char *comp_dir = "";
const char *const *dirs;
size_t ndirs;
comp_dir = "";
if (verbose)
- std::clog << "searching for sources for cu=" << cuname
+ clog << "searching for sources for cu=" << cuname
<< " comp_dir=" << comp_dir << " #files=" << nfiles
<< " #dirs=" << ndirs << endl;
- for (size_t f = 1; f < nfiles; f++)
+ if (comp_dir[0] == '\0' && cuname[0] != '/')
+ {
+ /* This is a common symptom for dwz-compressed debug files,
+ where the altdebug file cannot be resolved. */
+ 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)
continue;
if (string(hat).find("<built-in>")
- != std::string::npos) /* gcc intrinsics, don't bother record */
+ != string::npos) /* gcc intrinsics, don't bother recording */
continue;
string waldo;
waldo = (string (hat));
else if (comp_dir[0] != '\0') /* comp_dir relative */
waldo = (string (comp_dir) + string ("/") + string (hat));
- debug_sourcefiles.insert (waldo);
+ 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
+ /* Initialize a debuginfod client. */
+ 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;
+
+ /* Attempt to query debuginfod client to fetch source files. */
+ #ifdef ENABLE_LIBDEBUGINFOD
+ Dwfl_Module* dwflmod = pair.second;
+ /* Obtain source file's build ID. */
+ const unsigned char *bits;
+ GElf_Addr vaddr;
+ int bits_length = dwfl_module_build_id(dwflmod, &bits, &vaddr);
+ /* Ensure successful client and build ID acquisition. */
+ 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)
+ /* Files could not be located using debuginfod, search locally */
+ if (fd < 0)
+ fd = open(file_path.c_str(), O_RDONLY);
+ if (fd < 0)
+ {
+ if (verbose)
+ cerr << file_path << endl;
+ missing_files++;
+ continue;
+ }
+
+ /* Create an entry for each file including file information to be placed in the zip. */
+ 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();
+ /* Removing first "/"" to make the path "relative" before zipping, otherwise warnings are raised when unzipping. */
+ 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;
+ }
+
+ /* Write the file to the zip. */
+ 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[])
Dwfl *dwfl = NULL;
(void) argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
assert (dwfl != NULL);
- /* Process all loaded modules - probably just one, except if -K or -p is used. */
+ /* Process all loaded modules - probably just one, except if -K or -p is used. */
(void) dwfl_getmodules (dwfl, &collect_sourcefiles, NULL, 0);
if (!debug_sourcefiles.empty ())
- for (const string &element : debug_sourcefiles)
+ {
+ #ifdef HAVE_LIBARCHIVE
+ if (zip)
+ zip_files();
+ else
+ #endif
{
- std::cout << element;
- if (null_arg)
- std::cout << '\0';
- else
- std::cout << '\n';
+ for (const auto &pair : debug_sourcefiles)
+ {
+ cout << pair.first;
+ if (null_arg)
+ cout << '\0';
+ else
+ cout << '\n';
+ }
}
+ }
dwfl_end (dwfl);
return 0;