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 _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