| #!/usr/bin/env vpython |
| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Patch an orderfile. |
| |
| Starting with a list of symbols in a binary and an orderfile (ordered list of |
| symbols), matches the symbols in the orderfile and augments each symbol with |
| the symbols residing at the same address (due to having identical code). The |
| output is a list of symbols appropriate for the linker |
| option --symbol-ordering-file for lld. Note this is not usable with gold (which |
| uses section names to order the binary). |
| |
| Note: It is possible to have. |
| - Several symbols mapping to the same offset in the binary. |
| - Several offsets for a given symbol (because we strip the ".clone." and other |
| suffixes) |
| |
| The general pipeline is: |
| 1. Get the symbol infos (name, offset, size, section) from the binary |
| 2. Get the symbol names from the orderfile |
| 3. Find the orderfile symbol names in the symbols coming from the binary |
| 4. For each symbol found, get all the symbols at the same address |
| 5. Output them to an updated orderfile suitable lld |
| """ |
| |
| import argparse |
| import collections |
| import logging |
| import sys |
| |
| import cyglog_to_orderfile |
| import cygprofile_utils |
| import symbol_extractor |
| |
| # Suffixes for symbols. These are due to method splitting for inlining and |
| # method cloning for various reasons including constant propagation and |
| # inter-procedural optimization. |
| _SUFFIXES = ('.clone.', '.part.', '.isra.', '.constprop.') |
| |
| |
| def RemoveSuffixes(name): |
| """Strips method name suffixes from cloning and splitting. |
| |
| .clone. comes from cloning in -O3. |
| .part. comes from partial method splitting for inlining. |
| .isra. comes from inter-procedural optimizations. |
| .constprop. is cloning for constant propagation. |
| """ |
| for suffix in _SUFFIXES: |
| name = name.split(suffix)[0] |
| return name |
| |
| |
| def _UniqueGenerator(generator): |
| """Converts a generator to skip yielding elements already seen. |
| |
| Example: |
| @_UniqueGenerator |
| def Foo(): |
| yield 1 |
| yield 2 |
| yield 1 |
| yield 3 |
| |
| Foo() yields 1,2,3. |
| """ |
| def _FilteringFunction(*args, **kwargs): |
| returned = set() |
| for item in generator(*args, **kwargs): |
| if item in returned: |
| continue |
| returned.add(item) |
| yield item |
| |
| return _FilteringFunction |
| |
| |
| def _GroupSymbolsByOffset(binary_filename): |
| """Produce a map symbol name -> all symbol names at same offset. |
| |
| Suffixes are stripped. |
| """ |
| symbol_infos = [ |
| s._replace(name=RemoveSuffixes(s.name)) |
| for s in symbol_extractor.SymbolInfosFromBinary(binary_filename)] |
| offset_map = symbol_extractor.GroupSymbolInfosByOffset(symbol_infos) |
| missing_offsets = 0 |
| sym_to_matching = {} |
| for sym in symbol_infos: |
| if sym.offset not in offset_map: |
| missing_offsets += 1 |
| continue |
| matching = [s.name for s in offset_map[sym.offset]] |
| assert sym.name in matching |
| sym_to_matching[sym.name] = matching |
| return sym_to_matching |
| |
| |
| def _StripSuffixes(section_list): |
| """Remove all suffixes on items in a list of symbols.""" |
| return [RemoveSuffixes(section) for section in section_list] |
| |
| |
| @_UniqueGenerator |
| def ReadOrderfile(orderfile): |
| """Reads an orderfile and cleans up symbols. |
| |
| Args: |
| orderfile: The name of the orderfile. |
| |
| Yields: |
| Symbol names, cleaned and unique. |
| """ |
| with open(orderfile) as f: |
| for line in f.xreadlines(): |
| line = line.strip() |
| if line: |
| yield line |
| |
| |
| def GeneratePatchedOrderfile(unpatched_orderfile, native_lib_filename, |
| output_filename): |
| """Writes a patched orderfile. |
| |
| Args: |
| unpatched_orderfile: (str) Path to the unpatched orderfile. |
| native_lib_filename: (str) Path to the native library. |
| output_filename: (str) Path to the patched orderfile. |
| """ |
| symbol_to_matching = _GroupSymbolsByOffset(native_lib_filename) |
| profiled_symbols = ReadOrderfile(unpatched_orderfile) |
| missing_symbol_count = 0 |
| seen_symbols = set() |
| patched_symbols = [] |
| for sym in profiled_symbols: |
| if sym not in symbol_to_matching: |
| missing_symbol_count += 1 |
| continue |
| if sym in seen_symbols: |
| continue |
| for matching in symbol_to_matching[sym]: |
| patched_symbols.append(matching) |
| seen_symbols.add(matching) |
| assert sym in seen_symbols |
| logging.warning('missing symbol count = %d', missing_symbol_count) |
| |
| with open(output_filename, 'w') as f: |
| # Make sure the anchor functions are located in the right place, here and |
| # after everything else. |
| # See the comment in //base/android/library_loader/anchor_functions.cc. |
| # |
| # __cxx_global_var_init is one of the largest symbols (~38kB as of May |
| # 2018), called extremely early, and not instrumented. |
| for first_section in ('dummy_function_start_of_ordered_text', |
| '__cxx_global_var_init'): |
| f.write(first_section + '\n') |
| |
| for sym in patched_symbols: |
| f.write(sym + '\n') |
| |
| f.write('dummy_function_end_of_ordered_text') |
| |
| |
| def _CreateArgumentParser(): |
| """Creates and returns the argument parser.""" |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--target-arch', action='store', default='arm', |
| choices=['arm', 'arm64', 'x86', 'x86_64', 'x64', 'mips'], |
| help='The target architecture for the library.') |
| parser.add_argument('--unpatched-orderfile', required=True, |
| help='Path to the unpatched orderfile') |
| parser.add_argument('--native-library', required=True, |
| help='Path to the native library') |
| parser.add_argument('--output-file', required=True, help='Output filename') |
| return parser |
| |
| |
| def main(): |
| parser = _CreateArgumentParser() |
| options = parser.parse_args() |
| symbol_extractor.SetArchitecture(options.target_arch) |
| GeneratePatchedOrderfile(options.unpatched_orderfile, options.native_library, |
| options.output_file) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| logging.basicConfig(level=logging.INFO) |
| sys.exit(main()) |