1#!/usr/bin/env python2 2"""Verify that a ChromeOS sub-tree was built with a particular compiler""" 3 4from __future__ import print_function 5 6import argparse 7import fnmatch 8import os 9import sys 10 11from cros_utils import command_executer 12 13COMPILERS = ['gcc', 'clang'] 14 15COMPILER_STRINGS = {'gcc': 'GNU C', 'clang': 'clang version'} 16 17ERR_NO_DEBUG_INFO = 1024 18 19 20def 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 28def 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 37def FindAllFiles(root_dir): 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, _, 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, _, 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 58def 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 85def 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).' % (filename, 103 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.' % (bad_file.rstrip(), 125 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 136def 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) 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('\n\nUnable to verify the following files (no debug info in ' 226 'them):\n') 227 for f in unknown_files: 228 print(f) 229 return 1 230 231 232if __name__ == '__main__': 233 sys.exit(Main(sys.argv[1:])) 234