source: rtems-tools/tester/covoar/covoar.cc @ 558cab8

5
Last change on this file since 558cab8 was 70dac81, checked in by Cillian O'Donnell <cpodonnell8@…>, on 05/14/18 at 19:26:21

covoar: Fix build path checks for multiple executables.

  • Property mode set to 100644
File size: 16.7 KB
Line 
1#include <ctype.h>
2#include <errno.h>
3#include <fcntl.h>
4#include <limits.h>
5#include <signal.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <sys/types.h>
10#include <sys/stat.h>
11#include <unistd.h>
12
13#include <list>
14
15#include "app_common.h"
16#include "CoverageFactory.h"
17#include "CoverageMap.h"
18#include "DesiredSymbols.h"
19#include "ExecutableInfo.h"
20#include "Explanations.h"
21#include "ObjdumpProcessor.h"
22#include "ReportsBase.h"
23#include "TargetFactory.h"
24#include "GcovData.h"
25
26#include "rld-process.h"
27
28#ifdef _WIN32
29  #define kill(p,s) raise(s)
30#endif
31
32typedef std::list<std::string> CoverageNames;
33typedef std::list<Coverage::ExecutableInfo*> Executables;
34
35/*
36 * Create a build path from the executable paths. Also extract the build prefix
37 * and BSP names.
38 */
39static void createBuildPath(Executables& executablesToAnalyze,
40                            std::string& buildPath,
41                            std::string& buildPrefix,
42                            std::string& buildBSP)
43{
44  for (const auto& exe : executablesToAnalyze) {
45    rld::strings eparts;
46    rld::split(eparts, rld::path::path_abs(exe->getFileName()), RLD_PATH_SEPARATOR);
47    std::string fail; // empty means all is OK else an error string
48    for (rld::path::paths::reverse_iterator pri = eparts.rbegin();
49         pri != eparts.rend();
50         ++pri) {
51      if (*pri == "testsuites") {
52        ++pri;
53        if (pri == eparts.rend()) {
54          fail = "invalid executable path, no BSP";
55          break;
56        }
57        if (buildBSP.empty()) {
58          buildBSP = *pri;
59        } else {
60          if (buildBSP != *pri) {
61            fail = "executable BSP does not match: " + buildBSP;
62            break;
63          }
64        }
65        ++pri;
66        if (pri == eparts.rend() || *pri != "c") {
67          fail = "invalid executable path, no 'c'";
68          break;
69        }
70        ++pri;
71        if (pri == eparts.rend()) {
72          fail = "invalid executable path, no arch prefix";
73          break;
74        }
75        if (buildPrefix.empty()) {
76          buildPrefix = *pri;
77        } else {
78          if (buildPrefix != *pri) {
79            fail = "executable build prefix does not match: " + buildPrefix;
80            break;
81          }
82        }
83        ++pri;
84        if (pri == eparts.rend()) {
85          fail = "invalid executable path, no build top";
86          break;
87        }
88        //
89        // The remaining parts of the path is the build path. Iterator over them
90        // and collect into a new paths variable to join to make a path.
91        //
92        rld::path::paths bparts;
93        for (; pri != eparts.rend(); ++pri)
94          bparts.insert(bparts.begin(), *pri);
95        std::string thisBuildPath;
96        rld::path::path_join(thisBuildPath, bparts, thisBuildPath);
97        if (buildPath.empty()) {
98          buildPath = thisBuildPath;
99        } else {
100          if (buildPath != thisBuildPath) {
101            fail = "executable build path does not match: " + buildPath;
102          }
103        }
104        break;
105      }
106    }
107    if (!fail.empty()) {
108      std::cerr << "ERROR: " << fail << std::endl;
109      exit(EXIT_FAILURE);
110    }
111  }
112}
113
114/*
115 *  Print program usage message
116 */
117void usage(const std::string& progname)
118{
119  std::cerr <<"Usage: " << progname
120            <<" [-v] -T TARGET -f FORMAT [-E EXPLANATIONS] -1 EXECUTABLE coverage1 ... coverageN" << std::endl
121            << "--OR--" << std::endl
122            << "Usage: " << progname
123            << " [-v] -T TARGET -f FORMAT [-E EXPLANATIONS] -e EXE_EXTENSION -c COVERAGEFILE_EXTENSION EXECUTABLE1 ... EXECUTABLE2" << std::endl
124            << std::endl
125            << "  -v                        - verbose at initialization" << std::endl
126            << "  -T TARGET                 - target name" << std::endl
127            << "  -f FORMAT                 - coverage file format (RTEMS, QEMU, TSIM or Skyeye)" << std::endl
128            << "  -E EXPLANATIONS           - name of file with explanations" << std::endl
129            << "  -s SYMBOL_SET_FILE        - path to the INI format symbol sets" << std::endl
130            << "  -1 EXECUTABLE             - name of executable to get symbols from" << std::endl
131            << "  -e EXE_EXTENSION          - extension of the executables to analyze" << std::endl
132            << "  -c COVERAGEFILE_EXTENSION - extension of the coverage files to analyze" << std::endl
133            << "  -g GCNOS_LIST             - name of file with list of *.gcno files" << std::endl
134            << "  -p PROJECT_NAME           - name of the project" << std::endl
135            << "  -C ConfigurationFileName  - name of configuration file" << std::endl
136            << "  -O Output_Directory       - name of output directory (default=." << std::endl
137            << "  -d debug                  - disable cleaning of tempfile" << std::endl
138            << std::endl;
139}
140
141#define PrintableString(_s) \
142((!(_s)) ? "NOT SET" : (_s))
143
144static void
145fatal_signal( int signum )
146{
147  signal( signum, SIG_DFL );
148
149  rld::process::temporaries_clean_up();
150
151  /*
152   * Get the same signal again, this time not handled, so its normal effect
153   * occurs.
154   */
155  kill( getpid(), signum );
156}
157
158static void
159setup_signals( void )
160{
161  if ( signal( SIGINT, SIG_IGN ) != SIG_IGN )
162    signal( SIGINT, fatal_signal );
163#ifdef SIGHUP
164  if ( signal( SIGHUP, SIG_IGN ) != SIG_IGN )
165    signal( SIGHUP, fatal_signal );
166#endif
167  if ( signal( SIGTERM, SIG_IGN ) != SIG_IGN )
168    signal( SIGTERM, fatal_signal );
169#ifdef SIGPIPE
170  if ( signal( SIGPIPE, SIG_IGN ) != SIG_IGN )
171    signal( SIGPIPE, fatal_signal );
172#endif
173#ifdef SIGCHLD
174  signal( SIGCHLD, SIG_DFL );
175#endif
176}
177
178int main(
179  int    argc,
180  char** argv
181)
182{
183  CoverageNames                 coverageFileNames;
184  std::string                   coverageFileName;
185  Executables                   executablesToAnalyze;
186  Coverage::ExecutableInfo*     executableInfo = NULL;
187  std::string                   executableExtension = "exe";
188  std::string                   coverageExtension = "cov";
189  Coverage::CoverageFormats_t   coverageFormat;
190  Coverage::CoverageReaderBase* coverageReader = NULL;
191  char*                         executable = NULL;
192  const char*                   explanations = NULL;
193  const char*                   gcnosFileName = NULL;
194  char                          gcnoFileName[FILE_NAME_LENGTH];
195  char                          gcdaFileName[FILE_NAME_LENGTH];
196  char                          gcovBashCommand[256];
197  std::string                   target;
198  const char*                   format = "html";
199  FILE*                         gcnosFile = NULL;
200  Gcov::GcovData*               gcovFile;
201  const char*                   singleExecutable = NULL;
202  rld::process::tempfile        objdumpFile( ".dmp" );
203  rld::process::tempfile        err( ".err" );
204  rld::process::tempfile        syms( ".syms" );
205  bool                          debug = false;
206  std::string                   symbolSet;
207  std::string                   progname;
208  std::string                   option;
209  int                           opt;
210
211  setup_signals();
212
213  //
214  // Process command line options.
215  //
216  progname = rld::path::basename(argv[0]);
217
218  while ((opt = getopt(argc, argv, "1:L:e:c:g:E:f:s:S:T:O:p:vd")) != -1) {
219    switch (opt) {
220      case '1': singleExecutable    = optarg; break;
221      case 'L': dynamicLibrary      = optarg; break;
222      case 'e': executableExtension = optarg; break;
223      case 'c': coverageExtension   = optarg; break;
224      case 'g': gcnosFileName       = optarg; break;
225      case 'E': explanations        = optarg; break;
226      case 'f': format              = optarg; break;
227      case 'S': symbolSet           = optarg; break;
228      case 'T': target              = optarg; break;
229      case 'O': outputDirectory     = optarg; break;
230      case 'v': Verbose             = true;   break;
231      case 'p': projectName         = optarg; break;
232      case 'd': debug               = true;   break;
233      default: /* '?' */
234        usage(progname);
235        exit(EXIT_FAILURE);
236    }
237  }
238  try
239  {
240    /*
241     * Validate inputs.
242     */
243
244    /*
245     * Validate that we have a symbols of interest file.
246     */
247    if ( symbolSet.empty() ) {
248      option = "symbol set file -S";
249      throw option;
250    }
251
252    /*
253     * Has path to explanations.txt been specified.
254     */
255    if ( !explanations ) {
256      option = "explanations -E";
257      throw option;
258    }
259
260    /*
261     * Check for project name.
262     */
263    if ( !projectName ) {
264      option = "project name -p";
265      throw option;
266    }
267  }
268  catch( std::string option )
269  {
270    std::cerr << "error missing option: " + option << std::endl;
271    usage(progname);
272    exit(EXIT_FAILURE);
273  }
274
275  // If a single executable was specified, process the remaining
276  // arguments as coverage file names.
277  if (singleExecutable) {
278
279    // Ensure that the executable is readable.
280    if (!FileIsReadable( singleExecutable )) {
281      std::cerr << "warning: Unable to read executable: " << singleExecutable
282                << std::endl;
283    } else {
284
285      for (int i = optind; i < argc; i++) {
286        // Ensure that the coverage file is readable.
287        if (!FileIsReadable( argv[i] )) {
288          std::cerr << "warning: Unable to read coverage file: " << argv[i]
289                    << std::endl;
290        } else {
291          coverageFileNames.push_back( argv[i] );
292        }
293      }
294
295      // If there was at least one coverage file, create the
296      // executable information.
297      if (!coverageFileNames.empty()) {
298        if (dynamicLibrary) {
299          executableInfo = new Coverage::ExecutableInfo(
300            singleExecutable, dynamicLibrary
301          );
302        } else {
303          executableInfo = new Coverage::ExecutableInfo( singleExecutable );
304        }
305
306        executablesToAnalyze.push_back( executableInfo );
307      }
308    }
309  }
310  else {
311    // If not invoked with a single executable, process the remaining
312    // arguments as executables and derive the coverage file names.
313    for (int i = optind; i < argc; i++) {
314      // Ensure that the executable is readable.
315      if (!FileIsReadable( argv[i] )) {
316        std::cerr << "warning: Unable to read executable: " << argv[i] << std::endl;
317      } else {
318        coverageFileName = argv[i];
319        coverageFileName.append( "." + coverageExtension );
320
321        if (!FileIsReadable( coverageFileName.c_str() )) {
322          std::cerr << "warning: Unable to read coverage file: " << coverageFileName
323                    << std::endl;
324        } else {
325          executableInfo = new Coverage::ExecutableInfo( argv[i] );
326          executablesToAnalyze.push_back( executableInfo );
327          coverageFileNames.push_back( coverageFileName );
328        }
329      }
330    }
331  }
332
333  // Ensure that there is at least one executable to process.
334  if (executablesToAnalyze.empty()) {
335    std::cerr << "error: No information to analyze" << std::endl;
336    exit(EXIT_FAILURE);
337  }
338
339  // The executablesToAnalyze and coverageFileNames containers need
340  // to be the name size of some of the code below breaks. Lets
341  // check and make sure.
342  if (executablesToAnalyze.size() != coverageFileNames.size()) {
343    std::cerr << "ERROR: executables and coverage name size mismatch" << std::endl;
344    exit(EXIT_FAILURE);
345  }
346
347  //
348  // Find the top of the BSP's build tree and if we have found the top
349  // check the executable is under the same path and BSP.
350  //
351  std::string buildPath;
352  std::string buildTarget;
353  std::string buildBSP;
354  createBuildPath(executablesToAnalyze,
355                  buildPath,
356                  buildTarget,
357                  buildBSP);
358
359  //
360  // Use a command line target if provided.
361  //
362  if (!target.empty()) {
363    buildTarget = target;
364  }
365
366  if (Verbose) {
367    if (singleExecutable) {
368      std::cerr << "Processing a single executable and multiple coverage files"
369                << std::endl;
370    } else {
371      std::cerr << "Processing multiple executable/coverage file pairs" << std::endl;
372    }
373    std::cerr << "Coverage Format : " << format << std::endl
374              << "Target          : " << buildTarget.c_str() << std::endl
375              << std::endl;
376
377    // Process each executable/coverage file pair.
378    Executables::iterator eitr = executablesToAnalyze.begin();
379    for (const auto& cname : coverageFileNames) {
380      std::cerr << "Coverage file " << cname
381                << " for executable: " << (*eitr)->getFileName() << std::endl;
382      if (!singleExecutable)
383        eitr++;
384    }
385  }
386
387  //
388  // Create data to support analysis.
389  //
390
391  // Create data based on target.
392  TargetInfo = Target::TargetFactory( buildTarget );
393
394  // Create the set of desired symbols.
395  SymbolsToAnalyze = new Coverage::DesiredSymbols();
396
397  //
398  // Read symbol configuration file and load needed symbols.
399  //
400  if (!SymbolsToAnalyze->load( symbolSet, buildTarget, buildBSP, Verbose )) {
401      exit(EXIT_FAILURE);
402  }
403
404  if ( Verbose )
405    std::cerr << "Analyzing " << SymbolsToAnalyze->set.size()
406              << " symbols" << std::endl;
407
408  // Create explanations.
409  AllExplanations = new Coverage::Explanations();
410  if ( explanations )
411    AllExplanations->load( explanations );
412
413  // Create coverage map reader.
414  coverageReader = Coverage::CreateCoverageReader(coverageFormat);
415  if (!coverageReader) {
416    std::cerr << "error: Unable to create coverage file reader" << std::endl;
417    exit(EXIT_FAILURE);
418  }
419
420  // Create the objdump processor.
421  objdumpProcessor = new Coverage::ObjdumpProcessor();
422
423  // Prepare each executable for analysis.
424  for (auto& exe : executablesToAnalyze) {
425    if (Verbose)
426      std::cerr << "Extracting information from: " << exe->getFileName()
427                << std::endl;
428
429    // If a dynamic library was specified, determine the load address.
430    if (dynamicLibrary) {
431      exe->setLoadAddress( objdumpProcessor->determineLoadAddress( exe ) );
432    }
433
434    // Load the objdump for the symbols in this executable.
435    objdumpProcessor->load( exe, objdumpFile, err );
436  }
437
438  //
439  // Analyze the coverage data.
440  //
441
442  // Process each executable/coverage file pair.
443  Executables::iterator eitr = executablesToAnalyze.begin();
444  for (const auto& cname : coverageFileNames) {
445    Coverage::ExecutableInfo* exe = *eitr;
446    if (Verbose)
447      std::cerr << "Processing coverage file " << cname
448                << " for executable " << exe->getFileName()
449                << std::endl;
450
451    // Process its coverage file.
452    coverageReader->processFile( cname.c_str(), exe );
453
454    // Merge each symbols coverage map into a unified coverage map.
455    exe->mergeCoverage();
456
457    // DEBUG Print ExecutableInfo content
458    //exe->dumpExecutableInfo();
459
460    if (!singleExecutable) {
461      eitr++;
462    }
463  }
464
465  // Do necessary preprocessing of uncovered ranges and branches
466  if (Verbose)
467    std::cerr << "Preprocess uncovered ranges and branches" << std::endl;
468
469  SymbolsToAnalyze->preprocess();
470
471  //
472  // Generate Gcov reports
473  //
474  if (gcnosFileName) {
475    if (Verbose)
476      std::cerr << "Generating Gcov reports..." << std::endl;
477
478    gcnosFile = fopen ( gcnosFileName , "r" );
479
480    if ( !gcnosFile )
481      std::cerr << "Unable to open " << gcnosFileName << std::endl;
482    else {
483      while ( fscanf( gcnosFile, "%s", inputBuffer ) != EOF) {
484        gcovFile = new Gcov::GcovData();
485        strcpy( gcnoFileName, inputBuffer );
486
487        if ( Verbose )
488          std::cerr << "Processing file: " << gcnoFileName << std::endl;
489
490        if ( gcovFile->readGcnoFile( gcnoFileName ) ) {
491          // Those need to be in this order
492          gcovFile->processCounters();
493          gcovFile->writeReportFile();
494          gcovFile->writeGcdaFile();
495          gcovFile->writeGcovFile();
496        }
497
498        delete gcovFile;
499      }
500      fclose( gcnosFile );
501    }
502  }
503
504  // Determine the uncovered ranges and branches.
505  if (Verbose)
506    std::cerr << "Computing uncovered ranges and branches" << std::endl;
507
508  SymbolsToAnalyze->computeUncovered();
509
510  // Calculate remainder of statistics.
511  if (Verbose)
512    std::cerr << "Calculate statistics" << std::endl;
513
514  SymbolsToAnalyze->calculateStatistics();
515
516  // Look up the source lines for any uncovered ranges and branches.
517  if (Verbose)
518    std::cerr << "Looking up source lines for uncovered ranges and branches"
519              << std::endl;
520
521  SymbolsToAnalyze->findSourceForUncovered();
522
523  //
524  // Report the coverage data.
525  //
526  if (Verbose)
527    std::cerr << "Generate Reports" << std::endl;
528
529  Coverage::GenerateReports();
530
531  // Write explanations that were not found.
532  if ( explanations ) {
533    std::string notFound;
534
535    notFound = outputDirectory;
536    notFound += "/";
537    notFound += "ExplanationsNotFound.txt";
538
539    if (Verbose)
540      std::cerr << "Writing Not Found Report (" << notFound<< ')' << std::endl;
541
542    AllExplanations->writeNotFound( notFound.c_str() );
543  }
544
545  //Leave tempfiles around if debug flag (-d) is enabled.
546  if ( debug ) {
547    objdumpFile.override( "objdump_file" );
548    objdumpFile.keep();
549    err.override( "objdump_exec_log" );
550    err.keep();
551    syms.override( "symbols_list" );
552    syms.keep();
553  }
554
555  return 0;
556}
Note: See TracBrowser for help on using the repository browser.