blob: 5ec4e483e818f7752635a45cab2a1ed5b644b694 [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 Ferrisbf8a9402016-03-11 15:50:46 -080061ARCH = None
Ben Chengb42dad02013-04-25 15:14:04 -070062
Elliott Hughesc3c86192014-08-29 13:49:57 -070063
64# These are private. Do not access them from other modules.
65_CACHED_TOOLCHAIN = None
66_CACHED_TOOLCHAIN_ARCH = 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 """
149 if shutil.which(tool) is not None:
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():
Elliott Hughesc3c86192014-08-29 13:49:57 -0700157 """Returns the toolchain matching ARCH."""
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800158
Elliott Hughesc3c86192014-08-29 13:49:57 -0700159 global _CACHED_TOOLCHAIN, _CACHED_TOOLCHAIN_ARCH
160 if _CACHED_TOOLCHAIN is not None and _CACHED_TOOLCHAIN_ARCH == ARCH:
161 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
Elliott Hughesc3c86192014-08-29 13:49:57 -0700168 _CACHED_TOOLCHAIN_ARCH = ARCH
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800169 print("Using", _CACHED_TOOLCHAIN_ARCH, "toolchain from:", _CACHED_TOOLCHAIN)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700170 return _CACHED_TOOLCHAIN
171
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700172
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700173def SymbolInformation(lib, addr):
174 """Look up symbol information about an address.
175
176 Args:
177 lib: library (or executable) pathname containing symbols
178 addr: string hexidecimal address
179
180 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700181 A list of the form [(source_symbol, source_location,
182 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700183
Ben Chengb42dad02013-04-25 15:14:04 -0700184 If the function has been inlined then the list may contain
185 more than one element with the symbols for the most deeply
186 nested inlined location appearing first. The list is
187 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700188
Ben Chengb42dad02013-04-25 15:14:04 -0700189 Usually you want to display the source_location and
190 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700191 """
192 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700193 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700194
195
196def SymbolInformationForSet(lib, unique_addrs):
197 """Look up symbol information for a set of addresses from the given library.
198
199 Args:
200 lib: library (or executable) pathname containing symbols
201 unique_addrs: set of hexidecimal addresses
202
203 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700204 A dictionary of the form {addr: [(source_symbol, source_location,
205 object_symbol_with_offset)]} where each address has a list of
206 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700207
Ben Chengb42dad02013-04-25 15:14:04 -0700208 If the function has been inlined then the list may contain
209 more than one element with the symbols for the most deeply
210 nested inlined location appearing first. The list is
211 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700212
Ben Chengb42dad02013-04-25 15:14:04 -0700213 Usually you want to display the source_location and
214 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700215 """
216 if not lib:
217 return None
218
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800219 addr_to_line = CallLlvmSymbolizerForSet(lib, unique_addrs)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700220 if not addr_to_line:
221 return None
222
223 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
224 if not addr_to_objdump:
225 return None
226
227 result = {}
228 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700229 source_info = addr_to_line.get(addr)
230 if not source_info:
231 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700232 if addr in addr_to_objdump:
233 (object_symbol, object_offset) = addr_to_objdump.get(addr)
234 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
235 object_offset)
236 else:
237 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700238 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
239 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700240
241 return result
242
243
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800244def CallLlvmSymbolizerForSet(lib, unique_addrs):
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700245 """Look up line and symbol information for a set of addresses.
246
247 Args:
248 lib: library (or executable) pathname containing symbols
249 unique_addrs: set of string hexidecimal addresses look up.
250
251 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700252 A dictionary of the form {addr: [(symbol, file:line)]} where
253 each address has a list of associated symbols and locations
254 or an empty list if no symbol information was found.
255
256 If the function has been inlined then the list may contain
257 more than one element with the symbols for the most deeply
258 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700259 """
260 if not lib:
261 return None
262
Andreas Gampe3d97a462017-05-17 14:16:45 -0700263 result = {}
264 addrs = sorted(unique_addrs)
265
266 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
267 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
268
269 # Go through and handle all known addresses.
270 for x in range(len(addrs)):
271 next_addr = addrs.pop(0)
272 if next_addr in addr_cache:
273 result[next_addr] = addr_cache[next_addr]
274 else:
275 # Re-add, needs to be symbolized.
276 addrs.append(next_addr)
277
278 if not addrs:
279 # Everything was cached, we're done.
280 return result
281 else:
282 addr_cache = {}
283 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
284
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700285 symbols = SYMBOLS_DIR + lib
286 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700287 symbols = lib
288 if not os.path.exists(symbols):
289 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700290
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700291 # Make sure the symbols path is not a directory.
292 if os.path.isdir(symbols):
293 return None
294
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800295 cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines",
296 "--demangle", "--obj=" + symbols, "--output-style=GNU"]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700297 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700298
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700299 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700300 try:
301 child.stdin.write("0x%s\n" % addr)
302 child.stdin.flush()
303 records = []
304 first = True
305 while True:
306 symbol = child.stdout.readline().strip()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800307 if not symbol:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700308 break
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800309 location = child.stdout.readline().strip()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700310 records.append((symbol, location))
311 if first:
312 # Write a blank line as a sentinel so we know when to stop
313 # reading inlines from the output.
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800314 # The blank line will cause llvm-symbolizer to emit a blank line.
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700315 child.stdin.write("\n")
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800316 child.stdin.flush()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700317 first = False
318 except IOError as e:
319 # Remove the / in front of the library name to match other output.
320 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700321 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700322 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700323 return result
324
325
Ben Chengb42dad02013-04-25 15:14:04 -0700326def StripPC(addr):
327 """Strips the Thumb bit a program counter address when appropriate.
328
329 Args:
330 addr: the program counter address
331
332 Returns:
333 The stripped program counter address.
334 """
335 global ARCH
Ben Chengb42dad02013-04-25 15:14:04 -0700336 if ARCH == "arm":
337 return addr & ~1
338 return addr
339
Elliott Hughesc3c86192014-08-29 13:49:57 -0700340
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700341def CallObjdumpForSet(lib, unique_addrs):
342 """Use objdump to find out the names of the containing functions.
343
344 Args:
345 lib: library (or executable) pathname containing symbols
346 unique_addrs: set of string hexidecimal addresses to find the functions for.
347
348 Returns:
349 A dictionary of the form {addr: (string symbol, offset)}.
350 """
351 if not lib:
352 return None
353
Andreas Gampe3d97a462017-05-17 14:16:45 -0700354 result = {}
355 addrs = sorted(unique_addrs)
356
357 addr_cache = None
358 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
359 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
360
361 # Go through and handle all known addresses.
362 for x in range(len(addrs)):
363 next_addr = addrs.pop(0)
364 if next_addr in addr_cache:
365 result[next_addr] = addr_cache[next_addr]
366 else:
367 # Re-add, needs to be symbolized.
368 addrs.append(next_addr)
369
370 if not addrs:
371 # Everything was cached, we're done.
372 return result
373 else:
374 addr_cache = {}
375 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
376
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700377 symbols = SYMBOLS_DIR + lib
378 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700379 symbols = lib
380 if not os.path.exists(symbols):
381 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700382
Ben Chengb42dad02013-04-25 15:14:04 -0700383 start_addr_dec = str(StripPC(int(addrs[0], 16)))
384 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800385 cmd = [ToolPath("llvm-objdump"),
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700386 "--section=.text",
387 "--demangle",
388 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700389 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700390 "--stop-address=" + stop_addr_dec,
391 symbols]
392
393 # Function lines look like:
394 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
395 # We pull out the address and function first. Then we check for an optional
396 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
397 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
398 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
399
400 # A disassembly line looks like:
401 # 177b2: b510 push {r4, lr}
402 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
403
404 current_symbol = None # The current function symbol in the disassembly.
405 current_symbol_addr = 0 # The address of the current function.
406 addr_index = 0 # The address that we are currently looking for.
407
David Srbeckyfd1e4162021-04-27 22:24:36 +0100408 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700409 for line in stream:
410 # Is it a function line like:
411 # 000177b0 <android::IBinder::~IBinder()>:
412 components = func_regexp.match(line)
413 if components:
414 # This is a new function, so record the current function and its address.
415 current_symbol_addr = int(components.group(1), 16)
416 current_symbol = components.group(2)
417
418 # Does it have an optional offset like: "foo(..)+0x2c"?
419 components = offset_regexp.match(current_symbol)
420 if components:
421 current_symbol = components.group(1)
422 offset = components.group(2)
423 if offset:
424 current_symbol_addr -= int(offset, 16)
425
426 # Is it an disassembly line like:
427 # 177b2: b510 push {r4, lr}
428 components = asm_regexp.match(line)
429 if components:
430 addr = components.group(1)
431 target_addr = addrs[addr_index]
432 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700433 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700434 if i_addr == i_target:
435 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700436 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700437 addr_index += 1
438 if addr_index >= len(addrs):
439 break
440 stream.close()
441
442 return result
443
444
445def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700446 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
447 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
448
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800449 global _CACHED_CXX_FILT
450 if not _CACHED_CXX_FILT:
Julien Desprezfd06c732021-04-20 14:31:19 -0700451 toolchains = None
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -0700452 clang_dir = FindClangDir()
453 if clang_dir:
454 if os.path.exists(clang_dir + "/bin/llvm-cxxfilt"):
455 toolchains = [clang_dir + "/bin/llvm-cxxfilt"]
456 else:
457 raise Exception("bin/llvm-cxxfilt missing from " + clang_dir)
458 else:
459 # When run in CI, we don't have a way to find the clang version. But
460 # llvm-cxxfilt should be available in the following relative path.
461 toolchains = glob.glob("./clang-r*/bin/llvm-cxxfilt")
462 if toolchains and len(toolchains) != 1:
463 raise Exception("Expected one llvm-cxxfilt but found many: " + \
464 ", ".join(toolchains))
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800465 if not toolchains:
Julien Desprezfd06c732021-04-20 14:31:19 -0700466 raise Exception("Could not find llvm-cxxfilt tool")
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800467 _CACHED_CXX_FILT = sorted(toolchains)[-1]
468
469 cmd = [_CACHED_CXX_FILT]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700470 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700471 process.stdin.write(mangled_symbol)
472 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700473 process.stdin.flush()
474
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700475 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700476
477 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
478
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700479 return demangled_symbol
480
Elliott Hughesc3c86192014-08-29 13:49:57 -0700481
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700482def FormatSymbolWithOffset(symbol, offset):
483 if offset == 0:
484 return symbol
485 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700486
487
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800488def GetAbiFromToolchain(toolchain_var, bits):
489 toolchain = os.environ.get(toolchain_var)
490 if not toolchain:
491 return None
492
493 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain)
494 if toolchain_match:
495 abi = toolchain_match.group(1)
496 if abi == "aarch64":
497 return "arm64"
498 elif bits == 64:
499 if abi == "x86":
500 return "x86_64"
501 elif abi == "mips":
502 return "mips64"
503 return abi
504 return None
505
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700506def Get32BitArch():
507 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that.
508 # If not try ANDROID_TOOLCHAIN to find the arch.
509 # If this is not set, then default to arm.
510 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32)
511 if not arch:
512 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32)
513 if not arch:
514 return "arm"
515 return arch
516
517def Get64BitArch():
518 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the
519 # arch this way. If this is not set, then default to arm64.
520 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64)
521 if not arch:
522 return "arm64"
523 return arch
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800524
525def SetAbi(lines):
526 global ARCH
527
528 abi_line = re.compile("ABI: \'(.*)\'")
529 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700530 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800531
532 ARCH = None
533 for line in lines:
534 abi_match = abi_line.search(line)
535 if abi_match:
536 ARCH = abi_match.group(1)
537 break
538 trace_match = trace_line.search(line)
539 if trace_match:
540 # Try to guess the arch, we know the bitness.
541 if len(trace_match.group(1)) == 16:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700542 ARCH = Get64BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800543 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700544 ARCH = Get32BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800545 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700546 asan_trace_match = asan_trace_line.search(line)
547 if asan_trace_match:
548 # We might be able to guess the bitness by the length of the address.
549 if len(asan_trace_match.group(1)) > 8:
550 ARCH = Get64BitArch()
551 # We know for a fact this is 64 bit, so we are done.
552 break
553 else:
554 ARCH = Get32BitArch()
555 # This might be 32 bit, or just a small address. Keep going in this
556 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800557 if not ARCH:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700558 raise Exception("Could not determine arch from input, use --arch=XXX to specify it")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800559
Elliott Hughesc3c86192014-08-29 13:49:57 -0700560
561class FindToolchainTests(unittest.TestCase):
562 def assert_toolchain_found(self, abi):
563 global ARCH
564 ARCH = abi
565 FindToolchain() # Will throw on failure.
566
Andreas Gampee547eb32018-10-29 18:31:37 -0700567 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
Elliott Hughesc3c86192014-08-29 13:49:57 -0700568 def test_toolchains_found(self):
569 self.assert_toolchain_found("arm")
570 self.assert_toolchain_found("arm64")
571 self.assert_toolchain_found("mips")
572 self.assert_toolchain_found("x86")
573 self.assert_toolchain_found("x86_64")
574
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -0700575class FindClangDirTests(unittest.TestCase):
576 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
577 def test_clang_dir_found(self):
578 self.assertIsNotNone(FindClangDir())
579
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800580class SetArchTests(unittest.TestCase):
581 def test_abi_check(self):
582 global ARCH
583
584 SetAbi(["ABI: 'arm'"])
585 self.assertEqual(ARCH, "arm")
586 SetAbi(["ABI: 'arm64'"])
587 self.assertEqual(ARCH, "arm64")
588
589 SetAbi(["ABI: 'mips'"])
590 self.assertEqual(ARCH, "mips")
591 SetAbi(["ABI: 'mips64'"])
592 self.assertEqual(ARCH, "mips64")
593
594 SetAbi(["ABI: 'x86'"])
595 self.assertEqual(ARCH, "x86")
596 SetAbi(["ABI: 'x86_64'"])
597 self.assertEqual(ARCH, "x86_64")
598
599 def test_32bit_trace_line_toolchain(self):
600 global ARCH
601
602 os.environ.clear()
603 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
604 SetAbi(["#00 pc 000374e0"])
605 self.assertEqual(ARCH, "arm")
606
607 os.environ.clear()
608 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
609 SetAbi(["#00 pc 000374e0"])
610 self.assertEqual(ARCH, "mips")
611
612 os.environ.clear()
613 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
614 SetAbi(["#00 pc 000374e0"])
615 self.assertEqual(ARCH, "x86")
616
617 def test_32bit_trace_line_toolchain_2nd(self):
618 global ARCH
619
620 os.environ.clear()
621 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
622 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
623 SetAbi(["#00 pc 000374e0"])
624 self.assertEqual(ARCH, "arm")
625
626 os.environ.clear()
627 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
628 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
629 SetAbi(["#00 pc 000374e0"])
630 self.assertEqual(ARCH, "mips")
631
632 os.environ.clear()
633 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
634 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
635 SetAbi(["#00 pc 000374e0"])
636 self.assertEqual(ARCH, "x86")
637
638 def test_64bit_trace_line_toolchain(self):
639 global ARCH
640
641 os.environ.clear()
642 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
643 SetAbi(["#00 pc 00000000000374e0"])
644 self.assertEqual(ARCH, "arm64")
645
646 os.environ.clear()
647 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
648 SetAbi(["#00 pc 00000000000374e0"])
649 self.assertEqual(ARCH, "mips64")
650
651 os.environ.clear()
652 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
653 SetAbi(["#00 pc 00000000000374e0"])
654 self.assertEqual(ARCH, "x86_64")
655
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700656 def test_trace_default_abis(self):
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800657 global ARCH
658
659 os.environ.clear()
660 SetAbi(["#00 pc 000374e0"])
661 self.assertEqual(ARCH, "arm")
662 SetAbi(["#00 pc 00000000000374e0"])
663 self.assertEqual(ARCH, "arm64")
664
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700665 def test_32bit_asan_trace_line_toolchain(self):
666 global ARCH
667
668 os.environ.clear()
669 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
670 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
671 self.assertEqual(ARCH, "arm")
672
673 os.environ.clear()
674 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
675 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
676 self.assertEqual(ARCH, "mips")
677
678 os.environ.clear()
679 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
680 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
681 self.assertEqual(ARCH, "x86")
682
683 def test_32bit_asan_trace_line_toolchain_2nd(self):
684 global ARCH
685
686 os.environ.clear()
687 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
688 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
689 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
690 self.assertEqual(ARCH, "arm")
691
692 os.environ.clear()
693 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
694 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
695 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
696 self.assertEqual(ARCH, "mips")
697
698 os.environ.clear()
699 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
700 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
701 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
702 self.assertEqual(ARCH, "x86")
703
704 def test_64bit_asan_trace_line_toolchain(self):
705 global ARCH
706
707 os.environ.clear()
708 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
709 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
710 self.assertEqual(ARCH, "arm64")
711
712 os.environ.clear()
713 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
714 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
715 self.assertEqual(ARCH, "mips64")
716
717 os.environ.clear()
718 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
719 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
720 self.assertEqual(ARCH, "x86_64")
721
722 # Verify that if an address that might be 32 bit comes first, that
723 # encountering a 64 bit address returns a 64 bit abi.
724 ARCH = None
725 os.environ.clear()
726 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
727 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
728 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
729 self.assertEqual(ARCH, "x86_64")
730
731 def test_asan_trace_default_abis(self):
732 global ARCH
733
734 os.environ.clear()
735 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
736 self.assertEqual(ARCH, "arm64")
737 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
738 self.assertEqual(ARCH, "arm")
739
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800740 def test_no_abi(self):
741 global ARCH
742
Andreas Gampe9240b452018-10-26 14:17:30 -0700743 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex
744 # does not provide that name.
745 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
746 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp')
747 self.assertRaisesRegex(Exception,
748 "Could not determine arch from input, use --arch=XXX to specify it",
749 SetAbi, [])
Elliott Hughesc3c86192014-08-29 13:49:57 -0700750
751if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700752 unittest.main(verbosity=2)