blob: 821da8e65b28f263f00064ef7b264986fc1f0b36 [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
34def FindSymbolsDir():
35 saveddir = os.getcwd()
36 os.chdir(ANDROID_BUILD_TOP)
Andreas Gampe9240b452018-10-26 14:17:30 -070037 stream = None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070038 try:
Dan Willemsend3fc8fa2017-10-17 14:04:56 -070039 cmd = "build/soong/soong_ui.bash --dumpvar-mode --abs TARGET_OUT_UNSTRIPPED"
David Srbeckyfd1e4162021-04-27 22:24:36 +010040 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True, shell=True).stdout
Krzysztof Kosińskib1361112021-03-11 18:05:01 -080041 return str(stream.read().strip())
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070042 finally:
Andreas Gampe9240b452018-10-26 14:17:30 -070043 if stream is not None:
44 stream.close()
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070045 os.chdir(saveddir)
46
47SYMBOLS_DIR = FindSymbolsDir()
48
Christopher Ferrisbf8a9402016-03-11 15:50:46 -080049ARCH = None
Ben Chengb42dad02013-04-25 15:14:04 -070050
Elliott Hughesc3c86192014-08-29 13:49:57 -070051
52# These are private. Do not access them from other modules.
53_CACHED_TOOLCHAIN = None
54_CACHED_TOOLCHAIN_ARCH = None
Christopher Ferris49eda0e2020-12-09 14:34:01 -080055_CACHED_CXX_FILT = None
Elliott Hughesc3c86192014-08-29 13:49:57 -070056
Andreas Gampe3d97a462017-05-17 14:16:45 -070057# Caches for symbolized information.
58_SYMBOL_INFORMATION_ADDR2LINE_CACHE = {}
59_SYMBOL_INFORMATION_OBJDUMP_CACHE = {}
60_SYMBOL_DEMANGLING_CACHE = {}
61
Andreas Gampe46b00d62017-05-17 15:12:27 -070062# Caches for pipes to subprocesses.
63
64class ProcessCache:
65 _cmd2pipe = {}
66 _lru = []
67
68 # Max number of open pipes.
69 _PIPE_MAX_OPEN = 10
70
71 def GetProcess(self, cmd):
72 cmd_tuple = tuple(cmd) # Need to use a tuple as lists can't be dict keys.
73 # Pipe already available?
74 if cmd_tuple in self._cmd2pipe:
75 pipe = self._cmd2pipe[cmd_tuple]
76 # Update LRU.
77 self._lru = [(cmd_tuple, pipe)] + [i for i in self._lru if i[0] != cmd_tuple]
78 return pipe
79
80 # Not cached, yet. Open a new one.
81
82 # Check if too many are open, close the old ones.
83 while len(self._lru) >= self._PIPE_MAX_OPEN:
84 open_cmd, open_pipe = self._lru.pop()
85 del self._cmd2pipe[open_cmd]
86 self.TerminateProcess(open_pipe)
87
88 # Create and put into cache.
89 pipe = self.SpawnProcess(cmd)
90 self._cmd2pipe[cmd_tuple] = pipe
91 self._lru = [(cmd_tuple, pipe)] + self._lru
92 return pipe
93
94 def SpawnProcess(self, cmd):
David Srbeckyfd1e4162021-04-27 22:24:36 +010095 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
Andreas Gampe46b00d62017-05-17 15:12:27 -070096
97 def TerminateProcess(self, pipe):
98 pipe.stdin.close()
99 pipe.stdout.close()
100 pipe.terminate()
101 pipe.wait()
102
103 def KillAllProcesses(self):
104 for _, open_pipe in self._lru:
105 self.TerminateProcess(open_pipe)
106 _cmd2pipe = {}
107 _lru = []
108
109
110_PIPE_ADDR2LINE_CACHE = ProcessCache()
111_PIPE_CPPFILT_CACHE = ProcessCache()
112
113
114# Process cache cleanup on shutdown.
115
116def CloseAllPipes():
117 _PIPE_ADDR2LINE_CACHE.KillAllProcesses()
118 _PIPE_CPPFILT_CACHE.KillAllProcesses()
119
120
121atexit.register(CloseAllPipes)
122
123
124def PipeTermHandler(signum, frame):
125 CloseAllPipes()
126 os._exit(0)
127
128
129for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM):
130 signal.signal(sig, PipeTermHandler)
131
132
133
Ben Chengb42dad02013-04-25 15:14:04 -0700134
Elliott Hughes08365932014-06-13 18:12:25 -0700135def ToolPath(tool, toolchain=None):
Julien Desprezfd06c732021-04-20 14:31:19 -0700136 """Return a fully-qualified path to the specified tool, or just the tool if it's on PATH """
137 if shutil.which(tool) is not None:
138 return tool
Elliott Hughes08365932014-06-13 18:12:25 -0700139 if not toolchain:
140 toolchain = FindToolchain()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800141 return os.path.join(toolchain, tool)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700142
Elliott Hughesc3c86192014-08-29 13:49:57 -0700143
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700144def FindToolchain():
Elliott Hughesc3c86192014-08-29 13:49:57 -0700145 """Returns the toolchain matching ARCH."""
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800146
Elliott Hughesc3c86192014-08-29 13:49:57 -0700147 global _CACHED_TOOLCHAIN, _CACHED_TOOLCHAIN_ARCH
148 if _CACHED_TOOLCHAIN is not None and _CACHED_TOOLCHAIN_ARCH == ARCH:
149 return _CACHED_TOOLCHAIN
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700150
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800151 llvm_binutils_dir = ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/llvm-binutils-stable/";
152 if not os.path.exists(llvm_binutils_dir):
153 raise Exception("Could not find llvm tool chain directory %s" % (llvm_binutils_dir))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700154
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800155 _CACHED_TOOLCHAIN = llvm_binutils_dir
Elliott Hughesc3c86192014-08-29 13:49:57 -0700156 _CACHED_TOOLCHAIN_ARCH = ARCH
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800157 print("Using", _CACHED_TOOLCHAIN_ARCH, "toolchain from:", _CACHED_TOOLCHAIN)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700158 return _CACHED_TOOLCHAIN
159
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700160
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700161def SymbolInformation(lib, addr):
162 """Look up symbol information about an address.
163
164 Args:
165 lib: library (or executable) pathname containing symbols
166 addr: string hexidecimal address
167
168 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700169 A list of the form [(source_symbol, source_location,
170 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700171
Ben Chengb42dad02013-04-25 15:14:04 -0700172 If the function has been inlined then the list may contain
173 more than one element with the symbols for the most deeply
174 nested inlined location appearing first. The list is
175 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700176
Ben Chengb42dad02013-04-25 15:14:04 -0700177 Usually you want to display the source_location and
178 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700179 """
180 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700181 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700182
183
184def SymbolInformationForSet(lib, unique_addrs):
185 """Look up symbol information for a set of addresses from the given library.
186
187 Args:
188 lib: library (or executable) pathname containing symbols
189 unique_addrs: set of hexidecimal addresses
190
191 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700192 A dictionary of the form {addr: [(source_symbol, source_location,
193 object_symbol_with_offset)]} where each address has a list of
194 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700195
Ben Chengb42dad02013-04-25 15:14:04 -0700196 If the function has been inlined then the list may contain
197 more than one element with the symbols for the most deeply
198 nested inlined location appearing first. The list is
199 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700200
Ben Chengb42dad02013-04-25 15:14:04 -0700201 Usually you want to display the source_location and
202 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700203 """
204 if not lib:
205 return None
206
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800207 addr_to_line = CallLlvmSymbolizerForSet(lib, unique_addrs)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700208 if not addr_to_line:
209 return None
210
211 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
212 if not addr_to_objdump:
213 return None
214
215 result = {}
216 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700217 source_info = addr_to_line.get(addr)
218 if not source_info:
219 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700220 if addr in addr_to_objdump:
221 (object_symbol, object_offset) = addr_to_objdump.get(addr)
222 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
223 object_offset)
224 else:
225 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700226 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
227 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700228
229 return result
230
231
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800232def CallLlvmSymbolizerForSet(lib, unique_addrs):
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700233 """Look up line and symbol information for a set of addresses.
234
235 Args:
236 lib: library (or executable) pathname containing symbols
237 unique_addrs: set of string hexidecimal addresses look up.
238
239 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700240 A dictionary of the form {addr: [(symbol, file:line)]} where
241 each address has a list of associated symbols and locations
242 or an empty list if no symbol information was found.
243
244 If the function has been inlined then the list may contain
245 more than one element with the symbols for the most deeply
246 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700247 """
248 if not lib:
249 return None
250
Andreas Gampe3d97a462017-05-17 14:16:45 -0700251 result = {}
252 addrs = sorted(unique_addrs)
253
254 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
255 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
256
257 # Go through and handle all known addresses.
258 for x in range(len(addrs)):
259 next_addr = addrs.pop(0)
260 if next_addr in addr_cache:
261 result[next_addr] = addr_cache[next_addr]
262 else:
263 # Re-add, needs to be symbolized.
264 addrs.append(next_addr)
265
266 if not addrs:
267 # Everything was cached, we're done.
268 return result
269 else:
270 addr_cache = {}
271 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
272
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700273 symbols = SYMBOLS_DIR + lib
274 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700275 symbols = lib
276 if not os.path.exists(symbols):
277 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700278
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700279 # Make sure the symbols path is not a directory.
280 if os.path.isdir(symbols):
281 return None
282
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800283 cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines",
284 "--demangle", "--obj=" + symbols, "--output-style=GNU"]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700285 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700286
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700287 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700288 try:
289 child.stdin.write("0x%s\n" % addr)
290 child.stdin.flush()
291 records = []
292 first = True
293 while True:
294 symbol = child.stdout.readline().strip()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800295 if not symbol:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700296 break
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800297 location = child.stdout.readline().strip()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700298 records.append((symbol, location))
299 if first:
300 # Write a blank line as a sentinel so we know when to stop
301 # reading inlines from the output.
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800302 # The blank line will cause llvm-symbolizer to emit a blank line.
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700303 child.stdin.write("\n")
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800304 child.stdin.flush()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700305 first = False
306 except IOError as e:
307 # Remove the / in front of the library name to match other output.
308 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700309 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700310 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700311 return result
312
313
Ben Chengb42dad02013-04-25 15:14:04 -0700314def StripPC(addr):
315 """Strips the Thumb bit a program counter address when appropriate.
316
317 Args:
318 addr: the program counter address
319
320 Returns:
321 The stripped program counter address.
322 """
323 global ARCH
Ben Chengb42dad02013-04-25 15:14:04 -0700324 if ARCH == "arm":
325 return addr & ~1
326 return addr
327
Elliott Hughesc3c86192014-08-29 13:49:57 -0700328
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700329def CallObjdumpForSet(lib, unique_addrs):
330 """Use objdump to find out the names of the containing functions.
331
332 Args:
333 lib: library (or executable) pathname containing symbols
334 unique_addrs: set of string hexidecimal addresses to find the functions for.
335
336 Returns:
337 A dictionary of the form {addr: (string symbol, offset)}.
338 """
339 if not lib:
340 return None
341
Andreas Gampe3d97a462017-05-17 14:16:45 -0700342 result = {}
343 addrs = sorted(unique_addrs)
344
345 addr_cache = None
346 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
347 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
348
349 # Go through and handle all known addresses.
350 for x in range(len(addrs)):
351 next_addr = addrs.pop(0)
352 if next_addr in addr_cache:
353 result[next_addr] = addr_cache[next_addr]
354 else:
355 # Re-add, needs to be symbolized.
356 addrs.append(next_addr)
357
358 if not addrs:
359 # Everything was cached, we're done.
360 return result
361 else:
362 addr_cache = {}
363 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
364
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700365 symbols = SYMBOLS_DIR + lib
366 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700367 symbols = lib
368 if not os.path.exists(symbols):
369 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700370
Ben Chengb42dad02013-04-25 15:14:04 -0700371 start_addr_dec = str(StripPC(int(addrs[0], 16)))
372 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800373 cmd = [ToolPath("llvm-objdump"),
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700374 "--section=.text",
375 "--demangle",
376 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700377 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700378 "--stop-address=" + stop_addr_dec,
379 symbols]
380
381 # Function lines look like:
382 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
383 # We pull out the address and function first. Then we check for an optional
384 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
385 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
386 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
387
388 # A disassembly line looks like:
389 # 177b2: b510 push {r4, lr}
390 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
391
392 current_symbol = None # The current function symbol in the disassembly.
393 current_symbol_addr = 0 # The address of the current function.
394 addr_index = 0 # The address that we are currently looking for.
395
David Srbeckyfd1e4162021-04-27 22:24:36 +0100396 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700397 for line in stream:
398 # Is it a function line like:
399 # 000177b0 <android::IBinder::~IBinder()>:
400 components = func_regexp.match(line)
401 if components:
402 # This is a new function, so record the current function and its address.
403 current_symbol_addr = int(components.group(1), 16)
404 current_symbol = components.group(2)
405
406 # Does it have an optional offset like: "foo(..)+0x2c"?
407 components = offset_regexp.match(current_symbol)
408 if components:
409 current_symbol = components.group(1)
410 offset = components.group(2)
411 if offset:
412 current_symbol_addr -= int(offset, 16)
413
414 # Is it an disassembly line like:
415 # 177b2: b510 push {r4, lr}
416 components = asm_regexp.match(line)
417 if components:
418 addr = components.group(1)
419 target_addr = addrs[addr_index]
420 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700421 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700422 if i_addr == i_target:
423 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700424 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700425 addr_index += 1
426 if addr_index >= len(addrs):
427 break
428 stream.close()
429
430 return result
431
432
433def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700434 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
435 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
436
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800437 global _CACHED_CXX_FILT
438 if not _CACHED_CXX_FILT:
Julien Desprezfd06c732021-04-20 14:31:19 -0700439 toolchains = None
440 # TODO(b/187231324) do not hard-code prebuilt version number below
441 if os.path.exists('./clang-r416183b/bin/llvm-cxxfilt'):
442 toolchains = ["./clang-r416183b/bin/llvm-cxxfilt"]
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800443 if not toolchains:
Julien Desprezfd06c732021-04-20 14:31:19 -0700444 raise Exception("Could not find llvm-cxxfilt tool")
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800445 _CACHED_CXX_FILT = sorted(toolchains)[-1]
446
447 cmd = [_CACHED_CXX_FILT]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700448 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700449 process.stdin.write(mangled_symbol)
450 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700451 process.stdin.flush()
452
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700453 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700454
455 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
456
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700457 return demangled_symbol
458
Elliott Hughesc3c86192014-08-29 13:49:57 -0700459
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700460def FormatSymbolWithOffset(symbol, offset):
461 if offset == 0:
462 return symbol
463 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700464
465
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800466def GetAbiFromToolchain(toolchain_var, bits):
467 toolchain = os.environ.get(toolchain_var)
468 if not toolchain:
469 return None
470
471 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain)
472 if toolchain_match:
473 abi = toolchain_match.group(1)
474 if abi == "aarch64":
475 return "arm64"
476 elif bits == 64:
477 if abi == "x86":
478 return "x86_64"
479 elif abi == "mips":
480 return "mips64"
481 return abi
482 return None
483
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700484def Get32BitArch():
485 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that.
486 # If not try ANDROID_TOOLCHAIN to find the arch.
487 # If this is not set, then default to arm.
488 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32)
489 if not arch:
490 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32)
491 if not arch:
492 return "arm"
493 return arch
494
495def Get64BitArch():
496 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the
497 # arch this way. If this is not set, then default to arm64.
498 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64)
499 if not arch:
500 return "arm64"
501 return arch
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800502
503def SetAbi(lines):
504 global ARCH
505
506 abi_line = re.compile("ABI: \'(.*)\'")
507 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700508 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800509
510 ARCH = None
511 for line in lines:
512 abi_match = abi_line.search(line)
513 if abi_match:
514 ARCH = abi_match.group(1)
515 break
516 trace_match = trace_line.search(line)
517 if trace_match:
518 # Try to guess the arch, we know the bitness.
519 if len(trace_match.group(1)) == 16:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700520 ARCH = Get64BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800521 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700522 ARCH = Get32BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800523 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700524 asan_trace_match = asan_trace_line.search(line)
525 if asan_trace_match:
526 # We might be able to guess the bitness by the length of the address.
527 if len(asan_trace_match.group(1)) > 8:
528 ARCH = Get64BitArch()
529 # We know for a fact this is 64 bit, so we are done.
530 break
531 else:
532 ARCH = Get32BitArch()
533 # This might be 32 bit, or just a small address. Keep going in this
534 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800535 if not ARCH:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700536 raise Exception("Could not determine arch from input, use --arch=XXX to specify it")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800537
Elliott Hughesc3c86192014-08-29 13:49:57 -0700538
539class FindToolchainTests(unittest.TestCase):
540 def assert_toolchain_found(self, abi):
541 global ARCH
542 ARCH = abi
543 FindToolchain() # Will throw on failure.
544
Andreas Gampee547eb32018-10-29 18:31:37 -0700545 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
Elliott Hughesc3c86192014-08-29 13:49:57 -0700546 def test_toolchains_found(self):
547 self.assert_toolchain_found("arm")
548 self.assert_toolchain_found("arm64")
549 self.assert_toolchain_found("mips")
550 self.assert_toolchain_found("x86")
551 self.assert_toolchain_found("x86_64")
552
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800553class SetArchTests(unittest.TestCase):
554 def test_abi_check(self):
555 global ARCH
556
557 SetAbi(["ABI: 'arm'"])
558 self.assertEqual(ARCH, "arm")
559 SetAbi(["ABI: 'arm64'"])
560 self.assertEqual(ARCH, "arm64")
561
562 SetAbi(["ABI: 'mips'"])
563 self.assertEqual(ARCH, "mips")
564 SetAbi(["ABI: 'mips64'"])
565 self.assertEqual(ARCH, "mips64")
566
567 SetAbi(["ABI: 'x86'"])
568 self.assertEqual(ARCH, "x86")
569 SetAbi(["ABI: 'x86_64'"])
570 self.assertEqual(ARCH, "x86_64")
571
572 def test_32bit_trace_line_toolchain(self):
573 global ARCH
574
575 os.environ.clear()
576 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
577 SetAbi(["#00 pc 000374e0"])
578 self.assertEqual(ARCH, "arm")
579
580 os.environ.clear()
581 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
582 SetAbi(["#00 pc 000374e0"])
583 self.assertEqual(ARCH, "mips")
584
585 os.environ.clear()
586 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
587 SetAbi(["#00 pc 000374e0"])
588 self.assertEqual(ARCH, "x86")
589
590 def test_32bit_trace_line_toolchain_2nd(self):
591 global ARCH
592
593 os.environ.clear()
594 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
595 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
596 SetAbi(["#00 pc 000374e0"])
597 self.assertEqual(ARCH, "arm")
598
599 os.environ.clear()
600 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
601 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
602 SetAbi(["#00 pc 000374e0"])
603 self.assertEqual(ARCH, "mips")
604
605 os.environ.clear()
606 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
607 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
608 SetAbi(["#00 pc 000374e0"])
609 self.assertEqual(ARCH, "x86")
610
611 def test_64bit_trace_line_toolchain(self):
612 global ARCH
613
614 os.environ.clear()
615 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
616 SetAbi(["#00 pc 00000000000374e0"])
617 self.assertEqual(ARCH, "arm64")
618
619 os.environ.clear()
620 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
621 SetAbi(["#00 pc 00000000000374e0"])
622 self.assertEqual(ARCH, "mips64")
623
624 os.environ.clear()
625 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
626 SetAbi(["#00 pc 00000000000374e0"])
627 self.assertEqual(ARCH, "x86_64")
628
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700629 def test_trace_default_abis(self):
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800630 global ARCH
631
632 os.environ.clear()
633 SetAbi(["#00 pc 000374e0"])
634 self.assertEqual(ARCH, "arm")
635 SetAbi(["#00 pc 00000000000374e0"])
636 self.assertEqual(ARCH, "arm64")
637
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700638 def test_32bit_asan_trace_line_toolchain(self):
639 global ARCH
640
641 os.environ.clear()
642 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
643 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
644 self.assertEqual(ARCH, "arm")
645
646 os.environ.clear()
647 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
648 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
649 self.assertEqual(ARCH, "mips")
650
651 os.environ.clear()
652 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
653 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
654 self.assertEqual(ARCH, "x86")
655
656 def test_32bit_asan_trace_line_toolchain_2nd(self):
657 global ARCH
658
659 os.environ.clear()
660 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
661 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
662 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
663 self.assertEqual(ARCH, "arm")
664
665 os.environ.clear()
666 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
667 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
668 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
669 self.assertEqual(ARCH, "mips")
670
671 os.environ.clear()
672 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
673 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
674 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
675 self.assertEqual(ARCH, "x86")
676
677 def test_64bit_asan_trace_line_toolchain(self):
678 global ARCH
679
680 os.environ.clear()
681 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
682 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
683 self.assertEqual(ARCH, "arm64")
684
685 os.environ.clear()
686 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
687 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
688 self.assertEqual(ARCH, "mips64")
689
690 os.environ.clear()
691 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
692 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
693 self.assertEqual(ARCH, "x86_64")
694
695 # Verify that if an address that might be 32 bit comes first, that
696 # encountering a 64 bit address returns a 64 bit abi.
697 ARCH = None
698 os.environ.clear()
699 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
700 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
701 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
702 self.assertEqual(ARCH, "x86_64")
703
704 def test_asan_trace_default_abis(self):
705 global ARCH
706
707 os.environ.clear()
708 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
709 self.assertEqual(ARCH, "arm64")
710 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
711 self.assertEqual(ARCH, "arm")
712
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800713 def test_no_abi(self):
714 global ARCH
715
Andreas Gampe9240b452018-10-26 14:17:30 -0700716 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex
717 # does not provide that name.
718 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
719 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp')
720 self.assertRaisesRegex(Exception,
721 "Could not determine arch from input, use --arch=XXX to specify it",
722 SetAbi, [])
Elliott Hughesc3c86192014-08-29 13:49:57 -0700723
724if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700725 unittest.main(verbosity=2)