12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#!/usr/bin/env python
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# found in the LICENSE file.
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)"""A utility script to help building Syzygy-instrumented Chrome binaries."""
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
8c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import glob
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import logging
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import optparse
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import os
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import shutil
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import subprocess
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import sys
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# The default directory containing the Syzygy toolchain.
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)_DEFAULT_SYZYGY_DIR = os.path.abspath(os.path.join(
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    os.path.dirname(__file__), '../../../..',
202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    'third_party/syzygy/binaries/exe/'))
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Basenames of various tools.
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)_INSTRUMENT_EXE = 'instrument.exe'
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)_GENFILTER_EXE = 'genfilter.exe'
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)_LOGGER = logging.getLogger()
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _Shell(*cmd, **kw):
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """Shells out to "cmd". Returns a tuple of cmd's stdout, stderr."""
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  _LOGGER.info('Running command "%s".', cmd)
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  prog = subprocess.Popen(cmd, **kw)
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  stdout, stderr = prog.communicate()
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if prog.returncode != 0:
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    raise RuntimeError('Command "%s" returned %d.' % (cmd, prog.returncode))
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return stdout, stderr
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
41424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)def _CompileFilter(syzygy_dir, executable, symbol, filter_file,
42424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                   output_filter_file):
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """Compiles the provided filter writing the compiled filter file to
44424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  output_filter_file.
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  cmd = [os.path.abspath(os.path.join(syzygy_dir, _GENFILTER_EXE)),
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--action=compile',
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--input-image=%s' % executable,
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--input-pdb=%s' % symbol,
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--output-file=%s' % output_filter_file,
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--overwrite',
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         os.path.abspath(filter_file)]
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  _Shell(*cmd)
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not os.path.exists(output_filter_file):
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    raise RuntimeError('Compiled filter file missing: %s' % output_filter_file)
57424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  return
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _InstrumentBinary(syzygy_dir, mode, executable, symbol, dst_dir,
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                      filter_file):
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """Instruments the executable found in input_dir, and writes the resultant
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  instrumented executable and symbol files to dst_dir.
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  """
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  cmd = [os.path.abspath(os.path.join(syzygy_dir, _INSTRUMENT_EXE)),
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--overwrite',
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--mode=%s' % mode,
68c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)         '--debug-friendly',
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--input-image=%s' % executable,
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--input-pdb=%s' % symbol,
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--output-image=%s' % os.path.abspath(
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)             os.path.join(dst_dir, os.path.basename(executable))),
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)         '--output-pdb=%s' % os.path.abspath(
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)             os.path.join(dst_dir, os.path.basename(symbol)))]
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
763551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)  if mode == "asan":
773551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    cmd.append('--no-augment-pdb')
783551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # If a filter was specified then pass it on to the instrumenter.
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if filter_file:
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    cmd.append('--filter=%s' % os.path.abspath(filter_file))
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return _Shell(*cmd)
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def main(options):
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Make sure the destination directory exists.
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not os.path.isdir(options.destination_dir):
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    _LOGGER.info('Creating destination directory "%s".',
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                 options.destination_dir)
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    os.makedirs(options.destination_dir)
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Compile the filter if one was provided.
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if options.filter:
95424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    _CompileFilter(options.syzygy_dir,
96424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                   options.input_executable,
97424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                   options.input_symbol,
98424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                   options.filter,
99424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                   options.output_filter_file)
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  # Instruments the binaries into the destination directory.
1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  _InstrumentBinary(options.syzygy_dir,
1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    options.mode,
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    options.input_executable,
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    options.input_symbol,
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    options.destination_dir,
107424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                    options.output_filter_file)
1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def _ParseOptions():
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser = optparse.OptionParser()
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser.add_option('--input_executable',
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='The path to the input executable.')
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser.add_option('--input_symbol',
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='The path to the input symbol file.')
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser.add_option('--mode',
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='Specifies which instrumentation mode is to be used.')
1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser.add_option('--syzygy-dir', default=_DEFAULT_SYZYGY_DIR,
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='Instrumenter executable to use, defaults to "%default".')
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser.add_option('-d', '--destination_dir',
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='Destination directory for instrumented files.')
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  option_parser.add_option('--filter',
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      help='An optional filter. This will be compiled and passed to the '
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)           'instrumentation executable.')
125424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  option_parser.add_option('--output-filter-file',
126424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      help='The path where the compiled filter will be written. This is '
127424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)           'required if --filter is specified.')
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  options, args = option_parser.parse_args()
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not options.mode:
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    option_parser.error('You must provide an instrumentation mode.')
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not options.input_executable:
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    option_parser.error('You must provide an input executable.')
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not options.input_symbol:
1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    option_parser.error('You must provide an input symbol file.')
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if not options.destination_dir:
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    option_parser.error('You must provide a destination directory.')
138424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  if options.filter and not options.output_filter_file:
139424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    option_parser.error('You must provide a filter output file.')
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return options
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)if '__main__' == __name__:
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logging.basicConfig(level=logging.INFO)
1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  sys.exit(main(_ParseOptions()))
147