summaryrefslogtreecommitdiff
path: root/simpleperf/scripts/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'simpleperf/scripts/utils.py')
-rw-r--r--simpleperf/scripts/utils.py101
1 files changed, 36 insertions, 65 deletions
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py
index 5754f383..ea708c61 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/utils.py
@@ -70,17 +70,6 @@ def log_exit(msg):
def disable_debug_log():
logging.getLogger().setLevel(logging.WARN)
-def set_log_level(level_name):
- if level_name == 'debug':
- level = logging.DEBUG
- elif level_name == 'info':
- level = logging.INFO
- elif level_name == 'warning':
- level = logging.WARNING
- else:
- log_fatal('unknown log level: %s' % level_name)
- logging.getLogger().setLevel(level)
-
def str_to_bytes(str_value):
if not is_python3():
return str_value
@@ -148,16 +137,15 @@ EXPECTED_TOOLS = {
'adb': {
'is_binutils': False,
'test_option': 'version',
- 'path_in_ndk': lambda _: '../platform-tools/adb',
+ 'path_in_ndk': '../platform-tools/adb',
},
'readelf': {
'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,
+ 'addr2line': {
+ 'is_binutils': True,
+ 'accept_tool_without_arch': True
},
'objdump': {
'is_binutils': True,
@@ -197,7 +185,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'](platform)
+ path_in_ndk = tool_info['path_in_ndk']
path_in_ndk = path_in_ndk.replace('/', os.sep)
# 1. Find tool in the given ndk path.
@@ -326,7 +314,6 @@ class AdbHelper(object):
def get_android_version(self):
- """ Get Android version on device, like 7 is for Android N, 8 is for Android O."""
build_version = self.get_property('ro.build.version.release')
android_version = 0
if build_version:
@@ -376,11 +363,15 @@ def open_report_in_browser(report_path):
def is_elf_file(path):
if os.path.isfile(path):
with open(path, 'rb') as fh:
- return fh.read(4) == b'\x7fELF'
+ data = fh.read(4)
+ if len(data) == 4 and bytes_to_str(data) == '\x7fELF':
+ return True
return False
def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
""" Given the path of a shared library in perf.data, find its real path in the file system. """
+ if dso_path_in_record_file[0] != '/' or dso_path_in_record_file == '//anon':
+ return None
if binary_cache_path:
tmp_path = os.path.join(binary_cache_path, dso_path_in_record_file[1:])
if is_elf_file(tmp_path):
@@ -391,7 +382,7 @@ def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
class Addr2Nearestline(object):
- """ Use llvm-symbolizer to convert (dso_path, func_addr, addr) to (source_file, line).
+ """ Use addr2line to convert (dso_path, func_addr, addr) to (source_file, line) pairs.
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 +426,9 @@ class Addr2Nearestline(object):
self.source_lines = None
def __init__(self, ndk_path, binary_cache_path, with_function_name):
- 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.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.readelf = ReadElf(ndk_path)
self.dso_map = {} # map from dso_path to Dso.
self.binary_cache_path = binary_cache_path
@@ -504,11 +495,12 @@ class Addr2Nearestline(object):
break
if not addr_set:
return
- addr_request = '\n'.join(['0x%x' % addr for addr in sorted(addr_set)])
+ addr_request = '\n'.join(['%x' % addr for addr in sorted(addr_set)])
# 2. Use addr2line to collect line info.
try:
- subproc = subprocess.Popen(self._build_symbolizer_args(real_path),
+ option = '-ai' + ('fC' if self.with_function_name else '')
+ subproc = subprocess.Popen([self.addr2line_path, option, '-e', real_path],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(stdoutdata, _) = subproc.communicate(str_to_bytes(addr_request))
stdoutdata = bytes_to_str(stdoutdata)
@@ -519,9 +511,6 @@ 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)] = []
@@ -530,16 +519,27 @@ 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
- file_path, line_number = self._parse_source_location(line)
- if not file_path or not line_number:
+ # 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.
# 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)
@@ -561,29 +561,6 @@ 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:
@@ -754,7 +731,7 @@ class ReadElf(object):
pass
return 'unknown'
- def get_build_id(self, elf_file_path, with_padding=True):
+ def get_build_id(self, elf_file_path):
""" Get build id of an elf file. """
if is_elf_file(elf_file_path):
try:
@@ -763,22 +740,16 @@ class ReadElf(object):
result = re.search(r'Build ID:\s*(\S+)', output)
if result:
build_id = result.group(1)
- if with_padding:
- build_id = self.pad_build_id(build_id)
+ if len(build_id) < 40:
+ build_id += '0' * (40 - len(build_id))
+ else:
+ build_id = build_id[:40]
+ build_id = '0x' + build_id
return build_id
except subprocess.CalledProcessError:
pass
return ""
- @staticmethod
- def pad_build_id(build_id):
- """ Pad build id to 40 hex numbers (20 bytes). """
- if len(build_id) < 40:
- build_id += '0' * (40 - len(build_id))
- else:
- build_id = build_id[:40]
- return '0x' + build_id
-
def get_sections(self, elf_file_path):
""" Get sections of an elf file. """
section_names = []