예제 #1
0
def Run(args, on_config_error):
    # Up-front check for faster error-checking.
    for path in args.inputs:
        if not path.endswith('.size') and not path.endswith('.sizediff'):
            on_config_error('All inputs must end with ".size" or ".sizediff"')

    size_infos = []
    for path in args.inputs:
        if path.endswith('.sizediff'):
            size_infos.extend(archive.LoadAndPostProcessDeltaSizeInfo(path))
        else:
            size_infos.append(archive.LoadAndPostProcessSizeInfo(path))
    output_directory_finder = path_util.OutputDirectoryFinder(
        value=args.output_directory,
        any_path_within_output_directory=args.inputs[0])
    linker_name = size_infos[-1].build_config.get(
        models.BUILD_CONFIG_LINKER_NAME)
    tool_prefix_finder = path_util.ToolPrefixFinder(
        value=args.tool_prefix,
        output_directory=output_directory_finder.Tentative(),
        linker_name=linker_name)
    session = _Session(size_infos, output_directory_finder, tool_prefix_finder)

    if args.query:
        logging.info('Running query from command-line.')
        session.Eval(args.query)
    else:
        logging.info('Entering interactive console.')
        session.GoInteractive()
예제 #2
0
    def _ElfPathAndToolPrefixForSymbol(self, size_info, elf_path):
        tool_prefix = self._tool_prefix_finder.Tentative()
        orig_tool_prefix = size_info.metadata.get(models.METADATA_TOOL_PREFIX)
        if orig_tool_prefix:
            orig_tool_prefix = path_util.FromSrcRootRelative(orig_tool_prefix)
            if os.path.exists(path_util.GetObjDumpPath(orig_tool_prefix)):
                tool_prefix = orig_tool_prefix

        # TODO(agrieve): Would be even better to use objdump --info to check that
        #     the toolchain is for the correct architecture.
        assert tool_prefix is not None, (
            'Could not determine --tool-prefix. Possible fixes include setting '
            '--tool-prefix, or setting --output-directory')

        def build_id_matches(elf_path):
            found_build_id = archive.BuildIdFromElf(elf_path, tool_prefix)
            expected_build_id = size_info.metadata.get(
                models.METADATA_ELF_BUILD_ID)
            return found_build_id == expected_build_id

        filename = size_info.metadata.get(models.METADATA_ELF_FILENAME)
        paths_to_try = []
        if elf_path:
            paths_to_try.append(elf_path)
        else:
            auto_output_directory_finders = [
                path_util.OutputDirectoryFinder(
                    any_path_within_output_directory=s.size_path)
                for s in self._size_infos
            ] + [self._output_directory_finder]
            for output_directory_finder in auto_output_directory_finders:
                output_dir = output_directory_finder.Tentative()
                if output_dir:
                    # Local build: File is located in output directory.
                    paths_to_try.append(
                        os.path.normpath(os.path.join(output_dir, filename)))
                # Downloaded build: File is located beside .size file.
                paths_to_try.append(
                    os.path.normpath(
                        os.path.join(os.path.dirname(size_info.size_path),
                                     os.path.basename(filename))))

        paths_to_try = [p for p in paths_to_try if os.path.exists(p)]

        for i, elf_path in enumerate(paths_to_try):
            if build_id_matches(elf_path):
                return elf_path, tool_prefix

            # Show an error only once all paths are tried.
            if i + 1 == len(paths_to_try):
                assert False, 'Build ID does not match for %s' % elf_path

        assert False, (
            'Could not locate ELF file. If binary was built locally, ensure '
            '--output-directory is set. If output directory is unavailable, '
            'ensure {} is located beside {}, or pass its path explicitly using '
            'elf_path=').format(os.path.basename(filename),
                                size_info.size_path)
예제 #3
0
    def _DisassembleFunc(self,
                         symbol,
                         elf_path=None,
                         use_pager=None,
                         to_file=None):
        """Shows objdump disassembly for the given symbol.

    Args:
      symbol: Must be a .text symbol and not a SymbolGroup.
      elf_path: Path to the executable containing the symbol. Required only
          when auto-detection fails.
    """
        assert not symbol.IsGroup()
        assert symbol.address and symbol.section_name == models.SECTION_TEXT
        assert not symbol.IsDelta(), (
            'Cannot disasseble a Diff\'ed symbol. Try '
            'passing .before_symbol or .after_symbol.')
        size_info = self._SizeInfoForSymbol(symbol)
        container = symbol.container
        tool_prefix = self._ToolPrefixForSymbol(size_info)
        elf_path = self._ElfPathForSymbol(size_info, container, tool_prefix,
                                          elf_path)
        # Always use Android NDK's objdump because llvm-objdump does not print
        # the target of jump instructions, which is really useful.
        output_directory_finder = self._output_directory_finder
        if not output_directory_finder.Tentative():
            output_directory_finder = path_util.OutputDirectoryFinder(
                any_path_within_output_directory=elf_path)
        if output_directory_finder.Tentative():
            tool_prefix = path_util.ToolPrefixFinder(
                output_directory=output_directory_finder.Finalized(),
                linker_name='ld').Finalized()

        args = [
            path_util.GetObjDumpPath(tool_prefix),
            '--disassemble',
            '--source',
            '--line-numbers',
            '--demangle',
            '--start-address=0x%x' % symbol.address,
            '--stop-address=0x%x' % symbol.end_address,
            elf_path,
        ]

        # pylint: disable=unexpected-keyword-arg
        proc = subprocess.Popen(args, stdout=subprocess.PIPE, encoding='utf-8')
        lines = itertools.chain(('Showing disassembly for %r' % symbol,
                                 'Command: %s' % ' '.join(args)),
                                (l.rstrip() for l in proc.stdout))
        _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
        proc.kill()
예제 #4
0
def DeduceMainPaths(args, parser):
  """Computes main paths based on input, and deduces them if needed."""
  apk_path = args.apk_file
  elf_path = args.elf_file
  map_path = args.map_file
  any_input = apk_path or elf_path or map_path
  if not any_input:
    parser.error('Must pass at least one of --apk-file, --elf-file, --map-file')
  output_directory_finder = path_util.OutputDirectoryFinder(
      value=args.output_directory,
      any_path_within_output_directory=any_input)

  apk_so_path = None
  if apk_path:
    with zipfile.ZipFile(apk_path) as z:
      lib_infos = [f for f in z.infolist()
                   if f.filename.endswith('.so') and f.file_size > 0]
    assert lib_infos, 'APK has no .so files.'
    # TODO(agrieve): Add support for multiple .so files, and take into account
    #     secondary architectures.
    apk_so_path = max(lib_infos, key=lambda x:x.file_size).filename
    logging.debug('Sub-apk path=%s', apk_so_path)
    if not elf_path and output_directory_finder.Tentative():
      elf_path = os.path.join(
          output_directory_finder.Tentative(), 'lib.unstripped',
          os.path.basename(apk_so_path.replace('crazy.', '')))
      logging.debug('Detected --elf-file=%s', elf_path)

  if map_path:
    if not map_path.endswith('.map') and not map_path.endswith('.map.gz'):
      parser.error('Expected --map-file to end with .map or .map.gz')
  else:
    map_path = elf_path + '.map'
    if not os.path.exists(map_path):
      map_path += '.gz'
    if not os.path.exists(map_path):
      parser.error('Could not find .map(.gz)? file. Ensure you have built with '
                   'is_official_build=true, or use --map-file to point me a '
                   'linker map file.')

  linker_name = _DetectLinkerName(map_path)
  tool_prefix_finder = path_util.ToolPrefixFinder(
      value=args.tool_prefix,
      output_directory_finder=output_directory_finder,
      linker_name=linker_name)
  tool_prefix = tool_prefix_finder.Finalized()
  output_directory = None
  if not args.no_source_paths:
    output_directory = output_directory_finder.Finalized()
  return (output_directory, tool_prefix, apk_path, apk_so_path, elf_path,
          map_path)
예제 #5
0
    def _ElfPathForSymbol(self, size_info, container, tool_prefix, elf_path):
        def build_id_matches(elf_path):
            found_build_id = readelf.BuildIdFromElf(elf_path, tool_prefix)
            expected_build_id = container.metadata.get(
                models.METADATA_ELF_BUILD_ID)
            return found_build_id == expected_build_id

        filename = container.metadata.get(models.METADATA_ELF_FILENAME)
        paths_to_try = []
        if elf_path:
            paths_to_try.append(elf_path)
        else:
            auto_output_directory_finders = [
                path_util.OutputDirectoryFinder(
                    any_path_within_output_directory=s.size_path)
                for s in self._size_infos
            ] + [self._output_directory_finder]
            for output_directory_finder in auto_output_directory_finders:
                output_dir = output_directory_finder.Tentative()
                if output_dir:
                    # Local build: File is located in output directory.
                    paths_to_try.append(
                        os.path.normpath(os.path.join(output_dir, filename)))
                # Downloaded build: File is located beside .size file.
                paths_to_try.append(
                    os.path.normpath(
                        os.path.join(os.path.dirname(size_info.size_path),
                                     os.path.basename(filename))))

        paths_to_try = [p for p in paths_to_try if os.path.exists(p)]

        for i, path in enumerate(paths_to_try):
            if build_id_matches(path):
                return path

            # Show an error only once all paths are tried.
            if i + 1 == len(paths_to_try):
                assert False, 'Build ID does not match for %s' % elf_path

        assert False, (
            'Could not locate ELF file. If binary was built locally, ensure '
            '--output-directory is set. If output directory is unavailable, '
            'ensure {} is located beside {}, or pass its path explicitly using '
            'elf_path=').format(os.path.basename(filename),
                                size_info.size_path)
        return None
예제 #6
0
def Run(args, parser):
    for path in args.inputs:
        if not path.endswith('.size'):
            parser.error('All inputs must end with ".size"')

    size_infos = [archive.LoadAndPostProcessSizeInfo(p) for p in args.inputs]
    output_directory_finder = path_util.OutputDirectoryFinder(
        value=args.output_directory,
        any_path_within_output_directory=args.inputs[0])
    tool_prefix_finder = path_util.ToolPrefixFinder(
        value=args.tool_prefix,
        output_directory_finder=output_directory_finder)
    session = _Session(size_infos, output_directory_finder, tool_prefix_finder)

    if args.query:
        logging.info('Running query from command-line.')
        session.Eval(args.query)
    else:
        logging.info('Entering interactive console.')
        session.GoInteractive()
def Run(args, on_config_error):
    for path in args.inputs:
        if not path.endswith('.size'):
            on_config_error('All inputs must end with ".size"')

    size_infos = [archive.LoadAndPostProcessSizeInfo(p) for p in args.inputs]
    output_directory_finder = path_util.OutputDirectoryFinder(
        value=args.output_directory,
        any_path_within_output_directory=args.inputs[0])
    linker_name = size_infos[-1].metadata.get(models.METADATA_LINKER_NAME)
    tool_prefix_finder = path_util.ToolPrefixFinder(
        value=args.tool_prefix,
        output_directory_finder=output_directory_finder,
        linker_name=linker_name)
    session = _Session(size_infos, output_directory_finder, tool_prefix_finder)

    if args.query:
        logging.info('Running query from command-line.')
        session.Eval(args.query)
    else:
        logging.info('Entering interactive console.')
        session.GoInteractive()
예제 #8
0
def Run(args, on_config_error):
    # Up-front check for faster error-checking.
    for path in args.inputs:
        if not path.endswith('.size') and not path.endswith('.sizediff'):
            on_config_error('All inputs must end with ".size" or ".sizediff"')

    size_infos = []
    for path in args.inputs:
        if path.endswith('.sizediff'):
            size_infos.extend(archive.LoadAndPostProcessDeltaSizeInfo(path))
        else:
            size_infos.append(archive.LoadAndPostProcessSizeInfo(path))
    output_directory_finder = path_util.OutputDirectoryFinder(
        value=args.output_directory,
        any_path_within_output_directory=args.inputs[0])
    linker_name = size_infos[-1].build_config.get(
        models.BUILD_CONFIG_LINKER_NAME)
    tool_prefix_finder = path_util.ToolPrefixFinder(
        value=args.tool_prefix,
        output_directory=output_directory_finder.Tentative(),
        linker_name=linker_name)
    session = _Session(size_infos, output_directory_finder, tool_prefix_finder)

    if args.query:
        logging.info('Running query from command-line.')
        session.Eval(args.query)
    else:
        logging.info('Entering interactive console.')
        session.GoInteractive()

    # Exit without running GC, which can save multiple seconds due the large
    # number of objects created. It meants atexit and __del__ calls are not
    # made, but this shouldn't matter for console.
    sys.stdout.flush()
    sys.stderr.flush()
    os._exit(0)
예제 #9
0
    def _DisassembleFunc(self,
                         symbol,
                         elf_path=None,
                         use_pager=None,
                         to_file=None):
        """Shows objdump disassembly for the given symbol.

    Args:
      symbol: Must be a .text symbol and not a SymbolGroup.
      elf_path: Path to the executable containing the symbol. Required only
          when auto-detection fails.
    """
        assert not symbol.IsGroup()
        assert symbol.address and symbol.section_name == models.SECTION_TEXT
        assert not symbol.IsDelta(), (
            'Cannot disasseble a Diff\'ed symbol. Try '
            'passing .before_symbol or .after_symbol.')
        size_info = self._SizeInfoForSymbol(symbol)
        container = symbol.container
        tool_prefix = self._ToolPrefixForSymbol(size_info)
        elf_path = self._ElfPathForSymbol(size_info, container, tool_prefix,
                                          elf_path)
        # Always use Android NDK's objdump because llvm-objdump does not print
        # the target of jump instructions, which is really useful.
        output_directory_finder = self._output_directory_finder
        if not output_directory_finder.Tentative():
            output_directory_finder = path_util.OutputDirectoryFinder(
                any_path_within_output_directory=elf_path)
        if output_directory_finder.Tentative():
            tool_prefix = path_util.ToolPrefixFinder(
                output_directory=output_directory_finder.Finalized(),
                linker_name='ld').Finalized()
            # Running objdump from an output directory means that objdump can
            # interleave source file lines in the disassembly.
            objdump_pwd = output_directory_finder.Finalized()
        else:
            # Output directory is not set, so we cannot load tool_prefix from
            # build_vars.json, nor resolve the output directory-relative path stored
            # size_info.metadata.
            is_android = next(
                filter(None, (m.get(models.METADATA_APK_FILENAME)
                              for m in size_info.metadata)), None)
            arch = next(
                filter(None, (m.get(models.METADATA_ELF_ARCHITECTURE)
                              for m in size_info.metadata)), None)
            # Hardcode path for arm32.
            if is_android and arch == 'arm':
                tool_prefix = path_util.ANDROID_ARM_NDK_TOOL_PREFIX
            # If we do not know/guess the output directory, run from any directory 2
            # levels below src since it is better than a random cwd (because usually
            # source file paths are relative to an output directory two levels below
            # src and start with ../../).
            objdump_pwd = os.path.join(path_util.TOOLS_SRC_ROOT, 'tools',
                                       'binary_size')

        args = [
            os.path.relpath(path_util.GetObjDumpPath(tool_prefix),
                            objdump_pwd),
            '--disassemble',
            '--source',
            '--line-numbers',
            '--demangle',
            '--start-address=0x%x' % symbol.address,
            '--stop-address=0x%x' % symbol.end_address,
            os.path.relpath(elf_path, objdump_pwd),
        ]

        # pylint: disable=unexpected-keyword-arg
        proc = subprocess.Popen(args,
                                stdout=subprocess.PIPE,
                                encoding='utf-8',
                                cwd=objdump_pwd)
        lines = itertools.chain(('Showing disassembly for %r' % symbol,
                                 'Command: %s' % ' '.join(args)),
                                (l.rstrip() for l in proc.stdout))
        _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
        proc.kill()
예제 #10
0
def Run(args, parser):
  if not args.size_file.endswith('.size'):
    parser.error('size_file must end with .size')

  elf_path = args.elf_file
  map_path = args.map_file
  apk_path = args.apk_file
  pak_files = args.pak_file
  pak_info_file = args.pak_info_file
  any_input = apk_path or elf_path or map_path
  if not any_input:
    parser.error('Most pass at least one of --apk-file, --elf-file, --map-file')
  output_directory_finder = path_util.OutputDirectoryFinder(
      value=args.output_directory,
      any_path_within_output_directory=any_input)
  if apk_path:
    with zipfile.ZipFile(apk_path) as z:
      lib_infos = [f for f in z.infolist()
                   if f.filename.endswith('.so') and f.file_size > 0]
    assert lib_infos, 'APK has no .so files.'
    # TODO(agrieve): Add support for multiple .so files, and take into account
    #     secondary architectures.
    apk_so_path = max(lib_infos, key=lambda x:x.file_size).filename
    logging.debug('Sub-apk path=%s', apk_so_path)
    if not elf_path and output_directory_finder.Tentative():
      elf_path = os.path.join(
          output_directory_finder.Tentative(), 'lib.unstripped',
          os.path.basename(apk_so_path.replace('crazy.', '')))
      logging.debug('Detected --elf-file=%s', elf_path)

  if map_path:
    if not map_path.endswith('.map') and not map_path.endswith('.map.gz'):
      parser.error('Expected --map-file to end with .map or .map.gz')
  else:
    map_path = elf_path + '.map'
    if not os.path.exists(map_path):
      map_path += '.gz'
    if not os.path.exists(map_path):
      parser.error('Could not find .map(.gz)? file. Ensure you have built with '
                   'is_official_build=true, or use --map-file to point me a '
                   'linker map file.')

  linker_name = _DetectLinkerName(map_path)
  tool_prefix_finder = path_util.ToolPrefixFinder(
      value=args.tool_prefix,
      output_directory_finder=output_directory_finder,
      linker_name=linker_name)
  tool_prefix = tool_prefix_finder.Finalized()
  output_directory = None
  if not args.no_source_paths:
    output_directory = output_directory_finder.Finalized()

  metadata = CreateMetadata(map_path, elf_path, apk_path, tool_prefix,
                            output_directory)
  if apk_path and elf_path:
    # Extraction takes around 1 second, so do it in parallel.
    apk_elf_result = concurrent.ForkAndCall(
        _ElfInfoFromApk, (apk_path, apk_so_path, tool_prefix))

  section_sizes, raw_symbols = CreateSectionSizesAndSymbols(
      map_path, elf_path, tool_prefix, output_directory,
      track_string_literals=args.track_string_literals)
  if apk_path:
    AddApkInfo(section_sizes, raw_symbols, apk_path, output_directory,
               metadata, apk_elf_result)
  elif pak_files and pak_info_file:
    AddPakSymbolsFromFiles(
        section_sizes, raw_symbols, pak_files, pak_info_file)
  size_info = CreateSizeInfo(
      section_sizes, raw_symbols, metadata=metadata, normalize_names=False)

  if logging.getLogger().isEnabledFor(logging.INFO):
    for line in describe.DescribeSizeInfoCoverage(size_info):
      logging.info(line)
  logging.info('Recorded info for %d symbols', len(size_info.raw_symbols))
  logging.info('Recording metadata: \n  %s',
               '\n  '.join(describe.DescribeMetadata(size_info.metadata)))
  logging.info('Saving result to %s', args.size_file)
  file_format.SaveSizeInfo(size_info, args.size_file)
  size_in_mb = os.path.getsize(args.size_file) / 1024.0 / 1024.0
  logging.info('Done. File size is %.2fMiB.', size_in_mb)
예제 #11
0
def Run(args, parser):
    if not args.size_file.endswith('.size'):
        parser.error('size_file must end with .size')

    elf_path = args.elf_file
    map_path = args.map_file
    apk_path = args.apk_file
    any_input = apk_path or elf_path or map_path
    if not any_input:
        parser.error(
            'Most pass at least one of --apk-file, --elf-file, --map-file')
    output_directory_finder = path_util.OutputDirectoryFinder(
        value=args.output_directory,
        any_path_within_output_directory=any_input)
    if apk_path:
        with zipfile.ZipFile(apk_path) as z:
            lib_infos = [
                f for f in z.infolist()
                if f.filename.endswith('.so') and f.file_size > 0
            ]
        assert lib_infos, 'APK has no .so files.'
        # TODO(agrieve): Add support for multiple .so files, and take into account
        #     secondary architectures.
        apk_so_path = max(lib_infos, key=lambda x: x.file_size).filename
        logging.debug('Sub-apk path=%s', apk_so_path)
        if not elf_path and output_directory_finder.Tentative():
            elf_path = os.path.join(
                output_directory_finder.Tentative(), 'lib.unstripped',
                os.path.basename(apk_so_path.replace('crazy.', '')))
            logging.debug('Detected --elf-file=%s', elf_path)

    if map_path:
        if not map_path.endswith('.map') and not map_path.endswith('.map.gz'):
            parser.error('Expected --map-file to end with .map or .map.gz')
    else:
        map_path = elf_path + '.map'
        if not os.path.exists(map_path):
            map_path += '.gz'
        if not os.path.exists(map_path):
            parser.error(
                'Could not find .map(.gz)? file. Ensure you have built with '
                'is_official_build=true, or use --map-file to point me a '
                'linker map file.')

    linker_name = _DetectLinkerName(map_path)
    tool_prefix_finder = path_util.ToolPrefixFinder(
        value=args.tool_prefix,
        output_directory_finder=output_directory_finder,
        linker_name=linker_name)
    tool_prefix = tool_prefix_finder.Finalized()
    output_directory = None
    if not args.no_source_paths:
        output_directory = output_directory_finder.Finalized()

    metadata = CreateMetadata(map_path, elf_path, apk_path, tool_prefix,
                              output_directory)
    if apk_path and elf_path:
        # Extraction takes around 1 second, so do it in parallel.
        apk_elf_result = concurrent.ForkAndCall(
            _ElfInfoFromApk, (apk_path, apk_so_path, tool_prefix))

    size_info = CreateSizeInfo(
        map_path,
        elf_path,
        tool_prefix,
        output_directory,
        normalize_names=False,
        track_string_literals=args.track_string_literals)

    if metadata:
        size_info.metadata = metadata

        if apk_path:
            logging.debug('Extracting section sizes from .so within .apk')
            unstripped_section_sizes = size_info.section_sizes
            apk_build_id, size_info.section_sizes = apk_elf_result.get()
            assert apk_build_id == metadata[models.METADATA_ELF_BUILD_ID], (
                'BuildID for %s within %s did not match the one at %s' %
                (apk_so_path, apk_path, elf_path))

            packed_section_name = None
            architecture = metadata[models.METADATA_ELF_ARCHITECTURE]
            # Packing occurs enabled only arm32 & arm64.
            if architecture == 'arm':
                packed_section_name = '.rel.dyn'
            elif architecture == 'arm64':
                packed_section_name = '.rela.dyn'

            if packed_section_name:
                logging.debug('Recording size of unpacked relocations')
                if packed_section_name not in size_info.section_sizes:
                    logging.warning('Packed section not present: %s',
                                    packed_section_name)
                else:
                    size_info.section_sizes[
                        '%s (unpacked)' % packed_section_name] = (
                            unstripped_section_sizes.get(packed_section_name))

    logging.info('Recording metadata: \n  %s',
                 '\n  '.join(describe.DescribeMetadata(size_info.metadata)))
    logging.info('Saving result to %s', args.size_file)
    file_format.SaveSizeInfo(size_info, args.size_file)
    size_in_mb = os.path.getsize(args.size_file) / 1024.0 / 1024.0
    logging.info('Done. File size is %.2fMiB.', size_in_mb)