| OLD | NEW |
| 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 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 115 min(multiprocessing.cpu_count(), 4)) | 115 min(multiprocessing.cpu_count(), 4)) |
| 116 self.max_queue_size = max_queue_size | 116 self.max_queue_size = max_queue_size |
| 117 self.addr2line_timeout = addr2line_timeout | 117 self.addr2line_timeout = addr2line_timeout |
| 118 self.requests_counter = 0 # For generating monotonic request IDs. | 118 self.requests_counter = 0 # For generating monotonic request IDs. |
| 119 self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst. | 119 self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst. |
| 120 | 120 |
| 121 # If necessary, create disambiguation lookup table | 121 # If necessary, create disambiguation lookup table |
| 122 self.disambiguate = source_root_path is not None | 122 self.disambiguate = source_root_path is not None |
| 123 self.disambiguation_table = {} | 123 self.disambiguation_table = {} |
| 124 self.strip_base_path = strip_base_path | 124 self.strip_base_path = strip_base_path |
| 125 if(self.disambiguate): | 125 if self.disambiguate: |
| 126 self.source_root_path = os.path.abspath(source_root_path) | 126 self.source_root_path = os.path.abspath(source_root_path) |
| 127 self._CreateDisambiguationTable() | 127 self._CreateDisambiguationTable() |
| 128 | 128 |
| 129 # Create one addr2line instance. More instances will be created on demand | 129 # Create one addr2line instance. More instances will be created on demand |
| 130 # (up to |max_concurrent_jobs|) depending on the rate of the requests. | 130 # (up to |max_concurrent_jobs|) depending on the rate of the requests. |
| 131 self._CreateNewA2LInstance() | 131 self._CreateNewA2LInstance() |
| 132 | 132 |
| 133 def SymbolizeAsync(self, addr, callback_arg=None): | 133 def SymbolizeAsync(self, addr, callback_arg=None): |
| 134 """Requests symbolization of a given address. | 134 """Requests symbolization of a given address. |
| 135 | 135 |
| 136 This method is not guaranteed to return immediately. It generally does, but | 136 This method is not guaranteed to return immediately. It generally does, but |
| 137 in some scenarios (e.g. all addr2line instances have full queues) it can | 137 in some scenarios (e.g. all addr2line instances have full queues) it can |
| 138 block to create back-pressure. | 138 block to create back-pressure. |
| 139 | 139 |
| 140 Args: | 140 Args: |
| 141 addr: address to symbolize. | 141 addr: address to symbolize. |
| 142 callback_arg: optional argument which will be passed to the |callback|.""" | 142 callback_arg: optional argument which will be passed to the |callback|.""" |
| 143 assert(isinstance(addr, int)) | 143 assert isinstance(addr, int) |
| 144 | 144 |
| 145 # Process all the symbols that have been resolved in the meanwhile. | 145 # Process all the symbols that have been resolved in the meanwhile. |
| 146 # Essentially, this drains all the addr2line(s) out queues. | 146 # Essentially, this drains all the addr2line(s) out queues. |
| 147 for a2l_to_purge in self._a2l_instances: | 147 for a2l_to_purge in self._a2l_instances: |
| 148 a2l_to_purge.ProcessAllResolvedSymbolsInQueue() | 148 a2l_to_purge.ProcessAllResolvedSymbolsInQueue() |
| 149 a2l_to_purge.RecycleIfNecessary() | 149 a2l_to_purge.RecycleIfNecessary() |
| 150 | 150 |
| 151 # Find the best instance according to this logic: | 151 # Find the best instance according to this logic: |
| 152 # 1. Find an existing instance with the shortest queue. | 152 # 1. Find an existing instance with the shortest queue. |
| 153 # 2. If all of instances' queues are full, but there is room in the pool, | 153 # 2. If all of instances' queues are full, but there is room in the pool, |
| (...skipping 17 matching lines...) Expand all Loading... |
| 171 | 171 |
| 172 a2l.EnqueueRequest(addr, callback_arg) | 172 a2l.EnqueueRequest(addr, callback_arg) |
| 173 | 173 |
| 174 def Join(self): | 174 def Join(self): |
| 175 """Waits for all the outstanding requests to complete and terminates.""" | 175 """Waits for all the outstanding requests to complete and terminates.""" |
| 176 for a2l in self._a2l_instances: | 176 for a2l in self._a2l_instances: |
| 177 a2l.WaitForIdle() | 177 a2l.WaitForIdle() |
| 178 a2l.Terminate() | 178 a2l.Terminate() |
| 179 | 179 |
| 180 def _CreateNewA2LInstance(self): | 180 def _CreateNewA2LInstance(self): |
| 181 assert(len(self._a2l_instances) < self.max_concurrent_jobs) | 181 assert len(self._a2l_instances) < self.max_concurrent_jobs |
| 182 a2l = ELFSymbolizer.Addr2Line(self) | 182 a2l = ELFSymbolizer.Addr2Line(self) |
| 183 self._a2l_instances.append(a2l) | 183 self._a2l_instances.append(a2l) |
| 184 return a2l | 184 return a2l |
| 185 | 185 |
| 186 def _CreateDisambiguationTable(self): | 186 def _CreateDisambiguationTable(self): |
| 187 """ Non-unique file names will result in None entries""" | 187 """ Non-unique file names will result in None entries""" |
| 188 start_time = time.time() | 188 start_time = time.time() |
| 189 logging.info('Collecting information about available source files...') | 189 logging.info('Collecting information about available source files...') |
| 190 self.disambiguation_table = {} | 190 self.disambiguation_table = {} |
| 191 | 191 |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 255 """Waits for the next pending request to be symbolized.""" | 255 """Waits for the next pending request to be symbolized.""" |
| 256 if not self.queue_size: | 256 if not self.queue_size: |
| 257 return | 257 return |
| 258 | 258 |
| 259 # This outer loop guards against a2l hanging (detecting stdout timeout). | 259 # This outer loop guards against a2l hanging (detecting stdout timeout). |
| 260 while True: | 260 while True: |
| 261 start_time = datetime.datetime.now() | 261 start_time = datetime.datetime.now() |
| 262 timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) | 262 timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) |
| 263 | 263 |
| 264 # The inner loop guards against a2l crashing (checking if it exited). | 264 # The inner loop guards against a2l crashing (checking if it exited). |
| 265 while (datetime.datetime.now() - start_time < timeout): | 265 while datetime.datetime.now() - start_time < timeout: |
| 266 # poll() returns !None if the process exited. a2l should never exit. | 266 # poll() returns !None if the process exited. a2l should never exit. |
| 267 if self._proc.poll(): | 267 if self._proc.poll(): |
| 268 logging.warning('addr2line crashed, respawning (lib: %s).' % | 268 logging.warning('addr2line crashed, respawning (lib: %s).', |
| 269 self._lib_file_name) | 269 self._lib_file_name) |
| 270 self._RestartAddr2LineProcess() | 270 self._RestartAddr2LineProcess() |
| 271 # TODO(primiano): the best thing to do in this case would be | 271 # TODO(primiano): the best thing to do in this case would be |
| 272 # shrinking the pool size as, very likely, addr2line is crashed | 272 # shrinking the pool size as, very likely, addr2line is crashed |
| 273 # due to low memory (and the respawned one will die again soon). | 273 # due to low memory (and the respawned one will die again soon). |
| 274 | 274 |
| 275 try: | 275 try: |
| 276 lines = self._out_queue.get(block=True, timeout=0.25) | 276 lines = self._out_queue.get(block=True, timeout=0.25) |
| 277 except Queue.Empty: | 277 except Queue.Empty: |
| 278 # On timeout (1/4 s.) repeat the inner loop and check if either the | 278 # On timeout (1/4 s.) repeat the inner loop and check if either the |
| 279 # addr2line process did crash or we waited its output for too long. | 279 # addr2line process did crash or we waited its output for too long. |
| 280 continue | 280 continue |
| 281 | 281 |
| 282 # In nominal conditions, we get straight to this point. | 282 # In nominal conditions, we get straight to this point. |
| 283 self._ProcessSymbolOutput(lines) | 283 self._ProcessSymbolOutput(lines) |
| 284 return | 284 return |
| 285 | 285 |
| 286 # If this point is reached, we waited more than |addr2line_timeout|. | 286 # If this point is reached, we waited more than |addr2line_timeout|. |
| 287 logging.warning('Hung addr2line process, respawning (lib: %s).' % | 287 logging.warning('Hung addr2line process, respawning (lib: %s).', |
| 288 self._lib_file_name) | 288 self._lib_file_name) |
| 289 self._RestartAddr2LineProcess() | 289 self._RestartAddr2LineProcess() |
| 290 | 290 |
| 291 def ProcessAllResolvedSymbolsInQueue(self): | 291 def ProcessAllResolvedSymbolsInQueue(self): |
| 292 """Consumes all the addr2line output lines produced (without blocking).""" | 292 """Consumes all the addr2line output lines produced (without blocking).""" |
| 293 if not self.queue_size: | 293 if not self.queue_size: |
| 294 return | 294 return |
| 295 while True: | 295 while True: |
| 296 try: | 296 try: |
| 297 lines = self._out_queue.get_nowait() | 297 lines = self._out_queue.get_nowait() |
| (...skipping 10 matching lines...) Expand all Loading... |
| 308 self._RestartAddr2LineProcess() | 308 self._RestartAddr2LineProcess() |
| 309 | 309 |
| 310 | 310 |
| 311 def Terminate(self): | 311 def Terminate(self): |
| 312 """Kills the underlying addr2line process. | 312 """Kills the underlying addr2line process. |
| 313 | 313 |
| 314 The poller |_thread| will terminate as well due to the broken pipe.""" | 314 The poller |_thread| will terminate as well due to the broken pipe.""" |
| 315 try: | 315 try: |
| 316 self._proc.kill() | 316 self._proc.kill() |
| 317 self._proc.communicate() # Essentially wait() without risking deadlock. | 317 self._proc.communicate() # Essentially wait() without risking deadlock. |
| 318 except Exception: # An exception while terminating? How interesting. | 318 except Exception: # pylint: disable=broad-except |
| 319 # An exception while terminating? How interesting. |
| 319 pass | 320 pass |
| 320 self._proc = None | 321 self._proc = None |
| 321 | 322 |
| 322 def _WriteToA2lStdin(self, addr): | 323 def _WriteToA2lStdin(self, addr): |
| 323 self._proc.stdin.write('%s\n' % hex(addr)) | 324 self._proc.stdin.write('%s\n' % hex(addr)) |
| 324 if self._symbolizer.inlines: | 325 if self._symbolizer.inlines: |
| 325 # In the case of inlines we output an extra blank line, which causes | 326 # In the case of inlines we output an extra blank line, which causes |
| 326 # addr2line to emit a (??,??:0) tuple that we use as a boundary marker. | 327 # addr2line to emit a (??,??:0) tuple that we use as a boundary marker. |
| 327 self._proc.stdin.write('\n') | 328 self._proc.stdin.write('\n') |
| 328 self._proc.stdin.flush() | 329 self._proc.stdin.flush() |
| (...skipping 10 matching lines...) Expand all Loading... |
| 339 name = line1 if not line1.startswith('?') else None | 340 name = line1 if not line1.startswith('?') else None |
| 340 source_path = None | 341 source_path = None |
| 341 source_line = None | 342 source_line = None |
| 342 m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2) | 343 m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2) |
| 343 if m: | 344 if m: |
| 344 if not m.group(1).startswith('?'): | 345 if not m.group(1).startswith('?'): |
| 345 source_path = m.group(1) | 346 source_path = m.group(1) |
| 346 if not m.group(2).startswith('?'): | 347 if not m.group(2).startswith('?'): |
| 347 source_line = int(m.group(2)) | 348 source_line = int(m.group(2)) |
| 348 else: | 349 else: |
| 349 logging.warning('Got invalid symbol path from addr2line: %s' % line2) | 350 logging.warning('Got invalid symbol path from addr2line: %s', line2) |
| 350 | 351 |
| 351 # In case disambiguation is on, and needed | 352 # In case disambiguation is on, and needed |
| 352 was_ambiguous = False | 353 was_ambiguous = False |
| 353 disambiguated = False | 354 disambiguated = False |
| 354 if self._symbolizer.disambiguate: | 355 if self._symbolizer.disambiguate: |
| 355 if source_path and not posixpath.isabs(source_path): | 356 if source_path and not posixpath.isabs(source_path): |
| 356 path = self._symbolizer.disambiguation_table.get(source_path) | 357 path = self._symbolizer.disambiguation_table.get(source_path) |
| 357 was_ambiguous = True | 358 was_ambiguous = True |
| 358 disambiguated = path is not None | 359 disambiguated = path is not None |
| 359 source_path = path if disambiguated else source_path | 360 source_path = path if disambiguated else source_path |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 458 self.source_line = source_line | 459 self.source_line = source_line |
| 459 # In the case of |inlines|=True, the |inlined_by| points to the outer | 460 # In the case of |inlines|=True, the |inlined_by| points to the outer |
| 460 # function inlining the current one (and so on, to form a chain). | 461 # function inlining the current one (and so on, to form a chain). |
| 461 self.inlined_by = None | 462 self.inlined_by = None |
| 462 self.disambiguated = disambiguated | 463 self.disambiguated = disambiguated |
| 463 self.was_ambiguous = was_ambiguous | 464 self.was_ambiguous = was_ambiguous |
| 464 | 465 |
| 465 def __str__(self): | 466 def __str__(self): |
| 466 return '%s [%s:%d]' % ( | 467 return '%s [%s:%d]' % ( |
| 467 self.name or '??', self.source_path or '??', self.source_line or 0) | 468 self.name or '??', self.source_path or '??', self.source_line or 0) |
| OLD | NEW |