| #!/usr/bin/env python | |
| # | |
| # Copyright 2018 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """From several dumps of reached offsets, create the list of reached symbols.""" | |
| import argparse | |
| import logging | |
| import os | |
| import struct | |
| import sys | |
| _SRC_PATH = os.path.abspath( | |
| os.path.join(os.path.dirname(__file__), os.pardir, os.pardir) | |
| ) | |
| path = os.path.join(_SRC_PATH, 'tools', 'cygprofile') | |
| sys.path.append(path) | |
| import process_profiles | |
| SIZEOF_INT = 4 | |
| BITS_IN_INT = SIZEOF_INT * 8 | |
| # This is the number of bytes represented in a dump by one bit. | |
| BYTES_GRANULARITY = 4 | |
| def _DumpToOffsets(filename): | |
| """From a dump, returns a list of offsets in it. | |
| Args: | |
| filename: (str) Dump filename. | |
| Returns: | |
| ([int]) offsets in the dump, that is relative to .text start. | |
| """ | |
| bitfield = None | |
| offsets = [] | |
| with open(filename, 'r') as f: | |
| bitfield = f.read() | |
| assert len(bitfield) % SIZEOF_INT == 0 | |
| count = len(bitfield) / SIZEOF_INT | |
| for i in xrange(count): | |
| entry = struct.unpack_from('<I', bitfield, offset=i * SIZEOF_INT)[0] | |
| for bit in range(BITS_IN_INT): | |
| if entry & (1 << bit): | |
| offsets.append((i * BITS_IN_INT + bit) * BYTES_GRANULARITY) | |
| return offsets | |
| def _ReachedSymbols(offsets, offset_to_symbol): | |
| """Returns a list of reached symbols from offsets in .text. | |
| Args: | |
| offsets: ([int]) List of reached offsets. | |
| offset_to_symbol: [symbol_extractor.SymbolInfo or None] as returned by | |
| |SymbolOffsetProcessor.GetDumpOffsetToSymbolInfo()| | |
| Returns: | |
| ([symbol_extractor.SymbolInfo]) | |
| """ | |
| symbol_infos = set() | |
| missing = 0 | |
| for offset in offsets: | |
| # |offset_to_symbol| has one entry per BYTES_GRANULARITY bytes. | |
| index = offset / BYTES_GRANULARITY | |
| if index > len(offset_to_symbol): | |
| missing += 1 | |
| continue | |
| symbol_infos.add(offset_to_symbol[index]) | |
| if missing: | |
| logging.warning('Couldn\'t match %d symbols', missing) | |
| return symbol_infos | |
| def _CreateArgumentParser(): | |
| """Returns an ArgumentParser.""" | |
| parser = argparse.ArgumentParser(description='Outputs reached symbols') | |
| parser.add_argument( | |
| '--build-dir', type=str, help='Path to the build directory', required=True | |
| ) | |
| parser.add_argument( | |
| '--dumps', | |
| type=str, | |
| help='A comma-separated list of files with instrumentation dumps', | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| '--dumps-dir', | |
| type=str, | |
| help='Directory name withreached code dumps', | |
| required=False, | |
| ) | |
| parser.add_argument( | |
| '--library-name', | |
| default='libchrome.so', | |
| help=( | |
| 'Chrome shared library name (usually libchrome.so or libmonochrome.so' | |
| ), | |
| ) | |
| parser.add_argument('--output', required=True, help='Output filename') | |
| return parser | |
| def main(): | |
| logging.basicConfig(level=logging.INFO) | |
| parser = _CreateArgumentParser() | |
| args = parser.parse_args() | |
| dumps = [] | |
| if args.dumps: | |
| dumps.extend(args.dumps.split(',')) | |
| elif args.dumps_dir: | |
| for file_name in os.listdir(args.dumps_dir): | |
| dumps.append(os.path.join(args.dumps_dir, file_name)) | |
| else: | |
| logging.error('Either --dumps or --dumps-dir must be provided') | |
| parser.print_help() | |
| return 1 | |
| logging.info('Parsing dumps') | |
| offsets = set() | |
| for dump_filename in dumps: | |
| offsets |= set(_DumpToOffsets(dump_filename)) | |
| logging.info('Found %d reached locations', len(offsets)) | |
| library_path = os.path.join( | |
| args.build_dir, 'lib.unstripped', args.library_name | |
| ) | |
| processor = process_profiles.SymbolOffsetProcessor(library_path) | |
| logging.info('Finding Symbols') | |
| offset_to_symbol = processor.GetDumpOffsetToSymbolInfo() | |
| reached_symbol_infos = _ReachedSymbols(offsets, offset_to_symbol) | |
| reached_symbol_infos.remove(None) | |
| with open(args.output, 'w') as f: | |
| for s in reached_symbol_infos: | |
| f.write('%s\n' % s.name) | |
| # Print some stats. | |
| reached_size = sum(s.size for s in reached_symbol_infos) | |
| logging.info('Total reached size = {}'.format(reached_size)) | |
| all_symbol_infos = set() | |
| for i in offset_to_symbol: | |
| if i is not None: | |
| all_symbol_infos.add(i) | |
| total_size = sum(s.size for s in all_symbol_infos) | |
| logging.info('Total size of known symbols = {}'.format(total_size)) | |
| coverage_percent = float(reached_size) / total_size * 100 | |
| logging.info('Coverage: {0:.2f}%'.format(coverage_percent)) | |
| if __name__ == '__main__': | |
| main() | |
| sys.exit(0) |