blob: f4c239535334f3317d846fdd7985e4820dcc3e9e [file] [log] [blame]
Krzysztof Kosińskib1361112021-03-11 18:05:01 -08001#!/usr/bin/env python3
Iliyan Malchev4929d6a2011-08-04 17:44:40 -07002#
Ben Chengb42dad02013-04-25 15:14:04 -07003# Copyright (C) 2013 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070016
17"""Module for looking up symbolic debugging information.
18
19The information can include symbol names, offsets, and source locations.
20"""
21
Andreas Gampe46b00d62017-05-17 15:12:27 -070022import atexit
Elliott Hughes08365932014-06-13 18:12:25 -070023import glob
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070024import os
Yang Nie4b2a1a2014-11-06 17:42:33 -080025import platform
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070026import re
Julien Desprezfd06c732021-04-20 14:31:19 -070027import shutil
Andreas Gampe46b00d62017-05-17 15:12:27 -070028import signal
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070029import subprocess
Elliott Hughesc3c86192014-08-29 13:49:57 -070030import unittest
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070031
Krzysztof Kosińskib1361112021-03-11 18:05:01 -080032ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP", ".")
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070033
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -070034
35def FindClangDir():
36 get_clang_version = ANDROID_BUILD_TOP + "/build/soong/scripts/get_clang_version.py"
37 if os.path.exists(get_clang_version):
38 # We want the script to fail if get_clang_version.py exists but is unable
39 # to find the clang version.
40 version_output = subprocess.check_output(get_clang_version, text=True)
Pirama Arumuga Nainara26dc342021-07-02 09:11:37 -070041 return ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/" + version_output.strip()
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -070042 else:
43 return None
44
45
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070046def FindSymbolsDir():
47 saveddir = os.getcwd()
48 os.chdir(ANDROID_BUILD_TOP)
Andreas Gampe9240b452018-10-26 14:17:30 -070049 stream = None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070050 try:
Dan Willemsend3fc8fa2017-10-17 14:04:56 -070051 cmd = "build/soong/soong_ui.bash --dumpvar-mode --abs TARGET_OUT_UNSTRIPPED"
David Srbeckyfd1e4162021-04-27 22:24:36 +010052 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True, shell=True).stdout
Krzysztof Kosińskib1361112021-03-11 18:05:01 -080053 return str(stream.read().strip())
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070054 finally:
Andreas Gampe9240b452018-10-26 14:17:30 -070055 if stream is not None:
56 stream.close()
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070057 os.chdir(saveddir)
58
59SYMBOLS_DIR = FindSymbolsDir()
60
Christopher Ferrisf62a3be2023-03-09 16:13:57 -080061ARCH_IS_32BIT = None
Ben Chengb42dad02013-04-25 15:14:04 -070062
David Srbecky80547ae2021-11-01 21:59:59 +000063VERBOSE = False
Elliott Hughesc3c86192014-08-29 13:49:57 -070064
65# These are private. Do not access them from other modules.
66_CACHED_TOOLCHAIN = None
Christopher Ferris49eda0e2020-12-09 14:34:01 -080067_CACHED_CXX_FILT = None
Elliott Hughesc3c86192014-08-29 13:49:57 -070068
Andreas Gampe3d97a462017-05-17 14:16:45 -070069# Caches for symbolized information.
70_SYMBOL_INFORMATION_ADDR2LINE_CACHE = {}
71_SYMBOL_INFORMATION_OBJDUMP_CACHE = {}
72_SYMBOL_DEMANGLING_CACHE = {}
73
Andreas Gampe46b00d62017-05-17 15:12:27 -070074# Caches for pipes to subprocesses.
75
76class ProcessCache:
77 _cmd2pipe = {}
78 _lru = []
79
80 # Max number of open pipes.
81 _PIPE_MAX_OPEN = 10
82
83 def GetProcess(self, cmd):
84 cmd_tuple = tuple(cmd) # Need to use a tuple as lists can't be dict keys.
85 # Pipe already available?
86 if cmd_tuple in self._cmd2pipe:
87 pipe = self._cmd2pipe[cmd_tuple]
88 # Update LRU.
89 self._lru = [(cmd_tuple, pipe)] + [i for i in self._lru if i[0] != cmd_tuple]
90 return pipe
91
92 # Not cached, yet. Open a new one.
93
94 # Check if too many are open, close the old ones.
95 while len(self._lru) >= self._PIPE_MAX_OPEN:
96 open_cmd, open_pipe = self._lru.pop()
97 del self._cmd2pipe[open_cmd]
98 self.TerminateProcess(open_pipe)
99
100 # Create and put into cache.
101 pipe = self.SpawnProcess(cmd)
102 self._cmd2pipe[cmd_tuple] = pipe
103 self._lru = [(cmd_tuple, pipe)] + self._lru
104 return pipe
105
106 def SpawnProcess(self, cmd):
David Srbeckyfd1e4162021-04-27 22:24:36 +0100107 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
Andreas Gampe46b00d62017-05-17 15:12:27 -0700108
109 def TerminateProcess(self, pipe):
110 pipe.stdin.close()
111 pipe.stdout.close()
112 pipe.terminate()
113 pipe.wait()
114
115 def KillAllProcesses(self):
116 for _, open_pipe in self._lru:
117 self.TerminateProcess(open_pipe)
118 _cmd2pipe = {}
119 _lru = []
120
121
122_PIPE_ADDR2LINE_CACHE = ProcessCache()
123_PIPE_CPPFILT_CACHE = ProcessCache()
124
125
126# Process cache cleanup on shutdown.
127
128def CloseAllPipes():
129 _PIPE_ADDR2LINE_CACHE.KillAllProcesses()
130 _PIPE_CPPFILT_CACHE.KillAllProcesses()
131
132
133atexit.register(CloseAllPipes)
134
135
136def PipeTermHandler(signum, frame):
137 CloseAllPipes()
138 os._exit(0)
139
140
141for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM):
142 signal.signal(sig, PipeTermHandler)
143
144
145
Ben Chengb42dad02013-04-25 15:14:04 -0700146
Elliott Hughes08365932014-06-13 18:12:25 -0700147def ToolPath(tool, toolchain=None):
Julien Desprezfd06c732021-04-20 14:31:19 -0700148 """Return a fully-qualified path to the specified tool, or just the tool if it's on PATH """
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800149 if shutil.which(tool):
150 return tool
Elliott Hughes08365932014-06-13 18:12:25 -0700151 if not toolchain:
152 toolchain = FindToolchain()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800153 return os.path.join(toolchain, tool)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700154
Elliott Hughesc3c86192014-08-29 13:49:57 -0700155
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700156def FindToolchain():
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800157 """Returns the toolchain."""
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800158
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800159 global _CACHED_TOOLCHAIN
160 if _CACHED_TOOLCHAIN:
Elliott Hughesc3c86192014-08-29 13:49:57 -0700161 return _CACHED_TOOLCHAIN
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700162
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800163 llvm_binutils_dir = ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/llvm-binutils-stable/";
164 if not os.path.exists(llvm_binutils_dir):
165 raise Exception("Could not find llvm tool chain directory %s" % (llvm_binutils_dir))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700166
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800167 _CACHED_TOOLCHAIN = llvm_binutils_dir
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800168 print("Using toolchain from:", _CACHED_TOOLCHAIN)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700169 return _CACHED_TOOLCHAIN
170
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700171
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700172def SymbolInformation(lib, addr):
173 """Look up symbol information about an address.
174
175 Args:
176 lib: library (or executable) pathname containing symbols
177 addr: string hexidecimal address
178
179 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700180 A list of the form [(source_symbol, source_location,
181 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700182
Ben Chengb42dad02013-04-25 15:14:04 -0700183 If the function has been inlined then the list may contain
184 more than one element with the symbols for the most deeply
185 nested inlined location appearing first. The list is
186 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700187
Ben Chengb42dad02013-04-25 15:14:04 -0700188 Usually you want to display the source_location and
189 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700190 """
191 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700192 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700193
194
195def SymbolInformationForSet(lib, unique_addrs):
196 """Look up symbol information for a set of addresses from the given library.
197
198 Args:
199 lib: library (or executable) pathname containing symbols
200 unique_addrs: set of hexidecimal addresses
201
202 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700203 A dictionary of the form {addr: [(source_symbol, source_location,
204 object_symbol_with_offset)]} where each address has a list of
205 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700206
Ben Chengb42dad02013-04-25 15:14:04 -0700207 If the function has been inlined then the list may contain
208 more than one element with the symbols for the most deeply
209 nested inlined location appearing first. The list is
210 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700211
Ben Chengb42dad02013-04-25 15:14:04 -0700212 Usually you want to display the source_location and
213 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700214 """
215 if not lib:
216 return None
217
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800218 addr_to_line = CallLlvmSymbolizerForSet(lib, unique_addrs)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700219 if not addr_to_line:
220 return None
221
222 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
223 if not addr_to_objdump:
224 return None
225
226 result = {}
227 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700228 source_info = addr_to_line.get(addr)
229 if not source_info:
230 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700231 if addr in addr_to_objdump:
232 (object_symbol, object_offset) = addr_to_objdump.get(addr)
233 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
234 object_offset)
235 else:
236 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700237 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
238 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700239
240 return result
241
242
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800243def CallLlvmSymbolizerForSet(lib, unique_addrs):
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700244 """Look up line and symbol information for a set of addresses.
245
246 Args:
247 lib: library (or executable) pathname containing symbols
248 unique_addrs: set of string hexidecimal addresses look up.
249
250 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700251 A dictionary of the form {addr: [(symbol, file:line)]} where
252 each address has a list of associated symbols and locations
253 or an empty list if no symbol information was found.
254
255 If the function has been inlined then the list may contain
256 more than one element with the symbols for the most deeply
257 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700258 """
259 if not lib:
260 return None
261
Andreas Gampe3d97a462017-05-17 14:16:45 -0700262 result = {}
263 addrs = sorted(unique_addrs)
264
265 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
266 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
267
268 # Go through and handle all known addresses.
269 for x in range(len(addrs)):
270 next_addr = addrs.pop(0)
271 if next_addr in addr_cache:
272 result[next_addr] = addr_cache[next_addr]
273 else:
274 # Re-add, needs to be symbolized.
275 addrs.append(next_addr)
276
277 if not addrs:
278 # Everything was cached, we're done.
279 return result
280 else:
281 addr_cache = {}
282 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
283
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700284 symbols = SYMBOLS_DIR + lib
285 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700286 symbols = lib
287 if not os.path.exists(symbols):
288 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700289
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700290 # Make sure the symbols path is not a directory.
291 if os.path.isdir(symbols):
292 return None
293
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800294 cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines",
295 "--demangle", "--obj=" + symbols, "--output-style=GNU"]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700296 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700297
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700298 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700299 try:
300 child.stdin.write("0x%s\n" % addr)
301 child.stdin.flush()
302 records = []
303 first = True
304 while True:
305 symbol = child.stdout.readline().strip()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800306 if not symbol:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700307 break
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800308 location = child.stdout.readline().strip()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700309 records.append((symbol, location))
310 if first:
311 # Write a blank line as a sentinel so we know when to stop
312 # reading inlines from the output.
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800313 # The blank line will cause llvm-symbolizer to emit a blank line.
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700314 child.stdin.write("\n")
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800315 child.stdin.flush()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700316 first = False
317 except IOError as e:
318 # Remove the / in front of the library name to match other output.
319 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700320 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700321 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700322 return result
323
324
325def CallObjdumpForSet(lib, unique_addrs):
326 """Use objdump to find out the names of the containing functions.
327
328 Args:
329 lib: library (or executable) pathname containing symbols
330 unique_addrs: set of string hexidecimal addresses to find the functions for.
331
332 Returns:
333 A dictionary of the form {addr: (string symbol, offset)}.
334 """
335 if not lib:
336 return None
337
Andreas Gampe3d97a462017-05-17 14:16:45 -0700338 result = {}
339 addrs = sorted(unique_addrs)
340
341 addr_cache = None
342 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
343 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
344
345 # Go through and handle all known addresses.
346 for x in range(len(addrs)):
347 next_addr = addrs.pop(0)
348 if next_addr in addr_cache:
349 result[next_addr] = addr_cache[next_addr]
350 else:
351 # Re-add, needs to be symbolized.
352 addrs.append(next_addr)
353
354 if not addrs:
355 # Everything was cached, we're done.
356 return result
357 else:
358 addr_cache = {}
359 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
360
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700361 symbols = SYMBOLS_DIR + lib
362 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700363 symbols = lib
364 if not os.path.exists(symbols):
365 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700366
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800367 start_addr_dec = str(int(addrs[0], 16))
368 stop_addr_dec = str(int(addrs[-1], 16) + 8)
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800369 cmd = [ToolPath("llvm-objdump"),
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700370 "--section=.text",
371 "--demangle",
372 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700373 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700374 "--stop-address=" + stop_addr_dec,
375 symbols]
376
377 # Function lines look like:
378 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
379 # We pull out the address and function first. Then we check for an optional
380 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
381 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
382 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
383
384 # A disassembly line looks like:
385 # 177b2: b510 push {r4, lr}
386 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
387
388 current_symbol = None # The current function symbol in the disassembly.
389 current_symbol_addr = 0 # The address of the current function.
390 addr_index = 0 # The address that we are currently looking for.
391
David Srbeckyfd1e4162021-04-27 22:24:36 +0100392 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700393 for line in stream:
394 # Is it a function line like:
395 # 000177b0 <android::IBinder::~IBinder()>:
396 components = func_regexp.match(line)
397 if components:
398 # This is a new function, so record the current function and its address.
399 current_symbol_addr = int(components.group(1), 16)
400 current_symbol = components.group(2)
401
402 # Does it have an optional offset like: "foo(..)+0x2c"?
403 components = offset_regexp.match(current_symbol)
404 if components:
405 current_symbol = components.group(1)
406 offset = components.group(2)
407 if offset:
408 current_symbol_addr -= int(offset, 16)
409
410 # Is it an disassembly line like:
411 # 177b2: b510 push {r4, lr}
412 components = asm_regexp.match(line)
413 if components:
414 addr = components.group(1)
415 target_addr = addrs[addr_index]
416 i_addr = int(addr, 16)
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800417 i_target = int(target_addr, 16)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700418 if i_addr == i_target:
419 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700420 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700421 addr_index += 1
422 if addr_index >= len(addrs):
423 break
424 stream.close()
425
426 return result
427
428
429def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700430 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
431 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
432
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800433 global _CACHED_CXX_FILT
434 if not _CACHED_CXX_FILT:
Julien Desprezfd06c732021-04-20 14:31:19 -0700435 toolchains = None
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -0700436 clang_dir = FindClangDir()
437 if clang_dir:
438 if os.path.exists(clang_dir + "/bin/llvm-cxxfilt"):
439 toolchains = [clang_dir + "/bin/llvm-cxxfilt"]
440 else:
441 raise Exception("bin/llvm-cxxfilt missing from " + clang_dir)
442 else:
443 # When run in CI, we don't have a way to find the clang version. But
444 # llvm-cxxfilt should be available in the following relative path.
445 toolchains = glob.glob("./clang-r*/bin/llvm-cxxfilt")
446 if toolchains and len(toolchains) != 1:
447 raise Exception("Expected one llvm-cxxfilt but found many: " + \
448 ", ".join(toolchains))
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800449 if not toolchains:
Julien Desprezfd06c732021-04-20 14:31:19 -0700450 raise Exception("Could not find llvm-cxxfilt tool")
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800451 _CACHED_CXX_FILT = sorted(toolchains)[-1]
452
453 cmd = [_CACHED_CXX_FILT]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700454 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700455 process.stdin.write(mangled_symbol)
456 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700457 process.stdin.flush()
458
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700459 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700460
461 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
462
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700463 return demangled_symbol
464
Elliott Hughesc3c86192014-08-29 13:49:57 -0700465
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700466def FormatSymbolWithOffset(symbol, offset):
467 if offset == 0:
468 return symbol
469 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700470
David Srbecky80547ae2021-11-01 21:59:59 +0000471def FormatSymbolWithoutParameters(symbol):
472 """Remove parameters from function.
473
474 Rather than trying to parse the demangled C++ signature,
475 it just removes matching top level parenthesis.
476 """
477 if not symbol:
478 return symbol
479
480 result = symbol
481 result = result.replace(") const", ")") # Strip const keyword.
482 result = result.replace("operator<<", "operator\u00AB") # Avoid unmatched '<'.
483 result = result.replace("operator>>", "operator\u00BB") # Avoid unmatched '>'.
484 result = result.replace("operator->", "operator\u2192") # Avoid unmatched '>'.
485
486 nested = [] # Keeps tract of current nesting level of parenthesis.
487 for i in reversed(range(len(result))): # Iterate backward to make cutting easier.
488 c = result[i]
489 if c == ')' or c == '>':
490 if len(nested) == 0:
491 end = i + 1 # Mark the end of top-level pair.
492 nested.append(c)
493 if c == '(' or c == '<':
494 if len(nested) == 0 or {')':'(', '>':'<'}[nested.pop()] != c:
495 return symbol # Malformed: character does not match its pair.
496 if len(nested) == 0 and c == '(' and (end - i) > 2:
497 result = result[:i] + result[end:] # Remove substring (i, end).
498 if len(nested) > 0:
499 return symbol # Malformed: missing pair.
500
501 return result.strip()
Elliott Hughesc3c86192014-08-29 13:49:57 -0700502
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800503def SetBitness(lines):
504 global ARCH_IS_32BIT
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800505
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800506 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700507 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800508
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800509 ARCH_IS_32BIT = False
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800510 for line in lines:
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800511 trace_match = trace_line.search(line)
512 if trace_match:
513 # Try to guess the arch, we know the bitness.
514 if len(trace_match.group(1)) == 16:
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800515 ARCH_IS_32BIT = False
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800516 else:
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800517 ARCH_IS_32BIT = True
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800518 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700519 asan_trace_match = asan_trace_line.search(line)
520 if asan_trace_match:
521 # We might be able to guess the bitness by the length of the address.
522 if len(asan_trace_match.group(1)) > 8:
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800523 ARCH_IS_32BIT = False
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700524 # We know for a fact this is 64 bit, so we are done.
525 break
526 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700527 # This might be 32 bit, or just a small address. Keep going in this
528 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800529 ARCH_IS_32BIT = True
Elliott Hughesc3c86192014-08-29 13:49:57 -0700530
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -0700531class FindClangDirTests(unittest.TestCase):
532 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
533 def test_clang_dir_found(self):
534 self.assertIsNotNone(FindClangDir())
535
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800536class SetBitnessTests(unittest.TestCase):
537 def test_32bit_check(self):
538 global ARCH_IS_32BIT
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800539
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800540 SetBitness(["#00 pc 000374e0"])
541 self.assertTrue(ARCH_IS_32BIT)
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800542
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800543 def test_64bit_check(self):
544 global ARCH_IS_32BIT
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800545
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800546 SetBitness(["#00 pc 00000000000374e0"])
547 self.assertFalse(ARCH_IS_32BIT)
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800548
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700549 def test_32bit_asan_trace_line_toolchain(self):
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800550 global ARCH_IS_32BIT
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700551
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800552 SetBitness(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
553 self.assertTrue(ARCH_IS_32BIT)
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700554
555 def test_64bit_asan_trace_line_toolchain(self):
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800556 global ARCH_IS_32BIT
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700557
Christopher Ferrisf62a3be2023-03-09 16:13:57 -0800558 SetBitness(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
559 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
560 self.assertFalse(ARCH_IS_32BIT)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700561
David Srbecky80547ae2021-11-01 21:59:59 +0000562class FormatSymbolWithoutParametersTests(unittest.TestCase):
563 def test_c(self):
564 self.assertEqual(FormatSymbolWithoutParameters("foo"), "foo")
565 self.assertEqual(FormatSymbolWithoutParameters("foo+42"), "foo+42")
566
567 def test_simple(self):
568 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)"), "foo")
569 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)+42"), "foo+42")
570 self.assertEqual(FormatSymbolWithoutParameters("bar::foo(int i)+42"), "bar::foo+42")
571 self.assertEqual(FormatSymbolWithoutParameters("operator()"), "operator()")
572
573 def test_templates(self):
574 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T>& v)"), "bar::foo<T>")
575 self.assertEqual(FormatSymbolWithoutParameters("bar<T>::foo(vector<T>& v)"), "bar<T>::foo")
576 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T<U>>& v)"), "bar::foo<T>")
577 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<(EnumType)0>(vector<(EnumType)0>& v)"),
578 "bar::foo<(EnumType)0>")
579
580 def test_nested(self):
581 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)::bar(int j)"), "foo::bar")
582
Christopher Ferrisa47d6d02023-03-13 14:50:48 -0700583 def test_unbalanced(self):
David Srbecky80547ae2021-11-01 21:59:59 +0000584 self.assertEqual(FormatSymbolWithoutParameters("foo(bar(int i)"), "foo(bar(int i)")
585 self.assertEqual(FormatSymbolWithoutParameters("foo)bar(int i)"), "foo)bar(int i)")
586 self.assertEqual(FormatSymbolWithoutParameters("foo<bar(int i)"), "foo<bar(int i)")
587 self.assertEqual(FormatSymbolWithoutParameters("foo>bar(int i)"), "foo>bar(int i)")
588
Elliott Hughesc3c86192014-08-29 13:49:57 -0700589if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700590 unittest.main(verbosity=2)