blob: 6dbc5c445bb4f0b5ff5164577afcb2da7ec0b34c [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 Gampe9240b452018-10-26 14:17:30 -070031ANDROID_BUILD_TOP = str(os.environ["ANDROID_BUILD_TOP"])
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070032if not ANDROID_BUILD_TOP:
33 ANDROID_BUILD_TOP = "."
34
35def FindSymbolsDir():
36 saveddir = os.getcwd()
37 os.chdir(ANDROID_BUILD_TOP)
Andreas Gampe9240b452018-10-26 14:17:30 -070038 stream = None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070039 try:
Dan Willemsend3fc8fa2017-10-17 14:04:56 -070040 cmd = "build/soong/soong_ui.bash --dumpvar-mode --abs TARGET_OUT_UNSTRIPPED"
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070041 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout
Andreas Gampe9240b452018-10-26 14:17:30 -070042 return os.path.join(ANDROID_BUILD_TOP, str(stream.read().strip()))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070043 finally:
Andreas Gampe9240b452018-10-26 14:17:30 -070044 if stream is not None:
45 stream.close()
Iliyan Malchev4929d6a2011-08-04 17:44:40 -070046 os.chdir(saveddir)
47
48SYMBOLS_DIR = FindSymbolsDir()
49
Christopher Ferrisbf8a9402016-03-11 15:50:46 -080050ARCH = None
Ben Chengb42dad02013-04-25 15:14:04 -070051
Elliott Hughesc3c86192014-08-29 13:49:57 -070052
53# These are private. Do not access them from other modules.
54_CACHED_TOOLCHAIN = None
55_CACHED_TOOLCHAIN_ARCH = None
56
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):
95 return subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
96
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):
136 """Return a fully-qualified path to the specified tool"""
137 if not toolchain:
138 toolchain = FindToolchain()
139 return glob.glob(os.path.join(toolchain, "*-" + tool))[0]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700140
Elliott Hughesc3c86192014-08-29 13:49:57 -0700141
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700142def FindToolchain():
Elliott Hughesc3c86192014-08-29 13:49:57 -0700143 """Returns the toolchain matching ARCH."""
144 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
Elliott Hughesc3166be2014-07-07 15:06:28 -0700148 # We use slightly different names from GCC, and there's only one toolchain
Elliott Hughesc3c86192014-08-29 13:49:57 -0700149 # for x86/x86_64. Note that these are the names of the top-level directory
150 # rather than the _different_ names used lower down the directory hierarchy!
151 gcc_dir = ARCH
152 if gcc_dir == "arm64":
153 gcc_dir = "aarch64"
154 elif gcc_dir == "mips64":
155 gcc_dir = "mips"
156 elif gcc_dir == "x86_64":
157 gcc_dir = "x86"
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700158
Yang Nie4b2a1a2014-11-06 17:42:33 -0800159 os_name = platform.system().lower();
160
161 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 -0700162 if len(available_toolchains) == 0:
163 raise Exception("Could not find tool chain for %s" % (ARCH))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700164
Elliott Hughesc3c86192014-08-29 13:49:57 -0700165 toolchain = sorted(available_toolchains)[-1]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700166
Elliott Hughes08365932014-06-13 18:12:25 -0700167 if not os.path.exists(ToolPath("addr2line", toolchain)):
168 raise Exception("No addr2line for %s" % (toolchain))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700169
Elliott Hughesc3c86192014-08-29 13:49:57 -0700170 _CACHED_TOOLCHAIN = toolchain
171 _CACHED_TOOLCHAIN_ARCH = ARCH
Andreas Gampe9240b452018-10-26 14:17:30 -0700172 print("Using %s toolchain from: %s" % (_CACHED_TOOLCHAIN_ARCH, _CACHED_TOOLCHAIN))
Elliott Hughesc3c86192014-08-29 13:49:57 -0700173 return _CACHED_TOOLCHAIN
174
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700175
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700176def SymbolInformation(lib, addr):
177 """Look up symbol information about an address.
178
179 Args:
180 lib: library (or executable) pathname containing symbols
181 addr: string hexidecimal address
182
183 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700184 A list of the form [(source_symbol, source_location,
185 object_symbol_with_offset)].
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700186
Ben Chengb42dad02013-04-25 15:14:04 -0700187 If the function has been inlined then the list may contain
188 more than one element with the symbols for the most deeply
189 nested inlined location appearing first. The list is
190 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700191
Ben Chengb42dad02013-04-25 15:14:04 -0700192 Usually you want to display the source_location and
193 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700194 """
195 info = SymbolInformationForSet(lib, set([addr]))
Ben Chengb42dad02013-04-25 15:14:04 -0700196 return (info and info.get(addr)) or [(None, None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700197
198
199def SymbolInformationForSet(lib, unique_addrs):
200 """Look up symbol information for a set of addresses from the given library.
201
202 Args:
203 lib: library (or executable) pathname containing symbols
204 unique_addrs: set of hexidecimal addresses
205
206 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700207 A dictionary of the form {addr: [(source_symbol, source_location,
208 object_symbol_with_offset)]} where each address has a list of
209 associated symbols and locations. The list is always non-empty.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700210
Ben Chengb42dad02013-04-25 15:14:04 -0700211 If the function has been inlined then the list may contain
212 more than one element with the symbols for the most deeply
213 nested inlined location appearing first. The list is
214 always non-empty, even if no information is available.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700215
Ben Chengb42dad02013-04-25 15:14:04 -0700216 Usually you want to display the source_location and
217 object_symbol_with_offset from the last element in the list.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700218 """
219 if not lib:
220 return None
221
222 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
223 if not addr_to_line:
224 return None
225
226 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
227 if not addr_to_objdump:
228 return None
229
230 result = {}
231 for addr in unique_addrs:
Ben Chengb42dad02013-04-25 15:14:04 -0700232 source_info = addr_to_line.get(addr)
233 if not source_info:
234 source_info = [(None, None)]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700235 if addr in addr_to_objdump:
236 (object_symbol, object_offset) = addr_to_objdump.get(addr)
237 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
238 object_offset)
239 else:
240 object_symbol_with_offset = None
Ben Chengb42dad02013-04-25 15:14:04 -0700241 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
242 for (source_symbol, source_location) in source_info]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700243
244 return result
245
246
247def CallAddr2LineForSet(lib, unique_addrs):
248 """Look up line and symbol information for a set of addresses.
249
250 Args:
251 lib: library (or executable) pathname containing symbols
252 unique_addrs: set of string hexidecimal addresses look up.
253
254 Returns:
Ben Chengb42dad02013-04-25 15:14:04 -0700255 A dictionary of the form {addr: [(symbol, file:line)]} where
256 each address has a list of associated symbols and locations
257 or an empty list if no symbol information was found.
258
259 If the function has been inlined then the list may contain
260 more than one element with the symbols for the most deeply
261 nested inlined location appearing first.
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700262 """
263 if not lib:
264 return None
265
Andreas Gampe3d97a462017-05-17 14:16:45 -0700266 result = {}
267 addrs = sorted(unique_addrs)
268
269 if lib in _SYMBOL_INFORMATION_ADDR2LINE_CACHE:
270 addr_cache = _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib]
271
272 # Go through and handle all known addresses.
273 for x in range(len(addrs)):
274 next_addr = addrs.pop(0)
275 if next_addr in addr_cache:
276 result[next_addr] = addr_cache[next_addr]
277 else:
278 # Re-add, needs to be symbolized.
279 addrs.append(next_addr)
280
281 if not addrs:
282 # Everything was cached, we're done.
283 return result
284 else:
285 addr_cache = {}
286 _SYMBOL_INFORMATION_ADDR2LINE_CACHE[lib] = addr_cache
287
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700288 symbols = SYMBOLS_DIR + lib
289 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700290 symbols = lib
291 if not os.path.exists(symbols):
292 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700293
Christopher Ferris5f1b4f02016-09-19 13:24:37 -0700294 # Make sure the symbols path is not a directory.
295 if os.path.isdir(symbols):
296 return None
297
Ben Chengb42dad02013-04-25 15:14:04 -0700298 cmd = [ToolPath("addr2line"), "--functions", "--inlines",
299 "--demangle", "--exe=" + symbols]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700300 child = _PIPE_ADDR2LINE_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700301
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700302 for addr in addrs:
Christopher Ferris6fc7aef2018-08-09 12:40:05 -0700303 try:
304 child.stdin.write("0x%s\n" % addr)
305 child.stdin.flush()
306 records = []
307 first = True
308 while True:
309 symbol = child.stdout.readline().strip()
310 if symbol == "??":
311 symbol = None
312 location = child.stdout.readline().strip()
313 if location == "??:0" or location == "??:?":
314 location = None
315 if symbol is None and location is None:
316 break
317 records.append((symbol, location))
318 if first:
319 # Write a blank line as a sentinel so we know when to stop
320 # reading inlines from the output.
321 # The blank line will cause addr2line to emit "??\n??:0\n".
322 child.stdin.write("\n")
323 first = False
324 except IOError as e:
325 # Remove the / in front of the library name to match other output.
326 records = [(None, lib[1:] + " ***Error: " + str(e))]
Ben Chengb42dad02013-04-25 15:14:04 -0700327 result[addr] = records
Andreas Gampe3d97a462017-05-17 14:16:45 -0700328 addr_cache[addr] = records
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700329 return result
330
331
Ben Chengb42dad02013-04-25 15:14:04 -0700332def StripPC(addr):
333 """Strips the Thumb bit a program counter address when appropriate.
334
335 Args:
336 addr: the program counter address
337
338 Returns:
339 The stripped program counter address.
340 """
341 global ARCH
Ben Chengb42dad02013-04-25 15:14:04 -0700342 if ARCH == "arm":
343 return addr & ~1
344 return addr
345
Elliott Hughesc3c86192014-08-29 13:49:57 -0700346
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700347def CallObjdumpForSet(lib, unique_addrs):
348 """Use objdump to find out the names of the containing functions.
349
350 Args:
351 lib: library (or executable) pathname containing symbols
352 unique_addrs: set of string hexidecimal addresses to find the functions for.
353
354 Returns:
355 A dictionary of the form {addr: (string symbol, offset)}.
356 """
357 if not lib:
358 return None
359
Andreas Gampe3d97a462017-05-17 14:16:45 -0700360 result = {}
361 addrs = sorted(unique_addrs)
362
363 addr_cache = None
364 if lib in _SYMBOL_INFORMATION_OBJDUMP_CACHE:
365 addr_cache = _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib]
366
367 # Go through and handle all known addresses.
368 for x in range(len(addrs)):
369 next_addr = addrs.pop(0)
370 if next_addr in addr_cache:
371 result[next_addr] = addr_cache[next_addr]
372 else:
373 # Re-add, needs to be symbolized.
374 addrs.append(next_addr)
375
376 if not addrs:
377 # Everything was cached, we're done.
378 return result
379 else:
380 addr_cache = {}
381 _SYMBOL_INFORMATION_OBJDUMP_CACHE[lib] = addr_cache
382
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700383 symbols = SYMBOLS_DIR + lib
384 if not os.path.exists(symbols):
Christopher Ferrisece64c42015-08-20 20:09:09 -0700385 symbols = lib
386 if not os.path.exists(symbols):
387 return None
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700388
Ben Chengb42dad02013-04-25 15:14:04 -0700389 start_addr_dec = str(StripPC(int(addrs[0], 16)))
390 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700391 cmd = [ToolPath("objdump"),
392 "--section=.text",
393 "--demangle",
394 "--disassemble",
Ben Chengb42dad02013-04-25 15:14:04 -0700395 "--start-address=" + start_addr_dec,
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700396 "--stop-address=" + stop_addr_dec,
397 symbols]
398
399 # Function lines look like:
400 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
401 # We pull out the address and function first. Then we check for an optional
402 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
403 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
404 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
405
406 # A disassembly line looks like:
407 # 177b2: b510 push {r4, lr}
408 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
409
410 current_symbol = None # The current function symbol in the disassembly.
411 current_symbol_addr = 0 # The address of the current function.
412 addr_index = 0 # The address that we are currently looking for.
413
414 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700415 for line in stream:
416 # Is it a function line like:
417 # 000177b0 <android::IBinder::~IBinder()>:
418 components = func_regexp.match(line)
419 if components:
420 # This is a new function, so record the current function and its address.
421 current_symbol_addr = int(components.group(1), 16)
422 current_symbol = components.group(2)
423
424 # Does it have an optional offset like: "foo(..)+0x2c"?
425 components = offset_regexp.match(current_symbol)
426 if components:
427 current_symbol = components.group(1)
428 offset = components.group(2)
429 if offset:
430 current_symbol_addr -= int(offset, 16)
431
432 # Is it an disassembly line like:
433 # 177b2: b510 push {r4, lr}
434 components = asm_regexp.match(line)
435 if components:
436 addr = components.group(1)
437 target_addr = addrs[addr_index]
438 i_addr = int(addr, 16)
Ben Chengb42dad02013-04-25 15:14:04 -0700439 i_target = StripPC(int(target_addr, 16))
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700440 if i_addr == i_target:
441 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
Andreas Gampe3d97a462017-05-17 14:16:45 -0700442 addr_cache[target_addr] = result[target_addr]
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700443 addr_index += 1
444 if addr_index >= len(addrs):
445 break
446 stream.close()
447
448 return result
449
450
451def CallCppFilt(mangled_symbol):
Andreas Gampe3d97a462017-05-17 14:16:45 -0700452 if mangled_symbol in _SYMBOL_DEMANGLING_CACHE:
453 return _SYMBOL_DEMANGLING_CACHE[mangled_symbol]
454
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700455 cmd = [ToolPath("c++filt")]
Andreas Gampe46b00d62017-05-17 15:12:27 -0700456 process = _PIPE_CPPFILT_CACHE.GetProcess(cmd)
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700457 process.stdin.write(mangled_symbol)
458 process.stdin.write("\n")
Andreas Gampe46b00d62017-05-17 15:12:27 -0700459 process.stdin.flush()
460
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700461 demangled_symbol = process.stdout.readline().strip()
Andreas Gampe3d97a462017-05-17 14:16:45 -0700462
463 _SYMBOL_DEMANGLING_CACHE[mangled_symbol] = demangled_symbol
464
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700465 return demangled_symbol
466
Elliott Hughesc3c86192014-08-29 13:49:57 -0700467
Iliyan Malchev4929d6a2011-08-04 17:44:40 -0700468def FormatSymbolWithOffset(symbol, offset):
469 if offset == 0:
470 return symbol
471 return "%s+%d" % (symbol, offset)
Elliott Hughesc3c86192014-08-29 13:49:57 -0700472
473
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800474def GetAbiFromToolchain(toolchain_var, bits):
475 toolchain = os.environ.get(toolchain_var)
476 if not toolchain:
477 return None
478
479 toolchain_match = re.search("\/(aarch64|arm|mips|x86)\/", toolchain)
480 if toolchain_match:
481 abi = toolchain_match.group(1)
482 if abi == "aarch64":
483 return "arm64"
484 elif bits == 64:
485 if abi == "x86":
486 return "x86_64"
487 elif abi == "mips":
488 return "mips64"
489 return abi
490 return None
491
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700492def Get32BitArch():
493 # Check for ANDROID_TOOLCHAIN_2ND_ARCH first, if set, use that.
494 # If not try ANDROID_TOOLCHAIN to find the arch.
495 # If this is not set, then default to arm.
496 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN_2ND_ARCH", 32)
497 if not arch:
498 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 32)
499 if not arch:
500 return "arm"
501 return arch
502
503def Get64BitArch():
504 # Check for ANDROID_TOOLCHAIN, if it is set, we can figure out the
505 # arch this way. If this is not set, then default to arm64.
506 arch = GetAbiFromToolchain("ANDROID_TOOLCHAIN", 64)
507 if not arch:
508 return "arm64"
509 return arch
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800510
511def SetAbi(lines):
512 global ARCH
513
514 abi_line = re.compile("ABI: \'(.*)\'")
515 trace_line = re.compile("\#[0-9]+[ \t]+..[ \t]+([0-9a-f]{8}|[0-9a-f]{16})([ \t]+|$)")
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700516 asan_trace_line = re.compile("\#[0-9]+[ \t]+0x([0-9a-f]+)[ \t]+")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800517
518 ARCH = None
519 for line in lines:
520 abi_match = abi_line.search(line)
521 if abi_match:
522 ARCH = abi_match.group(1)
523 break
524 trace_match = trace_line.search(line)
525 if trace_match:
526 # Try to guess the arch, we know the bitness.
527 if len(trace_match.group(1)) == 16:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700528 ARCH = Get64BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800529 else:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700530 ARCH = Get32BitArch()
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800531 break
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700532 asan_trace_match = asan_trace_line.search(line)
533 if asan_trace_match:
534 # We might be able to guess the bitness by the length of the address.
535 if len(asan_trace_match.group(1)) > 8:
536 ARCH = Get64BitArch()
537 # We know for a fact this is 64 bit, so we are done.
538 break
539 else:
540 ARCH = Get32BitArch()
541 # This might be 32 bit, or just a small address. Keep going in this
542 # case, but if we couldn't figure anything else out, go with 32 bit.
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800543 if not ARCH:
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700544 raise Exception("Could not determine arch from input, use --arch=XXX to specify it")
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800545
Elliott Hughesc3c86192014-08-29 13:49:57 -0700546
547class FindToolchainTests(unittest.TestCase):
548 def assert_toolchain_found(self, abi):
549 global ARCH
550 ARCH = abi
551 FindToolchain() # Will throw on failure.
552
553 def test_toolchains_found(self):
554 self.assert_toolchain_found("arm")
555 self.assert_toolchain_found("arm64")
556 self.assert_toolchain_found("mips")
557 self.assert_toolchain_found("x86")
558 self.assert_toolchain_found("x86_64")
559
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800560class SetArchTests(unittest.TestCase):
561 def test_abi_check(self):
562 global ARCH
563
564 SetAbi(["ABI: 'arm'"])
565 self.assertEqual(ARCH, "arm")
566 SetAbi(["ABI: 'arm64'"])
567 self.assertEqual(ARCH, "arm64")
568
569 SetAbi(["ABI: 'mips'"])
570 self.assertEqual(ARCH, "mips")
571 SetAbi(["ABI: 'mips64'"])
572 self.assertEqual(ARCH, "mips64")
573
574 SetAbi(["ABI: 'x86'"])
575 self.assertEqual(ARCH, "x86")
576 SetAbi(["ABI: 'x86_64'"])
577 self.assertEqual(ARCH, "x86_64")
578
579 def test_32bit_trace_line_toolchain(self):
580 global ARCH
581
582 os.environ.clear()
583 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
584 SetAbi(["#00 pc 000374e0"])
585 self.assertEqual(ARCH, "arm")
586
587 os.environ.clear()
588 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
589 SetAbi(["#00 pc 000374e0"])
590 self.assertEqual(ARCH, "mips")
591
592 os.environ.clear()
593 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
594 SetAbi(["#00 pc 000374e0"])
595 self.assertEqual(ARCH, "x86")
596
597 def test_32bit_trace_line_toolchain_2nd(self):
598 global ARCH
599
600 os.environ.clear()
601 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
602 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
603 SetAbi(["#00 pc 000374e0"])
604 self.assertEqual(ARCH, "arm")
605
606 os.environ.clear()
607 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
608 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
609 SetAbi(["#00 pc 000374e0"])
610 self.assertEqual(ARCH, "mips")
611
612 os.environ.clear()
613 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
614 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
615 SetAbi(["#00 pc 000374e0"])
616 self.assertEqual(ARCH, "x86")
617
618 def test_64bit_trace_line_toolchain(self):
619 global ARCH
620
621 os.environ.clear()
622 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
623 SetAbi(["#00 pc 00000000000374e0"])
624 self.assertEqual(ARCH, "arm64")
625
626 os.environ.clear()
627 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
628 SetAbi(["#00 pc 00000000000374e0"])
629 self.assertEqual(ARCH, "mips64")
630
631 os.environ.clear()
632 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
633 SetAbi(["#00 pc 00000000000374e0"])
634 self.assertEqual(ARCH, "x86_64")
635
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700636 def test_trace_default_abis(self):
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800637 global ARCH
638
639 os.environ.clear()
640 SetAbi(["#00 pc 000374e0"])
641 self.assertEqual(ARCH, "arm")
642 SetAbi(["#00 pc 00000000000374e0"])
643 self.assertEqual(ARCH, "arm64")
644
Christopher Ferris5b820ba2016-09-06 14:07:29 -0700645 def test_32bit_asan_trace_line_toolchain(self):
646 global ARCH
647
648 os.environ.clear()
649 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
650 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
651 self.assertEqual(ARCH, "arm")
652
653 os.environ.clear()
654 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
655 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
656 self.assertEqual(ARCH, "mips")
657
658 os.environ.clear()
659 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
660 SetAbi(["#10 0xb5eeba5d (/system/vendor/lib/egl/libGLESv1_CM_adreno.so+0xfa5d)"])
661 self.assertEqual(ARCH, "x86")
662
663 def test_32bit_asan_trace_line_toolchain_2nd(self):
664 global ARCH
665
666 os.environ.clear()
667 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/arm/arm-linux-androideabi-4.9/bin"
668 os.environ["ANDROID_TOOLCHAIN_ARCH"] = "linux-x86/aarch64/aarch64-linux-android-4.9/bin"
669 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
670 self.assertEqual(ARCH, "arm")
671
672 os.environ.clear()
673 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/mips/mips-linux-androideabi-4.9/bin"
674 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
675 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
676 self.assertEqual(ARCH, "mips")
677
678 os.environ.clear()
679 os.environ["ANDROID_TOOLCHAIN_2ND_ARCH"] = "linux-x86/x86/x86-linux-androideabi-4.9/bin"
680 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/unknown/unknown-linux-androideabi-4.9/bin"
681 SetAbi(["#3 0xae1725b5 (/system/vendor/lib/libllvm-glnext.so+0x6435b5)"])
682 self.assertEqual(ARCH, "x86")
683
684 def test_64bit_asan_trace_line_toolchain(self):
685 global ARCH
686
687 os.environ.clear()
688 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/aarch/aarch-linux-androideabi-4.9/bin"
689 SetAbi(["#0 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
690 self.assertEqual(ARCH, "arm64")
691
692 os.environ.clear()
693 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/mips/arm-linux-androideabi-4.9/bin"
694 SetAbi(["#1 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
695 self.assertEqual(ARCH, "mips64")
696
697 os.environ.clear()
698 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
699 SetAbi(["#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
700 self.assertEqual(ARCH, "x86_64")
701
702 # Verify that if an address that might be 32 bit comes first, that
703 # encountering a 64 bit address returns a 64 bit abi.
704 ARCH = None
705 os.environ.clear()
706 os.environ["ANDROID_TOOLCHAIN"] = "linux-x86/x86/arm-linux-androideabi-4.9/bin"
707 SetAbi(["#12 0x5d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)",
708 "#12 0x11b35d33bf (/system/lib/libclang_rt.asan-arm-android.so+0x823bf)"])
709 self.assertEqual(ARCH, "x86_64")
710
711 def test_asan_trace_default_abis(self):
712 global ARCH
713
714 os.environ.clear()
715 SetAbi(["#4 0x1234349ab (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
716 self.assertEqual(ARCH, "arm64")
717 SetAbi(["#1 0xae17ec4f (/system/vendor/lib/libllvm-glnext.so+0x64fc4f)"])
718 self.assertEqual(ARCH, "arm")
719
Christopher Ferrisbf8a9402016-03-11 15:50:46 -0800720 def test_no_abi(self):
721 global ARCH
722
Andreas Gampe9240b452018-10-26 14:17:30 -0700723 # Python2 vs Python3 compatibility: Python3 warns on Regexp deprecation, but Regex
724 # does not provide that name.
725 if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
726 unittest.TestCase.assertRaisesRegex = getattr(unittest.TestCase, 'assertRaisesRegexp')
727 self.assertRaisesRegex(Exception,
728 "Could not determine arch from input, use --arch=XXX to specify it",
729 SetAbi, [])
Elliott Hughesc3c86192014-08-29 13:49:57 -0700730
731if __name__ == '__main__':
Andreas Gampe9240b452018-10-26 14:17:30 -0700732 unittest.main(verbosity=2)