| 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 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 44 'r': '.rodata', | 44 'r': '.rodata', |
| 45 't': '.text', | 45 't': '.text', |
| 46 } | 46 } |
| 47 | 47 |
| 48 | 48 |
| 49 class SizeInfo(object): | 49 class SizeInfo(object): |
| 50 """Represents all size information for a single binary. | 50 """Represents all size information for a single binary. |
| 51 | 51 |
| 52 Fields: | 52 Fields: |
| 53 section_sizes: A dict of section_name -> size. | 53 section_sizes: A dict of section_name -> size. |
| 54 symbols: A SymbolGroup with all symbols in it. | 54 raw_symbols: A flat list of all symbols. |
| 55 symbols: A SymbolGroup containing raw_symbols, but with some Symbols grouped |
| 56 into sub-SymbolGroups. |
| 55 metadata: A dict. | 57 metadata: A dict. |
| 56 """ | 58 """ |
| 57 __slots__ = ( | 59 __slots__ = ( |
| 58 'section_sizes', | 60 'section_sizes', |
| 61 'raw_symbols', |
| 59 'symbols', | 62 'symbols', |
| 60 'metadata', | 63 'metadata', |
| 61 ) | 64 ) |
| 62 | 65 |
| 63 """Root size information.""" | 66 """Root size information.""" |
| 64 def __init__(self, section_sizes, symbols, metadata=None): | 67 def __init__(self, section_sizes, raw_symbols, grouped_symbols=None, |
| 68 metadata=None): |
| 65 self.section_sizes = section_sizes # E.g. {'.text': 0} | 69 self.section_sizes = section_sizes # E.g. {'.text': 0} |
| 66 self.symbols = symbols # List of symbols sorted by address per-section. | 70 # List of symbols sorted by address per-section. |
| 71 self.raw_symbols = raw_symbols |
| 72 # Root SymbolGroup. Cloned symbols grouped together within sub-SymbolGroups. |
| 73 self.symbols = grouped_symbols |
| 67 self.metadata = metadata or {} | 74 self.metadata = metadata or {} |
| 68 | 75 |
| 69 | 76 |
| 70 class SizeInfoDiff(object): | 77 class SizeInfoDiff(object): |
| 71 """What you get when you Diff() two SizeInfo objects. | 78 """What you get when you Diff() two SizeInfo objects. |
| 72 | 79 |
| 73 Fields: | 80 Fields: |
| 74 section_sizes: A dict of section_name -> size delta. | 81 section_sizes: A dict of section_name -> size delta. |
| 75 symbols: A SymbolDiff with all symbols in it. | 82 symbols: A SymbolDiff with all symbols in it. |
| 76 old_metadata: metadata of the "old" SizeInfo. | 83 old_metadata: metadata of the "old" SizeInfo. |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 123 # TODO(agrieve): Also match generated functions such as: | 130 # TODO(agrieve): Also match generated functions such as: |
| 124 # startup._GLOBAL__sub_I_page_allocator.cc | 131 # startup._GLOBAL__sub_I_page_allocator.cc |
| 125 return self.name.endswith(']') and not self.name.endswith('[]') | 132 return self.name.endswith(']') and not self.name.endswith('[]') |
| 126 | 133 |
| 127 def _Key(self): | 134 def _Key(self): |
| 128 """Returns a tuple that can be used to see if two Symbol are the same. | 135 """Returns a tuple that can be used to see if two Symbol are the same. |
| 129 | 136 |
| 130 Keys are not guaranteed to be unique within a SymbolGroup. For example, it | 137 Keys are not guaranteed to be unique within a SymbolGroup. For example, it |
| 131 is common to have multiple "** merge strings" symbols, which will have a | 138 is common to have multiple "** merge strings" symbols, which will have a |
| 132 common key.""" | 139 common key.""" |
| 133 return (self.section_name, self.full_name or self.name) | 140 stripped_full_name = self.full_name |
| 141 if stripped_full_name: |
| 142 clone_idx = stripped_full_name.find(' [clone ') |
| 143 if clone_idx != -1: |
| 144 stripped_full_name = stripped_full_name[:clone_idx] |
| 145 return (self.section_name, stripped_full_name or self.name) |
| 134 | 146 |
| 135 | 147 |
| 136 class Symbol(BaseSymbol): | 148 class Symbol(BaseSymbol): |
| 137 """Represents a single symbol within a binary. | 149 """Represents a single symbol within a binary. |
| 138 | 150 |
| 139 Refer to module docs for field descriptions. | 151 Refer to module docs for field descriptions. |
| 140 """ | 152 """ |
| 141 | 153 |
| 142 __slots__ = ( | 154 __slots__ = ( |
| 143 'address', | 155 'address', |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 186 * group['0x1234'] # By symbol address | 198 * group['0x1234'] # By symbol address |
| 187 * without_group2 = group1 - group2 | 199 * without_group2 = group1 - group2 |
| 188 * unioned = group1 + group2 | 200 * unioned = group1 + group2 |
| 189 """ | 201 """ |
| 190 | 202 |
| 191 __slots__ = ( | 203 __slots__ = ( |
| 192 '_padding', | 204 '_padding', |
| 193 '_size', | 205 '_size', |
| 194 '_symbols', | 206 '_symbols', |
| 195 '_filtered_symbols', | 207 '_filtered_symbols', |
| 208 'full_name', |
| 196 'name', | 209 'name', |
| 197 'section_name', | 210 'section_name', |
| 198 'is_sorted', | 211 'is_sorted', |
| 199 ) | 212 ) |
| 200 | 213 |
| 201 def __init__(self, symbols, filtered_symbols=None, name=None, | 214 def __init__(self, symbols, filtered_symbols=None, name=None, |
| 202 section_name=None, is_sorted=False): | 215 full_name=None, section_name=None, is_sorted=False): |
| 203 self._padding = None | 216 self._padding = None |
| 204 self._size = None | 217 self._size = None |
| 205 self._symbols = symbols | 218 self._symbols = symbols |
| 206 self._filtered_symbols = filtered_symbols or [] | 219 self._filtered_symbols = filtered_symbols or [] |
| 207 self.name = name or '' | 220 self.name = name or '' |
| 221 self.full_name = full_name |
| 208 self.section_name = section_name or '.*' | 222 self.section_name = section_name or '.*' |
| 209 self.is_sorted = is_sorted | 223 self.is_sorted = is_sorted |
| 210 | 224 |
| 211 def __repr__(self): | 225 def __repr__(self): |
| 212 return 'Group(name=%s,count=%d,size=%d)' % ( | 226 return 'Group(name=%s,count=%d,size=%d)' % ( |
| 213 self.name, len(self), self.size) | 227 self.name, len(self), self.size) |
| 214 | 228 |
| 215 def __iter__(self): | 229 def __iter__(self): |
| 216 return iter(self._symbols) | 230 return iter(self._symbols) |
| 217 | 231 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 241 return self._CreateTransformed(new_symbols, section_name=self.section_name) | 255 return self._CreateTransformed(new_symbols, section_name=self.section_name) |
| 242 | 256 |
| 243 def __add__(self, other): | 257 def __add__(self, other): |
| 244 self_ids = set(id(s) for s in self) | 258 self_ids = set(id(s) for s in self) |
| 245 new_symbols = self._symbols + [s for s in other if id(s) not in self_ids] | 259 new_symbols = self._symbols + [s for s in other if id(s) not in self_ids] |
| 246 return self._CreateTransformed(new_symbols, section_name=self.section_name, | 260 return self._CreateTransformed(new_symbols, section_name=self.section_name, |
| 247 is_sorted=False) | 261 is_sorted=False) |
| 248 | 262 |
| 249 @property | 263 @property |
| 250 def address(self): | 264 def address(self): |
| 251 return 0 | 265 first = self._symbols[0].address |
| 252 | 266 return first if all(s.address == first for s in self._symbols) else 0 |
| 253 @property | |
| 254 def full_name(self): | |
| 255 return None | |
| 256 | 267 |
| 257 @property | 268 @property |
| 258 def is_anonymous(self): | 269 def is_anonymous(self): |
| 259 return False | 270 first = self._symbols[0].is_anonymous |
| 271 return first if all( |
| 272 s.is_anonymous == first for s in self._symbols) else False |
| 260 | 273 |
| 261 @property | 274 @property |
| 262 def object_path(self): | 275 def object_path(self): |
| 263 return None | 276 first = self._symbols[0].object_path |
| 277 return first if all(s.object_path == first for s in self._symbols) else None |
| 264 | 278 |
| 265 @property | 279 @property |
| 266 def source_path(self): | 280 def source_path(self): |
| 267 return None | 281 first = self._symbols[0].source_path |
| 282 return first if all(s.source_path == first for s in self._symbols) else None |
| 268 | 283 |
| 269 @property | 284 @property |
| 270 def size(self): | 285 def size(self): |
| 271 if self._size is None: | 286 if self._size is None: |
| 272 if self.IsBss(): | 287 if self.IsBss(): |
| 273 self._size = sum(s.size for s in self) | 288 self._size = sum(s.size for s in self) |
| 274 self._size = sum(s.size for s in self if not s.IsBss()) | 289 self._size = sum(s.size for s in self if not s.IsBss()) |
| 275 return self._size | 290 return self._size |
| 276 | 291 |
| 277 @property | 292 @property |
| (...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 503 | 518 |
| 504 All Symbols contained within have a |size| which is actually the size delta. | 519 All Symbols contained within have a |size| which is actually the size delta. |
| 505 Additionally, metadata is kept about which symbols were added / removed / | 520 Additionally, metadata is kept about which symbols were added / removed / |
| 506 changed. | 521 changed. |
| 507 """ | 522 """ |
| 508 __slots__ = ( | 523 __slots__ = ( |
| 509 '_added_ids', | 524 '_added_ids', |
| 510 '_removed_ids', | 525 '_removed_ids', |
| 511 ) | 526 ) |
| 512 | 527 |
| 513 def __init__(self, added, removed, similar): | 528 def __init__(self, added, removed, similar, name=None, full_name=None, |
| 529 section_name=None): |
| 514 self._added_ids = set(id(s) for s in added) | 530 self._added_ids = set(id(s) for s in added) |
| 515 self._removed_ids = set(id(s) for s in removed) | 531 self._removed_ids = set(id(s) for s in removed) |
| 516 symbols = [] | 532 symbols = [] |
| 517 symbols.extend(added) | 533 symbols.extend(added) |
| 518 symbols.extend(removed) | 534 symbols.extend(removed) |
| 519 symbols.extend(similar) | 535 symbols.extend(similar) |
| 520 super(SymbolDiff, self).__init__(symbols) | 536 super(SymbolDiff, self).__init__(symbols, name=name, full_name=full_name, |
| 537 section_name=section_name) |
| 521 | 538 |
| 522 def __repr__(self): | 539 def __repr__(self): |
| 523 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( | 540 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( |
| 524 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, | 541 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, |
| 525 self.unchanged_count, self.size) | 542 self.unchanged_count, self.size) |
| 526 | 543 |
| 527 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 544 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
| 528 section_name=None, is_sorted=None): | 545 section_name=None, is_sorted=None): |
| 529 ret = SymbolDiff.__new__(SymbolDiff) | 546 ret = SymbolDiff.__new__(SymbolDiff) |
| 530 # Printing sorts, so fast-path the same symbols case. | 547 # Printing sorts, so fast-path the same symbols case. |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 581 Returns a SymbolDiff when args are of type SymbolGroup. | 598 Returns a SymbolDiff when args are of type SymbolGroup. |
| 582 """ | 599 """ |
| 583 if isinstance(new, SizeInfo): | 600 if isinstance(new, SizeInfo): |
| 584 assert isinstance(old, SizeInfo) | 601 assert isinstance(old, SizeInfo) |
| 585 section_sizes = { | 602 section_sizes = { |
| 586 k:new.section_sizes[k] - v for k, v in old.section_sizes.iteritems()} | 603 k:new.section_sizes[k] - v for k, v in old.section_sizes.iteritems()} |
| 587 symbol_diff = Diff(new.symbols, old.symbols) | 604 symbol_diff = Diff(new.symbols, old.symbols) |
| 588 return SizeInfoDiff(section_sizes, symbol_diff, old.metadata, new.metadata) | 605 return SizeInfoDiff(section_sizes, symbol_diff, old.metadata, new.metadata) |
| 589 | 606 |
| 590 assert isinstance(new, SymbolGroup) and isinstance(old, SymbolGroup) | 607 assert isinstance(new, SymbolGroup) and isinstance(old, SymbolGroup) |
| 608 return _DiffSymbols(new, old) |
| 609 |
| 610 |
| 611 def _NegateAll(symbols): |
| 612 ret = [] |
| 613 for symbol in symbols: |
| 614 if symbol.IsGroup(): |
| 615 duped = SymbolDiff([], _NegateAll(symbol), [], name=symbol.name, |
| 616 full_name=symbol.full_name, |
| 617 section_name=symbol.section_name) |
| 618 else: |
| 619 duped = copy.copy(symbol) |
| 620 duped.size = -duped.size |
| 621 duped.padding = -duped.padding |
| 622 ret.append(duped) |
| 623 return ret |
| 624 |
| 625 |
| 626 def _DiffSymbols(new_group, old_group): |
| 591 symbols_by_key = collections.defaultdict(list) | 627 symbols_by_key = collections.defaultdict(list) |
| 592 for s in old: | 628 for s in old_group: |
| 593 symbols_by_key[s._Key()].append(s) | 629 symbols_by_key[s._Key()].append(s) |
| 594 | 630 |
| 595 added = [] | 631 added = [] |
| 596 removed = [] | |
| 597 similar = [] | 632 similar = [] |
| 598 # For similar symbols, padding is zeroed out. In order to not lose the | 633 # For similar symbols, padding is zeroed out. In order to not lose the |
| 599 # information entirely, store it in aggregate. | 634 # information entirely, store it in aggregate. |
| 600 padding_by_section_name = collections.defaultdict(int) | 635 padding_by_section_name = collections.defaultdict(int) |
| 601 for new_sym in new: | 636 for new_sym in new_group: |
| 602 matching_syms = symbols_by_key.get(new_sym._Key()) | 637 matching_syms = symbols_by_key.get(new_sym._Key()) |
| 603 if matching_syms: | 638 if matching_syms: |
| 604 old_sym = matching_syms.pop(0) | 639 old_sym = matching_syms.pop(0) |
| 605 # More stable/useful to compare size without padding. | 640 if old_sym.IsGroup() and new_sym.IsGroup(): |
| 606 size_diff = (new_sym.size_without_padding - | 641 merged_sym = _DiffSymbols(new_sym, old_sym) |
| 607 old_sym.size_without_padding) | 642 else: |
| 608 merged_sym = Symbol(new_sym.section_name, size_diff, | 643 size_diff = new_sym.size_without_padding - old_sym.size_without_padding |
| 609 address=new_sym.address, name=new_sym.name, | 644 merged_sym = Symbol(new_sym.section_name, size_diff, |
| 610 source_path=new_sym.source_path, | 645 address=new_sym.address, name=new_sym.name, |
| 611 object_path=new_sym.object_path, | 646 source_path=new_sym.source_path, |
| 612 full_name=new_sym.full_name, | 647 object_path=new_sym.object_path, |
| 613 is_anonymous=new_sym.is_anonymous) | 648 full_name=new_sym.full_name, |
| 649 is_anonymous=new_sym.is_anonymous) |
| 650 |
| 651 # Diffs are more stable when comparing size without padding, except when |
| 652 # the symbol is a padding-only symbol. |
| 653 if new_sym.size_without_padding == 0 and size_diff == 0: |
| 654 merged_sym.padding = new_sym.padding - old_sym.padding |
| 655 else: |
| 656 padding_by_section_name[new_sym.section_name] += ( |
| 657 new_sym.padding - old_sym.padding) |
| 658 |
| 614 similar.append(merged_sym) | 659 similar.append(merged_sym) |
| 615 padding_by_section_name[new_sym.section_name] += ( | |
| 616 new_sym.padding - old_sym.padding) | |
| 617 else: | 660 else: |
| 618 added.append(new_sym) | 661 added.append(new_sym) |
| 619 | 662 |
| 663 removed = [] |
| 620 for remaining_syms in symbols_by_key.itervalues(): | 664 for remaining_syms in symbols_by_key.itervalues(): |
| 621 for old_sym in remaining_syms: | 665 if remaining_syms: |
| 622 duped = copy.copy(old_sym) | 666 removed.extend(_NegateAll(remaining_syms)) |
| 623 duped.size = -duped.size | |
| 624 duped.padding = -duped.padding | |
| 625 removed.append(duped) | |
| 626 | 667 |
| 627 for section_name, padding in padding_by_section_name.iteritems(): | 668 for section_name, padding in padding_by_section_name.iteritems(): |
| 628 similar.append(Symbol(section_name, padding, | 669 if padding != 0: |
| 629 name="** aggregate padding of diff'ed symbols")) | 670 similar.append(Symbol(section_name, padding, |
| 630 return SymbolDiff(added, removed, similar) | 671 name="** aggregate padding of diff'ed symbols")) |
| 672 return SymbolDiff(added, removed, similar, name=new_group.name, |
| 673 full_name=new_group.full_name, |
| 674 section_name=new_group.section_name) |
| 631 | 675 |
| 632 | 676 |
| 633 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 677 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
| 634 idx = -len(separator) | 678 idx = -len(separator) |
| 635 prev_idx = None | 679 prev_idx = None |
| 636 for _ in xrange(count): | 680 for _ in xrange(count): |
| 637 idx = string.find(separator, idx + len(separator)) | 681 idx = string.find(separator, idx + len(separator)) |
| 638 if idx < 0: | 682 if idx < 0: |
| 639 break | 683 break |
| 640 prev_idx = idx | 684 prev_idx = idx |
| 641 return string[:prev_idx] | 685 return string[:prev_idx] |
| OLD | NEW |