diff options
author | Yabin Cui <yabinc@google.com> | 2019-08-30 15:51:33 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2019-08-30 15:51:33 -0700 |
commit | 5422a2d3ec6eec3b9e6cd73ccdda4d7f807678e7 (patch) | |
tree | 5eba5fee896e6f8f596b1ef0a7e34205824092af | |
parent | 3562ed57ed683252de85d75c4a3683305dd61920 (diff) | |
download | extras-5422a2d3ec6eec3b9e6cd73ccdda4d7f807678e7.tar.gz |
simpleperf: switch to llvm-symbolizer.
Bug: 74395415
Test: run test.py.
Change-Id: I4cc0013735bedcfd18813428e4892049277c38ff
-rwxr-xr-x | simpleperf/scripts/pprof_proto_generator.py | 4 | ||||
-rwxr-xr-x | simpleperf/scripts/test.py | 4 | ||||
-rw-r--r-- | simpleperf/scripts/utils.py | 65 |
3 files changed, 44 insertions, 29 deletions
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py index 4c59cc56..6487a66e 100755 --- a/simpleperf/scripts/pprof_proto_generator.py +++ b/simpleperf/scripts/pprof_proto_generator.py @@ -442,8 +442,8 @@ class PprofProfileGenerator(object): if not self.config.get('binary_cache_dir'): log_info("Can't generate line information because binary_cache is missing.") return - if not find_tool_path('addr2line', self.config['ndk_path']): - log_info("Can't generate line information because can't find addr2line.") + if not find_tool_path('llvm-symbolizer', self.config['ndk_path']): + log_info("Can't generate line information because can't find llvm-symbolizer.") return addr2line = Addr2Nearestline(self.config['ndk_path'], self.config['binary_cache_dir'], True) diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index e24f58f1..1066404b 100755 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -1016,7 +1016,7 @@ class TestTools(unittest.TestCase): { 'func_addr': 0x840, 'addr': 0x840, - 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:7', + 'source': 'system/extras/simpleperf/runtest/two_functions.cpp:6', 'function': 'Function1()', }, { @@ -1065,7 +1065,7 @@ class TestTools(unittest.TestCase): expected_lines.append(int(items[1])) for line in test_addr['function'].split('\n'): expected_functions.append(line.strip()) - self.assertEquals(len(expected_files), len(expected_functions)) + self.assertEqual(len(expected_files), len(expected_functions)) actual_source = addr2line.get_addr_source(dso, test_addr['addr']) self.assertTrue(actual_source is not None) diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py index 9891e606..2321b80f 100644 --- a/simpleperf/scripts/utils.py +++ b/simpleperf/scripts/utils.py @@ -148,15 +148,16 @@ EXPECTED_TOOLS = { 'adb': { 'is_binutils': False, 'test_option': 'version', - 'path_in_ndk': '../platform-tools/adb', + 'path_in_ndk': lambda _: '../platform-tools/adb', }, 'readelf': { 'is_binutils': True, 'accept_tool_without_arch': True, }, - 'addr2line': { - 'is_binutils': True, - 'accept_tool_without_arch': True + 'llvm-symbolizer': { + 'is_binutils': False, + 'path_in_ndk': + lambda platform: 'toolchains/llvm/prebuilt/%s-x86_64/bin/llvm-symbolizer' % platform, }, 'objdump': { 'is_binutils': True, @@ -196,7 +197,7 @@ def find_tool_path(toolname, ndk_path=None, arch=None): toolname_with_arch, path_in_ndk = _get_binutils_path_in_ndk(toolname, arch, platform) else: toolname_with_arch = toolname - path_in_ndk = tool_info['path_in_ndk'] + path_in_ndk = tool_info['path_in_ndk'](platform) path_in_ndk = path_in_ndk.replace('/', os.sep) # 1. Find tool in the given ndk path. @@ -391,7 +392,7 @@ def find_real_dso_path(dso_path_in_record_file, binary_cache_path): class Addr2Nearestline(object): - """ Use addr2line to convert (dso_path, func_addr, addr) to (source_file, line) pairs. + """ Use llvm-symbolizer to convert (dso_path, func_addr, addr) to (source_file, line). For instructions generated by C++ compilers without a matching statement in source code (like stack corruption check, switch optimization, etc.), addr2line can't generate line information. However, we want to assign the instruction to the nearest line before @@ -435,9 +436,9 @@ class Addr2Nearestline(object): self.source_lines = None def __init__(self, ndk_path, binary_cache_path, with_function_name): - self.addr2line_path = find_tool_path('addr2line', ndk_path) - if not self.addr2line_path: - log_exit("Can't find addr2line. Please set ndk path with --ndk_path option.") + self.symbolizer_path = find_tool_path('llvm-symbolizer', ndk_path) + if not self.symbolizer_path: + log_exit("Can't find llvm-symbolizer. Please set ndk path with --ndk_path option.") self.readelf = ReadElf(ndk_path) self.dso_map = {} # map from dso_path to Dso. self.binary_cache_path = binary_cache_path @@ -504,12 +505,11 @@ class Addr2Nearestline(object): break if not addr_set: return - addr_request = '\n'.join(['%x' % addr for addr in sorted(addr_set)]) + addr_request = '\n'.join(['0x%x' % addr for addr in sorted(addr_set)]) # 2. Use addr2line to collect line info. try: - option = '-ai' + ('fC' if self.with_function_name else '') - subproc = subprocess.Popen([self.addr2line_path, option, '-e', real_path], + subproc = subprocess.Popen(self._build_symbolizer_args(real_path), stdin=subprocess.PIPE, stdout=subprocess.PIPE) (stdoutdata, _) = subproc.communicate(str_to_bytes(addr_request)) stdoutdata = bytes_to_str(stdoutdata) @@ -520,6 +520,9 @@ class Addr2Nearestline(object): need_function_name = self.with_function_name cur_function_name = None for line in stdoutdata.strip().split('\n'): + line = line.strip() + if not line: + continue if line[:2] == '0x': # a new address cur_line_list = addr_map[int(line, 16)] = [] @@ -528,27 +531,16 @@ class Addr2Nearestline(object): need_function_name = False else: need_function_name = self.with_function_name - # a file:line. if cur_line_list is None: continue - # Handle lines like "C:\Users\...\file:32". - items = line.rsplit(':', 1) - if len(items) != 2: - continue - if '?' in line: - # if ? in line, it doesn't have a valid line info. + file_path, line_number = self._parse_source_location(line) + if not file_path or not line_number: # An addr can have a list of (file, line), when the addr belongs to an inlined # function. Sometimes only part of the list has ? mark. In this case, we think # the line info is valid if the first line doesn't have ? mark. if not cur_line_list: cur_line_list = None continue - (file_path, line_number) = items - line_number = line_number.split()[0] # Remove comments after line number - try: - line_number = int(line_number) - except ValueError: - continue file_id = self._get_file_id(file_path) if self.with_function_name: func_id = self._get_func_id(cur_function_name) @@ -570,6 +562,29 @@ class Addr2Nearestline(object): if shifted_addr == addr_obj.func_addr: break + def _build_symbolizer_args(self, binary_path): + args = [self.symbolizer_path, '-print-address', '-inlining', '-obj=%s' % binary_path] + if self.with_function_name: + args += ['-functions=linkage', '-demangle'] + else: + args.append('-functions=none') + return args + + def _parse_source_location(self, line): + file_path, line_number = None, None + # Handle lines in format filename:line:column, like "runtest/two_functions.cpp:14:25". + # Filename may contain ':' like "C:\Users\...\file". + items = line.rsplit(':', 2) + if len(items) == 3: + file_path, line_number = items[:2] + if not file_path or ('?' in file_path) or not line_number or ('?' in line_number): + return None, None + try: + line_number = int(line_number) + except ValueError: + return None, None + return file_path, line_number + def _get_file_id(self, file_path): file_id = self.file_name_to_id.get(file_path) if file_id is None: |