| 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 import collections | 6 import collections |
| 7 import copy | 7 import copy |
| 8 import os | 8 import os |
| 9 import re | 9 import re |
| 10 | 10 |
| 11 import match_util |
| 12 |
| 11 | 13 |
| 12 SECTION_TO_SECTION_NAME = { | 14 SECTION_TO_SECTION_NAME = { |
| 13 'b': '.bss', | 15 'b': '.bss', |
| 14 'd': '.data', | 16 'd': '.data', |
| 15 'r': '.rodata', | 17 'r': '.rodata', |
| 16 't': '.text', | 18 't': '.text', |
| 17 } | 19 } |
| 18 | 20 |
| 19 | 21 |
| 20 class SizeInfo(object): | 22 class SizeInfo(object): |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 82 | 84 |
| 83 class Symbol(BaseSymbol): | 85 class Symbol(BaseSymbol): |
| 84 """Represents a single symbol within a binary.""" | 86 """Represents a single symbol within a binary.""" |
| 85 | 87 |
| 86 __slots__ = ( | 88 __slots__ = ( |
| 87 'address', | 89 'address', |
| 88 'full_name', | 90 'full_name', |
| 89 'is_anonymous', | 91 'is_anonymous', |
| 90 'object_path', | 92 'object_path', |
| 91 'name', | 93 'name', |
| 92 'flags', | |
| 93 'padding', | 94 'padding', |
| 94 'section_name', | 95 'section_name', |
| 95 'source_path', | 96 'source_path', |
| 96 'size', | 97 'size', |
| 97 ) | 98 ) |
| 98 | 99 |
| 99 def __init__(self, section_name, size_without_padding, address=None, | 100 def __init__(self, section_name, size_without_padding, address=None, |
| 100 name=None, source_path=None, object_path=None, | 101 name=None, source_path=None, object_path=None, |
| 101 full_name=None, is_anonymous=False): | 102 full_name=None, is_anonymous=False): |
| 102 self.section_name = section_name | 103 self.section_name = section_name |
| (...skipping 13 matching lines...) Expand all Loading... |
| 116 self.section_name, self.address, self.size_without_padding, | 117 self.section_name, self.address, self.size_without_padding, |
| 117 self.padding, self.name, self.source_path or self.object_path, | 118 self.padding, self.name, self.source_path or self.object_path, |
| 118 int(self.is_anonymous)) | 119 int(self.is_anonymous)) |
| 119 | 120 |
| 120 | 121 |
| 121 class SymbolGroup(BaseSymbol): | 122 class SymbolGroup(BaseSymbol): |
| 122 """Represents a group of symbols using the same interface as Symbol. | 123 """Represents a group of symbols using the same interface as Symbol. |
| 123 | 124 |
| 124 SymbolGroups are immutable. All filtering / sorting will return new | 125 SymbolGroups are immutable. All filtering / sorting will return new |
| 125 SymbolGroups objects. | 126 SymbolGroups objects. |
| 127 |
| 128 Overrides many __functions__. E.g. the following are all valid: |
| 129 * len(group) |
| 130 * iter(group) |
| 131 * group[0] |
| 132 * group['0x1234'] # By symbol address |
| 133 * without_group2 = group1 - group2 |
| 134 * unioned = group1 + group2 |
| 126 """ | 135 """ |
| 127 | 136 |
| 128 __slots__ = ( | 137 __slots__ = ( |
| 129 'symbols', | 138 '_padding', |
| 130 'filtered_symbols', | 139 '_size', |
| 140 '_symbols', |
| 141 '_filtered_symbols', |
| 131 'name', | 142 'name', |
| 132 'section_name', | 143 'section_name', |
| 144 'is_sorted', |
| 133 ) | 145 ) |
| 134 | 146 |
| 135 def __init__(self, symbols, filtered_symbols=None, name=None, | 147 def __init__(self, symbols, filtered_symbols=None, name=None, |
| 136 section_name=None): | 148 section_name=None, is_sorted=False): |
| 137 self.symbols = symbols | 149 self._padding = None |
| 138 self.filtered_symbols = filtered_symbols or [] | 150 self._size = None |
| 151 self._symbols = symbols |
| 152 self._filtered_symbols = filtered_symbols or [] |
| 139 self.name = name or '' | 153 self.name = name or '' |
| 140 self.section_name = section_name or '.*' | 154 self.section_name = section_name or '.*' |
| 155 self.is_sorted = is_sorted |
| 141 | 156 |
| 142 def __repr__(self): | 157 def __repr__(self): |
| 143 return 'Group(name=%s,count=%d,size=%d)' % ( | 158 return 'Group(name=%s,count=%d,size=%d)' % ( |
| 144 self.name, len(self), self.size) | 159 self.name, len(self), self.size) |
| 145 | 160 |
| 146 def __iter__(self): | 161 def __iter__(self): |
| 147 return iter(self.symbols) | 162 return iter(self._symbols) |
| 148 | 163 |
| 149 def __len__(self): | 164 def __len__(self): |
| 150 return len(self.symbols) | 165 return len(self._symbols) |
| 151 | 166 |
| 152 def __getitem__(self, index): | 167 def __eq__(self, other): |
| 153 return self.symbols[index] | 168 return self._symbols == other._symbols |
| 169 |
| 170 def __getitem__(self, key): |
| 171 """|key| can be an index or an address. |
| 172 |
| 173 Raises if multiple symbols map to the address. |
| 174 """ |
| 175 if isinstance(key, slice): |
| 176 return self._symbols.__getitem__(key) |
| 177 if isinstance(key, basestring) or key > len(self._symbols): |
| 178 found = self.WhereAddressInRange(key) |
| 179 if len(found) != 1: |
| 180 raise KeyError('%d symbols found at address %s.' % (len(found), key)) |
| 181 return found[0] |
| 182 return self._symbols[key] |
| 154 | 183 |
| 155 def __sub__(self, other): | 184 def __sub__(self, other): |
| 156 other_ids = set(id(s) for s in other) | 185 if other.IsGroup(): |
| 186 other_ids = set(id(s) for s in other) |
| 187 else: |
| 188 other_ids = set((id(other),)) |
| 157 new_symbols = [s for s in self if id(s) not in other_ids] | 189 new_symbols = [s for s in self if id(s) not in other_ids] |
| 158 return self._CreateTransformed(new_symbols, section_name=self.section_name) | 190 return self._CreateTransformed(new_symbols, section_name=self.section_name) |
| 159 | 191 |
| 160 def __add__(self, other): | 192 def __add__(self, other): |
| 161 self_ids = set(id(s) for s in self) | 193 self_ids = set(id(s) for s in self) |
| 162 new_symbols = self.symbols + [s for s in other if id(s) not in self_ids] | 194 new_symbols = self._symbols + [s for s in other if id(s) not in self_ids] |
| 163 return self._CreateTransformed(new_symbols, section_name=self.section_name) | 195 return self._CreateTransformed(new_symbols, section_name=self.section_name, |
| 196 is_sorted=False) |
| 164 | 197 |
| 165 @property | 198 @property |
| 166 def address(self): | 199 def address(self): |
| 167 return 0 | 200 return 0 |
| 168 | 201 |
| 169 @property | 202 @property |
| 170 def full_name(self): | 203 def full_name(self): |
| 171 return None | 204 return None |
| 172 | 205 |
| 173 @property | 206 @property |
| 174 def is_anonymous(self): | 207 def is_anonymous(self): |
| 175 return False | 208 return False |
| 176 | 209 |
| 177 @property | 210 @property |
| 178 def source_path(self): | 211 def source_path(self): |
| 179 return None | 212 return None |
| 180 | 213 |
| 181 @property | 214 @property |
| 182 def size(self): | 215 def size(self): |
| 183 if self.IsBss(): | 216 if self._size is None: |
| 184 return sum(s.size for s in self) | 217 if self.IsBss(): |
| 185 return sum(s.size for s in self if not s.IsBss()) | 218 self._size = sum(s.size for s in self) |
| 219 self._size = sum(s.size for s in self if not s.IsBss()) |
| 220 return self._size |
| 186 | 221 |
| 187 @property | 222 @property |
| 188 def padding(self): | 223 def padding(self): |
| 189 return sum(s.padding for s in self) | 224 if self._padding is None: |
| 225 self._padding = sum(s.padding for s in self) |
| 226 return self._padding |
| 190 | 227 |
| 191 def IsGroup(self): | 228 def IsGroup(self): |
| 192 return True | 229 return True |
| 193 | 230 |
| 194 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 231 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
| 195 section_name=None): | 232 section_name=None, is_sorted=None): |
| 233 if is_sorted is None: |
| 234 is_sorted = self.is_sorted |
| 196 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, | 235 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, |
| 197 section_name=section_name) | 236 section_name=section_name, is_sorted=is_sorted) |
| 198 | 237 |
| 199 def Sorted(self, cmp_func=None, key=None, reverse=False): | 238 def Sorted(self, cmp_func=None, key=None, reverse=False): |
| 200 # Default to sorting by abs(size) then name. | 239 # Default to sorting by abs(size) then name. |
| 201 if cmp_func is None and key is None: | 240 if cmp_func is None and key is None: |
| 202 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name), | 241 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name), |
| 203 (b.IsBss(), abs(a.size), b.name)) | 242 (b.IsBss(), abs(a.size), b.name)) |
| 204 | 243 |
| 205 new_symbols = sorted(self.symbols, cmp_func, key, reverse) | 244 new_symbols = sorted(self._symbols, cmp_func, key, reverse) |
| 206 return self._CreateTransformed(new_symbols, | 245 return self._CreateTransformed( |
| 207 filtered_symbols=self.filtered_symbols, | 246 new_symbols, filtered_symbols=self._filtered_symbols, |
| 208 section_name=self.section_name) | 247 section_name=self.section_name, is_sorted=True) |
| 209 | 248 |
| 210 def SortedByName(self, reverse=False): | 249 def SortedByName(self, reverse=False): |
| 211 return self.Sorted(key=(lambda s:s.name), reverse=reverse) | 250 return self.Sorted(key=(lambda s:s.name), reverse=reverse) |
| 212 | 251 |
| 213 def SortedByAddress(self, reverse=False): | 252 def SortedByAddress(self, reverse=False): |
| 214 return self.Sorted(key=(lambda s:s.address), reverse=reverse) | 253 return self.Sorted(key=(lambda s:s.address), reverse=reverse) |
| 215 | 254 |
| 216 def SortedByCount(self, reverse=False): | 255 def SortedByCount(self, reverse=False): |
| 217 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), | 256 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), |
| 218 reverse=not reverse) | 257 reverse=not reverse) |
| (...skipping 15 matching lines...) Expand all Loading... |
| 234 ret.section_name = SECTION_TO_SECTION_NAME[section] | 273 ret.section_name = SECTION_TO_SECTION_NAME[section] |
| 235 else: | 274 else: |
| 236 ret = self.Filter(lambda s: s.section_name == section) | 275 ret = self.Filter(lambda s: s.section_name == section) |
| 237 ret.section_name = section | 276 ret.section_name = section |
| 238 return ret | 277 return ret |
| 239 | 278 |
| 240 def WhereIsGenerated(self): | 279 def WhereIsGenerated(self): |
| 241 return self.Filter(lambda s: s.IsGenerated()) | 280 return self.Filter(lambda s: s.IsGenerated()) |
| 242 | 281 |
| 243 def WhereNameMatches(self, pattern): | 282 def WhereNameMatches(self, pattern): |
| 244 regex = re.compile(pattern) | 283 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
| 245 return self.Filter(lambda s: regex.search(s.name)) | 284 return self.Filter(lambda s: regex.search(s.name)) |
| 246 | 285 |
| 247 def WhereObjectPathMatches(self, pattern): | 286 def WhereObjectPathMatches(self, pattern): |
| 248 regex = re.compile(pattern) | 287 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
| 249 return self.Filter(lambda s: regex.search(s.object_path)) | 288 return self.Filter(lambda s: regex.search(s.object_path)) |
| 250 | 289 |
| 251 def WhereSourcePathMatches(self, pattern): | 290 def WhereSourcePathMatches(self, pattern): |
| 252 regex = re.compile(pattern) | 291 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
| 253 return self.Filter(lambda s: regex.search(s.source_path)) | 292 return self.Filter(lambda s: regex.search(s.source_path)) |
| 254 | 293 |
| 255 def WherePathMatches(self, pattern): | 294 def WherePathMatches(self, pattern): |
| 256 regex = re.compile(pattern) | 295 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
| 257 return self.Filter(lambda s: regex.search(s.source_path or s.object_path)) | 296 return self.Filter(lambda s: (regex.search(s.source_path) or |
| 297 regex.search(s.object_path))) |
| 258 | 298 |
| 259 def WhereAddressInRange(self, start, end): | 299 def WhereMatches(self, pattern): |
| 260 return self.Filter(lambda s: s.address >= start and s.address <= end) | 300 """Looks for |pattern| within all paths & names.""" |
| 301 regex = re.compile(match_util.ExpandRegexIdentifierPlaceholder(pattern)) |
| 302 return self.Filter(lambda s: (regex.search(s.source_path) or |
| 303 regex.search(s.object_path) or |
| 304 regex.search(s.full_name or '') or |
| 305 regex.search(s.name))) |
| 306 |
| 307 def WhereAddressInRange(self, start, end=None): |
| 308 """Searches for addesses within [start, end). |
| 309 |
| 310 Args may be ints or hex strings. Default value for |end| is |start| + 1. |
| 311 """ |
| 312 if isinstance(start, basestring): |
| 313 start = int(start, 16) |
| 314 if end is None: |
| 315 end = start + 1 |
| 316 return self.Filter(lambda s: s.address >= start and s.address < end) |
| 261 | 317 |
| 262 def WhereHasAnyAttribution(self): | 318 def WhereHasAnyAttribution(self): |
| 263 return self.Filter(lambda s: s.name or s.source_path or s.object_path) | 319 return self.Filter(lambda s: s.name or s.source_path or s.object_path) |
| 264 | 320 |
| 265 def Inverted(self): | 321 def Inverted(self): |
| 266 return self._CreateTransformed(self.filtered_symbols, | 322 """Returns the symbols that were filtered out by the previous filter. |
| 267 filtered_symbols=self.symbols) | 323 |
| 324 Applies only when the previous call was a filter. |
| 325 |
| 326 Example: |
| 327 # Symbols that do not have "third_party" in their path. |
| 328 symbols.WherePathMatches(r'third_party').Inverted() |
| 329 # Symbols within third_party that do not contain the string "foo". |
| 330 symbols.WherePathMatches(r'third_party').WhereMatches('foo').Inverted() |
| 331 """ |
| 332 return self._CreateTransformed( |
| 333 self._filtered_symbols, filtered_symbols=self._symbols, is_sorted=False) |
| 268 | 334 |
| 269 def GroupBy(self, func, min_count=0): | 335 def GroupBy(self, func, min_count=0): |
| 270 """Returns a SymbolGroup of SymbolGroups, indexed by |func|. | 336 """Returns a SymbolGroup of SymbolGroups, indexed by |func|. |
| 271 | 337 |
| 272 Args: | 338 Args: |
| 273 func: Grouping function. Passed a symbol and returns a string for the | 339 func: Grouping function. Passed a symbol and returns a string for the |
| 274 name of the subgroup to put the symbol in. If None is returned, the | 340 name of the subgroup to put the symbol in. If None is returned, the |
| 275 symbol is omitted. | 341 symbol is omitted. |
| 276 min_count: Miniumum number of symbols for a group. If fewer than this many | 342 min_count: Miniumum number of symbols for a group. If fewer than this many |
| 277 symbols end up in a group, they will not be put within a group. | 343 symbols end up in a group, they will not be put within a group. |
| 278 Use a negative value to omit symbols entirely rather than | 344 Use a negative value to omit symbols entirely rather than |
| 279 include them outside of a group. | 345 include them outside of a group. |
| 280 """ | 346 """ |
| 281 new_syms = [] | 347 new_syms = [] |
| 282 filtered_symbols = [] | 348 filtered_symbols = [] |
| 283 symbols_by_token = collections.defaultdict(list) | 349 symbols_by_token = collections.defaultdict(list) |
| 284 # Index symbols by |func|. | 350 # Index symbols by |func|. |
| 285 for symbol in self: | 351 for symbol in self: |
| 286 token = func(symbol) | 352 token = func(symbol) |
| 287 if token is None: | 353 if token is None: |
| 288 filtered_symbols.append(symbol) | 354 filtered_symbols.append(symbol) |
| 289 symbols_by_token[token].append(symbol) | 355 symbols_by_token[token].append(symbol) |
| 290 # Create the subgroups. | 356 # Create the subgroups. |
| 291 include_singles = min_count >= 0 | 357 include_singles = min_count >= 0 |
| 292 min_count = abs(min_count) | 358 min_count = abs(min_count) |
| 293 for token, symbols in symbols_by_token.iteritems(): | 359 for token, symbols in symbols_by_token.iteritems(): |
| 294 if len(symbols) >= min_count: | 360 if len(symbols) >= min_count: |
| 295 new_syms.append(self._CreateTransformed(symbols, name=token, | 361 new_syms.append(self._CreateTransformed( |
| 296 section_name=self.section_name)) | 362 symbols, name=token, section_name=self.section_name, |
| 363 is_sorted=False)) |
| 297 elif include_singles: | 364 elif include_singles: |
| 298 new_syms.extend(symbols) | 365 new_syms.extend(symbols) |
| 299 else: | 366 else: |
| 300 filtered_symbols.extend(symbols) | 367 filtered_symbols.extend(symbols) |
| 301 return self._CreateTransformed(new_syms, filtered_symbols=filtered_symbols, | 368 return self._CreateTransformed( |
| 302 section_name=self.section_name) | 369 new_syms, filtered_symbols=filtered_symbols, |
| 370 section_name=self.section_name, is_sorted=False) |
| 303 | 371 |
| 304 def GroupBySectionName(self): | 372 def GroupBySectionName(self): |
| 305 return self.GroupBy(lambda s: s.section_name) | 373 return self.GroupBy(lambda s: s.section_name) |
| 306 | 374 |
| 307 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): | 375 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): |
| 308 """Groups by symbol namespace (as denoted by ::s). | 376 """Groups by symbol namespace (as denoted by ::s). |
| 309 | 377 |
| 310 Does not differentiate between C++ namespaces and C++ classes. | 378 Does not differentiate between C++ namespaces and C++ classes. |
| 311 | 379 |
| 312 Args: | 380 Args: |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 395 symbols.extend(removed) | 463 symbols.extend(removed) |
| 396 symbols.extend(similar) | 464 symbols.extend(similar) |
| 397 super(SymbolDiff, self).__init__(symbols) | 465 super(SymbolDiff, self).__init__(symbols) |
| 398 | 466 |
| 399 def __repr__(self): | 467 def __repr__(self): |
| 400 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( | 468 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( |
| 401 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, | 469 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, |
| 402 self.unchanged_count, self.size) | 470 self.unchanged_count, self.size) |
| 403 | 471 |
| 404 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 472 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
| 405 section_name=None): | 473 section_name=None, is_sorted=None): |
| 406 ret = SymbolDiff.__new__(SymbolDiff) | 474 ret = SymbolDiff.__new__(SymbolDiff) |
| 407 # Printing sorts, so fast-path the same symbols case. | 475 # Printing sorts, so fast-path the same symbols case. |
| 408 if len(symbols) == len(self.symbols): | 476 if len(symbols) == len(self._symbols): |
| 409 ret._added_ids = self._added_ids | 477 ret._added_ids = self._added_ids |
| 410 ret._removed_ids = self._removed_ids | 478 ret._removed_ids = self._removed_ids |
| 411 else: | 479 else: |
| 412 ret._added_ids = set(id(s) for s in symbols if self.IsAdded(s)) | 480 ret._added_ids = set(id(s) for s in symbols if self.IsAdded(s)) |
| 413 ret._removed_ids = set(id(s) for s in symbols if self.IsRemoved(s)) | 481 ret._removed_ids = set(id(s) for s in symbols if self.IsRemoved(s)) |
| 414 super(SymbolDiff, ret).__init__(symbols, filtered_symbols=filtered_symbols, | 482 super(SymbolDiff, ret).__init__( |
| 415 name=name, section_name=section_name) | 483 symbols, filtered_symbols=filtered_symbols, name=name, |
| 416 | 484 section_name=section_name, is_sorted=is_sorted) |
| 417 return ret | 485 return ret |
| 418 | 486 |
| 419 @property | 487 @property |
| 420 def added_count(self): | 488 def added_count(self): |
| 421 return len(self._added_ids) | 489 return len(self._added_ids) |
| 422 | 490 |
| 423 @property | 491 @property |
| 424 def removed_count(self): | 492 def removed_count(self): |
| 425 return len(self._removed_ids) | 493 return len(self._removed_ids) |
| 426 | 494 |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 511 | 579 |
| 512 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 580 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
| 513 idx = -len(separator) | 581 idx = -len(separator) |
| 514 prev_idx = None | 582 prev_idx = None |
| 515 for _ in xrange(count): | 583 for _ in xrange(count): |
| 516 idx = string.find(separator, idx + len(separator)) | 584 idx = string.find(separator, idx + len(separator)) |
| 517 if idx < 0: | 585 if idx < 0: |
| 518 break | 586 break |
| 519 prev_idx = idx | 587 prev_idx = idx |
| 520 return string[:prev_idx] | 588 return string[:prev_idx] |
| OLD | NEW |