Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(231)

Side by Side Diff: tools/binary_size/libsupersize/archive.py

Issue 2807343005: //tools/binary_size: Record packed .rel.dyn section size (Closed)
Patch Set: Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/binary_size/libsupersize/console.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2017 The Chromium Authors. All rights reserved. 1 # Copyright 2017 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Main Python API for analyzing binary size.""" 5 """Main Python API for analyzing binary size."""
6 6
7 import argparse 7 import argparse
8 import calendar 8 import calendar
9 import collections 9 import collections
10 import datetime 10 import datetime
11 import gzip 11 import gzip
12 import logging 12 import logging
13 import os 13 import os
14 import posixpath
14 import re 15 import re
15 import subprocess 16 import subprocess
16 import sys 17 import sys
18 import tempfile
19 import zipfile
17 20
18 import describe 21 import describe
19 import file_format 22 import file_format
20 import function_signature 23 import function_signature
21 import helpers 24 import helpers
22 import linker_map_parser 25 import linker_map_parser
23 import models 26 import models
24 import ninja_parser 27 import ninja_parser
25 import paths 28 import paths
26 29
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
198 'Symbol has negative size (likely not sorted propertly): ' 201 'Symbol has negative size (likely not sorted propertly): '
199 '%r\nprev symbol: %r' % (symbol, prev_symbol)) 202 '%r\nprev symbol: %r' % (symbol, prev_symbol))
200 203
201 204
202 def _ClusterSymbols(symbols): 205 def _ClusterSymbols(symbols):
203 """Returns a new list of symbols with some symbols moved into groups. 206 """Returns a new list of symbols with some symbols moved into groups.
204 207
205 Groups include: 208 Groups include:
206 * Symbols that have [clone] in their name (created by compiler optimization). 209 * Symbols that have [clone] in their name (created by compiler optimization).
207 * Star symbols (such as "** merge strings", and "** symbol gap") 210 * Star symbols (such as "** merge strings", and "** symbol gap")
211
212 To view created groups:
213 Print(size_info.symbols.Filter(lambda s: s.IsGroup()), recursive=True)
208 """ 214 """
209 # http://unix.stackexchange.com/questions/223013/function-symbol-gets-part-suf fix-after-compilation 215 # http://unix.stackexchange.com/questions/223013/function-symbol-gets-part-suf fix-after-compilation
210 # Example name suffixes: 216 # Example name suffixes:
211 # [clone .part.322] 217 # [clone .part.322] # GCC
212 # [clone .isra.322] 218 # [clone .isra.322] # GCC
213 # [clone .constprop.1064] 219 # [clone .constprop.1064] # GCC
220 # [clone .11064] # clang
214 221
215 # Step 1: Create name map, find clones, collect star syms into replacements. 222 # Step 1: Create name map, find clones, collect star syms into replacements.
216 logging.debug('Creating name -> symbol map') 223 logging.debug('Creating name -> symbol map')
217 clone_indices = [] 224 clone_indices = []
218 indices_by_full_name = {} 225 indices_by_full_name = {}
219 # (name, full_name) -> [(index, sym),...] 226 # (name, full_name) -> [(index, sym),...]
220 replacements_by_name = collections.defaultdict(list) 227 replacements_by_name = collections.defaultdict(list)
221 for i, symbol in enumerate(symbols): 228 for i, symbol in enumerate(symbols):
222 if symbol.name.startswith('**'): 229 if symbol.name.startswith('**'):
223 # "symbol gap 3" -> "symbol gaps" 230 # "symbol gap 3" -> "symbol gaps"
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
344 size_info = models.SizeInfo(section_sizes, raw_symbols) 351 size_info = models.SizeInfo(section_sizes, raw_symbols)
345 352
346 # Name normalization not strictly required, but makes for smaller files. 353 # Name normalization not strictly required, but makes for smaller files.
347 if raw_only: 354 if raw_only:
348 logging.info('Normalizing symbol names') 355 logging.info('Normalizing symbol names')
349 _NormalizeNames(size_info.raw_symbols) 356 _NormalizeNames(size_info.raw_symbols)
350 else: 357 else:
351 _PostProcessSizeInfo(size_info) 358 _PostProcessSizeInfo(size_info)
352 359
353 if logging.getLogger().isEnabledFor(logging.DEBUG): 360 if logging.getLogger().isEnabledFor(logging.DEBUG):
361 # Padding is reported in size coverage logs.
362 if raw_only:
363 _CalculatePadding(size_info.raw_symbols)
354 for line in describe.DescribeSizeInfoCoverage(size_info): 364 for line in describe.DescribeSizeInfoCoverage(size_info):
355 logging.info(line) 365 logging.info(line)
356 logging.info('Recorded info for %d symbols', len(size_info.raw_symbols)) 366 logging.info('Recorded info for %d symbols', len(size_info.raw_symbols))
357 return size_info 367 return size_info
358 368
359 369
360 def _DetectGitRevision(directory): 370 def _DetectGitRevision(directory):
361 try: 371 try:
362 git_rev = subprocess.check_output( 372 git_rev = subprocess.check_output(
363 ['git', '-C', directory, 'rev-parse', 'HEAD']) 373 ['git', '-C', directory, 'rev-parse', 'HEAD'])
(...skipping 28 matching lines...) Expand all
392 with open(args_path) as f: 402 with open(args_path) as f:
393 for l in f: 403 for l in f:
394 # Strips #s even if within string literal. Not a problem in practice. 404 # Strips #s even if within string literal. Not a problem in practice.
395 parts = l.split('#')[0].split('=') 405 parts = l.split('#')[0].split('=')
396 if len(parts) != 2: 406 if len(parts) != 2:
397 continue 407 continue
398 args[parts[0].strip()] = parts[1].strip() 408 args[parts[0].strip()] = parts[1].strip()
399 return ["%s=%s" % x for x in sorted(args.iteritems())] 409 return ["%s=%s" % x for x in sorted(args.iteritems())]
400 410
401 411
412 def _SectionSizesFromApk(apk_file, elf_file, build_id, lazy_paths):
413 with zipfile.ZipFile(apk_file) as apk, \
414 tempfile.NamedTemporaryFile() as f:
415 target = os.path.basename(elf_file)
416 target_info = next((f for f in apk.infolist()
417 if posixpath.basename(f.filename) == target), None)
418 assert target_info, (
419 'Could not find apk entry for %s in %s' % (target, apk_file))
420 f.write(apk.read(target_info))
421 f.flush()
422 apk_build_id = BuildIdFromElf(f.name, lazy_paths.tool_prefix)
423 assert apk_build_id == build_id, (
424 'BuildID for %s within %s did not match the one at %s' %
425 (target_info.filename, apk_file, elf_file))
426 return _SectionSizesFromElf(f.name, lazy_paths.tool_prefix)
427
428
402 def AddArguments(parser): 429 def AddArguments(parser):
403 parser.add_argument('size_file', help='Path to output .size file.') 430 parser.add_argument('size_file', help='Path to output .size file.')
404 parser.add_argument('--elf-file', required=True, 431 parser.add_argument('--apk-file',
432 help='.apk file to measure. When set, --elf-file will be '
433 'derived (if unset). Providing the .apk allows '
434 'for the size of packed relocations to be recorded')
435 parser.add_argument('--elf-file',
405 help='Path to input ELF file. Currently used for ' 436 help='Path to input ELF file. Currently used for '
406 'capturing metadata. Pass "" to skip ' 437 'capturing metadata.')
407 'metadata collection.')
408 parser.add_argument('--map-file', 438 parser.add_argument('--map-file',
409 help='Path to input .map(.gz) file. Defaults to ' 439 help='Path to input .map(.gz) file. Defaults to '
410 '{{elf_file}}.map(.gz)?') 440 '{{elf_file}}.map(.gz)?. If given without '
441 '--elf-file, no size metadata will be recorded.')
411 parser.add_argument('--no-source-paths', action='store_true', 442 parser.add_argument('--no-source-paths', action='store_true',
412 help='Do not use .ninja files to map ' 443 help='Do not use .ninja files to map '
413 'object_path -> source_path') 444 'object_path -> source_path')
414 parser.add_argument('--tool-prefix', default='', 445 parser.add_argument('--tool-prefix', default='',
415 help='Path prefix for c++filt.') 446 help='Path prefix for c++filt.')
416 parser.add_argument('--output-directory', 447 parser.add_argument('--output-directory',
417 help='Path to the root build directory.') 448 help='Path to the root build directory.')
418 449
419 450
420 def Run(args, parser): 451 def Run(args, parser):
421 if not args.size_file.endswith('.size'): 452 if not args.size_file.endswith('.size'):
422 parser.error('size_file must end with .size') 453 parser.error('size_file must end with .size')
423 454
455 any_input = args.apk_file or args.elf_file or args.map_file
456 if not any_input:
457 parser.error('Most pass at least one of --apk-file, --elf-file, --map-file')
458 lazy_paths = paths.LazyPaths(args=args, input_file=any_input)
459
460 elf_file = args.elf_file
461 if args.apk_file and not elf_file:
462 with zipfile.ZipFile(args.apk_file) as z:
463 lib_infos = [f for f in z.infolist()
464 if f.filename.endswith('.so') and f.file_size > 0]
465 assert lib_infos, 'APK has not .so files to measure.'
estevenson 2017/04/12 16:54:42 nit: awkward wording.
agrieve 2017/04/12 19:37:46 Done.
466 # TODO(agrieve): Add support for multiple .so files, and take into account
467 # secondary architectures.
estevenson 2017/04/12 16:54:42 _SectionSizesFromApk() would fail for 64 bit monoc
agrieve 2017/04/12 19:37:45 Didn't hit any of those other errors, but good cat
468 apk_so_path = max(lib_infos, key=lambda x:x.file_size).filename
469 elf_file = os.path.join(lazy_paths.output_directory, 'lib.unstripped',
470 os.path.basename(apk_so_path))
471 logging.debug('Detected --elf-file=%s', elf_file)
472
424 if args.map_file: 473 if args.map_file:
425 if (not args.map_file.endswith('.map') 474 if (not args.map_file.endswith('.map')
426 and not args.map_file.endswith('.map.gz')): 475 and not args.map_file.endswith('.map.gz')):
427 parser.error('Expected --map-file to end with .map or .map.gz') 476 parser.error('Expected --map-file to end with .map or .map.gz')
428 map_file_path = args.map_file 477 map_file_path = args.map_file
429 else: 478 else:
430 map_file_path = args.elf_file + '.map' 479 map_file_path = elf_file + '.map'
431 if not os.path.exists(map_file_path): 480 if not os.path.exists(map_file_path):
432 map_file_path += '.gz' 481 map_file_path += '.gz'
433 if not os.path.exists(map_file_path): 482 if not os.path.exists(map_file_path):
434 parser.error('Could not find .map(.gz)? file. Use --map-file.') 483 parser.error('Could not find .map(.gz)? file. Use --map-file.')
435 484
436 lazy_paths = paths.LazyPaths(args=args, input_file=args.elf_file)
437 metadata = None 485 metadata = None
438 if args.elf_file: 486 if elf_file:
439 logging.debug('Constructing metadata') 487 logging.debug('Constructing metadata')
440 git_rev = _DetectGitRevision(os.path.dirname(args.elf_file)) 488 git_rev = _DetectGitRevision(os.path.dirname(elf_file))
441 build_id = BuildIdFromElf(args.elf_file, lazy_paths.tool_prefix) 489 build_id = BuildIdFromElf(elf_file, lazy_paths.tool_prefix)
442 timestamp_obj = datetime.datetime.utcfromtimestamp(os.path.getmtime( 490 timestamp_obj = datetime.datetime.utcfromtimestamp(os.path.getmtime(
443 args.elf_file)) 491 elf_file))
444 timestamp = calendar.timegm(timestamp_obj.timetuple()) 492 timestamp = calendar.timegm(timestamp_obj.timetuple())
445 gn_args = _ParseGnArgs(os.path.join(lazy_paths.output_directory, 'args.gn')) 493 gn_args = _ParseGnArgs(os.path.join(lazy_paths.output_directory, 'args.gn'))
446 494
447 def relative_to_out(path): 495 def relative_to_out(path):
448 return os.path.relpath(path, lazy_paths.VerifyOutputDirectory()) 496 return os.path.relpath(path, lazy_paths.VerifyOutputDirectory())
449 497
450 metadata = { 498 metadata = {
451 models.METADATA_GIT_REVISION: git_rev, 499 models.METADATA_GIT_REVISION: git_rev,
452 models.METADATA_MAP_FILENAME: relative_to_out(map_file_path), 500 models.METADATA_MAP_FILENAME: relative_to_out(map_file_path),
453 models.METADATA_ELF_FILENAME: relative_to_out(args.elf_file), 501 models.METADATA_ELF_FILENAME: relative_to_out(elf_file),
454 models.METADATA_ELF_MTIME: timestamp, 502 models.METADATA_ELF_MTIME: timestamp,
455 models.METADATA_ELF_BUILD_ID: build_id, 503 models.METADATA_ELF_BUILD_ID: build_id,
456 models.METADATA_GN_ARGS: gn_args, 504 models.METADATA_GN_ARGS: gn_args,
457 } 505 }
458 506
459 size_info = CreateSizeInfo(map_file_path, lazy_paths, 507 size_info = CreateSizeInfo(map_file_path, lazy_paths,
460 no_source_paths=args.no_source_paths, 508 no_source_paths=args.no_source_paths,
461 raw_only=True) 509 raw_only=True)
462 510
463 if metadata: 511 if metadata:
464 size_info.metadata = metadata 512 size_info.metadata = metadata
465 logging.debug('Validating section sizes') 513 logging.debug('Validating section sizes')
466 elf_section_sizes = _SectionSizesFromElf(args.elf_file, 514 elf_section_sizes = _SectionSizesFromElf(elf_file, lazy_paths.tool_prefix)
467 lazy_paths.tool_prefix)
468 for k, v in elf_section_sizes.iteritems(): 515 for k, v in elf_section_sizes.iteritems():
469 assert v == size_info.section_sizes.get(k), ( 516 assert v == size_info.section_sizes.get(k), (
470 'ELF file and .map file do not match.') 517 'ELF file and .map file do not match.')
471 518
519 if args.apk_file:
520 logging.debug('Extracting section sizes from .so within .apk')
521 unpacked_rel_dyn = size_info.section_sizes.get('.rel.dyn', 0)
522 if not unpacked_rel_dyn:
523 logging.warning('Section .rel.dyn did not exist')
524 # TODO(agrieve): Extracting the .so is slow. Do it on a background thread.
525 size_info.section_sizes = _SectionSizesFromApk(
526 args.apk_file, elf_file, build_id, lazy_paths)
527 size_info.section_sizes['.rel.dyn (unpacked)'] = unpacked_rel_dyn
528
472 logging.info('Recording metadata: \n %s', 529 logging.info('Recording metadata: \n %s',
473 '\n '.join(describe.DescribeMetadata(size_info.metadata))) 530 '\n '.join(describe.DescribeMetadata(size_info.metadata)))
474 logging.info('Saving result to %s', args.size_file) 531 logging.info('Saving result to %s', args.size_file)
475 file_format.SaveSizeInfo(size_info, args.size_file) 532 file_format.SaveSizeInfo(size_info, args.size_file)
476 logging.info('Done') 533 logging.info('Done')
OLDNEW
« no previous file with comments | « no previous file | tools/binary_size/libsupersize/console.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698