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

Side by Side Diff: build/android/pylib/symbols/elf_symbolizer.py

Issue 339853004: binary_size_tool: fix for ambiguous addr2line output (Closed) Base URL: https://chromium.googlesource.com/chromium/src@master
Patch Set: updated descriptions, rebased, s/os.path.isabs/posixpath.isabs/ Created 6 years, 6 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 | build/android/pylib/symbols/elf_symbolizer_unittest.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 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 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 import collections 5 import collections
6 import datetime 6 import datetime
7 import logging 7 import logging
8 import multiprocessing 8 import multiprocessing
9 import os 9 import os
10 import posixpath 10 import posixpath
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
68 |max_queue_size| bound, a new addr2line instance is kicked in. 68 |max_queue_size| bound, a new addr2line instance is kicked in.
69 In the case of a very eager producer (i.e. all |max_concurrent_jobs| instances 69 In the case of a very eager producer (i.e. all |max_concurrent_jobs| instances
70 have a backlog of |max_queue_size|), back-pressure is applied on the caller by 70 have a backlog of |max_queue_size|), back-pressure is applied on the caller by
71 blocking the SymbolizeAsync method. 71 blocking the SymbolizeAsync method.
72 72
73 This module has been deliberately designed to be dependency free (w.r.t. of 73 This module has been deliberately designed to be dependency free (w.r.t. of
74 other modules in this project), to allow easy reuse in external projects. 74 other modules in this project), to allow easy reuse in external projects.
75 """ 75 """
76 76
77 def __init__(self, elf_file_path, addr2line_path, callback, inlines=False, 77 def __init__(self, elf_file_path, addr2line_path, callback, inlines=False,
78 max_concurrent_jobs=None, addr2line_timeout=30, max_queue_size=50): 78 max_concurrent_jobs=None, addr2line_timeout=30, max_queue_size=50,
79 source_root_path=None, strip_base_path=None):
79 """Args: 80 """Args:
80 elf_file_path: path of the elf file to be symbolized. 81 elf_file_path: path of the elf file to be symbolized.
81 addr2line_path: path of the toolchain's addr2line binary. 82 addr2line_path: path of the toolchain's addr2line binary.
82 callback: a callback which will be invoked for each resolved symbol with 83 callback: a callback which will be invoked for each resolved symbol with
83 the two args (sym_info, callback_arg). The former is an instance of 84 the two args (sym_info, callback_arg). The former is an instance of
84 |ELFSymbolInfo| and contains the symbol information. The latter is an 85 |ELFSymbolInfo| and contains the symbol information. The latter is an
85 embedder-provided argument which is passed to SymbolizeAsync(). 86 embedder-provided argument which is passed to SymbolizeAsync().
86 inlines: when True, the ELFSymbolInfo will contain also the details about 87 inlines: when True, the ELFSymbolInfo will contain also the details about
87 the outer inlining functions. When False, only the innermost function 88 the outer inlining functions. When False, only the innermost function
88 will be provided. 89 will be provided.
89 max_concurrent_jobs: Max number of addr2line instances spawned. 90 max_concurrent_jobs: Max number of addr2line instances spawned.
90 Parallelize responsibly, addr2line is a memory and I/O monster. 91 Parallelize responsibly, addr2line is a memory and I/O monster.
91 max_queue_size: Max number of outstanding requests per addr2line instance. 92 max_queue_size: Max number of outstanding requests per addr2line instance.
92 addr2line_timeout: Max time (in seconds) to wait for a addr2line response. 93 addr2line_timeout: Max time (in seconds) to wait for a addr2line response.
93 After the timeout, the instance will be considered hung and respawned. 94 After the timeout, the instance will be considered hung and respawned.
95 source_root_path: In some toolchains only the name of the source file is
96 is output, without any path information; disambiguation searches
97 through the source directory specified by |source_root_path| argument
98 for files whose name matches, adding the full path information to the
99 output. For example, if the toolchain outputs "unicode.cc" and there
100 is a file called "unicode.cc" located under |source_root_path|/foo,
101 the tool will replace "unicode.cc" with
102 "|source_root_path|/foo/unicode.cc". If there are multiple files with
103 the same name, disambiguation will fail because the tool cannot
104 determine which of the files was the source of the symbol.
105 strip_base_path: Rebases the symbols source paths onto |source_root_path|
106 (i.e replace |strip_base_path| with |source_root_path).
94 """ 107 """
95 assert(os.path.isfile(addr2line_path)), 'Cannot find ' + addr2line_path 108 assert(os.path.isfile(addr2line_path)), 'Cannot find ' + addr2line_path
96 self.elf_file_path = elf_file_path 109 self.elf_file_path = elf_file_path
97 self.addr2line_path = addr2line_path 110 self.addr2line_path = addr2line_path
98 self.callback = callback 111 self.callback = callback
99 self.inlines = inlines 112 self.inlines = inlines
100 self.max_concurrent_jobs = (max_concurrent_jobs or 113 self.max_concurrent_jobs = (max_concurrent_jobs or
101 min(multiprocessing.cpu_count(), 4)) 114 min(multiprocessing.cpu_count(), 4))
102 self.max_queue_size = max_queue_size 115 self.max_queue_size = max_queue_size
103 self.addr2line_timeout = addr2line_timeout 116 self.addr2line_timeout = addr2line_timeout
104 self.requests_counter = 0 # For generating monotonic request IDs. 117 self.requests_counter = 0 # For generating monotonic request IDs.
105 self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst. 118 self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst.
106 119
120 # If necessary, create disambiguation lookup table
121 self.disambiguate = source_root_path is not None
122 self.disambiguation_table = {}
123 self.strip_base_path = strip_base_path
124 if(self.disambiguate):
125 self.source_root_path = os.path.abspath(source_root_path)
126 self._CreateDisambiguationTable()
127
107 # Create one addr2line instance. More instances will be created on demand 128 # Create one addr2line instance. More instances will be created on demand
108 # (up to |max_concurrent_jobs|) depending on the rate of the requests. 129 # (up to |max_concurrent_jobs|) depending on the rate of the requests.
109 self._CreateNewA2LInstance() 130 self._CreateNewA2LInstance()
110 131
111 def SymbolizeAsync(self, addr, callback_arg=None): 132 def SymbolizeAsync(self, addr, callback_arg=None):
112 """Requests symbolization of a given address. 133 """Requests symbolization of a given address.
113 134
114 This method is not guaranteed to return immediately. It generally does, but 135 This method is not guaranteed to return immediately. It generally does, but
115 in some scenarios (e.g. all addr2line instances have full queues) it can 136 in some scenarios (e.g. all addr2line instances have full queues) it can
116 block to create back-pressure. 137 block to create back-pressure.
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
154 for a2l in self._a2l_instances: 175 for a2l in self._a2l_instances:
155 a2l.WaitForIdle() 176 a2l.WaitForIdle()
156 a2l.Terminate() 177 a2l.Terminate()
157 178
158 def _CreateNewA2LInstance(self): 179 def _CreateNewA2LInstance(self):
159 assert(len(self._a2l_instances) < self.max_concurrent_jobs) 180 assert(len(self._a2l_instances) < self.max_concurrent_jobs)
160 a2l = ELFSymbolizer.Addr2Line(self) 181 a2l = ELFSymbolizer.Addr2Line(self)
161 self._a2l_instances.append(a2l) 182 self._a2l_instances.append(a2l)
162 return a2l 183 return a2l
163 184
185 def _CreateDisambiguationTable(self):
186 """ Non-unique file names will result in None entries"""
187 self.disambiguation_table = {}
188
189 for root, _, filenames in os.walk(self.source_root_path):
190 for f in filenames:
191 self.disambiguation_table[f] = os.path.join(root, f) if (f not in
192 self.disambiguation_table) else None
193
164 194
165 class Addr2Line(object): 195 class Addr2Line(object):
166 """A python wrapper around an addr2line instance. 196 """A python wrapper around an addr2line instance.
167 197
168 The communication with the addr2line process looks as follows: 198 The communication with the addr2line process looks as follows:
169 [STDIN] [STDOUT] (from addr2line's viewpoint) 199 [STDIN] [STDOUT] (from addr2line's viewpoint)
170 > f001111 200 > f001111
171 > f002222 201 > f002222
172 < Symbol::Name(foo, bar) for f001111 202 < Symbol::Name(foo, bar) for f001111
173 < /path/to/source/file.c:line_number 203 < /path/to/source/file.c:line_number
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
305 source_line = None 335 source_line = None
306 m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2) 336 m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2)
307 if m: 337 if m:
308 if not m.group(1).startswith('?'): 338 if not m.group(1).startswith('?'):
309 source_path = m.group(1) 339 source_path = m.group(1)
310 if not m.group(2).startswith('?'): 340 if not m.group(2).startswith('?'):
311 source_line = int(m.group(2)) 341 source_line = int(m.group(2))
312 else: 342 else:
313 logging.warning('Got invalid symbol path from addr2line: %s' % line2) 343 logging.warning('Got invalid symbol path from addr2line: %s' % line2)
314 344
315 sym_info = ELFSymbolInfo(name, source_path, source_line) 345 # In case disambiguation is on, and needed
346 was_ambiguous = False
347 disambiguated = False
348 if self._symbolizer.disambiguate:
349 if source_path and not posixpath.isabs(source_path):
350 path = self._symbolizer.disambiguation_table.get(source_path)
351 was_ambiguous = True
352 disambiguated = path is not None
353 source_path = path if disambiguated else source_path
354
355 # Use absolute paths (so that paths are consistent, as disambiguation
356 # uses absolute paths)
357 if source_path and not was_ambiguous:
358 source_path = os.path.abspath(source_path)
359
360 if source_path and self._symbolizer.strip_base_path:
361 # Strip the base path
362 source_path = re.sub('^' + self._symbolizer.strip_base_path,
363 self._symbolizer.source_root_path or '', source_path)
364
365 sym_info = ELFSymbolInfo(name, source_path, source_line, was_ambiguous,
366 disambiguated)
316 if prev_sym_info: 367 if prev_sym_info:
317 prev_sym_info.inlined_by = sym_info 368 prev_sym_info.inlined_by = sym_info
318 if not innermost_sym_info: 369 if not innermost_sym_info:
319 innermost_sym_info = sym_info 370 innermost_sym_info = sym_info
320 371
321 self._processed_symbols_count += 1 372 self._processed_symbols_count += 1
322 self._symbolizer.callback(innermost_sym_info, callback_arg) 373 self._symbolizer.callback(innermost_sym_info, callback_arg)
323 374
324 def _RestartAddr2LineProcess(self): 375 def _RestartAddr2LineProcess(self):
325 if self._proc: 376 if self._proc:
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 437
387 @property 438 @property
388 def first_request_id(self): 439 def first_request_id(self):
389 """Returns the request_id of the oldest pending request in the queue.""" 440 """Returns the request_id of the oldest pending request in the queue."""
390 return self._request_queue[0][2] if self._request_queue else 0 441 return self._request_queue[0][2] if self._request_queue else 0
391 442
392 443
393 class ELFSymbolInfo(object): 444 class ELFSymbolInfo(object):
394 """The result of the symbolization passed as first arg. of each callback.""" 445 """The result of the symbolization passed as first arg. of each callback."""
395 446
396 def __init__(self, name, source_path, source_line): 447 def __init__(self, name, source_path, source_line, was_ambiguous=False,
448 disambiguated=False):
397 """All the fields here can be None (if addr2line replies with '??').""" 449 """All the fields here can be None (if addr2line replies with '??')."""
398 self.name = name 450 self.name = name
399 self.source_path = source_path 451 self.source_path = source_path
400 self.source_line = source_line 452 self.source_line = source_line
401 # In the case of |inlines|=True, the |inlined_by| points to the outer 453 # In the case of |inlines|=True, the |inlined_by| points to the outer
402 # function inlining the current one (and so on, to form a chain). 454 # function inlining the current one (and so on, to form a chain).
403 self.inlined_by = None 455 self.inlined_by = None
456 self.disambiguated = disambiguated
457 self.was_ambiguous = was_ambiguous
404 458
405 def __str__(self): 459 def __str__(self):
406 return '%s [%s:%d]' % ( 460 return '%s [%s:%d]' % (
407 self.name or '??', self.source_path or '??', self.source_line or 0) 461 self.name or '??', self.source_path or '??', self.source_line or 0)
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/symbols/elf_symbolizer_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698