1#!/usr/bin/env python3 2# 3# 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. 16 17"""stack symbolizes native crash dumps.""" 18 19import collections 20import functools 21import os 22import pathlib 23import re 24import subprocess 25import symbol 26import tempfile 27import unittest 28 29import example_crashes 30 31def ConvertTrace(lines): 32 tracer = TraceConverter() 33 print("Reading symbols from", symbol.SYMBOLS_DIR) 34 tracer.ConvertTrace(lines) 35 36class TraceConverter: 37 process_info_line = re.compile(r"(pid: [0-9]+, tid: [0-9]+.*)") 38 revision_line = re.compile(r"(Revision: '(.*)')") 39 signal_line = re.compile(r"(signal [0-9]+ \(.*\).*)") 40 abort_message_line = re.compile(r"(Abort message: '.*')") 41 thread_line = re.compile(r"(.*)(--- ){15}---") 42 dalvik_jni_thread_line = re.compile("(\".*\" prio=[0-9]+ tid=[0-9]+ NATIVE.*)") 43 dalvik_native_thread_line = re.compile("(\".*\" sysTid=[0-9]+ nice=[0-9]+.*)") 44 register_line = re.compile("$a") 45 trace_line = re.compile("$a") 46 sanitizer_trace_line = re.compile("$a") 47 value_line = re.compile("$a") 48 code_line = re.compile("$a") 49 zipinfo_central_directory_line = re.compile(r"Central\s+directory\s+entry") 50 zipinfo_central_info_match = re.compile( 51 r"^\s*(\S+)$\s*offset of local header from start of archive:\s*(\d+)" 52 r".*^\s*compressed size:\s+(\d+)", re.M | re.S) 53 unreachable_line = re.compile(r"((\d+ bytes in \d+ unreachable allocations)|" 54 r"(\d+ bytes unreachable at [0-9a-f]+)|" 55 r"(referencing \d+ unreachable bytes in \d+ allocation(s)?)|" 56 r"(and \d+ similar unreachable bytes in \d+ allocation(s)?))") 57 trace_lines = [] 58 value_lines = [] 59 last_frame = -1 60 width = "{8}" 61 spacing = "" 62 apk_info = dict() 63 64 register_names = { 65 "arm": "r0|r1|r2|r3|r4|r5|r6|r7|r8|r9|sl|fp|ip|sp|lr|pc|cpsr", 66 "arm64": "x0|x1|x2|x3|x4|x5|x6|x7|x8|x9|x10|x11|x12|x13|x14|x15|x16|x17|x18|x19|x20|x21|x22|x23|x24|x25|x26|x27|x28|x29|x30|sp|pc|pstate", 67 "mips": "zr|at|v0|v1|a0|a1|a2|a3|t0|t1|t2|t3|t4|t5|t6|t7|s0|s1|s2|s3|s4|s5|s6|s7|t8|t9|k0|k1|gp|sp|s8|ra|hi|lo|bva|epc", 68 "mips64": "zr|at|v0|v1|a0|a1|a2|a3|a4|a5|a6|a7|t0|t1|t2|t3|s0|s1|s2|s3|s4|s5|s6|s7|t8|t9|k0|k1|gp|sp|s8|ra|hi|lo|bva|epc", 69 "x86": "eax|ebx|ecx|edx|esi|edi|x?cs|x?ds|x?es|x?fs|x?ss|eip|ebp|esp|flags", 70 "x86_64": "rax|rbx|rcx|rdx|rsi|rdi|r8|r9|r10|r11|r12|r13|r14|r15|cs|ss|rip|rbp|rsp|eflags", 71 } 72 73 # We use the "file" command line tool to extract BuildId from ELF files. 74 ElfInfo = collections.namedtuple("ElfInfo", ["bitness", "build_id"]) 75 file_tool_output = re.compile(r"ELF (?P<bitness>32|64)-bit .*" 76 r"BuildID(\[.*\])?=(?P<build_id>[0-9a-f]+)") 77 78 def UpdateAbiRegexes(self): 79 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 80 self.width = "{16}" 81 self.spacing = " " 82 else: 83 self.width = "{8}" 84 self.spacing = "" 85 86 self.register_line = re.compile("(([ ]*\\b(" + self.register_names[symbol.ARCH] + ")\\b +[0-9a-f]" + self.width + "){2,5})") 87 88 # Note that both trace and value line matching allow for variable amounts of 89 # whitespace (e.g. \t). This is because the we want to allow for the stack 90 # tool to operate on AndroidFeedback provided system logs. AndroidFeedback 91 # strips out double spaces that are found in tombsone files and logcat output. 92 # 93 # Examples of matched trace lines include lines from tombstone files like: 94 # #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so 95 # 96 # Or lines from AndroidFeedback crash report system logs like: 97 # 03-25 00:51:05.520 I/DEBUG ( 65): #00 pc 001cf42e /data/data/com.my.project/lib/libmyproject.so 98 # Please note the spacing differences. 99 self.trace_line = re.compile( 100 r".*" # Random start stuff. 101 r"\#(?P<frame>[0-9]+)" # Frame number. 102 r"[ \t]+..[ \t]+" # (space)pc(space). 103 r"(?P<offset>[0-9a-f]" + self.width + ")[ \t]+" # Offset (hex number given without 104 # 0x prefix). 105 r"(?P<dso>\[[^\]]+\]|[^\r\n \t]*)" # Library name. 106 r"( \(offset (?P<so_offset>0x[0-9a-fA-F]+)\))?" # Offset into the file to find the start of the shared so. 107 r"(?P<symbolpresent> \((?P<symbol>.*?)\))?" # Is the symbol there? (non-greedy) 108 r"( \(BuildId: (?P<build_id>.*)\))?" # Optional build-id of the ELF file. 109 r"[ \t]*$") # End of line (to expand non-greedy match). 110 # pylint: disable-msg=C6310 111 # Sanitizer output. This is different from debuggerd output, and it is easier to handle this as 112 # its own regex. Example: 113 # 08-19 05:29:26.283 397 403 I : #0 0xb6a15237 (/system/lib/libclang_rt.asan-arm-android.so+0x4f237) 114 self.sanitizer_trace_line = re.compile( 115 r".*" # Random start stuff. 116 r"\#(?P<frame>[0-9]+)" # Frame number. 117 r"[ \t]+0x[0-9a-f]+[ \t]+" # PC, not interesting to us. 118 r"\(" # Opening paren. 119 r"(?P<dso>[^+]+)" # Library name. 120 r"\+" # '+' 121 r"0x(?P<offset>[0-9a-f]+)" # Offset (hex number given with 122 # 0x prefix). 123 r"\)") # Closing paren. 124 # pylint: disable-msg=C6310 125 # Examples of matched value lines include: 126 # bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so 127 # bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so (symbol) 128 # 03-25 00:51:05.530 I/DEBUG ( 65): bea4170c 8018e4e9 /data/data/com.my.project/lib/libmyproject.so 129 # Again, note the spacing differences. 130 self.value_line = re.compile(r"(.*)([0-9a-f]" + self.width + r")[ \t]+([0-9a-f]" + self.width + r")[ \t]+([^\r\n \t]*)( \((.*)\))?") 131 # Lines from 'code around' sections of the output will be matched before 132 # value lines because otheriwse the 'code around' sections will be confused as 133 # value lines. 134 # 135 # Examples include: 136 # 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8 137 # 03-25 00:51:05.530 I/DEBUG ( 65): 801cf40c ffffc4cc 00b2f2c5 00b2f1c7 00c1e1a8 138 self.code_line = re.compile(r"(.*)[ \t]*[a-f0-9]" + self.width + 139 r"[ \t]*[a-f0-9]" + self.width + 140 r"[ \t]*[a-f0-9]" + self.width + 141 r"[ \t]*[a-f0-9]" + self.width + 142 r"[ \t]*[a-f0-9]" + self.width + 143 r"[ \t]*[ \r\n]") # pylint: disable-msg=C6310 144 145 def CleanLine(self, ln): 146 # AndroidFeedback adds zero width spaces into its crash reports. These 147 # should be removed or the regular expresssions will fail to match. 148 return ln.encode().decode(encoding='utf8', errors='ignore') 149 150 def PrintTraceLines(self, trace_lines): 151 """Print back trace.""" 152 maxlen = max(len(tl[1]) for tl in trace_lines) 153 print("\nStack Trace:") 154 print(" RELADDR " + self.spacing + "FUNCTION".ljust(maxlen) + " FILE:LINE") 155 for tl in self.trace_lines: 156 (addr, symbol_with_offset, location) = tl 157 print(" %8s %s %s" % (addr, symbol_with_offset.ljust(maxlen), location)) 158 159 def PrintValueLines(self, value_lines): 160 """Print stack data values.""" 161 maxlen = max(len(tl[2]) for tl in self.value_lines) 162 print("\nStack Data:") 163 print(" ADDR " + self.spacing + "VALUE " + "FUNCTION".ljust(maxlen) + " FILE:LINE") 164 for vl in self.value_lines: 165 (addr, value, symbol_with_offset, location) = vl 166 print(" %8s %8s %s %s" % (addr, value, symbol_with_offset.ljust(maxlen), location)) 167 168 def PrintOutput(self, trace_lines, value_lines): 169 if self.trace_lines: 170 self.PrintTraceLines(self.trace_lines) 171 if self.value_lines: 172 self.PrintValueLines(self.value_lines) 173 174 def PrintDivider(self): 175 print("\n-----------------------------------------------------\n") 176 177 def DeleteApkTmpFiles(self): 178 for _, _, tmp_files in self.apk_info.values(): 179 for tmp_file in tmp_files.values(): 180 os.unlink(tmp_file) 181 182 def ConvertTrace(self, lines): 183 lines = [self.CleanLine(line) for line in lines] 184 try: 185 if not symbol.ARCH: 186 symbol.SetAbi(lines) 187 self.UpdateAbiRegexes() 188 for line in lines: 189 self.ProcessLine(line) 190 self.PrintOutput(self.trace_lines, self.value_lines) 191 finally: 192 # Delete any temporary files created while processing the lines. 193 self.DeleteApkTmpFiles() 194 195 def MatchTraceLine(self, line): 196 match = self.trace_line.match(line) 197 if match: 198 return {"frame": match.group("frame"), 199 "offset": match.group("offset"), 200 "so_offset": match.group("so_offset"), 201 "dso": match.group("dso"), 202 "symbol_present": bool(match.group("symbolpresent")), 203 "symbol_name": match.group("symbol"), 204 "build_id": match.group("build_id")} 205 match = self.sanitizer_trace_line.match(line) 206 if match: 207 return {"frame": match.group("frame"), 208 "offset": match.group("offset"), 209 "so_offset": None, 210 "dso": match.group("dso"), 211 "symbol_present": False, 212 "symbol_name": None, 213 "build_id": None} 214 return None 215 216 def ExtractLibFromApk(self, apk, shared_lib_name): 217 # Create a temporary file containing the shared library from the apk. 218 tmp_file = None 219 try: 220 tmp_fd, tmp_file = tempfile.mkstemp() 221 if subprocess.call(["unzip", "-p", apk, shared_lib_name], stdout=tmp_fd) == 0: 222 os.close(tmp_fd) 223 shared_file = tmp_file 224 tmp_file = None 225 return shared_file 226 finally: 227 if tmp_file: 228 os.close(tmp_fd) 229 os.unlink(tmp_file) 230 return None 231 232 def ProcessCentralInfo(self, offset_list, central_info): 233 match = self.zipinfo_central_info_match.search(central_info) 234 if not match: 235 raise Exception("Cannot find all info from zipinfo\n" + central_info) 236 name = match.group(1) 237 start = int(match.group(2)) 238 end = start + int(match.group(3)) 239 240 offset_list.append([name, start, end]) 241 return name, start, end 242 243 def GetLibFromApk(self, apk, offset): 244 # Convert the string to hex. 245 offset = int(offset, 16) 246 247 # Check if we already have information about this offset. 248 if apk in self.apk_info: 249 apk_full_path, offset_list, tmp_files = self.apk_info[apk] 250 for file_name, start, end in offset_list: 251 if offset >= start and offset < end: 252 if file_name in tmp_files: 253 return file_name, tmp_files[file_name] 254 tmp_file = self.ExtractLibFromApk(apk_full_path, file_name) 255 if tmp_file: 256 tmp_files[file_name] = tmp_file 257 return file_name, tmp_file 258 break 259 return None, None 260 261 if not "ANDROID_PRODUCT_OUT" in os.environ: 262 print("ANDROID_PRODUCT_OUT environment variable not set.") 263 return None, None 264 out_dir = os.environ["ANDROID_PRODUCT_OUT"] 265 if not os.path.exists(out_dir): 266 print("ANDROID_PRODUCT_OUT", out_dir, "does not exist.") 267 return None, None 268 if apk.startswith("/"): 269 apk_full_path = out_dir + apk 270 else: 271 apk_full_path = os.path.join(out_dir, apk) 272 if not os.path.exists(apk_full_path): 273 print("Cannot find apk", apk) 274 return None, None 275 276 cmd = subprocess.Popen(["zipinfo", "-v", apk_full_path], stdout=subprocess.PIPE, 277 encoding='utf8') 278 # Find the first central info marker. 279 for line in cmd.stdout: 280 if self.zipinfo_central_directory_line.search(line): 281 break 282 283 central_info = "" 284 file_name = None 285 offset_list = [] 286 for line in cmd.stdout: 287 match = self.zipinfo_central_directory_line.search(line) 288 if match: 289 cur_name, start, end = self.ProcessCentralInfo(offset_list, central_info) 290 if not file_name and offset >= start and offset < end: 291 file_name = cur_name 292 central_info = "" 293 else: 294 central_info += line 295 if central_info: 296 cur_name, start, end = self.ProcessCentralInfo(offset_list, central_info) 297 if not file_name and offset >= start and offset < end: 298 file_name = cur_name 299 300 # Make sure the offset_list is sorted, the zip file does not guarantee 301 # that the entries are in order. 302 offset_list = sorted(offset_list, key=lambda entry: entry[1]) 303 304 # Save the information from the zip. 305 tmp_files = dict() 306 self.apk_info[apk] = [apk_full_path, offset_list, tmp_files] 307 if not file_name: 308 return None, None 309 tmp_shared_lib = self.ExtractLibFromApk(apk_full_path, file_name) 310 if tmp_shared_lib: 311 tmp_files[file_name] = tmp_shared_lib 312 return file_name, tmp_shared_lib 313 return None, None 314 315 # Find all files in the symbols directory and group them by basename (without directory). 316 @functools.lru_cache(maxsize=None) 317 def GlobSymbolsDir(self, symbols_dir): 318 files_by_basename = {} 319 for path in sorted(pathlib.Path(symbols_dir).glob("**/*")): 320 files_by_basename.setdefault(path.name, []).append(path) 321 return files_by_basename 322 323 # Use the "file" command line tool to find the bitness and build_id of given ELF file. 324 @functools.lru_cache(maxsize=None) 325 def GetLibraryInfo(self, lib): 326 stdout = subprocess.check_output(["file", lib], text=True) 327 match = self.file_tool_output.search(stdout) 328 if match: 329 return self.ElfInfo(bitness=match.group("bitness"), build_id=match.group("build_id")) 330 return None 331 332 # Search for a library with the given basename and build_id anywhere in the symbols directory. 333 @functools.lru_cache(maxsize=None) 334 def GetLibraryByBuildId(self, symbols_dir, basename, build_id): 335 for candidate in self.GlobSymbolsDir(symbols_dir).get(basename, []): 336 info = self.GetLibraryInfo(candidate) 337 if info and info.build_id == build_id: 338 return "/" + str(candidate.relative_to(symbols_dir)) 339 return None 340 341 def GetLibPath(self, lib): 342 symbol_dir = symbol.SYMBOLS_DIR 343 if os.path.isfile(symbol_dir + lib): 344 return lib 345 346 # When using atest, test paths are different between the out/ directory 347 # and device. Apply fixups. 348 if not lib.startswith("/data/local/tests/") and not lib.startswith("/data/local/tmp/"): 349 print("WARNING: Cannot find %s in symbol directory" % lib) 350 return lib 351 352 test_name = lib.rsplit("/", 1)[-1] 353 test_dir = "/data/nativetest" 354 test_dir_bitness = "" 355 if symbol.ARCH.endswith("64"): 356 bitness = "64" 357 test_dir_bitness = "64" 358 else: 359 bitness = "32" 360 361 # Unfortunately, the location of the real symbol file is not 362 # standardized, so we need to go hunting for it. 363 364 # This is in vendor, look for the value in: 365 # /data/nativetest{64}/vendor/test_name/test_name 366 if lib.startswith("/data/local/tests/vendor/"): 367 lib_path = os.path.join(test_dir + test_dir_bitness, "vendor", test_name, test_name) 368 if os.path.isfile(symbol_dir + lib_path): 369 return lib_path 370 371 # Look for the path in: 372 # /data/nativetest{64}/test_name/test_name 373 lib_path = os.path.join(test_dir + test_dir_bitness, test_name, test_name) 374 if os.path.isfile(symbol_dir + lib_path): 375 return lib_path 376 377 # CtsXXX tests are in really non-standard locations try: 378 # /data/nativetest/{test_name} 379 lib_path = os.path.join(test_dir, test_name) 380 if os.path.isfile(symbol_dir + lib_path): 381 return lib_path 382 # Try: 383 # /data/nativetest/{test_name}{32|64} 384 lib_path += bitness 385 if os.path.isfile(symbol_dir + lib_path): 386 return lib_path 387 388 # Cannot find location, give up and return the original path 389 print("WARNING: Cannot find %s in symbol directory" % lib) 390 return lib 391 392 393 def ProcessLine(self, line): 394 ret = False 395 process_header = self.process_info_line.search(line) 396 signal_header = self.signal_line.search(line) 397 abort_message_header = self.abort_message_line.search(line) 398 thread_header = self.thread_line.search(line) 399 register_header = self.register_line.search(line) 400 revision_header = self.revision_line.search(line) 401 dalvik_jni_thread_header = self.dalvik_jni_thread_line.search(line) 402 dalvik_native_thread_header = self.dalvik_native_thread_line.search(line) 403 unreachable_header = self.unreachable_line.search(line) 404 if process_header or signal_header or abort_message_header or thread_header or \ 405 register_header or dalvik_jni_thread_header or dalvik_native_thread_header or \ 406 revision_header or unreachable_header: 407 ret = True 408 if self.trace_lines or self.value_lines: 409 self.PrintOutput(self.trace_lines, self.value_lines) 410 self.PrintDivider() 411 self.trace_lines = [] 412 self.value_lines = [] 413 self.last_frame = -1 414 if process_header: 415 print(process_header.group(1)) 416 if signal_header: 417 print(signal_header.group(1)) 418 if abort_message_header: 419 print(abort_message_header.group(1)) 420 if register_header: 421 print(register_header.group(1)) 422 if thread_header: 423 print(thread_header.group(1)) 424 if dalvik_jni_thread_header: 425 print(dalvik_jni_thread_header.group(1)) 426 if dalvik_native_thread_header: 427 print(dalvik_native_thread_header.group(1)) 428 if revision_header: 429 print(revision_header.group(1)) 430 if unreachable_header: 431 print(unreachable_header.group(1)) 432 return True 433 trace_line_dict = self.MatchTraceLine(line) 434 if trace_line_dict is not None: 435 ret = True 436 frame = int(trace_line_dict["frame"]) 437 code_addr = trace_line_dict["offset"] 438 area = trace_line_dict["dso"] 439 so_offset = trace_line_dict["so_offset"] 440 symbol_present = trace_line_dict["symbol_present"] 441 symbol_name = trace_line_dict["symbol_name"] 442 build_id = trace_line_dict["build_id"] 443 444 if frame <= self.last_frame and (self.trace_lines or self.value_lines): 445 self.PrintOutput(self.trace_lines, self.value_lines) 446 self.PrintDivider() 447 self.trace_lines = [] 448 self.value_lines = [] 449 self.last_frame = frame 450 451 if area == "<unknown>" or area == "[heap]" or area == "[stack]": 452 self.trace_lines.append((code_addr, "", area)) 453 else: 454 # If this is an apk, it usually means that there is actually 455 # a shared so that was loaded directly out of it. In that case, 456 # extract the shared library and the name of the shared library. 457 lib = None 458 # The format of the map name: 459 # Some.apk!libshared.so 460 # or 461 # Some.apk 462 if so_offset: 463 # If it ends in apk, we are done. 464 apk = None 465 if area.endswith(".apk"): 466 apk = area 467 else: 468 index = area.rfind(".so!") 469 if index != -1: 470 # Sometimes we'll see something like: 471 # #01 pc abcd libart.so!libart.so (offset 0x134000) 472 # Remove everything after the ! and zero the offset value. 473 area = area[0:index + 3] 474 so_offset = 0 475 else: 476 index = area.rfind(".apk!") 477 if index != -1: 478 apk = area[0:index + 4] 479 if apk: 480 lib_name, lib = self.GetLibFromApk(apk, so_offset) 481 if not lib: 482 lib = area 483 lib_name = None 484 485 if build_id: 486 # If we have the build_id, do a brute-force search of the symbols directory. 487 basename = os.path.basename(lib) 488 lib = self.GetLibraryByBuildId(symbol.SYMBOLS_DIR, basename, build_id) 489 if not lib: 490 print("WARNING: Cannot find {} with build id {} in symbols directory." 491 .format(basename, build_id)) 492 else: 493 # When using atest, test paths are different between the out/ directory 494 # and device. Apply fixups. 495 lib = self.GetLibPath(lib) 496 497 # If a calls b which further calls c and c is inlined to b, we want to 498 # display "a -> b -> c" in the stack trace instead of just "a -> c" 499 info = symbol.SymbolInformation(lib, code_addr) 500 nest_count = len(info) - 1 501 for (source_symbol, source_location, symbol_with_offset) in info: 502 if not source_symbol: 503 if symbol_present: 504 source_symbol = symbol.CallCppFilt(symbol_name) 505 else: 506 source_symbol = "<unknown>" 507 if not symbol.VERBOSE: 508 source_symbol = symbol.FormatSymbolWithoutParameters(source_symbol) 509 symbol_with_offset = symbol.FormatSymbolWithoutParameters(symbol_with_offset) 510 if not source_location: 511 source_location = area 512 if lib_name: 513 source_location += "(" + lib_name + ")" 514 if nest_count > 0: 515 nest_count = nest_count - 1 516 arrow = "v------>" 517 if symbol.ARCH == "arm64" or symbol.ARCH == "mips64" or symbol.ARCH == "x86_64": 518 arrow = "v-------------->" 519 self.trace_lines.append((arrow, source_symbol, source_location)) 520 else: 521 if not symbol_with_offset: 522 symbol_with_offset = source_symbol 523 self.trace_lines.append((code_addr, symbol_with_offset, source_location)) 524 if self.code_line.match(line): 525 # Code lines should be ignored. If this were exluded the 'code around' 526 # sections would trigger value_line matches. 527 return ret 528 if self.value_line.match(line): 529 ret = True 530 match = self.value_line.match(line) 531 (unused_, addr, value, area, symbol_present, symbol_name) = match.groups() 532 if area == "<unknown>" or area == "[heap]" or area == "[stack]" or not area: 533 self.value_lines.append((addr, value, "", area)) 534 else: 535 info = symbol.SymbolInformation(area, value) 536 (source_symbol, source_location, object_symbol_with_offset) = info.pop() 537 # If there is no information, skip this. 538 if source_symbol or source_location or object_symbol_with_offset: 539 if not source_symbol: 540 if symbol_present: 541 source_symbol = symbol.CallCppFilt(symbol_name) 542 else: 543 source_symbol = "<unknown>" 544 if not source_location: 545 source_location = area 546 if not object_symbol_with_offset: 547 object_symbol_with_offset = source_symbol 548 self.value_lines.append((addr, 549 value, 550 object_symbol_with_offset, 551 source_location)) 552 553 return ret 554 555 556class RegisterPatternTests(unittest.TestCase): 557 def assert_register_matches(self, abi, example_crash, stupid_pattern): 558 tc = TraceConverter() 559 lines = example_crash.split('\n') 560 symbol.SetAbi(lines) 561 tc.UpdateAbiRegexes() 562 for line in lines: 563 tc.ProcessLine(line) 564 is_register = (re.search(stupid_pattern, line) is not None) 565 matched = (tc.register_line.search(line) is not None) 566 self.assertEqual(matched, is_register, line) 567 tc.PrintOutput(tc.trace_lines, tc.value_lines) 568 569 def test_arm_registers(self): 570 self.assert_register_matches("arm", example_crashes.arm, '\\b(r0|r4|r8|ip)\\b') 571 572 def test_arm64_registers(self): 573 self.assert_register_matches("arm64", example_crashes.arm64, '\\b(x0|x4|x8|x12|x16|x20|x24|x28|sp)\\b') 574 575 def test_mips_registers(self): 576 self.assert_register_matches("mips", example_crashes.mips, '\\b(zr|a0|t0|t4|s0|s4|t8|gp|hi)\\b') 577 578 def test_mips64_registers(self): 579 self.assert_register_matches("mips64", example_crashes.mips64, '\\b(zr|a0|a4|t0|s0|s4|t8|gp|hi)\\b') 580 581 def test_x86_registers(self): 582 self.assert_register_matches("x86", example_crashes.x86, '\\b(eax|esi|xcs|eip)\\b') 583 584 def test_x86_64_registers(self): 585 self.assert_register_matches("x86_64", example_crashes.x86_64, '\\b(rax|rsi|r8|r12|cs|rip)\\b') 586 587class LibmemunreachablePatternTests(unittest.TestCase): 588 def test_libmemunreachable(self): 589 tc = TraceConverter() 590 lines = example_crashes.libmemunreachable.split('\n') 591 592 symbol.SetAbi(lines) 593 self.assertEqual(symbol.ARCH, "arm") 594 595 tc.UpdateAbiRegexes() 596 header_lines = 0 597 trace_lines = 0 598 for line in lines: 599 tc.ProcessLine(line) 600 if re.search(tc.unreachable_line, line) is not None: 601 header_lines += 1 602 if tc.MatchTraceLine(line) is not None: 603 trace_lines += 1 604 self.assertEqual(header_lines, 3) 605 self.assertEqual(trace_lines, 2) 606 tc.PrintOutput(tc.trace_lines, tc.value_lines) 607 608class LongASANStackTests(unittest.TestCase): 609 # Test that a long ASAN-style (non-padded frame numbers) stack trace is not split into two 610 # when the frame number becomes two digits. This happened before as the frame number was 611 # handled as a string and not converted to an integral. 612 def test_long_asan_crash(self): 613 tc = TraceConverter() 614 lines = example_crashes.long_asan_crash.splitlines() 615 symbol.SetAbi(lines) 616 tc.UpdateAbiRegexes() 617 # Test by making sure trace_line_count is monotonically non-decreasing. If the stack trace 618 # is split, a separator is printed and trace_lines is flushed. 619 trace_line_count = 0 620 for line in lines: 621 tc.ProcessLine(line) 622 self.assertLessEqual(trace_line_count, len(tc.trace_lines)) 623 trace_line_count = len(tc.trace_lines) 624 # The split happened at transition of frame #9 -> #10. Make sure we have parsed (and stored) 625 # more than ten frames. 626 self.assertGreater(trace_line_count, 10) 627 tc.PrintOutput(tc.trace_lines, tc.value_lines) 628 629class ValueLinesTest(unittest.TestCase): 630 def test_value_line_skipped(self): 631 tc = TraceConverter() 632 symbol.SetAbi(["ABI: 'arm'"]) 633 tc.UpdateAbiRegexes() 634 tc.ProcessLine(" 12345678 00001000 .") 635 self.assertEqual([], tc.value_lines) 636 637if __name__ == '__main__': 638 unittest.main(verbosity=2) 639