blob: 0a255e819e45fd37b493d2ab83feeb0ed2ae3639 [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
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
67_CACHED_TOOLCHAIN_ARCH = None
Christopher Ferris49eda0e2020-12-09 14:34:01 -080068_CACHED_CXX_FILT = None
Elliott Hughesc3c86192014-08-29 13:49:57 -070069
Andreas Gampe3d97a462017-05-17 14:16:45 -070070# Caches for symbolized information.
71_SYMBOL_INFORMATION_ADDR2LINE_CACHE = {}
72_SYMBOL_INFORMATION_OBJDUMP_CACHE = {}
73_SYMBOL_DEMANGLING_CACHE = {}
74
Andreas Gampe46b00d62017-05-17 15:12:27 -070075# Caches for pipes to subprocesses.
76
77class ProcessCache:
78 _cmd2pipe = {}
79 _lru = []
80
81 # Max number of open pipes.
82 _PIPE_MAX_OPEN = 10
83
84 def GetProcess(self, cmd):
85 cmd_tuple = tuple(cmd) # Need to use a tuple as lists can't be dict keys.
86 # Pipe already available?
87 if cmd_tuple in self._cmd2pipe:
88 pipe = self._cmd2pipe[cmd_tuple]
89 # Update LRU.
90 self._lru = [(cmd_tuple, pipe)] + [i for i in self._lru if i[0] != cmd_tuple]
91 return pipe
92
93 # Not cached, yet. Open a new one.
94
95 # Check if too many are open, close the old ones.
96 while len(self._lru) >= self._PIPE_MAX_OPEN:
97 open_cmd, open_pipe = self._lru.pop()
98 del self._cmd2pipe[open_cmd]
99 self.TerminateProcess(open_pipe)
100
101 # Create and put into cache.
102 pipe = self.SpawnProcess(cmd)
103 self._cmd2pipe[cmd_tuple] = pipe
104 self._lru = [(cmd_tuple, pipe)] + self._lru
105 return pipe
106
107 def SpawnProcess(self, cmd):
David Srbeckyfd1e4162021-04-27 22:24:36 +0100108 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
Andreas Gampe46b00d62017-05-17 15:12:27 -0700109
110 def TerminateProcess(self, pipe):
111 pipe.stdin.close()
112 pipe.stdout.close()
113 pipe.terminate()
114 pipe.wait()
115
116 def KillAllProcesses(self):
117 for _, open_pipe in self._lru:
118 self.TerminateProcess(open_pipe)
119 _cmd2pipe = {}
120 _lru = []
121
122
123_PIPE_ADDR2LINE_CACHE = ProcessCache()
124_PIPE_CPPFILT_CACHE = ProcessCache()
125
126
127# Process cache cleanup on shutdown.
128
129def CloseAllPipes():
130 _PIPE_ADDR2LINE_CACHE.KillAllProcesses()
131 _PIPE_CPPFILT_CACHE.KillAllProcesses()
132
133
134atexit.register(CloseAllPipes)
135
136
137def PipeTermHandler(signum, frame):
138 CloseAllPipes()
139 os._exit(0)
140
141
142for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM):
143 signal.signal(sig, PipeTermHandler)
144
145
146
Ben Chengb42dad02013-04-25 15:14:04 -0700147
Elliott Hughes08365932014-06-13 18:12:25 -0700148def ToolPath(tool, toolchain=None):
Julien Desprezfd06c732021-04-20 14:31:19 -0700149 """Return a fully-qualified path to the specified tool, or just the tool if it's on PATH """
150 if shutil.which(tool) is not None:
151 return tool
Elliott Hughes08365932014-06-13 18:12:25 -0700152 if not toolchain:
153 toolchain = FindToolchain()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800154 return os.path.join(toolchain, tool)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700155
Elliott Hughesc3c86192014-08-29 13:49:57 -0700156
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700157def FindToolchain():
Elliott Hughesc3c86192014-08-29 13:49:57 -0700158 """Returns the toolchain matching ARCH."""
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800159
Elliott Hughesc3c86192014-08-29 13:49:57 -0700160 global _CACHED_TOOLCHAIN, _CACHED_TOOLCHAIN_ARCH
161 if _CACHED_TOOLCHAIN is not None and _CACHED_TOOLCHAIN_ARCH == ARCH:
162 return _CACHED_TOOLCHAIN
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700163
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800164 llvm_binutils_dir = ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/llvm-binutils-stable/";
165 if not os.path.exists(llvm_binutils_dir):
166 raise Exception("Could not find llvm tool chain directory %s" % (llvm_binutils_dir))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700167
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800168 _CACHED_TOOLCHAIN = llvm_binutils_dir
Elliott Hughesc3c86192014-08-29 13:49:57 -0700169 _CACHED_TOOLCHAIN_ARCH = ARCH
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800170 print("Using", _CACHED_TOOLCHAIN_ARCH, "toolchain from:", _CACHED_TOOLCHAIN)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700171 return _CACHED_TOOLCHAIN
172
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700173
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700174def SymbolInformation(lib, addr):
175 """Look up symbol information about an address.
176
177 Args:
178 lib: library (or executable) pathname containing symbols
179 addr: string hexidecimal address
180
181 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700182 A list of the form [(source_symbol, source_location,
183 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700184
Ben Chengb42dad02013-04-25 15:14:04 -0700185 If the function has been inlined then the list may contain
186 more than one element with the symbols for the most deeply
187 nested inlined location appearing first. The list is
188 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700189
Ben Chengb42dad02013-04-25 15:14:04 -0700190 Usually you want to display the source_location and
191 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700192 """
193 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700194 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700195
196
197def SymbolInformationForSet(lib, unique_addrs):
198 """Look up symbol information for a set of addresses from the given library.
199
200 Args:
201 lib: library (or executable) pathname containing symbols
202 unique_addrs: set of hexidecimal addresses
203
204 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700205 A dictionary of the form {addr: [(source_symbol, source_location,
206 object_symbol_with_offset)]} where each address has a list of
207 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700208
Ben Chengb42dad02013-04-25 15:14:04 -0700209 If the function has been inlined then the list may contain
210 more than one element with the symbols for the most deeply
211 nested inlined location appearing first. The list is
212 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700213
Ben Chengb42dad02013-04-25 15:14:04 -0700214 Usually you want to display the source_location and
215 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700216 """
217 if not lib:
218 return None
219
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800220 addr_to_line = CallLlvmSymbolizerForSet(lib, unique_addrs)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700221 if not addr_to_line:
222 return None
223
224 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
225 if not addr_to_objdump:
226 return None
227
228 result = {}
229 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700230 source_info = addr_to_line.get(addr)
231 if not source_info:
232 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700233 if addr in addr_to_objdump:
234 (object_symbol, object_offset) = addr_to_objdump.get(addr)
235 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
236 object_offset)
237 else:
238 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700239 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
240 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700241
242 return result
243
244
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800245def CallLlvmSymbolizerForSet(lib, unique_addrs):
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700246 """Look up line and symbol information for a set of addresses.
247
248 Args:
249 lib: library (or executable) pathname containing symbols
250 unique_addrs: set of string hexidecimal addresses look up.
251
252 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700253 A dictionary of the form {addr: [(symbol, file:line)]} where
254 each address has a list of associated symbols and locations
255 or an empty list if no symbol information was found.
256
257 If the function has been inlined then the list may contain
258 more than one element with the symbols for the most deeply
259 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700260 """
261 if not lib:
262 return None
263
Andreas Gampe3d97a462017-05-17 14:16:45 -0700264 result = {}
265 addrs = sorted(unique_addrs)
266
267 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
268 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
269
270 # Go through and handle all known addresses.
271 for x in range(len(addrs)):
272 next_addr = addrs.pop(0)
273 if next_addr in addr_cache:
274 result[next_addr] = addr_cache[next_addr]
275 else:
276 # Re-add, needs to be symbolized.
277 addrs.append(next_addr)
278
279 if not addrs:
280 # Everything was cached, we're done.
281 return result
282 else:
283 addr_cache = {}
284 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
285
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700286 symbols = SYMBOLS_DIR + lib
287 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700288 symbols = lib
289 if not os.path.exists(symbols):
290 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700291
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700292 # Make sure the symbols path is not a directory.
293 if os.path.isdir(symbols):
294 return None
295
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800296 cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines",
297 "--demangle", "--obj=" + symbols, "--output-style=GNU"]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700298 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700299
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700300 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700301 try:
302 child.stdin.write("0x%s\n" % addr)
303 child.stdin.flush()
304 records = []
305 first = True
306 while True:
307 symbol = child.stdout.readline().strip()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800308 if not symbol:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700309 break
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800310 location = child.stdout.readline().strip()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700311 records.append((symbol, location))
312 if first:
313 # Write a blank line as a sentinel so we know when to stop
314 # reading inlines from the output.
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800315 # The blank line will cause llvm-symbolizer to emit a blank line.
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700316 child.stdin.write("\n")
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800317 child.stdin.flush()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700318 first = False
319 except IOError as e:
320 # Remove the / in front of the library name to match other output.
321 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700322 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700323 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700324 return result
325
326
Ben Chengb42dad02013-04-25 15:14:04 -0700327def StripPC(addr):
328 """Strips the Thumb bit a program counter address when appropriate.
329
330 Args:
331 addr: the program counter address
332
333 Returns:
334 The stripped program counter address.
335 """
336 global ARCH
Ben Chengb42dad02013-04-25 15:14:04 -0700337 if ARCH == "arm":
338 return addr & ~1
339 return addr
340
Elliott Hughesc3c86192014-08-29 13:49:57 -0700341
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700342def CallObjdumpForSet(lib, unique_addrs):
343 """Use objdump to find out the names of the containing functions.
344
345 Args:
346 lib: library (or executable) pathname containing symbols
347 unique_addrs: set of string hexidecimal addresses to find the functions for.
348
349 Returns:
350 A dictionary of the form {addr: (string symbol, offset)}.
351 """
352 if not lib:
353 return None
354
Andreas Gampe3d97a462017-05-17 14:16:45 -0700355 result = {}
356 addrs = sorted(unique_addrs)
357
358 addr_cache = None
359 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
360 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
361
362 # Go through and handle all known addresses.
363 for x in range(len(addrs)):
364 next_addr = addrs.pop(0)
365 if next_addr in addr_cache:
366 result[next_addr] = addr_cache[next_addr]
367 else:
368 # Re-add, needs to be symbolized.
369 addrs.append(next_addr)
370
371 if not addrs:
372 # Everything was cached, we're done.
373 return result
374 else:
375 addr_cache = {}
376 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
377
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700378 symbols = SYMBOLS_DIR + lib
379 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700380 symbols = lib
381 if not os.path.exists(symbols):
382 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700383
Ben Chengb42dad02013-04-25 15:14:04 -0700384 start_addr_dec = str(StripPC(int(addrs[0], 16)))
385 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800386 cmd = [ToolPath("llvm-objdump"),
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700387 "--section=.text",
388 "--demangle",
389 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700390 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700391 "--stop-address=" + stop_addr_dec,
392 symbols]
393
394 # Function lines look like:
395 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
396 # We pull out the address and function first. Then we check for an optional
397 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
398 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
399 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
400
401 # A disassembly line looks like:
402 # 177b2: b510 push {r4, lr}
403 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
404
405 current_symbol = None # The current function symbol in the disassembly.
406 current_symbol_addr = 0 # The address of the current function.
407 addr_index = 0 # The address that we are currently looking for.
408
David Srbeckyfd1e4162021-04-27 22:24:36 +0100409 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700410 for line in stream:
411 # Is it a function line like:
412 # 000177b0 <android::IBinder::~IBinder()>:
413 components = func_regexp.match(line)
414 if components:
415 # This is a new function, so record the current function and its address.
416 current_symbol_addr = int(components.group(1), 16)
417 current_symbol = components.group(2)
418
419 # Does it have an optional offset like: "foo(..)+0x2c"?
420 components = offset_regexp.match(current_symbol)
421 if components:
422 current_symbol = components.group(1)
423 offset = components.group(2)
424 if offset:
425 current_symbol_addr -= int(offset, 16)
426
427 # Is it an disassembly line like:
428 # 177b2: b510 push {r4, lr}
429 components = asm_regexp.match(line)
430 if components:
431 addr = components.group(1)
432 target_addr = addrs[addr_index]
433 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700434 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700435 if i_addr == i_target:
436 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700437 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700438 addr_index += 1
439 if addr_index >= len(addrs):
440 break
441 stream.close()
442
443 return result
444
445
446def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700447 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
448 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
449
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800450 global _CACHED_CXX_FILT
451 if not _CACHED_CXX_FILT:
Julien Desprezfd06c732021-04-20 14:31:19 -0700452 toolchains = None
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -0700453 clang_dir = FindClangDir()
454 if clang_dir:
455 if os.path.exists(clang_dir + "/bin/llvm-cxxfilt"):
456 toolchains = [clang_dir + "/bin/llvm-cxxfilt"]
457 else:
458 raise Exception("bin/llvm-cxxfilt missing from " + clang_dir)
459 else:
460 # When run in CI, we don't have a way to find the clang version. But
461 # llvm-cxxfilt should be available in the following relative path.
462 toolchains = glob.glob("./clang-r*/bin/llvm-cxxfilt")
463 if toolchains and len(toolchains) != 1:
464 raise Exception("Expected one llvm-cxxfilt but found many: " + \
465 ", ".join(toolchains))
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800466 if not toolchains:
Julien Desprezfd06c732021-04-20 14:31:19 -0700467 raise Exception("Could not find llvm-cxxfilt tool")
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800468 _CACHED_CXX_FILT = sorted(toolchains)[-1]
469
470 cmd = [_CACHED_CXX_FILT]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700471 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700472 process.stdin.write(mangled_symbol)
473 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700474 process.stdin.flush()
475
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700476 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700477
478 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
479
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700480 return demangled_symbol
481
Elliott Hughesc3c86192014-08-29 13:49:57 -0700482
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700483def FormatSymbolWithOffset(symbol, offset):
484 if offset == 0:
485 return symbol
486 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700487
David Srbecky80547ae2021-11-01 21:59:59 +0000488def FormatSymbolWithoutParameters(symbol):
489 """Remove parameters from function.
490
491 Rather than trying to parse the demangled C++ signature,
492 it just removes matching top level parenthesis.
493 """
494 if not symbol:
495 return symbol
496
497 result = symbol
498 result = result.replace(") const", ")") # Strip const keyword.
499 result = result.replace("operator<<", "operator\u00AB") # Avoid unmatched '<'.
500 result = result.replace("operator>>", "operator\u00BB") # Avoid unmatched '>'.
501 result = result.replace("operator->", "operator\u2192") # Avoid unmatched '>'.
502
503 nested = [] # Keeps tract of current nesting level of parenthesis.
504 for i in reversed(range(len(result))): # Iterate backward to make cutting easier.
505 c = result[i]
506 if c == ')' or c == '>':
507 if len(nested) == 0:
508 end = i + 1 # Mark the end of top-level pair.
509 nested.append(c)
510 if c == '(' or c == '<':
511 if len(nested) == 0 or {')':'(', '>':'<'}[nested.pop()] != c:
512 return symbol # Malformed: character does not match its pair.
513 if len(nested) == 0 and c == '(' and (end - i) > 2:
514 result = result[:i] + result[end:] # Remove substring (i, end).
515 if len(nested) > 0:
516 return symbol # Malformed: missing pair.
517
518 return result.strip()
Elliott Hughesc3c86192014-08-29 13:49:57 -0700519
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800520def GetAbiFromToolchain(toolchain_var, bits):
521 toolchain = os.environ.get(toolchain_var)
522 if not toolchain:
523 return None
524
525 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain)
526 if toolchain_match:
527 abi = toolchain_match.group(1)
528 if abi == "aarch64":
529 return "arm64"
530 elif bits == 64:
531 if abi == "x86":
532 return "x86_64"
533 elif abi == "mips":
534 return "mips64"
535 return abi
536 return None
537
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700538def Get32BitArch():
539 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that.
540 # If not try ANDROID_TOOLCHAIN to find the arch.
541 # If this is not set, then default to arm.
542 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32)
543 if not arch:
544 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32)
545 if not arch:
546 return "arm"
547 return arch
548
549def Get64BitArch():
550 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the
551 # arch this way. If this is not set, then default to arm64.
552 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64)
553 if not arch:
554 return "arm64"
555 return arch
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800556
557def SetAbi(lines):
558 global ARCH
559
560 abi_line = re.compile("ABI: \'(.*)\'")
561 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700562 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800563
564 ARCH = None
565 for line in lines:
566 abi_match = abi_line.search(line)
567 if abi_match:
568 ARCH = abi_match.group(1)
569 break
570 trace_match = trace_line.search(line)
571 if trace_match:
572 # Try to guess the arch, we know the bitness.
573 if len(trace_match.group(1)) == 16:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700574 ARCH = Get64BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800575 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700576 ARCH = Get32BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800577 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700578 asan_trace_match = asan_trace_line.search(line)
579 if asan_trace_match:
580 # We might be able to guess the bitness by the length of the address.
581 if len(asan_trace_match.group(1)) > 8:
582 ARCH = Get64BitArch()
583 # We know for a fact this is 64 bit, so we are done.
584 break
585 else:
586 ARCH = Get32BitArch()
587 # This might be 32 bit, or just a small address. Keep going in this
588 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800589 if not ARCH:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700590 raise Exception("Could not determine arch from input, use --arch=XXX to specify it")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800591
Elliott Hughesc3c86192014-08-29 13:49:57 -0700592
593class FindToolchainTests(unittest.TestCase):
594 def assert_toolchain_found(self, abi):
595 global ARCH
596 ARCH = abi
597 FindToolchain() # Will throw on failure.
598
Andreas Gampee547eb32018-10-29 18:31:37 -0700599 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
Elliott Hughesc3c86192014-08-29 13:49:57 -0700600 def test_toolchains_found(self):
601 self.assert_toolchain_found("arm")
602 self.assert_toolchain_found("arm64")
603 self.assert_toolchain_found("mips")
604 self.assert_toolchain_found("x86")
605 self.assert_toolchain_found("x86_64")
606
Pirama Arumuga Nainar8e96f312021-06-24 15:53:09 -0700607class FindClangDirTests(unittest.TestCase):
608 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
609 def test_clang_dir_found(self):
610 self.assertIsNotNone(FindClangDir())
611
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800612class SetArchTests(unittest.TestCase):
613 def test_abi_check(self):
614 global ARCH
615
616 SetAbi(["ABI: 'arm'"])
617 self.assertEqual(ARCH, "arm")
618 SetAbi(["ABI: 'arm64'"])
619 self.assertEqual(ARCH, "arm64")
620
621 SetAbi(["ABI: 'mips'"])
622 self.assertEqual(ARCH, "mips")
623 SetAbi(["ABI: 'mips64'"])
624 self.assertEqual(ARCH, "mips64")
625
626 SetAbi(["ABI: 'x86'"])
627 self.assertEqual(ARCH, "x86")
628 SetAbi(["ABI: 'x86_64'"])
629 self.assertEqual(ARCH, "x86_64")
630
631 def test_32bit_trace_line_toolchain(self):
632 global ARCH
633
634 os.environ.clear()
635 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
636 SetAbi(["#00 pc 000374e0"])
637 self.assertEqual(ARCH, "arm")
638
639 os.environ.clear()
640 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
641 SetAbi(["#00 pc 000374e0"])
642 self.assertEqual(ARCH, "mips")
643
644 os.environ.clear()
645 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
646 SetAbi(["#00 pc 000374e0"])
647 self.assertEqual(ARCH, "x86")
648
649 def test_32bit_trace_line_toolchain_2nd(self):
650 global ARCH
651
652 os.environ.clear()
653 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
654 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
655 SetAbi(["#00 pc 000374e0"])
656 self.assertEqual(ARCH, "arm")
657
658 os.environ.clear()
659 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
660 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
661 SetAbi(["#00 pc 000374e0"])
662 self.assertEqual(ARCH, "mips")
663
664 os.environ.clear()
665 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
666 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
667 SetAbi(["#00 pc 000374e0"])
668 self.assertEqual(ARCH, "x86")
669
670 def test_64bit_trace_line_toolchain(self):
671 global ARCH
672
673 os.environ.clear()
674 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
675 SetAbi(["#00 pc 00000000000374e0"])
676 self.assertEqual(ARCH, "arm64")
677
678 os.environ.clear()
679 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
680 SetAbi(["#00 pc 00000000000374e0"])
681 self.assertEqual(ARCH, "mips64")
682
683 os.environ.clear()
684 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
685 SetAbi(["#00 pc 00000000000374e0"])
686 self.assertEqual(ARCH, "x86_64")
687
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700688 def test_trace_default_abis(self):
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800689 global ARCH
690
691 os.environ.clear()
692 SetAbi(["#00 pc 000374e0"])
693 self.assertEqual(ARCH, "arm")
694 SetAbi(["#00 pc 00000000000374e0"])
695 self.assertEqual(ARCH, "arm64")
696
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700697 def test_32bit_asan_trace_line_toolchain(self):
698 global ARCH
699
700 os.environ.clear()
701 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
702 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
703 self.assertEqual(ARCH, "arm")
704
705 os.environ.clear()
706 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
707 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
708 self.assertEqual(ARCH, "mips")
709
710 os.environ.clear()
711 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
712 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
713 self.assertEqual(ARCH, "x86")
714
715 def test_32bit_asan_trace_line_toolchain_2nd(self):
716 global ARCH
717
718 os.environ.clear()
719 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
720 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
721 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
722 self.assertEqual(ARCH, "arm")
723
724 os.environ.clear()
725 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
726 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
727 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
728 self.assertEqual(ARCH, "mips")
729
730 os.environ.clear()
731 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
732 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
733 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
734 self.assertEqual(ARCH, "x86")
735
736 def test_64bit_asan_trace_line_toolchain(self):
737 global ARCH
738
739 os.environ.clear()
740 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
741 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
742 self.assertEqual(ARCH, "arm64")
743
744 os.environ.clear()
745 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
746 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
747 self.assertEqual(ARCH, "mips64")
748
749 os.environ.clear()
750 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
751 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
752 self.assertEqual(ARCH, "x86_64")
753
754 # Verify that if an address that might be 32 bit comes first, that
755 # encountering a 64 bit address returns a 64 bit abi.
756 ARCH = None
757 os.environ.clear()
758 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
759 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
760 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
761 self.assertEqual(ARCH, "x86_64")
762
763 def test_asan_trace_default_abis(self):
764 global ARCH
765
766 os.environ.clear()
767 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
768 self.assertEqual(ARCH, "arm64")
769 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
770 self.assertEqual(ARCH, "arm")
771
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800772 def test_no_abi(self):
773 global ARCH
774
Andreas Gampe9240b452018-10-26 14:17:30 -0700775 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex
776 # does not provide that name.
777 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
778 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp')
779 self.assertRaisesRegex(Exception,
780 "Could not determine arch from input, use --arch=XXX to specify it",
781 SetAbi, [])
Elliott Hughesc3c86192014-08-29 13:49:57 -0700782
David Srbecky80547ae2021-11-01 21:59:59 +0000783class FormatSymbolWithoutParametersTests(unittest.TestCase):
784 def test_c(self):
785 self.assertEqual(FormatSymbolWithoutParameters("foo"), "foo")
786 self.assertEqual(FormatSymbolWithoutParameters("foo+42"), "foo+42")
787
788 def test_simple(self):
789 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)"), "foo")
790 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)+42"), "foo+42")
791 self.assertEqual(FormatSymbolWithoutParameters("bar::foo(int i)+42"), "bar::foo+42")
792 self.assertEqual(FormatSymbolWithoutParameters("operator()"), "operator()")
793
794 def test_templates(self):
795 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T>& v)"), "bar::foo<T>")
796 self.assertEqual(FormatSymbolWithoutParameters("bar<T>::foo(vector<T>& v)"), "bar<T>::foo")
797 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<T>(vector<T<U>>& v)"), "bar::foo<T>")
798 self.assertEqual(FormatSymbolWithoutParameters("bar::foo<(EnumType)0>(vector<(EnumType)0>& v)"),
799 "bar::foo<(EnumType)0>")
800
801 def test_nested(self):
802 self.assertEqual(FormatSymbolWithoutParameters("foo(int i)::bar(int j)"), "foo::bar")
803
804 def test_unballanced(self):
805 self.assertEqual(FormatSymbolWithoutParameters("foo(bar(int i)"), "foo(bar(int i)")
806 self.assertEqual(FormatSymbolWithoutParameters("foo)bar(int i)"), "foo)bar(int i)")
807 self.assertEqual(FormatSymbolWithoutParameters("foo<bar(int i)"), "foo<bar(int i)")
808 self.assertEqual(FormatSymbolWithoutParameters("foo>bar(int i)"), "foo>bar(int i)")
809
Elliott Hughesc3c86192014-08-29 13:49:57 -0700810if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700811 unittest.main(verbosity=2)