source: rtems-tools/tester/covoar/ObjdumpProcessor.cc

Last change on this file was f5a3e89, checked in by Ryan Long <ryan.long@…>, on 12/13/21 at 15:48:35

ObjdumpProcessor?.cc: Fix formatting

  • Property mode set to 100644
File size: 15.4 KB
Line 
1/*! @file ObjdumpProcessor.cc
2 *  @brief ObjdumpProcessor Implementation
3 *
4 *  This file contains the implementation of the functions supporting
5 *  the reading of an objdump output file and adding nops to a
6 *  coverage map.
7 */
8
9#include <assert.h>
10#include <ctype.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <algorithm>
15#include <string>
16#include <fstream>
17#include <iomanip>
18
19#include "ObjdumpProcessor.h"
20#include "CoverageMap.h"
21#include "ExecutableInfo.h"
22#include "SymbolTable.h"
23#include "TargetFactory.h"
24
25#include "rld.h"
26#include "rld-process.h"
27
28#define MAX_LINE_LENGTH 512
29
30namespace Coverage {
31
32  void finalizeSymbol(
33    ExecutableInfo* const            executableInfo,
34    std::string&                     symbolName,
35    ObjdumpProcessor::objdumpLines_t instructions,
36    bool                             verbose,
37    DesiredSymbols&                  symbolsToAnalyze
38  ) {
39    // Find the symbol's coverage map.
40    try {
41      CoverageMapBase& coverageMap =
42        executableInfo->findCoverageMap( symbolName );
43
44      uint32_t firstInstructionAddress = UINT32_MAX;
45
46      // Find the address of the first instruction.
47      for ( auto& line : instructions ) {
48        if ( line.isInstruction ) {
49          firstInstructionAddress = line.address;
50          break;
51        }
52      }
53
54      if ( firstInstructionAddress == UINT32_MAX ) {
55        std::ostringstream what;
56        what << "Could not find first instruction address for symbol "
57             << symbolName << " in " << executableInfo->getFileName();
58        throw rld::error( what, "Coverage::finalizeSymbol" );
59      }
60
61      int      rangeIndex = -1;
62      uint32_t lowAddress = UINT32_MAX;
63      do {
64        rangeIndex++;
65        lowAddress = coverageMap.getLowAddressOfRange( rangeIndex );
66      } while ( firstInstructionAddress != lowAddress );
67
68      uint32_t sizeWithoutNops     = coverageMap.getSizeOfRange( rangeIndex );
69      uint32_t size                = sizeWithoutNops;
70      uint32_t highAddress         = lowAddress + size - 1;
71      uint32_t computedHighAddress = highAddress;
72
73      // Find the high address as reported by the address of the last NOP
74      // instruction. This ensures that NOPs get marked as executed later.
75      for (
76        auto instruction = instructions.rbegin();
77        instruction != instructions.rend();
78        instruction++
79      ) {
80        if ( instruction->isInstruction ) {
81          if ( instruction->isNop ) {
82            computedHighAddress = instruction->address + instruction->nopSize;
83          }
84
85          break;
86        }
87      }
88
89      if ( highAddress != computedHighAddress ) {
90        std::cerr << "Function's high address differs between DWARF and "
91                  << "objdump: " << symbolName << " (0x" << std::hex
92                  << highAddress << " and 0x"
93                  << computedHighAddress - 1 << ")" << std::dec << std::endl;
94
95        size = computedHighAddress - lowAddress;
96      }
97
98      // If there are NOT already saved instructions, save them.
99      SymbolInformation* symbolInfo = symbolsToAnalyze.find( symbolName );
100      if ( symbolInfo->instructions.empty() ) {
101        symbolInfo->sourceFile   = executableInfo;
102        symbolInfo->baseAddress  = lowAddress;
103        symbolInfo->instructions = instructions;
104      }
105
106      // Add the symbol to this executable's symbol table.
107      SymbolTable* theSymbolTable = executableInfo->getSymbolTable();
108      theSymbolTable->addSymbol(
109        symbolName,
110        lowAddress,
111        highAddress - lowAddress + 1
112      );
113
114      // Mark the start of each instruction in the coverage map.
115      for ( auto& instruction : instructions ) {
116        coverageMap.setIsStartOfInstruction( instruction.address );
117      }
118
119      // Create a unified coverage map for the symbol.
120      symbolsToAnalyze.createCoverageMap(
121        executableInfo->getFileName().c_str(),
122        symbolName,
123        size,
124        sizeWithoutNops,
125        verbose
126      );
127    } catch ( const ExecutableInfo::CoverageMapNotFoundError& e ) {
128      // Allow execution to continue even if a coverage map could not be
129      // found.
130      std::cerr << "Coverage map not found for symbol " << e.what()
131                << std::endl;
132    }
133  }
134
135  ObjdumpProcessor::ObjdumpProcessor(
136    DesiredSymbols&                      symbolsToAnalyze,
137    std::shared_ptr<Target::TargetBase>& targetInfo
138  ): symbolsToAnalyze_m( symbolsToAnalyze ),
139     targetInfo_m( targetInfo )
140  {
141  }
142
143  ObjdumpProcessor::~ObjdumpProcessor()
144  {
145  }
146
147  uint32_t ObjdumpProcessor::determineLoadAddress(
148    ExecutableInfo* theExecutable
149  )
150  {
151    #define METHOD "ERROR: ObjdumpProcessor::determineLoadAddress - "
152    std::ifstream loadAddressFile;
153    uint32_t      offset;
154    char          inputBuffer[ MAX_LINE_LENGTH ];
155
156    // This method should only be call for a dynamic library.
157    if ( !theExecutable->hasDynamicLibrary() ) {
158      return 0;
159    }
160
161    std::string dlinfoName = theExecutable->getFileName();
162    uint32_t address;
163    char inLibName[128];
164    std::string Library = theExecutable->getLibraryName();
165
166    dlinfoName += ".dlinfo";
167    // Read load address.
168    loadAddressFile.open( dlinfoName );
169    if ( !loadAddressFile.is_open() ) {
170      std::ostringstream what;
171      what << "Unable to open " << dlinfoName;
172      throw rld::error( what, METHOD );
173    }
174
175    // Process the dlinfo file.
176    while ( 1 ) {
177
178      // Get a line.
179      loadAddressFile.getline( inputBuffer, MAX_LINE_LENGTH );
180      if ( loadAddressFile.fail() && loadAddressFile.is_open() ) {
181        loadAddressFile.close();
182        std::ostringstream what;
183        what << "library " << Library << " not found in " << dlinfoName;
184        throw rld::error( what, METHOD );
185      }
186
187      sscanf( inputBuffer, "%s %x", inLibName, &offset );
188      std::string tmp = inLibName;
189      if ( tmp.find( Library ) != tmp.npos ) {
190        // std::cerr << inLibName << " - 0x"
191        //           << std::setfill( '0' ) << std::setw( 8 ) << std::hex
192        //           << offset << std::endl
193        //           << std::dec << std::setfill( ' ' );
194        address = offset;
195        break;
196      }
197    }
198
199    return address;
200
201    #undef METHOD
202  }
203
204  bool ObjdumpProcessor::IsBranch( const std::string& instruction )
205  {
206    if ( !targetInfo_m ) {
207      fprintf(
208        stderr,
209        "ERROR: ObjdumpProcessor::IsBranch - unknown architecture\n"
210      );
211      assert( 0 );
212      return false;
213    }
214
215    return targetInfo_m->isBranch( instruction );
216  }
217
218  bool ObjdumpProcessor::isBranchLine( const std::string& line )
219  {
220    if ( !targetInfo_m ) {
221      fprintf(
222        stderr,
223        "ERROR: ObjdumpProcessor::isBranchLine - unknown architecture\n"
224      );
225      assert( 0 );
226      return false;
227    }
228
229    return  targetInfo_m->isBranchLine( line );
230  }
231
232  bool ObjdumpProcessor::isNop(
233    const std::string& line,
234    int&               size
235  )
236  {
237    if ( !targetInfo_m ) {
238      fprintf(
239        stderr,
240        "ERROR: ObjdumpProcessor::isNop - unknown architecture\n"
241      );
242      assert(0);
243      return false;
244    }
245
246    return targetInfo_m->isNopLine( line, size );
247  }
248
249  void ObjdumpProcessor::getFile(
250    std::string             fileName,
251    rld::process::tempfile& objdumpFile,
252    rld::process::tempfile& err
253  )
254  {
255    rld::process::status        status;
256    rld::process::arg_container args = {
257      targetInfo_m->getObjdump(),
258      "-Cda",
259      "--section=.text",
260      "--source",
261      fileName
262    };
263
264    try
265    {
266      status = rld::process::execute(
267        targetInfo_m->getObjdump(),
268        args,
269        objdumpFile.name(),
270        err.name()
271      );
272      if (
273        ( status.type != rld::process::status::normal ) ||
274        ( status.code != 0 )
275      ) {
276        throw rld::error( "Objdump error", "generating objdump" );
277      }
278    } catch( rld::error& err )
279      {
280        std::cout << "Error while running " << targetInfo_m->getObjdump()
281                  << " on " << fileName << std::endl;
282        std::cout << err.what << " in " << err.where << std::endl;
283        return;
284      }
285
286    objdumpFile.open( true );
287  }
288
289  uint32_t ObjdumpProcessor::getAddressAfter( uint32_t address )
290  {
291    objdumpFile_t::iterator itr;
292
293    itr = find ( objdumpList.begin(), objdumpList.end(), address );
294    if ( itr == objdumpList.end() ) {
295      return 0;
296    }
297
298    itr++;
299    if ( itr == objdumpList.end() ) {
300      return 0;
301    }
302
303    return (*itr);
304
305  }
306
307  void ObjdumpProcessor::loadAddressTable (
308    ExecutableInfo* const   executableInformation,
309    rld::process::tempfile& objdumpFile,
310    rld::process::tempfile& err
311  )
312  {
313    int         items;
314    uint32_t    offset;
315    char        terminator;
316    std::string line;
317
318    // Obtain the objdump file.
319    if ( !executableInformation->hasDynamicLibrary() ) {
320      getFile( executableInformation->getFileName(), objdumpFile, err );
321    } else {
322      getFile( executableInformation->getLibraryName(), objdumpFile, err );
323    }
324
325    // Process all lines from the objdump file.
326    while ( true ) {
327
328      // Get the line.
329      objdumpFile.read_line( line );
330      if ( line.empty() ) {
331        break;
332      }
333
334      // See if it is the dump of an instruction.
335      items = sscanf( line.c_str(), "%x%c", &offset, &terminator );
336
337      // If it looks like an instruction ...
338      if ( ( items == 2 ) && ( terminator == ':' ) ) {
339        objdumpList.push_back(
340          executableInformation->getLoadAddress() + offset
341        );
342      }
343    }
344  }
345
346  void ObjdumpProcessor::load(
347    ExecutableInfo* const   executableInformation,
348    rld::process::tempfile& objdumpFile,
349    rld::process::tempfile& err,
350    bool                    verbose
351  )
352  {
353    std::string    currentSymbol = "";
354    uint32_t       instructionOffset;
355    int            items;
356    int            found;
357    objdumpLine_t  lineInfo;
358    uint32_t       offset;
359    bool           processSymbol = false;
360    char           symbol[ MAX_LINE_LENGTH ];
361    char           terminator1;
362    char           terminatorOne;
363    char           terminator2;
364    objdumpLines_t theInstructions;
365    char           instruction[ MAX_LINE_LENGTH ];
366    char           ID[ MAX_LINE_LENGTH ];
367    std::string    call = "";
368    std::string    jumpTableID = "";
369    std::string    line = "";
370
371    // Obtain the objdump file.
372    if ( !executableInformation->hasDynamicLibrary() ) {
373      getFile( executableInformation->getFileName(), objdumpFile, err );
374    } else {
375      getFile( executableInformation->getLibraryName(), objdumpFile, err );
376    }
377
378    while ( true ) {
379      // Get the line.
380      objdumpFile.read_line( line );
381      if ( line.empty() ) {
382        // If we are currently processing a symbol, finalize it.
383        if ( processSymbol ) {
384          finalizeSymbol(
385            executableInformation,
386            currentSymbol,
387            theInstructions,
388            verbose,
389            symbolsToAnalyze_m
390          );
391
392          std::cerr << "WARNING: ObjdumpProcessor::load - analysis of symbol "
393                    << currentSymbol << std::endl
394                    << "         may be incorrect.  It was the last symbol in "
395                    << executableInformation->getFileName() << std::endl
396                    << "         and the length of its last instruction"
397                    << " is assumed          to be one."
398                    << std::endl;
399        }
400
401        objdumpFile.close();
402        break;
403      }
404
405      // Remove any extra line break
406      if ( line.back() == '\n' ) {
407        line.erase( line.end() - 1 );
408      }
409
410      lineInfo.line          = line;
411      lineInfo.address       = 0xffffffff;
412      lineInfo.isInstruction = false;
413      lineInfo.isNop         = false;
414      lineInfo.nopSize       = 0;
415      lineInfo.isBranch      = false;
416
417      instruction[0] = '\0';
418      ID[0] = '\0';
419
420      // Look for the start of a symbol's objdump and extract
421      // offset and symbol (i.e. offset <symbolname>:).
422      items = sscanf(
423        line.c_str(),
424        "%x <%[^>]>%c",
425        &offset,
426        symbol,
427        &terminator1
428      );
429
430      // See if it is a jump table.
431      found = sscanf(
432        line.c_str(),
433        "%x%c\t%*[^\t]%c%s %*x %*[^+]%s",
434        &instructionOffset,
435        &terminatorOne,
436        &terminator2,
437        instruction,
438        ID
439      );
440      call = instruction;
441      jumpTableID = ID;
442
443      // If all items found, we are at the beginning of a symbol's objdump.
444      if ( ( items == 3 ) && ( terminator1 == ':' ) ) {
445        // If we are currently processing a symbol, finalize it.
446        if ( processSymbol ) {
447          finalizeSymbol(
448            executableInformation,
449            currentSymbol,
450            theInstructions,
451            verbose,
452            symbolsToAnalyze_m
453          );
454        }
455
456        // Start processing of a new symbol.
457        currentSymbol = "";
458        processSymbol = false;
459        theInstructions.clear();
460
461        // Look for a '.' character and strip everything after it.
462        // There is a chance that the compiler splits function bodies to improve
463        // inlining. If there exists some inlinable function that contains a
464        // branch where one path is more expensive and less likely to be taken
465        // than the other, inlining only the branch instruction and the less
466        // expensive path results in smaller code size while preserving most of
467        // the performance improvement.
468        // When this happens, the compiler will generate a function with a
469        // ".part.n" suffix. For our purposes, this generated function part is
470        // equivalent to the original function and should be treated as such.
471        char *periodIndex = strstr( symbol, "." );
472        if ( periodIndex != NULL ) {
473          *periodIndex = 0;
474        }
475
476        // See if the new symbol is one that we care about.
477        if ( symbolsToAnalyze_m.isDesired( symbol ) ) {
478          currentSymbol = symbol;
479          processSymbol = true;
480          theInstructions.push_back( lineInfo );
481        }
482      }
483      // If it looks like a jump table, finalize the symbol.
484      else if (
485        ( found == 5 ) &&
486        ( terminatorOne == ':' ) &&
487        ( terminator2 == '\t' ) &&
488        ( call.find( "call" ) != std::string::npos ) &&
489        ( jumpTableID.find( "+0x" ) != std::string::npos ) &&
490        processSymbol
491      ) {
492        // If we are currently processing a symbol, finalize it.
493        if ( processSymbol ) {
494          finalizeSymbol(
495            executableInformation,
496            currentSymbol,
497            theInstructions,
498            verbose,
499            symbolsToAnalyze_m
500          );
501        }
502
503        processSymbol = false;
504      }
505      else if ( processSymbol ) {
506
507        // See if it is the dump of an instruction.
508        items = sscanf(
509          line.c_str(),
510          "%x%c\t%*[^\t]%c",
511          &instructionOffset,
512          &terminator1,
513          &terminator2
514        );
515
516        // If it looks like an instruction ...
517        if (
518          ( items == 3 ) &&
519          ( terminator1 == ':' ) &&
520          ( terminator2 == '\t' )
521        ) {
522          // update the line's information, save it and ...
523          lineInfo.address =
524           executableInformation->getLoadAddress() + instructionOffset;
525          lineInfo.isInstruction = true;
526          lineInfo.isNop         = isNop( line.c_str(), lineInfo.nopSize );
527          lineInfo.isBranch      = isBranchLine( line.c_str() );
528        }
529
530        // Always save the line.
531        theInstructions.push_back( lineInfo );
532      }
533    }
534  }
535
536  void ObjdumpProcessor::setTargetInfo(
537    std::shared_ptr<Target::TargetBase>& targetInfo
538  )
539  {
540    targetInfo_m = targetInfo;
541  }
542}
Note: See TracBrowser for help on using the repository browser.