blob: e6560e42d14f1031f19292206d90d36ce9a82484 [file] [log] [blame]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -07001#!/usr/bin/python
2#
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
Andreas Gampee547eb32018-10-29 18:31:37 -070031try:
32 ANDROID_BUILD_TOP = str(os.environ["ANDROID_BUILD_TOP"])
33 if not ANDROID_BUILD_TOP:
34 ANDROID_BUILD_TOP = "."
35except:
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070036 ANDROID_BUILD_TOP = "."
37
38def FindSymbolsDir():
39 saveddir = os.getcwd()
40 os.chdir(ANDROID_BUILD_TOP)
Andreas Gampe9240b452018-10-26 14:17:30 -070041 stream = None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070042 try:
Dan Willemsend3fc8fa2017-10-17 14:04:56 -070043 cmd = "build/soong/soong_ui.bash --dumpvar-mode --abs TARGET_OUT_UNSTRIPPED"
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070044 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout
Andreas Gampe9240b452018-10-26 14:17:30 -070045 return os.path.join(ANDROID_BUILD_TOP, str(stream.read().strip()))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070046 finally:
Andreas Gampe9240b452018-10-26 14:17:30 -070047 if stream is not None:
48 stream.close()
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070049 os.chdir(saveddir)
50
51SYMBOLS_DIR = FindSymbolsDir()
52
Christopher Ferrisbf8a9402016-03-11 15:50:46 -080053ARCH = None
Ben Chengb42dad02013-04-25 15:14:04 -070054
Elliott Hughesc3c86192014-08-29 13:49:57 -070055
56# These are private. Do not access them from other modules.
57_CACHED_TOOLCHAIN = None
58_CACHED_TOOLCHAIN_ARCH = None
59
Andreas Gampe3d97a462017-05-17 14:16:45 -070060# Caches for symbolized information.
61_SYMBOL_INFORMATION_ADDR2LINE_CACHE = {}
62_SYMBOL_INFORMATION_OBJDUMP_CACHE = {}
63_SYMBOL_DEMANGLING_CACHE = {}
64
Andreas Gampe46b00d62017-05-17 15:12:27 -070065# Caches for pipes to subprocesses.
66
67class ProcessCache:
68 _cmd2pipe = {}
69 _lru = []
70
71 # Max number of open pipes.
72 _PIPE_MAX_OPEN = 10
73
74 def GetProcess(self, cmd):
75 cmd_tuple = tuple(cmd) # Need to use a tuple as lists can't be dict keys.
76 # Pipe already available?
77 if cmd_tuple in self._cmd2pipe:
78 pipe = self._cmd2pipe[cmd_tuple]
79 # Update LRU.
80 self._lru = [(cmd_tuple, pipe)] + [i for i in self._lru if i[0] != cmd_tuple]
81 return pipe
82
83 # Not cached, yet. Open a new one.
84
85 # Check if too many are open, close the old ones.
86 while len(self._lru) >= self._PIPE_MAX_OPEN:
87 open_cmd, open_pipe = self._lru.pop()
88 del self._cmd2pipe[open_cmd]
89 self.TerminateProcess(open_pipe)
90
91 # Create and put into cache.
92 pipe = self.SpawnProcess(cmd)
93 self._cmd2pipe[cmd_tuple] = pipe
94 self._lru = [(cmd_tuple, pipe)] + self._lru
95 return pipe
96
97 def SpawnProcess(self, cmd):
98 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
99
100 def TerminateProcess(self, pipe):
101 pipe.stdin.close()
102 pipe.stdout.close()
103 pipe.terminate()
104 pipe.wait()
105
106 def KillAllProcesses(self):
107 for _, open_pipe in self._lru:
108 self.TerminateProcess(open_pipe)
109 _cmd2pipe = {}
110 _lru = []
111
112
113_PIPE_ADDR2LINE_CACHE = ProcessCache()
114_PIPE_CPPFILT_CACHE = ProcessCache()
115
116
117# Process cache cleanup on shutdown.
118
119def CloseAllPipes():
120 _PIPE_ADDR2LINE_CACHE.KillAllProcesses()
121 _PIPE_CPPFILT_CACHE.KillAllProcesses()
122
123
124atexit.register(CloseAllPipes)
125
126
127def PipeTermHandler(signum, frame):
128 CloseAllPipes()
129 os._exit(0)
130
131
132for sig in (signal.SIGABRT, signal.SIGINT, signal.SIGTERM):
133 signal.signal(sig, PipeTermHandler)
134
135
136
Ben Chengb42dad02013-04-25 15:14:04 -0700137
Elliott Hughes08365932014-06-13 18:12:25 -0700138def ToolPath(tool, toolchain=None):
139 """Return a fully-qualified path to the specified tool"""
140 if not toolchain:
141 toolchain = FindToolchain()
142 return glob.glob(os.path.join(toolchain, "*-" + tool))[0]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700143
Elliott Hughesc3c86192014-08-29 13:49:57 -0700144
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700145def FindToolchain():
Elliott Hughesc3c86192014-08-29 13:49:57 -0700146 """Returns the toolchain matching ARCH."""
147 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
Elliott Hughesc3166be2014-07-07 15:06:28 -0700151 # We use slightly different names from GCC, and there's only one toolchain
Elliott Hughesc3c86192014-08-29 13:49:57 -0700152 # for x86/x86_64. Note that these are the names of the top-level directory
153 # rather than the _different_ names used lower down the directory hierarchy!
154 gcc_dir = ARCH
155 if gcc_dir == "arm64":
156 gcc_dir = "aarch64"
157 elif gcc_dir == "mips64":
158 gcc_dir = "mips"
159 elif gcc_dir == "x86_64":
160 gcc_dir = "x86"
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700161
Yang Nie4b2a1a2014-11-06 17:42:33 -0800162 os_name = platform.system().lower();
163
164 available_toolchains = glob.glob("%s/prebuilts/gcc/%s-x86/%s/*-linux-*/bin/" % (ANDROID_BUILD_TOP, os_name, gcc_dir))
Elliott Hughesc3c86192014-08-29 13:49:57 -0700165 if len(available_toolchains) == 0:
166 raise Exception("Could not find tool chain for %s" % (ARCH))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700167
Elliott Hughesc3c86192014-08-29 13:49:57 -0700168 toolchain = sorted(available_toolchains)[-1]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700169
Elliott Hughes08365932014-06-13 18:12:25 -0700170 if not os.path.exists(ToolPath("addr2line", toolchain)):
171 raise Exception("No addr2line for %s" % (toolchain))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700172
Elliott Hughesc3c86192014-08-29 13:49:57 -0700173 _CACHED_TOOLCHAIN = toolchain
174 _CACHED_TOOLCHAIN_ARCH = ARCH
Andreas Gampe9240b452018-10-26 14:17:30 -0700175 print("Using %s toolchain from: %s" % (_CACHED_TOOLCHAIN_ARCH, _CACHED_TOOLCHAIN))
Elliott Hughesc3c86192014-08-29 13:49:57 -0700176 return _CACHED_TOOLCHAIN
177
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700178
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700179def SymbolInformation(lib, addr):
180 """Look up symbol information about an address.
181
182 Args:
183 lib: library (or executable) pathname containing symbols
184 addr: string hexidecimal address
185
186 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700187 A list of the form [(source_symbol, source_location,
188 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700189
Ben Chengb42dad02013-04-25 15:14:04 -0700190 If the function has been inlined then the list may contain
191 more than one element with the symbols for the most deeply
192 nested inlined location appearing first. The list is
193 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700194
Ben Chengb42dad02013-04-25 15:14:04 -0700195 Usually you want to display the source_location and
196 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700197 """
198 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700199 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700200
201
202def SymbolInformationForSet(lib, unique_addrs):
203 """Look up symbol information for a set of addresses from the given library.
204
205 Args:
206 lib: library (or executable) pathname containing symbols
207 unique_addrs: set of hexidecimal addresses
208
209 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700210 A dictionary of the form {addr: [(source_symbol, source_location,
211 object_symbol_with_offset)]} where each address has a list of
212 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700213
Ben Chengb42dad02013-04-25 15:14:04 -0700214 If the function has been inlined then the list may contain
215 more than one element with the symbols for the most deeply
216 nested inlined location appearing first. The list is
217 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700218
Ben Chengb42dad02013-04-25 15:14:04 -0700219 Usually you want to display the source_location and
220 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700221 """
222 if not lib:
223 return None
224
225 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
226 if not addr_to_line:
227 return None
228
229 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
230 if not addr_to_objdump:
231 return None
232
233 result = {}
234 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700235 source_info = addr_to_line.get(addr)
236 if not source_info:
237 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700238 if addr in addr_to_objdump:
239 (object_symbol, object_offset) = addr_to_objdump.get(addr)
240 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
241 object_offset)
242 else:
243 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700244 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
245 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700246
247 return result
248
249
250def CallAddr2LineForSet(lib, unique_addrs):
251 """Look up line and symbol information for a set of addresses.
252
253 Args:
254 lib: library (or executable) pathname containing symbols
255 unique_addrs: set of string hexidecimal addresses look up.
256
257 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700258 A dictionary of the form {addr: [(symbol, file:line)]} where
259 each address has a list of associated symbols and locations
260 or an empty list if no symbol information was found.
261
262 If the function has been inlined then the list may contain
263 more than one element with the symbols for the most deeply
264 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700265 """
266 if not lib:
267 return None
268
Andreas Gampe3d97a462017-05-17 14:16:45 -0700269 result = {}
270 addrs = sorted(unique_addrs)
271
272 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
273 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
274
275 # Go through and handle all known addresses.
276 for x in range(len(addrs)):
277 next_addr = addrs.pop(0)
278 if next_addr in addr_cache:
279 result[next_addr] = addr_cache[next_addr]
280 else:
281 # Re-add, needs to be symbolized.
282 addrs.append(next_addr)
283
284 if not addrs:
285 # Everything was cached, we're done.
286 return result
287 else:
288 addr_cache = {}
289 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
290
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700291 symbols = SYMBOLS_DIR + lib
292 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700293 symbols = lib
294 if not os.path.exists(symbols):
295 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700296
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700297 # Make sure the symbols path is not a directory.
298 if os.path.isdir(symbols):
299 return None
300
Ben Chengb42dad02013-04-25 15:14:04 -0700301 cmd = [ToolPath("addr2line"), "--functions", "--inlines",
302 "--demangle", "--exe=" + symbols]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700303 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700304
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700305 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700306 try:
307 child.stdin.write("0x%s\n" % addr)
308 child.stdin.flush()
309 records = []
310 first = True
311 while True:
312 symbol = child.stdout.readline().strip()
313 if symbol == "??":
314 symbol = None
315 location = child.stdout.readline().strip()
316 if location == "??:0" or location == "??:?":
317 location = None
318 if symbol is None and location is None:
319 break
320 records.append((symbol, location))
321 if first:
322 # Write a blank line as a sentinel so we know when to stop
323 # reading inlines from the output.
324 # The blank line will cause addr2line to emit "??\n??:0\n".
325 child.stdin.write("\n")
326 first = False
327 except IOError as e:
328 # Remove the / in front of the library name to match other output.
329 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700330 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700331 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700332 return result
333
334
Ben Chengb42dad02013-04-25 15:14:04 -0700335def StripPC(addr):
336 """Strips the Thumb bit a program counter address when appropriate.
337
338 Args:
339 addr: the program counter address
340
341 Returns:
342 The stripped program counter address.
343 """
344 global ARCH
Ben Chengb42dad02013-04-25 15:14:04 -0700345 if ARCH == "arm":
346 return addr & ~1
347 return addr
348
Elliott Hughesc3c86192014-08-29 13:49:57 -0700349
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700350def CallObjdumpForSet(lib, unique_addrs):
351 """Use objdump to find out the names of the containing functions.
352
353 Args:
354 lib: library (or executable) pathname containing symbols
355 unique_addrs: set of string hexidecimal addresses to find the functions for.
356
357 Returns:
358 A dictionary of the form {addr: (string symbol, offset)}.
359 """
360 if not lib:
361 return None
362
Andreas Gampe3d97a462017-05-17 14:16:45 -0700363 result = {}
364 addrs = sorted(unique_addrs)
365
366 addr_cache = None
367 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
368 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
369
370 # Go through and handle all known addresses.
371 for x in range(len(addrs)):
372 next_addr = addrs.pop(0)
373 if next_addr in addr_cache:
374 result[next_addr] = addr_cache[next_addr]
375 else:
376 # Re-add, needs to be symbolized.
377 addrs.append(next_addr)
378
379 if not addrs:
380 # Everything was cached, we're done.
381 return result
382 else:
383 addr_cache = {}
384 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
385
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700386 symbols = SYMBOLS_DIR + lib
387 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700388 symbols = lib
389 if not os.path.exists(symbols):
390 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700391
Ben Chengb42dad02013-04-25 15:14:04 -0700392 start_addr_dec = str(StripPC(int(addrs[0], 16)))
393 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700394 cmd = [ToolPath("objdump"),
395 "--section=.text",
396 "--demangle",
397 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700398 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700399 "--stop-address=" + stop_addr_dec,
400 symbols]
401
402 # Function lines look like:
403 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
404 # We pull out the address and function first. Then we check for an optional
405 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
406 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
407 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
408
409 # A disassembly line looks like:
410 # 177b2: b510 push {r4, lr}
411 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
412
413 current_symbol = None # The current function symbol in the disassembly.
414 current_symbol_addr = 0 # The address of the current function.
415 addr_index = 0 # The address that we are currently looking for.
416
417 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700418 for line in stream:
419 # Is it a function line like:
420 # 000177b0 <android::IBinder::~IBinder()>:
421 components = func_regexp.match(line)
422 if components:
423 # This is a new function, so record the current function and its address.
424 current_symbol_addr = int(components.group(1), 16)
425 current_symbol = components.group(2)
426
427 # Does it have an optional offset like: "foo(..)+0x2c"?
428 components = offset_regexp.match(current_symbol)
429 if components:
430 current_symbol = components.group(1)
431 offset = components.group(2)
432 if offset:
433 current_symbol_addr -= int(offset, 16)
434
435 # Is it an disassembly line like:
436 # 177b2: b510 push {r4, lr}
437 components = asm_regexp.match(line)
438 if components:
439 addr = components.group(1)
440 target_addr = addrs[addr_index]
441 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700442 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700443 if i_addr == i_target:
444 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700445 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700446 addr_index += 1
447 if addr_index >= len(addrs):
448 break
449 stream.close()
450
451 return result
452
453
454def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700455 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
456 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
457
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700458 cmd = [ToolPath("c++filt")]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700459 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700460 process.stdin.write(mangled_symbol)
461 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700462 process.stdin.flush()
463
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700464 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700465
466 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
467
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700468 return demangled_symbol
469
Elliott Hughesc3c86192014-08-29 13:49:57 -0700470
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700471def FormatSymbolWithOffset(symbol, offset):
472 if offset == 0:
473 return symbol
474 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700475
476
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800477def GetAbiFromToolchain(toolchain_var, bits):
478 toolchain = os.environ.get(toolchain_var)
479 if not toolchain:
480 return None
481
482 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain)
483 if toolchain_match:
484 abi = toolchain_match.group(1)
485 if abi == "aarch64":
486 return "arm64"
487 elif bits == 64:
488 if abi == "x86":
489 return "x86_64"
490 elif abi == "mips":
491 return "mips64"
492 return abi
493 return None
494
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700495def Get32BitArch():
496 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that.
497 # If not try ANDROID_TOOLCHAIN to find the arch.
498 # If this is not set, then default to arm.
499 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32)
500 if not arch:
501 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32)
502 if not arch:
503 return "arm"
504 return arch
505
506def Get64BitArch():
507 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the
508 # arch this way. If this is not set, then default to arm64.
509 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64)
510 if not arch:
511 return "arm64"
512 return arch
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800513
514def SetAbi(lines):
515 global ARCH
516
517 abi_line = re.compile("ABI: \'(.*)\'")
518 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700519 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800520
521 ARCH = None
522 for line in lines:
523 abi_match = abi_line.search(line)
524 if abi_match:
525 ARCH = abi_match.group(1)
526 break
527 trace_match = trace_line.search(line)
528 if trace_match:
529 # Try to guess the arch, we know the bitness.
530 if len(trace_match.group(1)) == 16:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700531 ARCH = Get64BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800532 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700533 ARCH = Get32BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800534 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700535 asan_trace_match = asan_trace_line.search(line)
536 if asan_trace_match:
537 # We might be able to guess the bitness by the length of the address.
538 if len(asan_trace_match.group(1)) > 8:
539 ARCH = Get64BitArch()
540 # We know for a fact this is 64 bit, so we are done.
541 break
542 else:
543 ARCH = Get32BitArch()
544 # This might be 32 bit, or just a small address. Keep going in this
545 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800546 if not ARCH:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700547 raise Exception("Could not determine arch from input, use --arch=XXX to specify it")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800548
Elliott Hughesc3c86192014-08-29 13:49:57 -0700549
550class FindToolchainTests(unittest.TestCase):
551 def assert_toolchain_found(self, abi):
552 global ARCH
553 ARCH = abi
554 FindToolchain() # Will throw on failure.
555
Andreas Gampee547eb32018-10-29 18:31:37 -0700556 @unittest.skipIf(ANDROID_BUILD_TOP == '.', 'Test only supported in an Android tree.')
Elliott Hughesc3c86192014-08-29 13:49:57 -0700557 def test_toolchains_found(self):
558 self.assert_toolchain_found("arm")
559 self.assert_toolchain_found("arm64")
560 self.assert_toolchain_found("mips")
561 self.assert_toolchain_found("x86")
562 self.assert_toolchain_found("x86_64")
563
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800564class SetArchTests(unittest.TestCase):
565 def test_abi_check(self):
566 global ARCH
567
568 SetAbi(["ABI: 'arm'"])
569 self.assertEqual(ARCH, "arm")
570 SetAbi(["ABI: 'arm64'"])
571 self.assertEqual(ARCH, "arm64")
572
573 SetAbi(["ABI: 'mips'"])
574 self.assertEqual(ARCH, "mips")
575 SetAbi(["ABI: 'mips64'"])
576 self.assertEqual(ARCH, "mips64")
577
578 SetAbi(["ABI: 'x86'"])
579 self.assertEqual(ARCH, "x86")
580 SetAbi(["ABI: 'x86_64'"])
581 self.assertEqual(ARCH, "x86_64")
582
583 def test_32bit_trace_line_toolchain(self):
584 global ARCH
585
586 os.environ.clear()
587 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
588 SetAbi(["#00 pc 000374e0"])
589 self.assertEqual(ARCH, "arm")
590
591 os.environ.clear()
592 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
593 SetAbi(["#00 pc 000374e0"])
594 self.assertEqual(ARCH, "mips")
595
596 os.environ.clear()
597 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
598 SetAbi(["#00 pc 000374e0"])
599 self.assertEqual(ARCH, "x86")
600
601 def test_32bit_trace_line_toolchain_2nd(self):
602 global ARCH
603
604 os.environ.clear()
605 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
606 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
607 SetAbi(["#00 pc 000374e0"])
608 self.assertEqual(ARCH, "arm")
609
610 os.environ.clear()
611 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
612 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
613 SetAbi(["#00 pc 000374e0"])
614 self.assertEqual(ARCH, "mips")
615
616 os.environ.clear()
617 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
618 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
619 SetAbi(["#00 pc 000374e0"])
620 self.assertEqual(ARCH, "x86")
621
622 def test_64bit_trace_line_toolchain(self):
623 global ARCH
624
625 os.environ.clear()
626 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
627 SetAbi(["#00 pc 00000000000374e0"])
628 self.assertEqual(ARCH, "arm64")
629
630 os.environ.clear()
631 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
632 SetAbi(["#00 pc 00000000000374e0"])
633 self.assertEqual(ARCH, "mips64")
634
635 os.environ.clear()
636 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
637 SetAbi(["#00 pc 00000000000374e0"])
638 self.assertEqual(ARCH, "x86_64")
639
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700640 def test_trace_default_abis(self):
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800641 global ARCH
642
643 os.environ.clear()
644 SetAbi(["#00 pc 000374e0"])
645 self.assertEqual(ARCH, "arm")
646 SetAbi(["#00 pc 00000000000374e0"])
647 self.assertEqual(ARCH, "arm64")
648
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700649 def test_32bit_asan_trace_line_toolchain(self):
650 global ARCH
651
652 os.environ.clear()
653 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
654 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
655 self.assertEqual(ARCH, "arm")
656
657 os.environ.clear()
658 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
659 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
660 self.assertEqual(ARCH, "mips")
661
662 os.environ.clear()
663 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
664 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
665 self.assertEqual(ARCH, "x86")
666
667 def test_32bit_asan_trace_line_toolchain_2nd(self):
668 global ARCH
669
670 os.environ.clear()
671 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
672 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
673 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
674 self.assertEqual(ARCH, "arm")
675
676 os.environ.clear()
677 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
678 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
679 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
680 self.assertEqual(ARCH, "mips")
681
682 os.environ.clear()
683 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
684 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
685 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
686 self.assertEqual(ARCH, "x86")
687
688 def test_64bit_asan_trace_line_toolchain(self):
689 global ARCH
690
691 os.environ.clear()
692 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
693 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
694 self.assertEqual(ARCH, "arm64")
695
696 os.environ.clear()
697 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
698 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
699 self.assertEqual(ARCH, "mips64")
700
701 os.environ.clear()
702 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
703 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
704 self.assertEqual(ARCH, "x86_64")
705
706 # Verify that if an address that might be 32 bit comes first, that
707 # encountering a 64 bit address returns a 64 bit abi.
708 ARCH = None
709 os.environ.clear()
710 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
711 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
712 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
713 self.assertEqual(ARCH, "x86_64")
714
715 def test_asan_trace_default_abis(self):
716 global ARCH
717
718 os.environ.clear()
719 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
720 self.assertEqual(ARCH, "arm64")
721 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
722 self.assertEqual(ARCH, "arm")
723
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800724 def test_no_abi(self):
725 global ARCH
726
Andreas Gampe9240b452018-10-26 14:17:30 -0700727 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex
728 # does not provide that name.
729 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
730 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp')
731 self.assertRaisesRegex(Exception,
732 "Could not determine arch from input, use --arch=XXX to specify it",
733 SetAbi, [])
Elliott Hughesc3c86192014-08-29 13:49:57 -0700734
735if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700736 unittest.main(verbosity=2)