Home | History | Annotate | Download | only in win
      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 
     26 _LOGGER = logging.getLogger()
     27 
     28 
     29 def _Shell(*cmd, **kw):
     30   """Shells out to "cmd". Returns a tuple of cmd's stdout, stderr."""
     31   _LOGGER.info('Running command "%s".', cmd)
     32   prog = subprocess.Popen(cmd, **kw)
     33 
     34   stdout, stderr = prog.communicate()
     35   if prog.returncode != 0:
     36     raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
     37 
     38   return stdout, stderr
     39 
     40 
     41 def _CompileFilter(syzygy_dir, executable, symbol, filter_file,
     42                    output_filter_file):
     43   """Compiles the provided filter writing the compiled filter file to
     44   output_filter_file.
     45   """
     46   cmd = [os.path.abspath(os.path.join(syzygy_dir, _GENFILTER_EXE)),
     47          '--action=compile',
     48          '--input-image=%s' % executable,
     49          '--input-pdb=%s' % symbol,
     50          '--output-file=%s' % output_filter_file,
     51          '--overwrite',
     52          os.path.abspath(filter_file)]
     53 
     54   _Shell(*cmd)
     55   if not os.path.exists(output_filter_file):
     56     raise RuntimeError('Compiled filter file missing: %s' % output_filter_file)
     57   return
     58 
     59 
     60 def _InstrumentBinary(syzygy_dir, mode, executable, symbol, dst_dir,
     61                       filter_file):
     62   """Instruments the executable found in input_dir, and writes the resultant
     63   instrumented executable and symbol files to dst_dir.
     64   """
     65   cmd = [os.path.abspath(os.path.join(syzygy_dir, _INSTRUMENT_EXE)),
     66          '--overwrite',
     67          '--mode=%s' % mode,
     68          '--debug-friendly',
     69          '--input-image=%s' % executable,
     70          '--input-pdb=%s' % symbol,
     71          '--output-image=%s' % os.path.abspath(
     72              os.path.join(dst_dir, os.path.basename(executable))),
     73          '--output-pdb=%s' % os.path.abspath(
     74              os.path.join(dst_dir, os.path.basename(symbol)))]
     75 
     76   if mode == "asan":
     77     cmd.append('--no-augment-pdb')
     78 
     79   # If a filter was specified then pass it on to the instrumenter.
     80   if filter_file:
     81     cmd.append('--filter=%s' % os.path.abspath(filter_file))
     82 
     83   return _Shell(*cmd)
     84 
     85 
     86 def main(options):
     87   # Make sure the destination directory exists.
     88   if not os.path.isdir(options.destination_dir):
     89     _LOGGER.info('Creating destination directory "%s".',
     90                  options.destination_dir)
     91     os.makedirs(options.destination_dir)
     92 
     93   # Compile the filter if one was provided.
     94   if options.filter:
     95     _CompileFilter(options.syzygy_dir,
     96                    options.input_executable,
     97                    options.input_symbol,
     98                    options.filter,
     99                    options.output_filter_file)
    100 
    101   # Instruments the binaries into the destination directory.
    102   _InstrumentBinary(options.syzygy_dir,
    103                     options.mode,
    104                     options.input_executable,
    105                     options.input_symbol,
    106                     options.destination_dir,
    107                     options.output_filter_file)
    108 
    109 
    110 def _ParseOptions():
    111   option_parser = optparse.OptionParser()
    112   option_parser.add_option('--input_executable',
    113       help='The path to the input executable.')
    114   option_parser.add_option('--input_symbol',
    115       help='The path to the input symbol file.')
    116   option_parser.add_option('--mode',
    117       help='Specifies which instrumentation mode is to be used.')
    118   option_parser.add_option('--syzygy-dir', default=_DEFAULT_SYZYGY_DIR,
    119       help='Instrumenter executable to use, defaults to "%default".')
    120   option_parser.add_option('-d', '--destination_dir',
    121       help='Destination directory for instrumented files.')
    122   option_parser.add_option('--filter',
    123       help='An optional filter. This will be compiled and passed to the '
    124            'instrumentation executable.')
    125   option_parser.add_option('--output-filter-file',
    126       help='The path where the compiled filter will be written. This is '
    127            'required if --filter is specified.')
    128   options, args = option_parser.parse_args()
    129 
    130   if not options.mode:
    131     option_parser.error('You must provide an instrumentation mode.')
    132   if not options.input_executable:
    133     option_parser.error('You must provide an input executable.')
    134   if not options.input_symbol:
    135     option_parser.error('You must provide an input symbol file.')
    136   if not options.destination_dir:
    137     option_parser.error('You must provide a destination directory.')
    138   if options.filter and not options.output_filter_file:
    139     option_parser.error('You must provide a filter output file.')
    140 
    141   return options
    142 
    143 
    144 if '__main__' == __name__:
    145   logging.basicConfig(level=logging.INFO)
    146   sys.exit(main(_ParseOptions()))
    147