blob: 360a2cbc34286ad27dadbc3abcacc688e4ac4229 [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
Andreas Gampe46b00d62017-05-17 15:12:27 -070027import signal
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070028import subprocess
Elliott Hughesc3c86192014-08-29 13:49:57 -070029import unittest
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070030
Krzysztof Kosińskib1361112021-03-11 18:05:01 -080031ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP", ".")
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070032
33def FindSymbolsDir():
34 saveddir = os.getcwd()
35 os.chdir(ANDROID_BUILD_TOP)
Andreas Gampe9240b452018-10-26 14:17:30 -070036 stream = None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070037 try:
Dan Willemsend3fc8fa2017-10-17 14:04:56 -070038 cmd = "build/soong/soong_ui.bash --dumpvar-mode --abs TARGET_OUT_UNSTRIPPED"
David Srbeckyfd1e4162021-04-27 22:24:36 +010039 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True, shell=True).stdout
Krzysztof Kosińskib1361112021-03-11 18:05:01 -080040 return str(stream.read().strip())
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070041 finally:
Andreas Gampe9240b452018-10-26 14:17:30 -070042 if stream is not None:
43 stream.close()
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070044 os.chdir(saveddir)
45
46SYMBOLS_DIR = FindSymbolsDir()
47
Christopher Ferrisbf8a9402016-03-11 15:50:46 -080048ARCH = None
Ben Chengb42dad02013-04-25 15:14:04 -070049
Elliott Hughesc3c86192014-08-29 13:49:57 -070050
51# These are private. Do not access them from other modules.
52_CACHED_TOOLCHAIN = None
53_CACHED_TOOLCHAIN_ARCH = None
Christopher Ferris49eda0e2020-12-09 14:34:01 -080054_CACHED_CXX_FILT = None
Elliott Hughesc3c86192014-08-29 13:49:57 -070055
Andreas Gampe3d97a462017-05-17 14:16:45 -070056# Caches for symbolized information.
57_SYMBOL_INFORMATION_ADDR2LINE_CACHE = {}
58_SYMBOL_INFORMATION_OBJDUMP_CACHE = {}
59_SYMBOL_DEMANGLING_CACHE = {}
60
Andreas Gampe46b00d62017-05-17 15:12:27 -070061# Caches for pipes to subprocesses.
62
63class ProcessCache:
64 _cmd2pipe = {}
65 _lru = []
66
67 # Max number of open pipes.
68 _PIPE_MAX_OPEN = 10
69
70 def GetProcess(self, cmd):
71 cmd_tuple = tuple(cmd) # Need to use a tuple as lists can't be dict keys.
72 # Pipe already available?
73 if cmd_tuple in self._cmd2pipe:
74 pipe = self._cmd2pipe[cmd_tuple]
75 # Update LRU.
76 self._lru = [(cmd_tuple, pipe)] + [i for i in self._lru if i[0] != cmd_tuple]
77 return pipe
78
79 # Not cached, yet. Open a new one.
80
81 # Check if too many are open, close the old ones.
82 while len(self._lru) >= self._PIPE_MAX_OPEN:
83 open_cmd, open_pipe = self._lru.pop()
84 del self._cmd2pipe[open_cmd]
85 self.TerminateProcess(open_pipe)
86
87 # Create and put into cache.
88 pipe = self.SpawnProcess(cmd)
89 self._cmd2pipe[cmd_tuple] = pipe
90 self._lru = [(cmd_tuple, pipe)] + self._lru
91 return pipe
92
93 def SpawnProcess(self, cmd):
David Srbeckyfd1e4162021-04-27 22:24:36 +010094 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
Andreas Gampe46b00d62017-05-17 15:12:27 -070095
96 def TerminateProcess(self, pipe):
97 pipe.stdin.close()
98 pipe.stdout.close()
99 pipe.terminate()
100 pipe.wait()
101
102 def KillAllProcesses(self):
103 for _, open_pipe in self._lru:
104 self.TerminateProcess(open_pipe)
105 _cmd2pipe = {}
106 _lru = []
107
108
109_PIPE_ADDR2LINE_CACHE = ProcessCache()
110_PIPE_CPPFILT_CACHE = ProcessCache()
111
112
113# Process cache cleanup on shutdown.
114
115def CloseAllPipes():
116 _PIPE_ADDR2LINE_CACHE.KillAllProcesses()
117 _PIPE_CPPFILT_CACHE.KillAllProcesses()
118
119
120atexit.register(CloseAllPipes)
121
122
123def PipeTermHandler(signum, frame):
124 CloseAllPipes()
125 os._exit(0)
126
127
128for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM):
129 signal.signal(sig, PipeTermHandler)
130
131
132
Ben Chengb42dad02013-04-25 15:14:04 -0700133
Elliott Hughes08365932014-06-13 18:12:25 -0700134def ToolPath(tool, toolchain=None):
135 """Return a fully-qualified path to the specified tool"""
136 if not toolchain:
137 toolchain = FindToolchain()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800138 return os.path.join(toolchain, tool)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700139
Elliott Hughesc3c86192014-08-29 13:49:57 -0700140
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700141def FindToolchain():
Elliott Hughesc3c86192014-08-29 13:49:57 -0700142 """Returns the toolchain matching ARCH."""
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800143
Elliott Hughesc3c86192014-08-29 13:49:57 -0700144 global _CACHED_TOOLCHAIN, _CACHED_TOOLCHAIN_ARCH
145 if _CACHED_TOOLCHAIN is not None and _CACHED_TOOLCHAIN_ARCH == ARCH:
146 return _CACHED_TOOLCHAIN
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700147
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800148 llvm_binutils_dir = ANDROID_BUILD_TOP + "/prebuilts/clang/host/linux-x86/llvm-binutils-stable/";
149 if not os.path.exists(llvm_binutils_dir):
150 raise Exception("Could not find llvm tool chain directory %s" % (llvm_binutils_dir))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700151
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800152 _CACHED_TOOLCHAIN = llvm_binutils_dir
Elliott Hughesc3c86192014-08-29 13:49:57 -0700153 _CACHED_TOOLCHAIN_ARCH = ARCH
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800154 print("Using", _CACHED_TOOLCHAIN_ARCH, "toolchain from:", _CACHED_TOOLCHAIN)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700155 return _CACHED_TOOLCHAIN
156
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700157
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700158def SymbolInformation(lib, addr):
159 """Look up symbol information about an address.
160
161 Args:
162 lib: library (or executable) pathname containing symbols
163 addr: string hexidecimal address
164
165 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700166 A list of the form [(source_symbol, source_location,
167 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700168
Ben Chengb42dad02013-04-25 15:14:04 -0700169 If the function has been inlined then the list may contain
170 more than one element with the symbols for the most deeply
171 nested inlined location appearing first. The list is
172 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700173
Ben Chengb42dad02013-04-25 15:14:04 -0700174 Usually you want to display the source_location and
175 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700176 """
177 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700178 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700179
180
181def SymbolInformationForSet(lib, unique_addrs):
182 """Look up symbol information for a set of addresses from the given library.
183
184 Args:
185 lib: library (or executable) pathname containing symbols
186 unique_addrs: set of hexidecimal addresses
187
188 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700189 A dictionary of the form {addr: [(source_symbol, source_location,
190 object_symbol_with_offset)]} where each address has a list of
191 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700192
Ben Chengb42dad02013-04-25 15:14:04 -0700193 If the function has been inlined then the list may contain
194 more than one element with the symbols for the most deeply
195 nested inlined location appearing first. The list is
196 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700197
Ben Chengb42dad02013-04-25 15:14:04 -0700198 Usually you want to display the source_location and
199 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700200 """
201 if not lib:
202 return None
203
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800204 addr_to_line = CallLlvmSymbolizerForSet(lib, unique_addrs)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700205 if not addr_to_line:
206 return None
207
208 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
209 if not addr_to_objdump:
210 return None
211
212 result = {}
213 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700214 source_info = addr_to_line.get(addr)
215 if not source_info:
216 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700217 if addr in addr_to_objdump:
218 (object_symbol, object_offset) = addr_to_objdump.get(addr)
219 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
220 object_offset)
221 else:
222 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700223 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
224 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700225
226 return result
227
228
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800229def CallLlvmSymbolizerForSet(lib, unique_addrs):
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700230 """Look up line and symbol information for a set of addresses.
231
232 Args:
233 lib: library (or executable) pathname containing symbols
234 unique_addrs: set of string hexidecimal addresses look up.
235
236 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700237 A dictionary of the form {addr: [(symbol, file:line)]} where
238 each address has a list of associated symbols and locations
239 or an empty list if no symbol information was found.
240
241 If the function has been inlined then the list may contain
242 more than one element with the symbols for the most deeply
243 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700244 """
245 if not lib:
246 return None
247
Andreas Gampe3d97a462017-05-17 14:16:45 -0700248 result = {}
249 addrs = sorted(unique_addrs)
250
251 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
252 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
253
254 # Go through and handle all known addresses.
255 for x in range(len(addrs)):
256 next_addr = addrs.pop(0)
257 if next_addr in addr_cache:
258 result[next_addr] = addr_cache[next_addr]
259 else:
260 # Re-add, needs to be symbolized.
261 addrs.append(next_addr)
262
263 if not addrs:
264 # Everything was cached, we're done.
265 return result
266 else:
267 addr_cache = {}
268 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
269
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700270 symbols = SYMBOLS_DIR + lib
271 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700272 symbols = lib
273 if not os.path.exists(symbols):
274 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700275
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700276 # Make sure the symbols path is not a directory.
277 if os.path.isdir(symbols):
278 return None
279
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800280 cmd = [ToolPath("llvm-symbolizer"), "--functions", "--inlines",
281 "--demangle", "--obj=" + symbols, "--output-style=GNU"]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700282 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700283
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700284 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700285 try:
286 child.stdin.write("0x%s\n" % addr)
287 child.stdin.flush()
288 records = []
289 first = True
290 while True:
291 symbol = child.stdout.readline().strip()
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800292 if not symbol:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700293 break
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800294 location = child.stdout.readline().strip()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700295 records.append((symbol, location))
296 if first:
297 # Write a blank line as a sentinel so we know when to stop
298 # reading inlines from the output.
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800299 # The blank line will cause llvm-symbolizer to emit a blank line.
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700300 child.stdin.write("\n")
Krzysztof Kosińskib1361112021-03-11 18:05:01 -0800301 child.stdin.flush()
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700302 first = False
303 except IOError as e:
304 # Remove the / in front of the library name to match other output.
305 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700306 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700307 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700308 return result
309
310
Ben Chengb42dad02013-04-25 15:14:04 -0700311def StripPC(addr):
312 """Strips the Thumb bit a program counter address when appropriate.
313
314 Args:
315 addr: the program counter address
316
317 Returns:
318 The stripped program counter address.
319 """
320 global ARCH
Ben Chengb42dad02013-04-25 15:14:04 -0700321 if ARCH == "arm":
322 return addr & ~1
323 return addr
324
Elliott Hughesc3c86192014-08-29 13:49:57 -0700325
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700326def CallObjdumpForSet(lib, unique_addrs):
327 """Use objdump to find out the names of the containing functions.
328
329 Args:
330 lib: library (or executable) pathname containing symbols
331 unique_addrs: set of string hexidecimal addresses to find the functions for.
332
333 Returns:
334 A dictionary of the form {addr: (string symbol, offset)}.
335 """
336 if not lib:
337 return None
338
Andreas Gampe3d97a462017-05-17 14:16:45 -0700339 result = {}
340 addrs = sorted(unique_addrs)
341
342 addr_cache = None
343 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
344 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
345
346 # Go through and handle all known addresses.
347 for x in range(len(addrs)):
348 next_addr = addrs.pop(0)
349 if next_addr in addr_cache:
350 result[next_addr] = addr_cache[next_addr]
351 else:
352 # Re-add, needs to be symbolized.
353 addrs.append(next_addr)
354
355 if not addrs:
356 # Everything was cached, we're done.
357 return result
358 else:
359 addr_cache = {}
360 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
361
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700362 symbols = SYMBOLS_DIR + lib
363 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700364 symbols = lib
365 if not os.path.exists(symbols):
366 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700367
Ben Chengb42dad02013-04-25 15:14:04 -0700368 start_addr_dec = str(StripPC(int(addrs[0], 16)))
369 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800370 cmd = [ToolPath("llvm-objdump"),
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700371 "--section=.text",
372 "--demangle",
373 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700374 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700375 "--stop-address=" + stop_addr_dec,
376 symbols]
377
378 # Function lines look like:
379 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
380 # We pull out the address and function first. Then we check for an optional
381 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
382 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
383 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
384
385 # A disassembly line looks like:
386 # 177b2: b510 push {r4, lr}
387 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
388
389 current_symbol = None # The current function symbol in the disassembly.
390 current_symbol_addr = 0 # The address of the current function.
391 addr_index = 0 # The address that we are currently looking for.
392
David Srbeckyfd1e4162021-04-27 22:24:36 +0100393 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700394 for line in stream:
395 # Is it a function line like:
396 # 000177b0 <android::IBinder::~IBinder()>:
397 components = func_regexp.match(line)
398 if components:
399 # This is a new function, so record the current function and its address.
400 current_symbol_addr = int(components.group(1), 16)
401 current_symbol = components.group(2)
402
403 # Does it have an optional offset like: "foo(..)+0x2c"?
404 components = offset_regexp.match(current_symbol)
405 if components:
406 current_symbol = components.group(1)
407 offset = components.group(2)
408 if offset:
409 current_symbol_addr -= int(offset, 16)
410
411 # Is it an disassembly line like:
412 # 177b2: b510 push {r4, lr}
413 components = asm_regexp.match(line)
414 if components:
415 addr = components.group(1)
416 target_addr = addrs[addr_index]
417 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700418 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700419 if i_addr == i_target:
420 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700421 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700422 addr_index += 1
423 if addr_index >= len(addrs):
424 break
425 stream.close()
426
427 return result
428
429
430def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700431 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
432 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
433
Christopher Ferris49eda0e2020-12-09 14:34:01 -0800434 # TODO: Replace with llvm-cxxfilt when available.
435 global _CACHED_CXX_FILT
436 if not _CACHED_CXX_FILT:
437 os_name = platform.system().lower()
438 toolchains = glob.glob("%s/prebuilts/gcc/%s-*/host/*-linux-*/bin/*c++filt" %
439 (ANDROID_BUILD_TOP, os_name))
440 if not toolchains:
441 raise Exception("Could not find gcc c++filt tool")
442 _CACHED_CXX_FILT = sorted(toolchains)[-1]
443
444 cmd = [_CACHED_CXX_FILT]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700445 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700446 process.stdin.write(mangled_symbol)
447 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700448 process.stdin.flush()
449
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700450 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700451
452 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
453
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700454 return demangled_symbol
455
Elliott Hughesc3c86192014-08-29 13:49:57 -0700456
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700457def FormatSymbolWithOffset(symbol, offset):
458 if offset == 0:
459 return symbol
460 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700461
462
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800463def GetAbiFromToolchain(toolchain_var, bits):
464 toolchain = os.environ.get(toolchain_var)
465 if not toolchain:
466 return None
467
468 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain)
469 if toolchain_match:
470 abi = toolchain_match.group(1)
471 if abi == "aarch64":
472 return "arm64"
473 elif bits == 64:
474 if abi == "x86":
475 return "x86_64"
476 elif abi == "mips":
477 return "mips64"
478 return abi
479 return None
480
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700481def Get32BitArch():
482 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that.
483 # If not try ANDROID_TOOLCHAIN to find the arch.
484 # If this is not set, then default to arm.
485 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32)
486 if not arch:
487 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32)
488 if not arch:
489 return "arm"
490 return arch
491
492def Get64BitArch():
493 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the
494 # arch this way. If this is not set, then default to arm64.
495 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64)
496 if not arch:
497 return "arm64"
498 return arch
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800499
500def SetAbi(lines):
501 global ARCH
502
503 abi_line = re.compile("ABI: \'(.*)\'")
504 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700505 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800506
507 ARCH = None
508 for line in lines:
509 abi_match = abi_line.search(line)
510 if abi_match:
511 ARCH = abi_match.group(1)
512 break
513 trace_match = trace_line.search(line)
514 if trace_match:
515 # Try to guess the arch, we know the bitness.
516 if len(trace_match.group(1)) == 16:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700517 ARCH = Get64BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800518 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700519 ARCH = Get32BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800520 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700521 asan_trace_match = asan_trace_line.search(line)
522 if asan_trace_match:
523 # We might be able to guess the bitness by the length of the address.
524 if len(asan_trace_match.group(1)) > 8:
525 ARCH = Get64BitArch()
526 # We know for a fact this is 64 bit, so we are done.
527 break
528 else:
529 ARCH = Get32BitArch()
530 # This might be 32 bit, or just a small address. Keep going in this
531 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800532 if not ARCH:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700533 raise Exception("Could not determine arch from input, use --arch=XXX to specify it")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800534
Elliott Hughesc3c86192014-08-29 13:49:57 -0700535
536class FindToolchainTests(unittest.TestCase):
537 def assert_toolchain_found(self, abi):
538 global ARCH
539 ARCH = abi
540 FindToolchain() # Will throw on failure.
541
Andreas Gampee547eb32018-10-29 18:31:37 -0700542 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
Elliott Hughesc3c86192014-08-29 13:49:57 -0700543 def test_toolchains_found(self):
544 self.assert_toolchain_found("arm")
545 self.assert_toolchain_found("arm64")
546 self.assert_toolchain_found("mips")
547 self.assert_toolchain_found("x86")
548 self.assert_toolchain_found("x86_64")
549
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800550class SetArchTests(unittest.TestCase):
551 def test_abi_check(self):
552 global ARCH
553
554 SetAbi(["ABI: 'arm'"])
555 self.assertEqual(ARCH, "arm")
556 SetAbi(["ABI: 'arm64'"])
557 self.assertEqual(ARCH, "arm64")
558
559 SetAbi(["ABI: 'mips'"])
560 self.assertEqual(ARCH, "mips")
561 SetAbi(["ABI: 'mips64'"])
562 self.assertEqual(ARCH, "mips64")
563
564 SetAbi(["ABI: 'x86'"])
565 self.assertEqual(ARCH, "x86")
566 SetAbi(["ABI: 'x86_64'"])
567 self.assertEqual(ARCH, "x86_64")
568
569 def test_32bit_trace_line_toolchain(self):
570 global ARCH
571
572 os.environ.clear()
573 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
574 SetAbi(["#00 pc 000374e0"])
575 self.assertEqual(ARCH, "arm")
576
577 os.environ.clear()
578 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
579 SetAbi(["#00 pc 000374e0"])
580 self.assertEqual(ARCH, "mips")
581
582 os.environ.clear()
583 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
584 SetAbi(["#00 pc 000374e0"])
585 self.assertEqual(ARCH, "x86")
586
587 def test_32bit_trace_line_toolchain_2nd(self):
588 global ARCH
589
590 os.environ.clear()
591 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
592 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
593 SetAbi(["#00 pc 000374e0"])
594 self.assertEqual(ARCH, "arm")
595
596 os.environ.clear()
597 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
598 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
599 SetAbi(["#00 pc 000374e0"])
600 self.assertEqual(ARCH, "mips")
601
602 os.environ.clear()
603 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
604 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
605 SetAbi(["#00 pc 000374e0"])
606 self.assertEqual(ARCH, "x86")
607
608 def test_64bit_trace_line_toolchain(self):
609 global ARCH
610
611 os.environ.clear()
612 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
613 SetAbi(["#00 pc 00000000000374e0"])
614 self.assertEqual(ARCH, "arm64")
615
616 os.environ.clear()
617 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
618 SetAbi(["#00 pc 00000000000374e0"])
619 self.assertEqual(ARCH, "mips64")
620
621 os.environ.clear()
622 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
623 SetAbi(["#00 pc 00000000000374e0"])
624 self.assertEqual(ARCH, "x86_64")
625
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700626 def test_trace_default_abis(self):
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800627 global ARCH
628
629 os.environ.clear()
630 SetAbi(["#00 pc 000374e0"])
631 self.assertEqual(ARCH, "arm")
632 SetAbi(["#00 pc 00000000000374e0"])
633 self.assertEqual(ARCH, "arm64")
634
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700635 def test_32bit_asan_trace_line_toolchain(self):
636 global ARCH
637
638 os.environ.clear()
639 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
640 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
641 self.assertEqual(ARCH, "arm")
642
643 os.environ.clear()
644 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
645 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
646 self.assertEqual(ARCH, "mips")
647
648 os.environ.clear()
649 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
650 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
651 self.assertEqual(ARCH, "x86")
652
653 def test_32bit_asan_trace_line_toolchain_2nd(self):
654 global ARCH
655
656 os.environ.clear()
657 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
658 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
659 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
660 self.assertEqual(ARCH, "arm")
661
662 os.environ.clear()
663 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
664 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
665 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
666 self.assertEqual(ARCH, "mips")
667
668 os.environ.clear()
669 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
670 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
671 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
672 self.assertEqual(ARCH, "x86")
673
674 def test_64bit_asan_trace_line_toolchain(self):
675 global ARCH
676
677 os.environ.clear()
678 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
679 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
680 self.assertEqual(ARCH, "arm64")
681
682 os.environ.clear()
683 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
684 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
685 self.assertEqual(ARCH, "mips64")
686
687 os.environ.clear()
688 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
689 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
690 self.assertEqual(ARCH, "x86_64")
691
692 # Verify that if an address that might be 32 bit comes first, that
693 # encountering a 64 bit address returns a 64 bit abi.
694 ARCH = None
695 os.environ.clear()
696 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
697 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
698 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
699 self.assertEqual(ARCH, "x86_64")
700
701 def test_asan_trace_default_abis(self):
702 global ARCH
703
704 os.environ.clear()
705 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
706 self.assertEqual(ARCH, "arm64")
707 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
708 self.assertEqual(ARCH, "arm")
709
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800710 def test_no_abi(self):
711 global ARCH
712
Andreas Gampe9240b452018-10-26 14:17:30 -0700713 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex
714 # does not provide that name.
715 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
716 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp')
717 self.assertRaisesRegex(Exception,
718 "Could not determine arch from input, use --arch=XXX to specify it",
719 SetAbi, [])
Elliott Hughesc3c86192014-08-29 13:49:57 -0700720
721if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700722 unittest.main(verbosity=2)