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