OLD | NEW |
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 """Classes that comprise the data model for binary size analysis. | 4 """Classes that comprise the data model for binary size analysis. |
5 | 5 |
6 The primary classes are Symbol, and SymbolGroup. | 6 The primary classes are Symbol, and SymbolGroup. |
7 | 7 |
8 Description of common properties: | 8 Description of common properties: |
9 * address: The start address of the symbol. | 9 * address: The start address of the symbol. |
10 May be 0 (e.g. for .bss or for SymbolGroups). | 10 May be 0 (e.g. for .bss or for SymbolGroups). |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
48 'd': '.data', | 48 'd': '.data', |
49 'r': '.rodata', | 49 'r': '.rodata', |
50 't': '.text', | 50 't': '.text', |
51 } | 51 } |
52 | 52 |
53 FLAG_ANONYMOUS = 1 | 53 FLAG_ANONYMOUS = 1 |
54 FLAG_STARTUP = 2 | 54 FLAG_STARTUP = 2 |
55 FLAG_UNLIKELY = 4 | 55 FLAG_UNLIKELY = 4 |
56 FLAG_REL = 8 | 56 FLAG_REL = 8 |
57 FLAG_REL_LOCAL = 16 | 57 FLAG_REL_LOCAL = 16 |
| 58 FLAG_GENERATED_SOURCE = 32 |
58 | 59 |
59 | 60 |
60 class SizeInfo(object): | 61 class SizeInfo(object): |
61 """Represents all size information for a single binary. | 62 """Represents all size information for a single binary. |
62 | 63 |
63 Fields: | 64 Fields: |
64 section_sizes: A dict of section_name -> size. | 65 section_sizes: A dict of section_name -> size. |
65 symbols: A SymbolGroup containing all symbols, sorted by address. | 66 symbols: A SymbolGroup containing all symbols, sorted by address. |
66 metadata: A dict. | 67 metadata: A dict. |
67 """ | 68 """ |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
129 | 130 |
130 @property | 131 @property |
131 def end_address(self): | 132 def end_address(self): |
132 return self.address + self.size_without_padding | 133 return self.address + self.size_without_padding |
133 | 134 |
134 @property | 135 @property |
135 def is_anonymous(self): | 136 def is_anonymous(self): |
136 return bool(self.flags & FLAG_ANONYMOUS) | 137 return bool(self.flags & FLAG_ANONYMOUS) |
137 | 138 |
138 @property | 139 @property |
| 140 def generated_source(self): |
| 141 return bool(self.flags & FLAG_GENERATED_SOURCE) |
| 142 |
| 143 @generated_source.setter |
| 144 def generated_source(self, value): |
| 145 if value: |
| 146 self.flags |= FLAG_GENERATED_SOURCE |
| 147 else: |
| 148 self.flags &= ~FLAG_GENERATED_SOURCE |
| 149 |
| 150 @property |
139 def num_aliases(self): | 151 def num_aliases(self): |
140 return len(self.aliases) if self.aliases else 1 | 152 return len(self.aliases) if self.aliases else 1 |
141 | 153 |
142 def FlagsString(self): | 154 def FlagsString(self): |
143 # Most flags are 0. | 155 # Most flags are 0. |
144 flags = self.flags | 156 flags = self.flags |
145 if not flags and not self.aliases: | 157 if not flags and not self.aliases: |
146 return '{}' | 158 return '{}' |
147 parts = [] | 159 parts = [] |
148 if flags & FLAG_ANONYMOUS: | 160 if flags & FLAG_ANONYMOUS: |
149 parts.append('anon') | 161 parts.append('anon') |
150 if flags & FLAG_STARTUP: | 162 if flags & FLAG_STARTUP: |
151 parts.append('startup') | 163 parts.append('startup') |
152 if flags & FLAG_UNLIKELY: | 164 if flags & FLAG_UNLIKELY: |
153 parts.append('unlikely') | 165 parts.append('unlikely') |
154 if flags & FLAG_REL: | 166 if flags & FLAG_REL: |
155 parts.append('rel') | 167 parts.append('rel') |
156 if flags & FLAG_REL_LOCAL: | 168 if flags & FLAG_REL_LOCAL: |
157 parts.append('rel.loc') | 169 parts.append('rel.loc') |
| 170 if flags & FLAG_GENERATED_SOURCE: |
| 171 parts.append('gen') |
158 # Not actually a part of flags, but useful to show it here. | 172 # Not actually a part of flags, but useful to show it here. |
159 if self.aliases: | 173 if self.aliases: |
160 parts.append('{} aliases'.format(self.num_aliases)) | 174 parts.append('{} aliases'.format(self.num_aliases)) |
161 return '{%s}' % ','.join(parts) | 175 return '{%s}' % ','.join(parts) |
162 | 176 |
163 def IsBss(self): | 177 def IsBss(self): |
164 return self.section_name == '.bss' | 178 return self.section_name == '.bss' |
165 | 179 |
166 def IsGroup(self): | 180 def IsGroup(self): |
167 return False | 181 return False |
168 | 182 |
169 def IsGenerated(self): | 183 def IsGeneratedByToolchain(self): |
170 # TODO(agrieve): Also match generated functions such as: | 184 return '.' in self.name or ( |
171 # startup._GLOBAL__sub_I_page_allocator.cc | 185 self.name.endswith(']') and not self.name.endswith('[]')) |
172 return self.name.endswith(']') and not self.name.endswith('[]') | |
173 | 186 |
174 | 187 |
175 class Symbol(BaseSymbol): | 188 class Symbol(BaseSymbol): |
176 """Represents a single symbol within a binary. | 189 """Represents a single symbol within a binary. |
177 | 190 |
178 Refer to module docs for field descriptions. | 191 Refer to module docs for field descriptions. |
179 """ | 192 """ |
180 | 193 |
181 __slots__ = ( | 194 __slots__ = ( |
182 'address', | 195 'address', |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
295 section_name=self.section_name) | 308 section_name=self.section_name) |
296 | 309 |
297 def __add__(self, other): | 310 def __add__(self, other): |
298 self_ids = set(id(s) for s in self) | 311 self_ids = set(id(s) for s in self) |
299 after_symbols = self._symbols + [s for s in other if id(s) not in self_ids] | 312 after_symbols = self._symbols + [s for s in other if id(s) not in self_ids] |
300 return self._CreateTransformed( | 313 return self._CreateTransformed( |
301 after_symbols, section_name=self.section_name, is_sorted=False) | 314 after_symbols, section_name=self.section_name, is_sorted=False) |
302 | 315 |
303 @property | 316 @property |
304 def address(self): | 317 def address(self): |
305 first = self._symbols[0].address | 318 first = self._symbols[0].address if self else 0 |
306 return first if all(s.address == first for s in self._symbols) else 0 | 319 return first if all(s.address == first for s in self._symbols) else 0 |
307 | 320 |
308 @property | 321 @property |
309 def flags(self): | 322 def flags(self): |
310 first = self._symbols[0].flags | 323 first = self._symbols[0].flags if self else 0 |
311 return first if all(s.flags == first for s in self._symbols) else 0 | 324 return first if all(s.flags == first for s in self._symbols) else 0 |
312 | 325 |
313 @property | 326 @property |
314 def object_path(self): | 327 def object_path(self): |
315 first = self._symbols[0].object_path | 328 first = self._symbols[0].object_path if self else '' |
316 return first if all(s.object_path == first for s in self._symbols) else '' | 329 return first if all(s.object_path == first for s in self._symbols) else '' |
317 | 330 |
318 @property | 331 @property |
319 def source_path(self): | 332 def source_path(self): |
320 first = self._symbols[0].source_path | 333 first = self._symbols[0].source_path if self else '' |
321 return first if all(s.source_path == first for s in self._symbols) else '' | 334 return first if all(s.source_path == first for s in self._symbols) else '' |
322 | 335 |
323 def IterUniqueSymbols(self): | 336 def IterUniqueSymbols(self): |
324 seen_aliases_lists = set() | 337 seen_aliases_lists = set() |
325 for s in self: | 338 for s in self: |
326 if not s.aliases: | 339 if not s.aliases: |
327 yield s | 340 yield s |
328 elif id(s.aliases) not in seen_aliases_lists: | 341 elif id(s.aliases) not in seen_aliases_lists: |
329 seen_aliases_lists.add(id(s.aliases)) | 342 seen_aliases_lists.add(id(s.aliases)) |
330 yield s | 343 yield s |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
377 Subgroups include: | 390 Subgroups include: |
378 * Symbols that have [clone] in their name (created during inlining). | 391 * Symbols that have [clone] in their name (created during inlining). |
379 * Star symbols (such as "** merge strings", and "** symbol gap") | 392 * Star symbols (such as "** merge strings", and "** symbol gap") |
380 | 393 |
381 To view created groups: | 394 To view created groups: |
382 Print(clustered.Filter(lambda s: s.IsGroup()), recursive=True) | 395 Print(clustered.Filter(lambda s: s.IsGroup()), recursive=True) |
383 """ | 396 """ |
384 return self._CreateTransformed(cluster_symbols.ClusterSymbols(self)) | 397 return self._CreateTransformed(cluster_symbols.ClusterSymbols(self)) |
385 | 398 |
386 def Sorted(self, cmp_func=None, key=None, reverse=False): | 399 def Sorted(self, cmp_func=None, key=None, reverse=False): |
387 # Default to sorting by abs(size) then name. | |
388 if cmp_func is None and key is None: | 400 if cmp_func is None and key is None: |
389 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name), | 401 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.pss), a.name), |
390 (b.IsBss(), abs(a.size), b.name)) | 402 (b.IsBss(), abs(a.pss), b.name)) |
391 | 403 |
392 after_symbols = sorted(self._symbols, cmp_func, key, reverse) | 404 after_symbols = sorted(self._symbols, cmp_func, key, reverse) |
393 return self._CreateTransformed( | 405 return self._CreateTransformed( |
394 after_symbols, filtered_symbols=self._filtered_symbols, | 406 after_symbols, filtered_symbols=self._filtered_symbols, |
395 section_name=self.section_name, is_sorted=True) | 407 section_name=self.section_name, is_sorted=True) |
396 | 408 |
397 def SortedByName(self, reverse=False): | 409 def SortedByName(self, reverse=False): |
398 return self.Sorted(key=(lambda s:s.name), reverse=reverse) | 410 return self.Sorted(key=(lambda s:s.name), reverse=reverse) |
399 | 411 |
400 def SortedByAddress(self, reverse=False): | 412 def SortedByAddress(self, reverse=False): |
401 return self.Sorted(key=(lambda s:s.address), reverse=reverse) | 413 return self.Sorted(key=(lambda s:(s.address, s.object_path, s.name)), |
| 414 reverse=reverse) |
402 | 415 |
403 def SortedByCount(self, reverse=False): | 416 def SortedByCount(self, reverse=False): |
404 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), | 417 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), |
405 reverse=not reverse) | 418 reverse=not reverse) |
406 | 419 |
407 def Filter(self, func): | 420 def Filter(self, func): |
408 filtered_and_kept = ([], []) | 421 filtered_and_kept = ([], []) |
409 symbol = None | 422 symbol = None |
410 try: | 423 try: |
411 for symbol in self: | 424 for symbol in self: |
(...skipping 11 matching lines...) Expand all Loading... |
423 | 436 |
424 def WhereInSection(self, section): | 437 def WhereInSection(self, section): |
425 if len(section) == 1: | 438 if len(section) == 1: |
426 ret = self.Filter(lambda s: s.section == section) | 439 ret = self.Filter(lambda s: s.section == section) |
427 ret.section_name = SECTION_TO_SECTION_NAME[section] | 440 ret.section_name = SECTION_TO_SECTION_NAME[section] |
428 else: | 441 else: |
429 ret = self.Filter(lambda s: s.section_name == section) | 442 ret = self.Filter(lambda s: s.section_name == section) |
430 ret.section_name = section | 443 ret.section_name = section |
431 return ret | 444 return ret |
432 | 445 |
433 def WhereIsGenerated(self): | 446 def WhereSourceIsGenerated(self): |
434 return self.Filter(lambda s: s.IsGenerated()) | 447 return self.Filter(lambda s: s.generated_source) |
| 448 |
| 449 def WhereGeneratedByToolchain(self): |
| 450 return self.Filter(lambda s: s.IsGeneratedByToolchain()) |
435 | 451 |
436 def WhereNameMatches(self, pattern): | 452 def WhereNameMatches(self, pattern): |
437 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) | 453 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
438 return self.Filter(lambda s: regex.search(s.name)) | 454 return self.Filter(lambda s: regex.search(s.name)) |
439 | 455 |
| 456 def WhereFullNameMatches(self, pattern): |
| 457 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
| 458 return self.Filter(lambda s: regex.search(s.full_name or s.name)) |
| 459 |
440 def WhereObjectPathMatches(self, pattern): | 460 def WhereObjectPathMatches(self, pattern): |
441 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) | 461 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
442 return self.Filter(lambda s: regex.search(s.object_path)) | 462 return self.Filter(lambda s: regex.search(s.object_path)) |
443 | 463 |
444 def WhereSourcePathMatches(self, pattern): | 464 def WhereSourcePathMatches(self, pattern): |
445 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) | 465 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
446 return self.Filter(lambda s: regex.search(s.source_path)) | 466 return self.Filter(lambda s: regex.search(s.source_path)) |
447 | 467 |
448 def WherePathMatches(self, pattern): | 468 def WherePathMatches(self, pattern): |
449 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) | 469 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
(...skipping 12 matching lines...) Expand all Loading... |
462 """Searches for addesses within [start, end). | 482 """Searches for addesses within [start, end). |
463 | 483 |
464 Args may be ints or hex strings. Default value for |end| is |start| + 1. | 484 Args may be ints or hex strings. Default value for |end| is |start| + 1. |
465 """ | 485 """ |
466 if isinstance(start, basestring): | 486 if isinstance(start, basestring): |
467 start = int(start, 16) | 487 start = int(start, 16) |
468 if end is None: | 488 if end is None: |
469 end = start + 1 | 489 end = start + 1 |
470 return self.Filter(lambda s: s.address >= start and s.address < end) | 490 return self.Filter(lambda s: s.address >= start and s.address < end) |
471 | 491 |
| 492 def WhereHasPath(self): |
| 493 return self.Filter(lambda s: s.source_path or s.object_path) |
| 494 |
472 def WhereHasAnyAttribution(self): | 495 def WhereHasAnyAttribution(self): |
473 return self.Filter(lambda s: s.name or s.source_path or s.object_path) | 496 return self.Filter(lambda s: s.name or s.source_path or s.object_path) |
474 | 497 |
475 def Inverted(self): | 498 def Inverted(self): |
476 """Returns the symbols that were filtered out by the previous filter. | 499 """Returns the symbols that were filtered out by the previous filter. |
477 | 500 |
478 Applies only when the previous call was a filter. | 501 Applies only when the previous call was a filter. |
479 | 502 |
480 Example: | 503 Example: |
481 # Symbols that do not have "third_party" in their path. | 504 # Symbols that do not have "third_party" in their path. |
(...skipping 30 matching lines...) Expand all Loading... |
512 min_count = abs(min_count) | 535 min_count = abs(min_count) |
513 for token, symbols in symbols_by_token.iteritems(): | 536 for token, symbols in symbols_by_token.iteritems(): |
514 if len(symbols) >= min_count: | 537 if len(symbols) >= min_count: |
515 after_syms.append(self._CreateTransformed( | 538 after_syms.append(self._CreateTransformed( |
516 symbols, name=token, section_name=self.section_name, | 539 symbols, name=token, section_name=self.section_name, |
517 is_sorted=False)) | 540 is_sorted=False)) |
518 elif include_singles: | 541 elif include_singles: |
519 after_syms.extend(symbols) | 542 after_syms.extend(symbols) |
520 else: | 543 else: |
521 filtered_symbols.extend(symbols) | 544 filtered_symbols.extend(symbols) |
522 return self._CreateTransformed( | 545 grouped = self._CreateTransformed( |
523 after_syms, filtered_symbols=filtered_symbols, | 546 after_syms, filtered_symbols=filtered_symbols, |
524 section_name=self.section_name, is_sorted=False) | 547 section_name=self.section_name, is_sorted=False) |
| 548 # Grouping is rarely an intermediate step, so assume sorting is useful. |
| 549 return grouped.Sorted() |
525 | 550 |
526 def GroupBySectionName(self): | 551 def GroupBySectionName(self): |
527 return self.GroupBy(lambda s: s.section_name) | 552 return self.GroupBy(lambda s: s.section_name) |
528 | 553 |
529 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): | 554 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): |
530 """Groups by symbol namespace (as denoted by ::s). | 555 """Groups by symbol namespace (as denoted by ::s). |
531 | 556 |
532 Does not differentiate between C++ namespaces and C++ classes. | 557 Does not differentiate between C++ namespaces and C++ classes. |
533 | 558 |
534 Args: | 559 Args: |
(...skipping 14 matching lines...) Expand all Loading... |
549 | 574 |
550 # Remove after the final :: (not part of the namespace). | 575 # Remove after the final :: (not part of the namespace). |
551 colon_idx = name.rfind('::') | 576 colon_idx = name.rfind('::') |
552 if colon_idx == -1: | 577 if colon_idx == -1: |
553 return fallback | 578 return fallback |
554 name = name[:colon_idx] | 579 name = name[:colon_idx] |
555 | 580 |
556 return _ExtractPrefixBeforeSeparator(name, '::', depth) | 581 return _ExtractPrefixBeforeSeparator(name, '::', depth) |
557 return self.GroupBy(extract_namespace, min_count=min_count) | 582 return self.GroupBy(extract_namespace, min_count=min_count) |
558 | 583 |
559 def GroupBySourcePath(self, depth=0, fallback='{no path}', | 584 def GroupByPath(self, depth=0, fallback='{no path}', |
560 fallback_to_object_path=True, min_count=0): | 585 fallback_to_object_path=True, min_count=0): |
561 """Groups by source_path. | 586 """Groups by source_path. |
562 | 587 |
563 Args: | 588 Args: |
564 depth: When 0 (default), groups by entire path. When 1, groups by | 589 depth: When 0 (default), groups by entire path. When 1, groups by |
565 top-level directory, when 2, groups by top 2 directories, etc. | 590 top-level directory, when 2, groups by top 2 directories, etc. |
566 fallback: Use this value when no namespace exists. | 591 fallback: Use this value when no namespace exists. |
567 fallback_to_object_path: When True (default), uses object_path when | 592 fallback_to_object_path: When True (default), uses object_path when |
568 source_path is missing. | 593 source_path is missing. |
569 min_count: Miniumum number of symbols for a group. If fewer than this many | 594 min_count: Miniumum number of symbols for a group. If fewer than this many |
570 symbols end up in a group, they will not be put within a group. | 595 symbols end up in a group, they will not be put within a group. |
571 Use a negative value to omit symbols entirely rather than | 596 Use a negative value to omit symbols entirely rather than |
572 include them outside of a group. | 597 include them outside of a group. |
573 """ | 598 """ |
574 def extract_path(symbol): | 599 def extract_path(symbol): |
575 path = symbol.source_path | 600 path = symbol.source_path |
576 if fallback_to_object_path and not path: | 601 if fallback_to_object_path and not path: |
577 path = symbol.object_path | 602 path = symbol.object_path |
578 path = path or fallback | 603 path = path or fallback |
579 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) | 604 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) |
580 return self.GroupBy(extract_path, min_count=min_count) | 605 return self.GroupBy(extract_path, min_count=min_count) |
581 | 606 |
582 def GroupByObjectPath(self, depth=0, fallback='{no path}', min_count=0): | |
583 """Groups by object_path. | |
584 | |
585 Args: | |
586 depth: When 0 (default), groups by entire path. When 1, groups by | |
587 top-level directory, when 2, groups by top 2 directories, etc. | |
588 fallback: Use this value when no namespace exists. | |
589 min_count: Miniumum number of symbols for a group. If fewer than this many | |
590 symbols end up in a group, they will not be put within a group. | |
591 Use a negative value to omit symbols entirely rather than | |
592 include them outside of a group. | |
593 """ | |
594 def extract_path(symbol): | |
595 path = symbol.object_path or fallback | |
596 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) | |
597 return self.GroupBy(extract_path, min_count=min_count) | |
598 | |
599 | 607 |
600 class SymbolDiff(SymbolGroup): | 608 class SymbolDiff(SymbolGroup): |
601 """A SymbolGroup subclass representing a diff of two other SymbolGroups. | 609 """A SymbolGroup subclass representing a diff of two other SymbolGroups. |
602 | 610 |
603 All Symbols contained within have a |size| which is actually the size delta. | 611 All Symbols contained within have a |size| which is actually the size delta. |
604 Additionally, metadata is kept about which symbols were added / removed / | 612 Additionally, metadata is kept about which symbols were added / removed / |
605 changed. | 613 changed. |
606 """ | 614 """ |
607 __slots__ = ( | 615 __slots__ = ( |
608 '_added_ids', | 616 '_added_ids', |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
697 | 705 |
698 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 706 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
699 idx = -len(separator) | 707 idx = -len(separator) |
700 prev_idx = None | 708 prev_idx = None |
701 for _ in xrange(count): | 709 for _ in xrange(count): |
702 idx = string.find(separator, idx + len(separator)) | 710 idx = string.find(separator, idx + len(separator)) |
703 if idx < 0: | 711 if idx < 0: |
704 break | 712 break |
705 prev_idx = idx | 713 prev_idx = idx |
706 return string[:prev_idx] | 714 return string[:prev_idx] |
OLD | NEW |