| 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). |
| 11 * size: The number of bytes this symbol takes up, including padding that comes | 11 * size: The number of bytes this symbol takes up, including padding that comes |
| 12 before |address|. | 12 before |address|. |
| 13 * num_aliases: The number of symbols with the same address (including self). |
| 14 * pss: size / num_aliases. |
| 13 * padding: The number of bytes of padding before |address| due to this symbol. | 15 * padding: The number of bytes of padding before |address| due to this symbol. |
| 14 * name: Symbol names with parameter list removed. | 16 * name: Symbol names with parameter list removed. |
| 15 Never None, but will be '' for anonymous symbols. | 17 Never None, but will be '' for anonymous symbols. |
| 16 * full_name: Symbols names with parameter list left in. | 18 * full_name: Symbols names with parameter list left in. |
| 17 Never None, but will be '' for anonymous symbols, and for symbols that do | 19 Never None, but will be '' for anonymous symbols, and for symbols that do |
| 18 not contain a parameter list. | 20 not contain a parameter list. |
| 19 * is_anonymous: True when the symbol exists in an anonymous namespace (which | 21 * is_anonymous: True when the symbol exists in an anonymous namespace (which |
| 20 are removed from both full_name and name during normalization). | 22 are removed from both full_name and name during normalization). |
| 21 * section_name: E.g. ".text", ".rodata", ".data.rel.local" | 23 * section_name: E.g. ".text", ".rodata", ".data.rel.local" |
| 22 * section: The second character of |section_name|. E.g. "t", "r", "d". | 24 * section: The second character of |section_name|. E.g. "t", "r", "d". |
| 23 """ | 25 """ |
| 24 | 26 |
| 25 import collections | 27 import collections |
| 26 import copy | 28 import logging |
| 27 import os | 29 import os |
| 28 import re | 30 import re |
| 29 | 31 |
| 30 import match_util | 32 import match_util |
| 31 | 33 |
| 32 | 34 |
| 33 METADATA_GIT_REVISION = 'git_revision' | 35 METADATA_GIT_REVISION = 'git_revision' |
| 34 METADATA_APK_FILENAME = 'apk_file_name' # Path relative to output_directory. | 36 METADATA_APK_FILENAME = 'apk_file_name' # Path relative to output_directory. |
| 35 METADATA_MAP_FILENAME = 'map_file_name' # Path relative to output_directory. | 37 METADATA_MAP_FILENAME = 'map_file_name' # Path relative to output_directory. |
| 36 METADATA_ELF_ARCHITECTURE = 'elf_arch' # "Machine" field from readelf -h | 38 METADATA_ELF_ARCHITECTURE = 'elf_arch' # "Machine" field from readelf -h |
| (...skipping 10 matching lines...) Expand all Loading... |
| 47 't': '.text', | 49 't': '.text', |
| 48 } | 50 } |
| 49 | 51 |
| 50 FLAG_ANONYMOUS = 1 | 52 FLAG_ANONYMOUS = 1 |
| 51 FLAG_STARTUP = 2 | 53 FLAG_STARTUP = 2 |
| 52 FLAG_UNLIKELY = 4 | 54 FLAG_UNLIKELY = 4 |
| 53 FLAG_REL = 8 | 55 FLAG_REL = 8 |
| 54 FLAG_REL_LOCAL = 16 | 56 FLAG_REL_LOCAL = 16 |
| 55 | 57 |
| 56 | 58 |
| 59 def _StripCloneSuffix(name): |
| 60 clone_idx = name.find(' [clone ') |
| 61 if clone_idx != -1: |
| 62 return name[:clone_idx] |
| 63 return name |
| 64 |
| 65 |
| 57 class SizeInfo(object): | 66 class SizeInfo(object): |
| 58 """Represents all size information for a single binary. | 67 """Represents all size information for a single binary. |
| 59 | 68 |
| 60 Fields: | 69 Fields: |
| 61 section_sizes: A dict of section_name -> size. | 70 section_sizes: A dict of section_name -> size. |
| 62 raw_symbols: A flat list of all symbols. | 71 raw_symbols: A flat list of all symbols. |
| 63 symbols: A SymbolGroup containing raw_symbols, but with some Symbols grouped | 72 symbols: A SymbolGroup containing raw_symbols, but with some Symbols grouped |
| 64 into sub-SymbolGroups. | 73 into sub-SymbolGroups. |
| 65 metadata: A dict. | 74 metadata: A dict. |
| 66 """ | 75 """ |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 125 return self.size - self.padding | 134 return self.size - self.padding |
| 126 | 135 |
| 127 @property | 136 @property |
| 128 def end_address(self): | 137 def end_address(self): |
| 129 return self.address + self.size_without_padding | 138 return self.address + self.size_without_padding |
| 130 | 139 |
| 131 @property | 140 @property |
| 132 def is_anonymous(self): | 141 def is_anonymous(self): |
| 133 return bool(self.flags & FLAG_ANONYMOUS) | 142 return bool(self.flags & FLAG_ANONYMOUS) |
| 134 | 143 |
| 144 @property |
| 145 def num_aliases(self): |
| 146 return len(self.aliases) if self.aliases else 1 |
| 147 |
| 135 def FlagsString(self): | 148 def FlagsString(self): |
| 136 # Most flags are 0. | 149 # Most flags are 0. |
| 137 flags = self.flags | 150 flags = self.flags |
| 138 if not flags: | 151 if not flags and not self.aliases: |
| 139 return '{}' | 152 return '{}' |
| 140 parts = [] | 153 parts = [] |
| 141 if flags & FLAG_ANONYMOUS: | 154 if flags & FLAG_ANONYMOUS: |
| 142 parts.append('anon') | 155 parts.append('anon') |
| 143 if flags & FLAG_STARTUP: | 156 if flags & FLAG_STARTUP: |
| 144 parts.append('startup') | 157 parts.append('startup') |
| 145 if flags & FLAG_UNLIKELY: | 158 if flags & FLAG_UNLIKELY: |
| 146 parts.append('unlikely') | 159 parts.append('unlikely') |
| 147 if flags & FLAG_REL: | 160 if flags & FLAG_REL: |
| 148 parts.append('rel') | 161 parts.append('rel') |
| 149 if flags & FLAG_REL_LOCAL: | 162 if flags & FLAG_REL_LOCAL: |
| 150 parts.append('rel.loc') | 163 parts.append('rel.loc') |
| 164 # Not actually a part of flags, but useful to show it here. |
| 165 if self.aliases: |
| 166 parts.append('{} aliases'.format(self.num_aliases)) |
| 151 return '{%s}' % ','.join(parts) | 167 return '{%s}' % ','.join(parts) |
| 152 | 168 |
| 153 def IsBss(self): | 169 def IsBss(self): |
| 154 return self.section_name == '.bss' | 170 return self.section_name == '.bss' |
| 155 | 171 |
| 156 def IsGroup(self): | 172 def IsGroup(self): |
| 157 return False | 173 return False |
| 158 | 174 |
| 159 def IsGenerated(self): | 175 def IsGenerated(self): |
| 160 # TODO(agrieve): Also match generated functions such as: | 176 # TODO(agrieve): Also match generated functions such as: |
| 161 # startup._GLOBAL__sub_I_page_allocator.cc | 177 # startup._GLOBAL__sub_I_page_allocator.cc |
| 162 return self.name.endswith(']') and not self.name.endswith('[]') | 178 return self.name.endswith(']') and not self.name.endswith('[]') |
| 163 | 179 |
| 164 def _Key(self): | 180 def _Key(self): |
| 165 """Returns a tuple that can be used to see if two Symbol are the same. | 181 """Returns a tuple that can be used to see if two Symbol are the same. |
| 166 | 182 |
| 167 Keys are not guaranteed to be unique within a SymbolGroup. For example, it | 183 Keys are not guaranteed to be unique within a SymbolGroup. For example, it |
| 168 is common to have multiple "** merge strings" symbols, which will have a | 184 is common to have multiple "** merge strings" symbols, which will have a |
| 169 common key.""" | 185 common key.""" |
| 170 stripped_full_name = self.full_name | 186 stripped_full_name = self.full_name |
| 171 if stripped_full_name: | 187 if stripped_full_name: |
| 172 clone_idx = stripped_full_name.find(' [clone ') | 188 stripped_full_name = _StripCloneSuffix(stripped_full_name) |
| 173 if clone_idx != -1: | |
| 174 stripped_full_name = stripped_full_name[:clone_idx] | |
| 175 return (self.section_name, stripped_full_name or self.name) | 189 return (self.section_name, stripped_full_name or self.name) |
| 176 | 190 |
| 177 | 191 |
| 178 class Symbol(BaseSymbol): | 192 class Symbol(BaseSymbol): |
| 179 """Represents a single symbol within a binary. | 193 """Represents a single symbol within a binary. |
| 180 | 194 |
| 181 Refer to module docs for field descriptions. | 195 Refer to module docs for field descriptions. |
| 182 """ | 196 """ |
| 183 | 197 |
| 184 __slots__ = ( | 198 __slots__ = ( |
| 185 'address', | 199 'address', |
| 186 'full_name', | 200 'full_name', |
| 187 'flags', | 201 'flags', |
| 188 'object_path', | 202 'object_path', |
| 189 'name', | 203 'name', |
| 204 'aliases', |
| 190 'padding', | 205 'padding', |
| 191 'section_name', | 206 'section_name', |
| 192 'source_path', | 207 'source_path', |
| 193 'size', | 208 'size', |
| 194 ) | 209 ) |
| 195 | 210 |
| 196 def __init__(self, section_name, size_without_padding, address=None, | 211 def __init__(self, section_name, size_without_padding, address=None, |
| 197 name=None, source_path=None, object_path=None, full_name=None, | 212 name=None, source_path=None, object_path=None, full_name=None, |
| 198 flags=0): | 213 flags=0, aliases=None): |
| 199 self.section_name = section_name | 214 self.section_name = section_name |
| 200 self.address = address or 0 | 215 self.address = address or 0 |
| 201 self.name = name or '' | 216 self.name = name or '' |
| 202 self.full_name = full_name or '' | 217 self.full_name = full_name or '' |
| 203 self.source_path = source_path or '' | 218 self.source_path = source_path or '' |
| 204 self.object_path = object_path or '' | 219 self.object_path = object_path or '' |
| 205 self.size = size_without_padding | 220 self.size = size_without_padding |
| 206 self.flags = flags | 221 self.flags = flags |
| 222 self.aliases = aliases |
| 207 self.padding = 0 | 223 self.padding = 0 |
| 208 | 224 |
| 209 def __repr__(self): | 225 def __repr__(self): |
| 210 return ('%s@%x(size_without_padding=%d,padding=%d,name=%s,path=%s,flags=%s)' | 226 template = ('{}@{:x}(size_without_padding={},padding={},name={},' |
| 211 % (self.section_name, self.address, self.size_without_padding, | 227 'object_path={},source_path={},flags={})') |
| 212 self.padding, self.name, self.source_path or self.object_path, | 228 return template.format( |
| 213 self.FlagsString())) | 229 self.section_name, self.address, self.size_without_padding, |
| 230 self.padding, self.name, self.object_path, self.source_path, |
| 231 self.FlagsString()) |
| 232 |
| 233 @property |
| 234 def pss(self): |
| 235 return float(self.size) / self.num_aliases |
| 236 |
| 237 @property |
| 238 def pss_without_padding(self): |
| 239 return float(self.size_without_padding) / self.num_aliases |
| 214 | 240 |
| 215 | 241 |
| 216 class SymbolGroup(BaseSymbol): | 242 class SymbolGroup(BaseSymbol): |
| 217 """Represents a group of symbols using the same interface as Symbol. | 243 """Represents a group of symbols using the same interface as Symbol. |
| 218 | 244 |
| 219 SymbolGroups are immutable. All filtering / sorting will return new | 245 SymbolGroups are immutable. All filtering / sorting will return new |
| 220 SymbolGroups objects. | 246 SymbolGroups objects. |
| 221 | 247 |
| 222 Overrides many __functions__. E.g. the following are all valid: | 248 Overrides many __functions__. E.g. the following are all valid: |
| 223 * len(group) | 249 * len(group) |
| 224 * iter(group) | 250 * iter(group) |
| 225 * group[0] | 251 * group[0] |
| 226 * group['0x1234'] # By symbol address | 252 * group['0x1234'] # By symbol address |
| 227 * without_group2 = group1 - group2 | 253 * without_group2 = group1 - group2 |
| 228 * unioned = group1 + group2 | 254 * unioned = group1 + group2 |
| 229 """ | 255 """ |
| 230 | 256 |
| 231 __slots__ = ( | 257 __slots__ = ( |
| 232 '_padding', | 258 '_padding', |
| 233 '_size', | 259 '_size', |
| 260 '_pss', |
| 234 '_symbols', | 261 '_symbols', |
| 235 '_filtered_symbols', | 262 '_filtered_symbols', |
| 236 'full_name', | 263 'full_name', |
| 237 'name', | 264 'name', |
| 238 'section_name', | 265 'section_name', |
| 239 'is_sorted', | 266 'is_sorted', |
| 240 ) | 267 ) |
| 241 | 268 |
| 242 def __init__(self, symbols, filtered_symbols=None, name=None, | 269 def __init__(self, symbols, filtered_symbols=None, name=None, |
| 243 full_name=None, section_name=None, is_sorted=False): | 270 full_name=None, section_name=None, is_sorted=False): |
| 244 self._padding = None | 271 self._padding = None |
| 245 self._size = None | 272 self._size = None |
| 273 self._pss = None |
| 246 self._symbols = symbols | 274 self._symbols = symbols |
| 247 self._filtered_symbols = filtered_symbols or [] | 275 self._filtered_symbols = filtered_symbols or [] |
| 248 self.name = name or '' | 276 self.name = name or '' |
| 249 self.full_name = full_name | 277 self.full_name = full_name |
| 250 self.section_name = section_name or '.*' | 278 self.section_name = section_name or '.*' |
| 251 self.is_sorted = is_sorted | 279 self.is_sorted = is_sorted |
| 252 | 280 |
| 253 def __repr__(self): | 281 def __repr__(self): |
| 254 return 'Group(name=%s,count=%d,size=%d)' % ( | 282 return 'Group(name=%s,count=%d,size=%d)' % ( |
| 255 self.name, len(self), self.size) | 283 self.name, len(self), self.size) |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 295 return first if all(s.address == first for s in self._symbols) else 0 | 323 return first if all(s.address == first for s in self._symbols) else 0 |
| 296 | 324 |
| 297 @property | 325 @property |
| 298 def flags(self): | 326 def flags(self): |
| 299 first = self._symbols[0].flags | 327 first = self._symbols[0].flags |
| 300 return first if all(s.flags == first for s in self._symbols) else 0 | 328 return first if all(s.flags == first for s in self._symbols) else 0 |
| 301 | 329 |
| 302 @property | 330 @property |
| 303 def object_path(self): | 331 def object_path(self): |
| 304 first = self._symbols[0].object_path | 332 first = self._symbols[0].object_path |
| 305 return first if all(s.object_path == first for s in self._symbols) else None | 333 return first if all(s.object_path == first for s in self._symbols) else '' |
| 306 | 334 |
| 307 @property | 335 @property |
| 308 def source_path(self): | 336 def source_path(self): |
| 309 first = self._symbols[0].source_path | 337 first = self._symbols[0].source_path |
| 310 return first if all(s.source_path == first for s in self._symbols) else None | 338 return first if all(s.source_path == first for s in self._symbols) else '' |
| 339 |
| 340 def IterUniqueSymbols(self): |
| 341 seen_aliases_lists = set() |
| 342 for s in self: |
| 343 if not s.aliases: |
| 344 yield s |
| 345 elif id(s.aliases) not in seen_aliases_lists: |
| 346 seen_aliases_lists.add(id(s.aliases)) |
| 347 yield s |
| 311 | 348 |
| 312 @property | 349 @property |
| 313 def size(self): | 350 def size(self): |
| 314 if self._size is None: | 351 if self._size is None: |
| 315 if self.IsBss(): | 352 if self.IsBss(): |
| 316 self._size = sum(s.size for s in self) | 353 self._size = sum(s.size for s in self) |
| 317 self._size = sum(s.size for s in self if not s.IsBss()) | 354 else: |
| 355 self._size = sum(s.size for s in self.IterUniqueSymbols()) |
| 318 return self._size | 356 return self._size |
| 319 | 357 |
| 320 @property | 358 @property |
| 359 def pss(self): |
| 360 if self._pss is None: |
| 361 if self.IsBss(): |
| 362 self._pss = self.size |
| 363 else: |
| 364 self._pss = sum(s.pss for s in self) |
| 365 return self._pss |
| 366 |
| 367 @property |
| 321 def padding(self): | 368 def padding(self): |
| 322 if self._padding is None: | 369 if self._padding is None: |
| 323 self._padding = sum(s.padding for s in self) | 370 self._padding = sum(s.padding for s in self.IterUniqueSymbols()) |
| 324 return self._padding | 371 return self._padding |
| 325 | 372 |
| 373 @property |
| 374 def aliases(self): |
| 375 return None |
| 376 |
| 326 def IsGroup(self): | 377 def IsGroup(self): |
| 327 return True | 378 return True |
| 328 | 379 |
| 329 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 380 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
| 330 section_name=None, is_sorted=None): | 381 section_name=None, is_sorted=None): |
| 331 if is_sorted is None: | 382 if is_sorted is None: |
| 332 is_sorted = self.is_sorted | 383 is_sorted = self.is_sorted |
| 333 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, | 384 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, |
| 334 section_name=section_name, is_sorted=is_sorted) | 385 section_name=section_name, is_sorted=is_sorted) |
| 335 | 386 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 349 | 400 |
| 350 def SortedByAddress(self, reverse=False): | 401 def SortedByAddress(self, reverse=False): |
| 351 return self.Sorted(key=(lambda s:s.address), reverse=reverse) | 402 return self.Sorted(key=(lambda s:s.address), reverse=reverse) |
| 352 | 403 |
| 353 def SortedByCount(self, reverse=False): | 404 def SortedByCount(self, reverse=False): |
| 354 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), | 405 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), |
| 355 reverse=not reverse) | 406 reverse=not reverse) |
| 356 | 407 |
| 357 def Filter(self, func): | 408 def Filter(self, func): |
| 358 filtered_and_kept = ([], []) | 409 filtered_and_kept = ([], []) |
| 359 for symbol in self: | 410 symbol = None |
| 360 filtered_and_kept[int(bool(func(symbol)))].append(symbol) | 411 try: |
| 412 for symbol in self: |
| 413 filtered_and_kept[int(bool(func(symbol)))].append(symbol) |
| 414 except: |
| 415 logging.warning('Filter failed on symbol %r', symbol) |
| 416 raise |
| 417 |
| 361 return self._CreateTransformed(filtered_and_kept[1], | 418 return self._CreateTransformed(filtered_and_kept[1], |
| 362 filtered_symbols=filtered_and_kept[0], | 419 filtered_symbols=filtered_and_kept[0], |
| 363 section_name=self.section_name) | 420 section_name=self.section_name) |
| 364 | 421 |
| 365 def WhereBiggerThan(self, min_size): | 422 def WhereBiggerThan(self, min_size): |
| 366 return self.Filter(lambda s: s.size >= min_size) | 423 return self.Filter(lambda s: s.size >= min_size) |
| 367 | 424 |
| 368 def WhereInSection(self, section): | 425 def WhereInSection(self, section): |
| 369 if len(section) == 1: | 426 if len(section) == 1: |
| 370 ret = self.Filter(lambda s: s.section == section) | 427 ret = self.Filter(lambda s: s.section == section) |
| (...skipping 237 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 608 key = id(sym) | 665 key = id(sym) |
| 609 return key not in self._added_ids and key not in self._removed_ids | 666 return key not in self._added_ids and key not in self._removed_ids |
| 610 | 667 |
| 611 def IsRemoved(self, sym): | 668 def IsRemoved(self, sym): |
| 612 return id(sym) in self._removed_ids | 669 return id(sym) in self._removed_ids |
| 613 | 670 |
| 614 def WhereNotUnchanged(self): | 671 def WhereNotUnchanged(self): |
| 615 return self.Filter(lambda s: not self.IsSimilar(s) or s.size) | 672 return self.Filter(lambda s: not self.IsSimilar(s) or s.size) |
| 616 | 673 |
| 617 | 674 |
| 618 def Diff(before, after): | |
| 619 """Diffs two SizeInfo or SymbolGroup objects. | |
| 620 | |
| 621 When diffing SizeInfos, a SizeInfoDiff is returned. | |
| 622 When diffing SymbolGroups, a SymbolDiff is returned. | |
| 623 | |
| 624 Returns: | |
| 625 Returns a SizeInfo when args are of type SizeInfo. | |
| 626 Returns a SymbolDiff when args are of type SymbolGroup. | |
| 627 """ | |
| 628 if isinstance(after, SizeInfo): | |
| 629 assert isinstance(before, SizeInfo) | |
| 630 section_sizes = {k: after.section_sizes[k] - v | |
| 631 for k, v in before.section_sizes.iteritems()} | |
| 632 symbol_diff = _DiffSymbols(before.symbols, after.symbols) | |
| 633 return SizeInfoDiff(section_sizes, symbol_diff, before.metadata, | |
| 634 after.metadata) | |
| 635 | |
| 636 assert isinstance(after, SymbolGroup) and isinstance(before, SymbolGroup) | |
| 637 return _DiffSymbols(before, after) | |
| 638 | |
| 639 | |
| 640 def _NegateAll(symbols): | |
| 641 ret = [] | |
| 642 for symbol in symbols: | |
| 643 if symbol.IsGroup(): | |
| 644 duped = SymbolDiff([], _NegateAll(symbol), [], name=symbol.name, | |
| 645 full_name=symbol.full_name, | |
| 646 section_name=symbol.section_name) | |
| 647 else: | |
| 648 duped = copy.copy(symbol) | |
| 649 duped.size = -duped.size | |
| 650 duped.padding = -duped.padding | |
| 651 ret.append(duped) | |
| 652 return ret | |
| 653 | |
| 654 | |
| 655 def _DiffSymbols(before, after): | |
| 656 symbols_by_key = collections.defaultdict(list) | |
| 657 for s in before: | |
| 658 symbols_by_key[s._Key()].append(s) | |
| 659 | |
| 660 added = [] | |
| 661 similar = [] | |
| 662 # For similar symbols, padding is zeroed out. In order to not lose the | |
| 663 # information entirely, store it in aggregate. | |
| 664 padding_by_section_name = collections.defaultdict(int) | |
| 665 for after_sym in after: | |
| 666 matching_syms = symbols_by_key.get(after_sym._Key()) | |
| 667 if matching_syms: | |
| 668 before_sym = matching_syms.pop(0) | |
| 669 if before_sym.IsGroup() and after_sym.IsGroup(): | |
| 670 merged_sym = _DiffSymbols(before_sym, after_sym) | |
| 671 else: | |
| 672 size_diff = (after_sym.size_without_padding - | |
| 673 before_sym.size_without_padding) | |
| 674 merged_sym = Symbol(after_sym.section_name, size_diff, | |
| 675 address=after_sym.address, name=after_sym.name, | |
| 676 source_path=after_sym.source_path, | |
| 677 object_path=after_sym.object_path, | |
| 678 full_name=after_sym.full_name, | |
| 679 flags=after_sym.flags) | |
| 680 | |
| 681 # Diffs are more stable when comparing size without padding, except when | |
| 682 # the symbol is a padding-only symbol. | |
| 683 if after_sym.size_without_padding == 0 and size_diff == 0: | |
| 684 merged_sym.padding = after_sym.padding - before_sym.padding | |
| 685 else: | |
| 686 padding_by_section_name[after_sym.section_name] += ( | |
| 687 after_sym.padding - before_sym.padding) | |
| 688 | |
| 689 similar.append(merged_sym) | |
| 690 else: | |
| 691 added.append(after_sym) | |
| 692 | |
| 693 removed = [] | |
| 694 for remaining_syms in symbols_by_key.itervalues(): | |
| 695 if remaining_syms: | |
| 696 removed.extend(_NegateAll(remaining_syms)) | |
| 697 | |
| 698 for section_name, padding in padding_by_section_name.iteritems(): | |
| 699 if padding != 0: | |
| 700 similar.append(Symbol(section_name, padding, | |
| 701 name="** aggregate padding of diff'ed symbols")) | |
| 702 return SymbolDiff(added, removed, similar, name=after.name, | |
| 703 full_name=after.full_name, | |
| 704 section_name=after.section_name) | |
| 705 | |
| 706 | |
| 707 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 675 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
| 708 idx = -len(separator) | 676 idx = -len(separator) |
| 709 prev_idx = None | 677 prev_idx = None |
| 710 for _ in xrange(count): | 678 for _ in xrange(count): |
| 711 idx = string.find(separator, idx + len(separator)) | 679 idx = string.find(separator, idx + len(separator)) |
| 712 if idx < 0: | 680 if idx < 0: |
| 713 break | 681 break |
| 714 prev_idx = idx | 682 prev_idx = idx |
| 715 return string[:prev_idx] | 683 return string[:prev_idx] |
| OLD | NEW |