Home | History | Annotate | Download | only in toolchain-utils
      1 #!/usr/bin/env python2
      2 """Verify that a ChromeOS sub-tree was built with a particular compiler"""
      3 
      4 from __future__ import print_function
      5 
      6 import argparse
      7 import fnmatch
      8 import os
      9 import sys
     10 
     11 from cros_utils import command_executer
     12 
     13 COMPILERS = ['gcc', 'clang']
     14 
     15 COMPILER_STRINGS = {'gcc': 'GNU C', 'clang': 'clang version'}
     16 
     17 ERR_NO_DEBUG_INFO = 1024
     18 
     19 
     20 def UsageError(parser, message):
     21   """Output error message and help/usage info."""
     22 
     23   print('ERROR: %s' % message)
     24   parser.print_help()
     25   sys.exit(0)
     26 
     27 
     28 def CreateTmpDwarfFile(filename, dwarf_file, cmd_executer):
     29   """Create temporary dwarfdump file, to be parsed later."""
     30 
     31   cmd = ('readelf --debug-dump=info %s | grep -A5 DW_AT_producer > %s' %
     32          (filename, dwarf_file))
     33   retval = cmd_executer.RunCommand(cmd, print_to_console=False)
     34   return retval
     35 
     36 
     37 def FindAllFiles(root_dir, cmd_executer):
     38   """Create a list of all the *.debug and *.dwp files to be checked."""
     39 
     40   file_list = []
     41   tmp_list = [
     42       os.path.join(dirpath, f)
     43       for dirpath, dirnames, files in os.walk(root_dir)
     44       for f in fnmatch.filter(files, '*.debug')
     45   ]
     46   for f in tmp_list:
     47     if 'build-id' not in f:
     48       file_list.append(f)
     49   tmp_list = [
     50       os.path.join(dirpath, f)
     51       for dirpath, dirnames, files in os.walk(root_dir)
     52       for f in fnmatch.filter(files, '*.dwp')
     53   ]
     54   file_list += tmp_list
     55   return file_list
     56 
     57 
     58 def VerifyArgs(compiler, filename, tmp_dir, root_dir, options, parser):
     59   """Verify that the option values and combinations are valid."""
     60 
     61   if options.filename and options.all_files:
     62     UsageError(parser, 'Cannot use both --file and --all_files.')
     63   if options.filename and options.root_dir:
     64     UsageError(parser, 'Cannot use both --file and --root_dir.')
     65   if options.all_files and not options.root_dir:
     66     UsageError(parser, 'Missing --root_dir option.')
     67   if options.root_dir and not options.all_files:
     68     UsageError(parser, 'Missing --all_files option.')
     69   if not options.filename and not options.all_files:
     70     UsageError(parser, 'Must specify either --file or --all_files.')
     71 
     72   # Verify that the values specified are valid.
     73   if filename:
     74     if not os.path.exists(filename):
     75       UsageError(parser, 'Cannot find %s' % filename)
     76   compiler = options.compiler.lower()
     77   if compiler not in COMPILERS:
     78     UsageError(parser, '%s is not a valid compiler (gcc or clang).' % compiler)
     79   if root_dir and not os.path.exists(root_dir):
     80     UsageError(parser, '%s does not exist.' % root_dir)
     81   if not os.path.exists(tmp_dir):
     82     os.makedirs(tmp_dir)
     83 
     84 
     85 def CheckFile(filename, compiler, tmp_dir, options, cmd_executer):
     86   """Verify the information in a single file."""
     87 
     88   print('Checking %s' % filename)
     89   # Verify that file contains debug information.
     90   cmd = 'readelf -SW %s | grep debug_info' % filename
     91   retval = cmd_executer.RunCommand(cmd, print_to_console=False)
     92   if retval != 0:
     93     print('No debug info in this file. Unable to verify compiler.')
     94     # There's no debug info in this file, so skip it.
     95     return ERR_NO_DEBUG_INFO
     96 
     97   tmp_name = os.path.basename(filename) + '.dwarf'
     98   dwarf_file = os.path.join(tmp_dir, tmp_name)
     99   status = CreateTmpDwarfFile(filename, dwarf_file, cmd_executer)
    100 
    101   if status != 0:
    102     print('Unable to create dwarf file for %s (status: %d).' %
    103           (filename, status))
    104     return status
    105 
    106   comp_str = COMPILER_STRINGS[compiler]
    107 
    108   retval = 0
    109   with open(dwarf_file, 'r') as in_file:
    110     lines = in_file.readlines()
    111     looking_for_name = False
    112     for line in lines:
    113       if 'DW_AT_producer' in line:
    114         looking_for_name = False
    115         if 'GNU AS' in line:
    116           continue
    117         if comp_str not in line:
    118           looking_for_name = True
    119           retval = 1
    120       elif looking_for_name:
    121         if 'DW_AT_name' in line:
    122           words = line.split(':')
    123           bad_file = words[-1]
    124           print('FAIL:  %s was not compiled with %s.' %
    125                 (bad_file.rstrip(), compiler))
    126           looking_for_name = False
    127         elif 'DW_TAG_' in line:
    128           looking_for_name = False
    129 
    130   if not options.keep_file:
    131     os.remove(dwarf_file)
    132 
    133   return retval
    134 
    135 
    136 def Main(argv):
    137 
    138   cmd_executer = command_executer.GetCommandExecuter()
    139   parser = argparse.ArgumentParser()
    140   parser.add_argument(
    141       '--file', dest='filename', help='Name of file to be verified.')
    142   parser.add_argument(
    143       '--compiler',
    144       dest='compiler',
    145       required=True,
    146       help='Desired compiler (gcc or clang)')
    147   parser.add_argument(
    148       '--tmp_dir',
    149       dest='tmp_dir',
    150       help='Directory in which to put dwarf dump file.'
    151       ' Defaults to /tmp')
    152   parser.add_argument(
    153       '--keep_file',
    154       dest='keep_file',
    155       default=False,
    156       action='store_true',
    157       help='Do not delete dwarf file when done.')
    158   parser.add_argument(
    159       '--all_files',
    160       dest='all_files',
    161       default=False,
    162       action='store_true',
    163       help='Find and check ALL .debug/.dwp files '
    164       'in subtree.  Must be used with --root_dir '
    165       '(and NOT with --file).')
    166   parser.add_argument(
    167       '--root_dir',
    168       dest='root_dir',
    169       help='Root of subtree in which to look for '
    170       'files.  Must be used with --all_files, and'
    171       ' not with --file.')
    172 
    173   options = parser.parse_args(argv)
    174 
    175   compiler = options.compiler
    176   filename = None
    177   if options.filename:
    178     filename = os.path.realpath(os.path.expanduser(options.filename))
    179   tmp_dir = '/tmp'
    180   if options.tmp_dir:
    181     tmp_dir = os.path.realpath(os.path.expanduser(options.tmp_dir))
    182   root_dir = None
    183   if options.root_dir:
    184     root_dir = os.path.realpath(os.path.expanduser(options.root_dir))
    185 
    186   VerifyArgs(compiler, filename, tmp_dir, root_dir, options, parser)
    187 
    188   file_list = []
    189   if filename:
    190     file_list.append(filename)
    191   else:
    192     file_list = FindAllFiles(root_dir, cmd_executer)
    193 
    194   bad_files = []
    195   unknown_files = []
    196   all_pass = True
    197   for f in file_list:
    198     result = CheckFile(f, compiler, tmp_dir, options, cmd_executer)
    199     if result == ERR_NO_DEBUG_INFO:
    200       unknown_files.append(f)
    201       all_pass = False
    202     elif result != 0:
    203       bad_files.append(f)
    204       all_pass = False
    205 
    206   if all_pass:
    207     print('\n\nSUCCESS:  All compilation units were compiled with %s.\n' %
    208           compiler)
    209     return 0
    210   else:
    211     if len(bad_files) == 0:
    212       print(
    213           '\n\n*Mostly* SUCCESS: All files that could be checked were compiled '
    214           'with %s.' % compiler)
    215       print(
    216           '\n\nUnable to verify the following files (no debug info in them):\n')
    217       for f in unknown_files:
    218         print(f)
    219     else:
    220       print('\n\nFAILED:  The following files were not compiled with %s:\n' %
    221             compiler)
    222       for f in bad_files:
    223         print(f)
    224       if len(unknown_files) > 0:
    225         print(
    226             '\n\nUnable to verify the following files (no debug info in them):\n'
    227         )
    228         for f in unknown_files:
    229           print(f)
    230     return 1
    231 
    232 
    233 if __name__ == '__main__':
    234   sys.exit(Main(sys.argv[1:]))
    235