LCOV - code coverage report
Current view: top level - src - srcfiles.cxx (source / functions) Hit Total Coverage
Test: elfutils-0.191 Lines: 91 103 88.3 %
Date: 2024-07-23 19:52:03 Functions: 4 4 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 78 139 56.1 %

           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                 :        164 : parse_opt (int key, char *arg, struct argp_state *state)
     144                 :            : {
     145                 :            :   /* Suppress "unused parameter" warning.  */
     146                 :        164 :   (void)arg;
     147   [ +  +  +  +  :        164 :   switch (key)
                      + ]
     148                 :            :     {
     149                 :         28 :     case ARGP_KEY_INIT:
     150                 :         28 :       state->child_inputs[0] = state->input;
     151                 :         28 :       break;
     152                 :            : 
     153                 :         12 :     case '0':
     154                 :         12 :       null_arg = true;
     155                 :         12 :       break;
     156                 :            : 
     157                 :         12 :     case 'v':
     158                 :         12 :       verbose = true;
     159                 :         12 :       break;
     160                 :            : 
     161                 :          8 :     case 'c':
     162                 :          8 :       CU_only = true;
     163                 :          8 :       break;
     164                 :            : 
     165                 :            :     #ifdef HAVE_LIBARCHIVE
     166                 :            :       case 'z':
     167                 :            :       zip = true;
     168                 :            :       break;
     169                 :            : 
     170                 :            :       #ifdef ENABLE_LIBDEBUGINFOD
     171                 :            :         case 'b':
     172                 :            :         no_backup = true;
     173                 :            :         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                 :     314122 : string canonicalize_path(string path)
     186                 :            : {
     187                 :     314122 :     stringstream ss(path);
     188                 :     314122 :     string token;
     189                 :     314122 :     vector<string> tokens;
     190                 :            :     /* Extract each directory of the path and place into a vector.  */
     191   [ +  -  +  + ]:    2521580 :     while (getline(ss, token, '/')) {
     192                 :            :       /* Ignore any empty //, or /./ dirs.  */
     193   [ +  +  +  + ]:    4312002 :         if (token == "" || token == ".")
     194                 :     334912 :             continue;
     195                 :            :       /* When /..  is encountered, remove the most recent directory from the vector.  */
     196         [ +  + ]:    1872546 :         else if (token == "..") {
     197         [ +  - ]:     214308 :             if (!tokens.empty())
     198                 :     214308 :                 tokens.pop_back();
     199                 :            :         } else
     200         [ +  - ]:    1658238 :             tokens.push_back(token);
     201                 :            :     }
     202         [ +  - ]:     314122 :     stringstream result;
     203         [ +  - ]:     314122 :     if (tokens.empty())
     204         [ #  # ]:          0 :         return "/";
     205                 :            :     /* Reconstruct the path from the extracted directories.  */
     206         [ +  + ]:    1758052 :     for (const string &t : tokens) {
     207   [ +  -  +  - ]:    1443930 :         result << '/' << t;
     208                 :            :     }
     209         [ +  - ]:     314122 :     return result.str();
     210         [ +  + ]:     426100 : }
     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                 :         58 : 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                 :         58 :   Dwarf *dbg;
     226                 :         58 :   Dwarf_Addr bias; /* ignored - for addressing purposes only.  */
     227                 :            : 
     228                 :         58 :   dbg = dwfl_module_getdwarf (dwflmod, &bias);
     229                 :            : 
     230                 :         58 :   Dwarf_Off offset = 0;
     231                 :         58 :   Dwarf_Off old_offset;
     232                 :         58 :   size_t hsize;
     233                 :            :   /* Traverse all CUs of this module.  */
     234         [ +  + ]:      22976 :   while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
     235                 :            :     {
     236                 :      22918 :       Dwarf_Die cudie_mem;
     237                 :      22918 :       Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
     238                 :            : 
     239         [ -  + ]:      22918 :       if (cudie == NULL)
     240                 :          0 :         continue;
     241                 :            : 
     242         [ -  + ]:      22918 :       const char *cuname = dwarf_diename (cudie) ?: "<unknown>";
     243                 :      22918 :       Dwarf_Files *files;
     244                 :      22918 :       size_t nfiles;
     245         [ -  + ]:      22918 :       if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
     246                 :          0 :         continue;
     247                 :            : 
     248                 :            :       /* extract DW_AT_comp_dir to resolve relative file names.  */
     249                 :      22918 :       const char *comp_dir = "";
     250                 :      22918 :       const char *const *dirs;
     251                 :      22918 :       size_t ndirs;
     252                 :            : 
     253   [ +  -  -  + ]:      22918 :       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         [ +  + ]:      22918 :       if (verbose)
     259                 :      11152 :         clog << "searching for sources for cu=" << cuname
     260                 :      11152 :                   << " comp_dir=" << comp_dir << " #files=" << nfiles
     261                 :      11152 :                   << " #dirs=" << ndirs << endl;
     262                 :            : 
     263   [ +  -  -  - ]:      22918 :       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         [ +  + ]:     340234 :       for (size_t f = 1; f < nfiles; ++f)
     272                 :            :         {
     273                 :     317316 :           const char *hat;
     274         [ +  + ]:     317316 :           if (CU_only)
     275                 :            :           {
     276   [ +  -  -  + ]:      39920 :             if (strcmp(cuname, "<unknown>") == 0 || strcmp(cuname, "<artificial>") == 0 )
     277                 :          0 :               continue;
     278                 :            :             hat = cuname;
     279                 :            :           }
     280                 :            :           else
     281                 :     277396 :             hat = dwarf_filesrc (files, f, NULL, NULL);
     282                 :            : 
     283         [ -  + ]:     277396 :           if (hat == NULL)
     284                 :          0 :             continue;
     285                 :            : 
     286         [ +  + ]:     634632 :           if (string(hat).find("<built-in>")
     287         [ +  + ]:     317316 :               != string::npos) /* gcc intrinsics, don't bother recording */
     288                 :       3194 :             continue;
     289                 :            : 
     290         [ +  + ]:     314122 :           string waldo;
     291         [ +  + ]:     314122 :           if (hat[0] == '/') /* absolute */
     292   [ +  -  -  + ]:      44724 :             waldo = (string (hat));
     293         [ +  - ]:     269398 :           else if (comp_dir[0] != '\0') /* comp_dir relative */
     294   [ +  -  +  -  :     501242 :             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   [ +  -  +  -  :     610356 :           waldo = canonicalize_path (waldo);
             +  +  +  - ]
     302   [ +  -  +  -  :     628244 :           debug_sourcefiles.insert (make_pair(waldo, dwflmod));
             +  -  -  - ]
     303                 :     317316 :         }
     304                 :            :     }
     305                 :         58 :   return DWARF_CB_OK;
     306                 :            : }
     307                 :            : 
     308                 :            : #ifdef HAVE_LIBARCHIVE
     309                 :            : void zip_files()
     310                 :            : {
     311                 :            :   struct archive *a = archive_write_new();
     312                 :            :   struct stat st;
     313                 :            :   char buff[BUFFER_SIZE];
     314                 :            :   int len;
     315                 :            :   int fd;
     316                 :            :   #ifdef ENABLE_LIBDEBUGINFOD
     317                 :            :   /* Initialize a debuginfod client.  */
     318                 :            :   static unique_ptr <debuginfod_client, void (*)(debuginfod_client*)>
     319                 :            :     client (debuginfod_begin(), &debuginfod_end);
     320                 :            :   #endif
     321                 :            : 
     322                 :            :   archive_write_set_format_zip(a);
     323                 :            :   archive_write_open_fd(a, STDOUT_FILENO);
     324                 :            : 
     325                 :            :   int missing_files = 0;
     326                 :            :   for (const auto &pair : debug_sourcefiles)
     327                 :            :   {
     328                 :            :     fd = -1;
     329                 :            :     const std::string &file_path = pair.first;
     330                 :            : 
     331                 :            :     /* Attempt to query debuginfod client to fetch source files.  */
     332                 :            :     #ifdef ENABLE_LIBDEBUGINFOD
     333                 :            :     Dwfl_Module* dwflmod = pair.second;
     334                 :            :     /* Obtain source file's build ID.  */
     335                 :            :     const unsigned char *bits;
     336                 :            :     GElf_Addr vaddr;
     337                 :            :     int bits_length = dwfl_module_build_id(dwflmod, &bits, &vaddr);
     338                 :            :     /* Ensure successful client and build ID acquisition.  */
     339                 :            :     if (client.get() != NULL && bits_length > 0)
     340                 :            :     {
     341                 :            :       fd = debuginfod_find_source(client.get(),
     342                 :            :                                     bits, bits_length,
     343                 :            :                                     file_path.c_str(), NULL);
     344                 :            :     }
     345                 :            :     else
     346                 :            :     {
     347                 :            :         if (client.get() == NULL)
     348                 :            :             cerr << "Error: Failed to initialize debuginfod client." << endl;
     349                 :            :         else
     350                 :            :             cerr << "Error: Invalid build ID length (" << bits_length << ")." << endl;
     351                 :            :     }
     352                 :            :     #endif
     353                 :            : 
     354                 :            :     if (!no_backup)
     355                 :            :       /* Files could not be located using debuginfod, search locally */
     356                 :            :       if (fd < 0)
     357                 :            :         fd = open(file_path.c_str(), O_RDONLY);
     358                 :            :     if (fd < 0)
     359                 :            :     {
     360                 :            :       if (verbose)
     361                 :            :         cerr << file_path << endl;
     362                 :            :       missing_files++;
     363                 :            :       continue;
     364                 :            :     }
     365                 :            : 
     366                 :            :     /* Create an entry for each file including file information to be placed in the zip.  */
     367                 :            :     if (fstat(fd, &st) == -1)
     368                 :            :     {
     369                 :            :       if (verbose)
     370                 :            :         cerr << file_path << endl;
     371                 :            :       missing_files++;
     372                 :            :       if (verbose)
     373                 :            :         cerr << "Error: Failed to get file status for " << file_path << ": " << strerror(errno) << endl;
     374                 :            :       continue;
     375                 :            :     }
     376                 :            :     struct archive_entry *entry = archive_entry_new();
     377                 :            :     /* Removing first "/"" to make the path "relative" before zipping, otherwise warnings are raised when unzipping.  */
     378                 :            :     string entry_name = file_path.substr(file_path.find_first_of('/') + 1);
     379                 :            :     archive_entry_set_pathname(entry, entry_name.c_str());
     380                 :            :     archive_entry_copy_stat(entry, &st);
     381                 :            :     if (archive_write_header(a, entry) != ARCHIVE_OK)
     382                 :            :     {
     383                 :            :       if (verbose)
     384                 :            :         cerr << file_path << endl;
     385                 :            :       missing_files++;
     386                 :            :       if (verbose)
     387                 :            :         cerr << "Error: failed to write header for " << file_path << ": " << archive_error_string(a) << endl;
     388                 :            :       continue;
     389                 :            :     }
     390                 :            : 
     391                 :            :     /* Write the file to the zip.  */
     392                 :            :     len = read(fd, buff, sizeof(buff));
     393                 :            :     if (len == -1)
     394                 :            :     {
     395                 :            :       if (verbose)
     396                 :            :         cerr << file_path << endl;
     397                 :            :       missing_files++;
     398                 :            :       if (verbose)
     399                 :            :         cerr << "Error: Failed to open file: " << file_path << ": " << strerror(errno) <<endl;
     400                 :            :       continue;
     401                 :            :     }
     402                 :            :     while (len > 0)
     403                 :            :     {
     404                 :            :       if (archive_write_data(a, buff, len) < ARCHIVE_OK)
     405                 :            :       {
     406                 :            :         if (verbose)
     407                 :            :           cerr << "Error: Failed to read from the file: " << file_path << ": " << strerror(errno) << endl;
     408                 :            :         break;
     409                 :            :       }
     410                 :            :       len = read(fd, buff, sizeof(buff));
     411                 :            :     }
     412                 :            :     close(fd);
     413                 :            :     archive_entry_free(entry);
     414                 :            :   }
     415                 :            :   if (verbose && missing_files > 0 )
     416                 :            :     cerr << missing_files << " file(s) listed above could not be found.  " << endl;
     417                 :            : 
     418                 :            :   archive_write_close(a);
     419                 :            :   archive_write_free(a);
     420                 :            : }
     421                 :            : #endif
     422                 :            : 
     423                 :            : int
     424                 :         28 : main (int argc, char *argv[])
     425                 :            : {
     426                 :         28 :   int remaining;
     427                 :            : 
     428                 :            :   /* Parse and process arguments.  This includes opening the modules.  */
     429                 :         28 :   argp_children[0].argp = dwfl_standard_argp ();
     430                 :         28 :   argp_children[0].group = 1;
     431                 :            : 
     432                 :         28 :   Dwfl *dwfl = NULL;
     433                 :         28 :   (void) argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
     434         [ -  + ]:         26 :   assert (dwfl != NULL);
     435                 :            :   /* Process all loaded modules - probably just one, except if -K or -p is used.  */
     436                 :         26 :   (void) dwfl_getmodules (dwfl, &collect_sourcefiles, NULL, 0);
     437                 :            : 
     438         [ +  - ]:         26 :   if (!debug_sourcefiles.empty ())
     439                 :            :   {
     440                 :            :     #ifdef HAVE_LIBARCHIVE
     441                 :            :       if (zip)
     442                 :            :         zip_files();
     443                 :            :       else
     444                 :            :     #endif
     445                 :         26 :       {
     446         [ +  + ]:      27416 :         for (const auto &pair : debug_sourcefiles)
     447                 :            :           {
     448                 :      27390 :             cout << pair.first;
     449         [ +  + ]:      27390 :             if (null_arg)
     450                 :      13192 :               cout << '\0';
     451                 :            :             else
     452                 :      14198 :               cout << '\n';
     453                 :            :           }
     454                 :            :       }
     455                 :            :   }
     456                 :            : 
     457                 :         26 :   dwfl_end (dwfl);
     458                 :         26 :   return 0;
     459                 :            : }

Generated by: LCOV version 1.16