diff options
author | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-24 19:23:59 +0000 |
---|---|---|
committer | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-24 19:23:59 +0000 |
commit | eb3ad0de43537c5bff297326b11b121c77cc18e6 (patch) | |
tree | 27166dfb4c32812fea4f1b1e2122264c613c87fa /build | |
parent | 71684265df2d4efc314ddca3fba4125be44ef1a1 (diff) | |
download | chromium_src-eb3ad0de43537c5bff297326b11b121c77cc18e6.zip chromium_src-eb3ad0de43537c5bff297326b11b121c77cc18e6.tar.gz chromium_src-eb3ad0de43537c5bff297326b11b121c77cc18e6.tar.bz2 |
Add fast ELF Symbolizer to memory_inspector.
This CL introduces a multiprocess, pipelined and asynchronous ELF
symbolizer (based on addr2line) which gives honor to a bulkly workstation
when symbolizing large batches of symbols.
BUG=340294,339059
Review URL: https://codereview.chromium.org/167893009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@252963 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'build')
-rw-r--r-- | build/android/pylib/symbols/PRESUBMIT.py | 21 | ||||
-rw-r--r-- | build/android/pylib/symbols/__init__.py | 0 | ||||
-rw-r--r-- | build/android/pylib/symbols/elf_symbolizer.py | 382 | ||||
-rwxr-xr-x | build/android/pylib/symbols/elf_symbolizer_unittest.py | 173 | ||||
-rw-r--r-- | build/android/pylib/symbols/mock_addr2line/__init__.py | 0 | ||||
-rwxr-xr-x | build/android/pylib/symbols/mock_addr2line/mock_addr2line | 79 |
6 files changed, 655 insertions, 0 deletions
diff --git a/build/android/pylib/symbols/PRESUBMIT.py b/build/android/pylib/symbols/PRESUBMIT.py new file mode 100644 index 0000000..b4d94ae --- /dev/null +++ b/build/android/pylib/symbols/PRESUBMIT.py @@ -0,0 +1,21 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +def CommonChecks(input_api, output_api): + output = [] + output.extend(input_api.canned_checks.RunPylint(input_api, output_api)) + output.extend(input_api.canned_checks.RunUnitTestsInDirectory( + input_api, + output_api, + input_api.PresubmitLocalPath(), + whitelist=[r'^.+_unittest\.py$'])) + return output + + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api)
\ No newline at end of file diff --git a/build/android/pylib/symbols/__init__.py b/build/android/pylib/symbols/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/build/android/pylib/symbols/__init__.py diff --git a/build/android/pylib/symbols/elf_symbolizer.py b/build/android/pylib/symbols/elf_symbolizer.py new file mode 100644 index 0000000..ffcd22f --- /dev/null +++ b/build/android/pylib/symbols/elf_symbolizer.py @@ -0,0 +1,382 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import collections +import datetime +import logging +import multiprocessing +import os +import posixpath +import Queue +import re +import subprocess +import sys +import threading + + +class ELFSymbolizer(object): + """An uber-fast (multiprocessing, pipelined and asynchronous) ELF symbolizer. + + This class is a frontend for addr2line (part of GNU binutils), designed to + symbolize batches of large numbers of symbols for a given ELF file. It + supports sharding symbolization against many addr2line instances and + pipelining of multiple requests per each instance (in order to hide addr2line + internals and OS pipe latencies). + + The interface exhibited by this class is a very simple asynchronous interface, + which is based on the following three methods: + - SymbolizeAsync(): used to request (enqueue) resolution of a given address. + - The |callback| method: used to communicated back the symbol information. + - Join(): called to conclude the batch to gather the last outstanding results. + In essence, before the Join method returns, this class will have issued as + many callbacks as the number of SymbolizeAsync() calls. In this regard, note + that due to multiprocess sharding, callbacks can be delivered out of order. + + Some background about addr2line: + - it is invoked passing the elf path in the cmdline, piping the addresses in + its stdin and getting results on its stdout. + - it has pretty large response times for the first requests, but it + works very well in streaming mode once it has been warmed up. + - it doesn't scale by itself (on more cores). However, spawning multiple + instances at the same time on the same file is pretty efficient as they + keep hitting the pagecache and become mostly CPU bound. + - it might hang or crash, mostly for OOM. This class deals with both of these + problems. + + Despite the "scary" imports and the multi* words above, (almost) no multi- + threading/processing is involved from the python viewpoint. Concurrency + here is achieved by spawning several addr2line subprocesses and handling their + output pipes asynchronously. Therefore, all the code here (with the exception + of the Queue instance in Addr2Line) should be free from mind-blowing + thread-safety concerns. + + The multiprocess sharding works as follows: + The symbolizer tries to use the lowest number of addr2line instances as + possible (with respect of |max_concurrent_jobs|) and enqueue all the requests + in a single addr2line instance. For few symbols (i.e. dozens) sharding isn't + worth the startup cost. + The multiprocess logic kicks in as soon as the queues for the existing + instances grow. Specifically, once all the existing instances reach the + |max_queue_size| bound, a new addr2line instance is kicked in. + In the case of a very eager producer (i.e. all |max_concurrent_jobs| instances + have a backlog of |max_queue_size|), back-pressure is applied on the caller by + blocking the SymbolizeAsync method. + + This module has been deliberately designed to be dependency free (w.r.t. of + other modules in this project), to allow easy reuse in external projects. + """ + + def __init__(self, elf_file_path, addr2line_path, callback, inlines=False, + max_concurrent_jobs=None, addr2line_timeout=30, max_queue_size=50): + """Args: + elf_file_path: path of the elf file to be symbolized. + addr2line_path: path of the toolchain's addr2line binary. + callback: a callback which will be invoked for each resolved symbol with + the two args (sym_info, callback_arg). The former is an instance of + |ELFSymbolInfo| and contains the symbol information. The latter is an + embedder-provided argument which is passed to SymbolizeAsync(). + inlines: when True, the ELFSymbolInfo will contain also the details about + the outer inlining functions. When False, only the innermost function + will be provided. + max_concurrent_jobs: Max number of addr2line instances spawned. + Parallelize responsibly, addr2line is a memory and I/O monster. + max_queue_size: Max number of outstanding requests per addr2line instance. + addr2line_timeout: Max time (in seconds) to wait for a addr2line response. + After the timeout, the instance will be considered hung and respawned. + """ + assert(os.path.isfile(addr2line_path)), 'Cannot find ' + addr2line_path + self.elf_file_path = elf_file_path + self.addr2line_path = addr2line_path + self.callback = callback + self.inlines = inlines + self.max_concurrent_jobs = (max_concurrent_jobs or + min(multiprocessing.cpu_count(), 4)) + self.max_queue_size = max_queue_size + self.addr2line_timeout = addr2line_timeout + self.requests_counter = 0 # For generating monotonic request IDs. + self._a2l_instances = [] # Up to |max_concurrent_jobs| _Addr2Line inst. + + # Create one addr2line instance. More instances will be created on demand + # (up to |max_concurrent_jobs|) depending on the rate of the requests. + self._CreateNewA2LInstance() + + def SymbolizeAsync(self, addr, callback_arg=None): + """Requests symbolization of a given address. + + This method is not guaranteed to return immediately. It generally does, but + in some scenarios (e.g. all addr2line instances have full queues) it can + block to create back-pressure. + + Args: + addr: address to symbolize. + callback_arg: optional argument which will be passed to the |callback|.""" + assert(isinstance(addr, int)) + + # Process all the symbols that have been resolved in the meanwhile. + # Essentially, this drains all the addr2line(s) out queues. + for a2l_to_purge in self._a2l_instances: + a2l_to_purge.ProcessAllResolvedSymbolsInQueue() + + # Find the best instance according to this logic: + # 1. Find an existing instance with the shortest queue. + # 2. If all of instances' queues are full, but there is room in the pool, + # (i.e. < |max_concurrent_jobs|) create a new instance. + # 3. If there were already |max_concurrent_jobs| instances and all of them + # had full queues, make back-pressure. + + # 1. + def _SortByQueueSizeAndReqID(a2l): + return (a2l.queue_size, a2l.first_request_id) + a2l = min(self._a2l_instances, key=_SortByQueueSizeAndReqID) + + # 2. + if (a2l.queue_size >= self.max_queue_size and + len(self._a2l_instances) < self.max_concurrent_jobs): + a2l = self._CreateNewA2LInstance() + + # 3. + if a2l.queue_size >= self.max_queue_size: + a2l.WaitForNextSymbolInQueue() + + a2l.EnqueueRequest(addr, callback_arg) + + def Join(self): + """Waits for all the outstanding requests to complete and terminates.""" + for a2l in self._a2l_instances: + a2l.WaitForIdle() + a2l.Terminate() + + def _CreateNewA2LInstance(self): + assert(len(self._a2l_instances) < self.max_concurrent_jobs) + a2l = ELFSymbolizer.Addr2Line(self) + self._a2l_instances.append(a2l) + return a2l + + + class Addr2Line(object): + """A python wrapper around an addr2line instance. + + The communication with the addr2line process looks as follows: + [STDIN] [STDOUT] (from addr2line's viewpoint) + > f001111 + > f002222 + < Symbol::Name(foo, bar) for f001111 + < /path/to/source/file.c:line_number + > f003333 + < Symbol::Name2() for f002222 + < /path/to/source/file.c:line_number + < Symbol::Name3() for f003333 + < /path/to/source/file.c:line_number + """ + + SYM_ADDR_RE = re.compile(r'([^:]+):(\?|\d+).*') + + def __init__(self, symbolizer): + self._symbolizer = symbolizer + self._lib_file_name = posixpath.basename(symbolizer.elf_file_path) + + # The request queue (i.e. addresses pushed to addr2line's stdin and not + # yet retrieved on stdout) + self._request_queue = collections.deque() + + # This is essentially len(self._request_queue). It has been optimized to a + # separate field because turned out to be a perf hot-spot. + self.queue_size = 0 + + # Objects required to handle the addr2line subprocess. + self._proc = None # Subprocess.Popen(...) instance. + self._thread = None # Threading.thread instance. + self._out_queue = None # Queue.Queue instance (for buffering a2l stdout). + self._RestartAddr2LineProcess() + + def EnqueueRequest(self, addr, callback_arg): + """Pushes an address to addr2line's stdin (and keeps track of it).""" + self._symbolizer.requests_counter += 1 # For global "age" of requests. + req_idx = self._symbolizer.requests_counter + self._request_queue.append((addr, callback_arg, req_idx)) + self.queue_size += 1 + self._WriteToA2lStdin(addr) + + def WaitForIdle(self): + """Waits until all the pending requests have been symbolized.""" + while self.queue_size > 0: + self.WaitForNextSymbolInQueue() + + def WaitForNextSymbolInQueue(self): + """Waits for the next pending request to be symbolized.""" + if not self.queue_size: + return + + # This outer loop guards against a2l hanging (detecting stdout timeout). + while True: + start_time = datetime.datetime.now() + timeout = datetime.timedelta(seconds=self._symbolizer.addr2line_timeout) + + # The inner loop guards against a2l crashing (checking if it exited). + while (datetime.datetime.now() - start_time < timeout): + # poll() returns !None if the process exited. a2l should never exit. + if self._proc.poll(): + logging.warning('addr2line crashed, respawning (lib: %s).' % + self._lib_file_name) + self._RestartAddr2LineProcess() + # TODO(primiano): the best thing to do in this case would be + # shrinking the pool size as, very likely, addr2line is crashed + # due to low memory (and the respawned one will die again soon). + + try: + lines = self._out_queue.get(block=True, timeout=0.25) + except Queue.Empty: + # On timeout (1/4 s.) repeat the inner loop and check if either the + # addr2line process did crash or we waited its output for too long. + continue + + # In nominal conditions, we get straight to this point. + self._ProcessSymbolOutput(lines) + return + + # If this point is reached, we waited more than |addr2line_timeout|. + logging.warning('Hung addr2line process, respawning (lib: %s).' % + self._lib_file_name) + self._RestartAddr2LineProcess() + + def ProcessAllResolvedSymbolsInQueue(self): + """Consumes all the addr2line output lines produced (without blocking).""" + if not self.queue_size: + return + while True: + try: + lines = self._out_queue.get_nowait() + except Queue.Empty: + break + self._ProcessSymbolOutput(lines) + + def Terminate(self): + """Kills the underlying addr2line process. + + The poller |_thread| will terminate as well due to the broken pipe.""" + try: + self._proc.kill() + self._proc.communicate() # Essentially wait() without risking deadlock. + except Exception: # An exception while terminating? How interesting. + pass + self._proc = None + + def _WriteToA2lStdin(self, addr): + self._proc.stdin.write('%s\n' % hex(addr)) + if self._symbolizer.inlines: + # In the case of inlines we output an extra blank line, which causes + # addr2line to emit a (??,??:0) tuple that we use as a boundary marker. + self._proc.stdin.write('\n') + self._proc.stdin.flush() + + def _ProcessSymbolOutput(self, lines): + """Parses an addr2line symbol output and triggers the client callback.""" + (_, callback_arg, _) = self._request_queue.popleft() + self.queue_size -= 1 + + innermost_sym_info = None + sym_info = None + for (line1, line2) in lines: + prev_sym_info = sym_info + name = line1 if not line1.startswith('?') else None + source_path = None + source_line = None + m = ELFSymbolizer.Addr2Line.SYM_ADDR_RE.match(line2) + if m: + if not m.group(1).startswith('?'): + source_path = m.group(1) + source_line = int(m.group(2)) + else: + logging.warning('Got invalid symbol path from addr2line: %s' % line2) + + sym_info = ELFSymbolInfo(name, source_path, source_line) + if prev_sym_info: + prev_sym_info.inlined_by = sym_info + if not innermost_sym_info: + innermost_sym_info = sym_info + + self._symbolizer.callback(innermost_sym_info, callback_arg) + + def _RestartAddr2LineProcess(self): + if self._proc: + self.Terminate() + + # The only reason of existence of this Queue (and the corresponding + # Thread below) is the lack of a subprocess.stdout.poll_avail_lines(). + # Essentially this is a pipe able to extract a couple of lines atomically. + self._out_queue = Queue.Queue() + + # Start the underlying addr2line process in line buffered mode. + + cmd = [self._symbolizer.addr2line_path, '--functions', '--demangle', + '--exe=' + self._symbolizer.elf_file_path] + if self._symbolizer.inlines: + cmd += ['--inlines'] + self._proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, stderr=sys.stderr, close_fds=True) + + # Start the poller thread, which simply moves atomically the lines read + # from the addr2line's stdout to the |_out_queue|. + self._thread = threading.Thread( + target=ELFSymbolizer.Addr2Line.StdoutReaderThread, + args=(self._proc.stdout, self._out_queue, self._symbolizer.inlines)) + self._thread.daemon = True # Don't prevent early process exit. + self._thread.start() + + # Replay the pending requests on the new process (only for the case + # of a hung addr2line timing out during the game). + for (addr, _, _) in self._request_queue: + self._WriteToA2lStdin(addr) + + @staticmethod + def StdoutReaderThread(process_pipe, queue, inlines): + """The poller thread fn, which moves the addr2line stdout to the |queue|. + + This is the only piece of code not running on the main thread. It merely + writes to a Queue, which is thread-safe. In the case of inlines, it + detects the ??,??:0 marker and sends the lines atomically, such that the + main thread always receives all the lines corresponding to one symbol in + one shot.""" + try: + lines_for_one_symbol = [] + while True: + line1 = process_pipe.readline().rstrip('\r\n') + line2 = process_pipe.readline().rstrip('\r\n') + if not line1 or not line2: + break + inline_has_more_lines = inlines and (len(lines_for_one_symbol) == 0 or + (line1 != '??' and line2 != '??:0')) + if not inlines or inline_has_more_lines: + lines_for_one_symbol += [(line1, line2)] + if inline_has_more_lines: + continue + queue.put(lines_for_one_symbol) + lines_for_one_symbol = [] + process_pipe.close() + + # Every addr2line processes will die at some point, please die silently. + except (IOError, OSError): + pass + + @property + def first_request_id(self): + """Returns the request_id of the oldest pending request in the queue.""" + return self._request_queue[0][2] if self._request_queue else 0 + + +class ELFSymbolInfo(object): + """The result of the symbolization passed as first arg. of each callback.""" + + def __init__(self, name, source_path, source_line): + """All the fields here can be None (if addr2line replies with '??').""" + self.name = name + self.source_path = source_path + self.source_line = source_line + # In the case of |inlines|=True, the |inlined_by| points to the outer + # function inlining the current one (and so on, to form a chain). + self.inlined_by = None + + def __str__(self): + return '%s [%s:%d]' % ( + self.name or '??', self.source_path or '??', self.source_line or 0) diff --git a/build/android/pylib/symbols/elf_symbolizer_unittest.py b/build/android/pylib/symbols/elf_symbolizer_unittest.py new file mode 100755 index 0000000..978e621 --- /dev/null +++ b/build/android/pylib/symbols/elf_symbolizer_unittest.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import functools +import logging +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(__file__)) +import elf_symbolizer +import mock_addr2line + + +_MOCK_A2L_PATH = os.path.join(os.path.dirname(mock_addr2line.__file__), + 'mock_addr2line') +_INCOMPLETE_MOCK_ADDR = 1024 * 1024 +_UNKNOWN_MOCK_ADDR = 2 * 1024 * 1024 +_INLINE_MOCK_ADDR = 3 * 1024 * 1024 + + +class ELFSymbolizerTest(unittest.TestCase): + def setUp(self): + self._callback = functools.partial( + ELFSymbolizerTest._SymbolizeCallback, self) + self._resolved_addresses = set() + # Mute warnings, we expect them due to the crash/hang tests. + logging.getLogger().setLevel(logging.ERROR) + + def testParallelism1(self): + self._RunTest(max_concurrent_jobs=1, num_symbols=100) + + def testParallelism4(self): + self._RunTest(max_concurrent_jobs=4, num_symbols=100) + + def testParallelism8(self): + self._RunTest(max_concurrent_jobs=8, num_symbols=100) + + def testCrash(self): + os.environ['MOCK_A2L_CRASH_EVERY'] = '99' + self._RunTest(max_concurrent_jobs=1, num_symbols=100) + os.environ['MOCK_A2L_CRASH_EVERY'] = '0' + + def testHang(self): + os.environ['MOCK_A2L_HANG_EVERY'] = '99' + self._RunTest(max_concurrent_jobs=1, num_symbols=100) + os.environ['MOCK_A2L_HANG_EVERY'] = '0' + + def testInlines(self): + """Stimulate the inline processing logic.""" + symbolizer = elf_symbolizer.ELFSymbolizer( + elf_file_path='/path/doesnt/matter/mock_lib1.so', + addr2line_path=_MOCK_A2L_PATH, + callback=self._callback, + inlines=True, + max_concurrent_jobs=4) + + for addr in xrange(1000): + exp_inline = False + exp_unknown = False + + # First 100 addresses with inlines. + if addr < 100: + addr += _INLINE_MOCK_ADDR + exp_inline = True + + # Followed by 100 without inlines. + elif addr < 200: + pass + + # Followed by 100 interleaved inlines and not inlines. + elif addr < 300: + if addr & 1: + addr += _INLINE_MOCK_ADDR + exp_inline = True + + # Followed by 100 interleaved inlines and unknonwn. + elif addr < 400: + if addr & 1: + addr += _INLINE_MOCK_ADDR + exp_inline = True + else: + addr += _UNKNOWN_MOCK_ADDR + exp_unknown = True + + exp_name = 'mock_sym_for_addr_%d' % addr if not exp_unknown else None + exp_source_path = 'mock_src/mock_lib1.so.c' if not exp_unknown else None + exp_source_line = addr if not exp_unknown else None + cb_arg = (addr, exp_name, exp_source_path, exp_source_line, exp_inline) + symbolizer.SymbolizeAsync(addr, cb_arg) + + symbolizer.Join() + + def testIncompleteSyminfo(self): + """Stimulate the symbol-not-resolved logic.""" + symbolizer = elf_symbolizer.ELFSymbolizer( + elf_file_path='/path/doesnt/matter/mock_lib1.so', + addr2line_path=_MOCK_A2L_PATH, + callback=self._callback, + max_concurrent_jobs=1) + + # Test symbols with valid name but incomplete path. + addr = _INCOMPLETE_MOCK_ADDR + exp_name = 'mock_sym_for_addr_%d' % addr + exp_source_path = None + exp_source_line = None + cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False) + symbolizer.SymbolizeAsync(addr, cb_arg) + + # Test symbols with no name or sym info. + addr = _UNKNOWN_MOCK_ADDR + exp_name = None + exp_source_path = None + exp_source_line = None + cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False) + symbolizer.SymbolizeAsync(addr, cb_arg) + + symbolizer.Join() + + def _RunTest(self, max_concurrent_jobs, num_symbols): + symbolizer = elf_symbolizer.ELFSymbolizer( + elf_file_path='/path/doesnt/matter/mock_lib1.so', + addr2line_path=_MOCK_A2L_PATH, + callback=self._callback, + max_concurrent_jobs=max_concurrent_jobs, + addr2line_timeout=0.5) + + for addr in xrange(num_symbols): + exp_name = 'mock_sym_for_addr_%d' % addr + exp_source_path = 'mock_src/mock_lib1.so.c' + exp_source_line = addr + cb_arg = (addr, exp_name, exp_source_path, exp_source_line, False) + symbolizer.SymbolizeAsync(addr, cb_arg) + + symbolizer.Join() + + # Check that all the expected callbacks have been received. + for addr in xrange(num_symbols): + self.assertIn(addr, self._resolved_addresses) + self._resolved_addresses.remove(addr) + + # Check for unexpected callbacks. + self.assertEqual(len(self._resolved_addresses), 0) + + def _SymbolizeCallback(self, sym_info, cb_arg): + self.assertTrue(isinstance(sym_info, elf_symbolizer.ELFSymbolInfo)) + self.assertTrue(isinstance(cb_arg, tuple)) + self.assertEqual(len(cb_arg), 5) + + # Unpack expectations from the callback extra argument. + (addr, exp_name, exp_source_path, exp_source_line, exp_inlines) = cb_arg + if exp_name is None: + self.assertIsNone(sym_info.name) + else: + self.assertTrue(sym_info.name.startswith(exp_name)) + self.assertEqual(sym_info.source_path, exp_source_path) + self.assertEqual(sym_info.source_line, exp_source_line) + + if exp_inlines: + self.assertEqual(sym_info.name, exp_name + '_inner') + self.assertEqual(sym_info.inlined_by.name, exp_name + '_middle') + self.assertEqual(sym_info.inlined_by.inlined_by.name, + exp_name + '_outer') + + # Check against duplicate callbacks. + self.assertNotIn(addr, self._resolved_addresses) + self._resolved_addresses.add(addr) + + +if __name__ == '__main__': + unittest.main()
\ No newline at end of file diff --git a/build/android/pylib/symbols/mock_addr2line/__init__.py b/build/android/pylib/symbols/mock_addr2line/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/build/android/pylib/symbols/mock_addr2line/__init__.py diff --git a/build/android/pylib/symbols/mock_addr2line/mock_addr2line b/build/android/pylib/symbols/mock_addr2line/mock_addr2line new file mode 100755 index 0000000..cd58f56 --- /dev/null +++ b/build/android/pylib/symbols/mock_addr2line/mock_addr2line @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Simple mock for addr2line. + +Outputs mock symbol information, with each symbol being a function of the +original address (so it is easy to double-check consistency in unittests). +""" + +import optparse +import os +import posixpath +import sys +import time + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('-e', '--exe', dest='exe') # Path of the debug-library.so. + # Silently swallow the other unnecessary arguments. + parser.add_option('-C', '--demangle', action='store_true') + parser.add_option('-f', '--functions', action='store_true') + parser.add_option('-i', '--inlines', action='store_true') + options, _ = parser.parse_args(argv[1:]) + lib_file_name = posixpath.basename(options.exe) + processed_sym_count = 0 + crash_every = int(os.environ.get('MOCK_A2L_CRASH_EVERY', 0)) + hang_every = int(os.environ.get('MOCK_A2L_HANG_EVERY', 0)) + + while(True): + line = sys.stdin.readline().rstrip('\r') + if not line: + break + + # An empty line should generate '??,??:0' (is used as marker for inlines). + if line == '\n': + print '??' + print '??:0' + sys.stdout.flush() + continue + + addr = int(line, 16) + processed_sym_count += 1 + if crash_every and processed_sym_count % crash_every == 0: + sys.exit(1) + if hang_every and processed_sym_count % hang_every == 0: + time.sleep(1) + + # Addresses < 1M will return good mock symbol information. + if addr < 1024 * 1024: + print 'mock_sym_for_addr_%d' % addr + print 'mock_src/%s.c:%d' % (lib_file_name, addr) + + # Addresses 1M <= x < 2M will return symbols with a name but a missing path. + elif addr < 2 * 1024 * 1024: + print 'mock_sym_for_addr_%d' % addr + print '??:0' + + # Addresses 2M <= x < 3M will return unknown symbol information. + elif addr < 3 * 1024 * 1024: + print '??' + print '??' + + # Addresses 3M <= x < 4M will return inlines. + elif addr < 4 * 1024 * 1024: + print 'mock_sym_for_addr_%d_inner' % addr + print 'mock_src/%s.c:%d' % (lib_file_name, addr) + print 'mock_sym_for_addr_%d_middle' % addr + print 'mock_src/%s.c:%d' % (lib_file_name, addr) + print 'mock_sym_for_addr_%d_outer' % addr + print 'mock_src/%s.c:%d' % (lib_file_name, addr) + + sys.stdout.flush() + + +if __name__ == '__main__': + main(sys.argv)
\ No newline at end of file |