#!/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 with'
'reached 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)