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
8import glob
9import logging
10import optparse
11import os
12import shutil
13import subprocess
14import 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
29def _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
41def _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
60def _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
86def 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
110def _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
144if '__main__' == __name__:
145  logging.basicConfig(level=logging.INFO)
146  sys.exit(main(_ParseOptions()))
147