1 #!/usr/bin/env python 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """A utility script to help building Syzygy-instrumented Chrome binaries.""" 7 8 import glob 9 import logging 10 import optparse 11 import os 12 import shutil 13 import subprocess 14 import sys 15 16 17 # The default directory containing the Syzygy toolchain. 18 _DEFAULT_SYZYGY_DIR = os.path.abspath(os.path.join( 19 os.path.dirname(__file__), '../../../..', 20 'third_party/syzygy/binaries/exe/')) 21 22 # Basenames of various tools. 23 _INSTRUMENT_EXE = 'instrument.exe' 24 _GENFILTER_EXE = 'genfilter.exe' 25 _ASAN_AGENT_DLL = 'asan_rtl.dll' 26 27 # Default agents for known modes. 28 _DEFAULT_AGENT_DLLS = { 'asan': _ASAN_AGENT_DLL } 29 30 _LOGGER = logging.getLogger() 31 32 33 def _Shell(*cmd, **kw): 34 """Shells out to "cmd". Returns a tuple of cmd's stdout, stderr.""" 35 _LOGGER.info('Running command "%s".', cmd) 36 prog = subprocess.Popen(cmd, **kw) 37 38 stdout, stderr = prog.communicate() 39 if prog.returncode != 0: 40 raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode)) 41 42 return stdout, stderr 43 44 45 def _CompileFilter(syzygy_dir, executable, symbol, dst_dir, filter_file): 46 """Compiles the provided filter writing the compiled filter file to 47 dst_dir. Returns the absolute path of the compiled filter. 48 """ 49 output_filter_file = os.path.abspath(os.path.join( 50 dst_dir, os.path.basename(filter_file) + '.json')) 51 cmd = [os.path.abspath(os.path.join(syzygy_dir, _GENFILTER_EXE)), 52 '--action=compile', 53 '--input-image=%s' % executable, 54 '--input-pdb=%s' % symbol, 55 '--output-file=%s' % output_filter_file, 56 '--overwrite', 57 os.path.abspath(filter_file)] 58 59 _Shell(*cmd) 60 if not os.path.exists(output_filter_file): 61 raise RuntimeError('Compiled filter file missing: %s' % output_filter_file) 62 return output_filter_file 63 64 65 def _InstrumentBinary(syzygy_dir, mode, executable, symbol, dst_dir, 66 filter_file): 67 """Instruments the executable found in input_dir, and writes the resultant 68 instrumented executable and symbol files to dst_dir. 69 """ 70 cmd = [os.path.abspath(os.path.join(syzygy_dir, _INSTRUMENT_EXE)), 71 '--overwrite', 72 '--mode=%s' % mode, 73 '--debug-friendly', 74 '--input-image=%s' % executable, 75 '--input-pdb=%s' % symbol, 76 '--output-image=%s' % os.path.abspath( 77 os.path.join(dst_dir, os.path.basename(executable))), 78 '--output-pdb=%s' % os.path.abspath( 79 os.path.join(dst_dir, os.path.basename(symbol)))] 80 81 # If a filter was specified then pass it on to the instrumenter. 82 if filter_file: 83 cmd.append('--filter=%s' % os.path.abspath(filter_file)) 84 85 return _Shell(*cmd) 86 87 88 def _CopyAgentDLL(agent_dll, destination_dir): 89 """Copy the agent DLL and PDB to the destination directory.""" 90 dirname, agent_name = os.path.split(agent_dll); 91 agent_dst_name = os.path.join(destination_dir, agent_name); 92 shutil.copyfile(agent_dll, agent_dst_name) 93 94 # Search for the corresponding PDB file. We use this approach because 95 # the naming convention for PDBs has changed recently (from 'foo.pdb' 96 # to 'foo.dll.pdb') and we want to support both conventions during the 97 # transition. 98 agent_pdbs = glob.glob(os.path.splitext(agent_dll)[0] + '*.pdb') 99 if len(agent_pdbs) != 1: 100 raise RuntimeError('Failed to locate PDB file for %s' % agent_name) 101 agent_pdb = agent_pdbs[0] 102 agent_dst_pdb = os.path.join(destination_dir, os.path.split(agent_pdb)[1]) 103 shutil.copyfile(agent_pdb, agent_dst_pdb) 104 105 106 def main(options): 107 # Make sure the destination directory exists. 108 if not os.path.isdir(options.destination_dir): 109 _LOGGER.info('Creating destination directory "%s".', 110 options.destination_dir) 111 os.makedirs(options.destination_dir) 112 113 # Compile the filter if one was provided. 114 filter_file = None 115 if options.filter: 116 filter_file = _CompileFilter(options.syzygy_dir, 117 options.input_executable, 118 options.input_symbol, 119 options.destination_dir, 120 options.filter) 121 122 # Instruments the binaries into the destination directory. 123 _InstrumentBinary(options.syzygy_dir, 124 options.mode, 125 options.input_executable, 126 options.input_symbol, 127 options.destination_dir, 128 filter_file) 129 130 # Copy the agent DLL and PDB to the destination directory. 131 _CopyAgentDLL(options.agent_dll, options.destination_dir); 132 133 134 def _ParseOptions(): 135 option_parser = optparse.OptionParser() 136 option_parser.add_option('--input_executable', 137 help='The path to the input executable.') 138 option_parser.add_option('--input_symbol', 139 help='The path to the input symbol file.') 140 option_parser.add_option('--mode', 141 help='Specifies which instrumentation mode is to be used.') 142 option_parser.add_option('--agent_dll', 143 help='The agent DLL used by this instrumentation. If not specified a ' 144 'default will be searched for.') 145 option_parser.add_option('--syzygy-dir', default=_DEFAULT_SYZYGY_DIR, 146 help='Instrumenter executable to use, defaults to "%default".') 147 option_parser.add_option('-d', '--destination_dir', 148 help='Destination directory for instrumented files.') 149 option_parser.add_option('--filter', 150 help='An optional filter. This will be compiled and passed to the ' 151 'instrumentation executable.') 152 options, args = option_parser.parse_args() 153 154 if not options.mode: 155 option_parser.error('You must provide an instrumentation mode.') 156 if not options.input_executable: 157 option_parser.error('You must provide an input executable.') 158 if not options.input_symbol: 159 option_parser.error('You must provide an input symbol file.') 160 if not options.destination_dir: 161 option_parser.error('You must provide a destination directory.') 162 163 if not options.agent_dll: 164 if not options.mode in _DEFAULT_AGENT_DLLS: 165 option_parser.error('No known default agent DLL for mode "%s".' % 166 options.mode) 167 options.agent_dll = os.path.abspath(os.path.join(options.syzygy_dir, 168 _DEFAULT_AGENT_DLLS[options.mode])) 169 _LOGGER.info('Using default agent DLL: %s' % options.agent_dll) 170 171 return options 172 173 174 if '__main__' == __name__: 175 logging.basicConfig(level=logging.INFO) 176 sys.exit(main(_ParseOptions())) 177