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 register_names = { 66 "arm": "r0|r1|r2|r3|r4|r5|r6|r7|r8|r9|sl|fp|ip|sp|lr|pc|cpsr", 67 "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", 68 "x86": "eax|ebx|ecx|edx|esi|edi|x?cs|x?ds|x?es|x?fs|x?ss|eip|ebp|esp|flags", 69 "x86_64": "rax|rbx|rcx|rdx|rsi|rdi|r8|r9|r10|r11|r12|r13|r14|r15|cs|ss|rip|rbp|rsp|eflags", 70 "riscv64": "ra|sp|gp|tp|t0|t1|t2|s0|s1|a0|a1|a2|a3|a4|a5|a6|a7|s2|s3|s4|s5|s6|s7|s8|s9|s10|s11|t3|t4|t5|t6|pc", 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 readelf_output = re.compile(r"Class:\s*ELF(?P<bitness>32|64).*" 76 r"Build ID:\s*(?P<build_id>[0-9a-f]+)", 77 flags=re.DOTALL) 78 79 def UpdateBitnessRegexes(self): 80 if symbol.ARCH_IS_32BIT: 81 self.width = "{8}" 82 self.spacing = "" 83 else: 84 self.width = "{16}" 85 self.spacing = " " 86 self.register_line = re.compile(" (([ ]*\\b(\S*)\\b +[0-9a-f]" + self.width + "){1,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 symbol.ARCH_IS_32BIT is None: 186 symbol.SetBitness(lines) 187 self.UpdateBitnessRegexes() 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 if os.path.isfile(path): 321 files_by_basename.setdefault(path.name, []).append(path) 322 return files_by_basename 323 324 # Use the "file" command line tool to find the bitness and build_id of given ELF file. 325 @functools.lru_cache(maxsize=None) 326 def GetLibraryInfo(self, lib): 327 stdout = subprocess.check_output([symbol.ToolPath("llvm-readelf"), "-h", "-n", lib], text=True) 328 match = self.readelf_output.search(stdout) 329 if match: 330 return self.ElfInfo(bitness=match.group("bitness"), build_id=match.group("build_id")) 331 return None 332 333 # Search for a library with the given basename and build_id anywhere in the symbols directory. 334 @functools.lru_cache(maxsize=None) 335 def GetLibraryByBuildId(self, symbols_dir, basename, build_id): 336 for candidate in self.GlobSymbolsDir(symbols_dir).get(basename, []): 337 info = self.GetLibraryInfo(candidate) 338 if info and info.build_id == build_id: 339 return "/" + str(candidate.relative_to(symbols_dir)) 340 return None 341 342 def GetLibPath(self, lib): 343 if lib in self.lib_to_path: 344 return self.lib_to_path[lib] 345 346 lib_path = self.FindLibPath(lib) 347 self.lib_to_path[lib] = lib_path 348 return lib_path 349 350 def FindLibPath(self, lib): 351 symbol_dir = symbol.SYMBOLS_DIR 352 if os.path.isfile(symbol_dir + lib): 353 return lib 354 355 # Try and rewrite any apex files if not found in symbols. 356 # For some reason, the directory in symbols does not match 357 # the path on system. 358 # The path is com.android.<directory> on device, but 359 # com.google.android.<directory> in symbols. 360 new_lib = lib.replace("/com.android.", "/com.google.android.") 361 if os.path.isfile(symbol_dir + new_lib): 362 return new_lib 363 364 # When using atest, test paths are different between the out/ directory 365 # and device. Apply fixups. 366 if not lib.startswith("/data/local/tests/") and not lib.startswith("/data/local/tmp/"): 367 print("WARNING: Cannot find %s in symbol directory" % lib) 368 return lib 369 370 test_name = lib.rsplit("/", 1)[-1] 371 test_dir = "/data/nativetest" 372 test_dir_bitness = "" 373 if symbol.ARCH_IS_32BIT: 374 bitness = "32" 375 else: 376 bitness = "64" 377 test_dir_bitness = "64" 378 379 # Unfortunately, the location of the real symbol file is not 380 # standardized, so we need to go hunting for it. 381 382 # This is in vendor, look for the value in: 383 # /data/nativetest{64}/vendor/test_name/test_name 384 if lib.startswith("/data/local/tests/vendor/"): 385 lib_path = os.path.join(test_dir + test_dir_bitness, "vendor", test_name, test_name) 386 if os.path.isfile(symbol_dir + lib_path): 387 return lib_path 388 389 # Look for the path in: 390 # /data/nativetest{64}/test_name/test_name 391 lib_path = os.path.join(test_dir + test_dir_bitness, test_name, test_name) 392 if os.path.isfile(symbol_dir + lib_path): 393 return lib_path 394 395 # CtsXXX tests are in really non-standard locations try: 396 # /data/nativetest/{test_name} 397 lib_path = os.path.join(test_dir, test_name) 398 if os.path.isfile(symbol_dir + lib_path): 399 return lib_path 400 # Try: 401 # /data/nativetest/{test_name}{32|64} 402 lib_path += bitness 403 if os.path.isfile(symbol_dir + lib_path): 404 return lib_path 405 406 # Cannot find location, give up and return the original path 407 print("WARNING: Cannot find %s in symbol directory" % lib) 408 return lib 409 410 411 def ProcessLine(self, line): 412 ret = False 413 process_header = self.process_info_line.search(line) 414 signal_header = self.signal_line.search(line) 415 abort_message_header = self.abort_message_line.search(line) 416 thread_header = self.thread_line.search(line) 417 register_header = self.register_line.search(line) 418 revision_header = self.revision_line.search(line) 419 dalvik_jni_thread_header = self.dalvik_jni_thread_line.search(line) 420 dalvik_native_thread_header = self.dalvik_native_thread_line.search(line) 421 unreachable_header = self.unreachable_line.search(line) 422 if process_header or signal_header or abort_message_header or thread_header or \ 423 register_header or dalvik_jni_thread_header or dalvik_native_thread_header or \ 424 revision_header or unreachable_header: 425 ret = True 426 if self.trace_lines or self.value_lines: 427 self.PrintOutput(self.trace_lines, self.value_lines) 428 self.PrintDivider() 429 self.trace_lines = [] 430 self.value_lines = [] 431 self.last_frame = -1 432 if process_header: 433 print(process_header.group(1)) 434 if signal_header: 435 print(signal_header.group(1)) 436 if abort_message_header: 437 print(abort_message_header.group(1)) 438 if register_header: 439 print(register_header.group(1)) 440 if thread_header: 441 print(thread_header.group(1)) 442 if dalvik_jni_thread_header: 443 print(dalvik_jni_thread_header.group(1)) 444 if dalvik_native_thread_header: 445 print(dalvik_native_thread_header.group(1)) 446 if revision_header: 447 print(revision_header.group(1)) 448 if unreachable_header: 449 print(unreachable_header.group(1)) 450 return True 451 trace_line_dict = self.MatchTraceLine(line) 452 if trace_line_dict is not None: 453 ret = True 454 frame = int(trace_line_dict["frame"]) 455 code_addr = trace_line_dict["offset"] 456 area = trace_line_dict["dso"] 457 so_offset = trace_line_dict["so_offset"] 458 symbol_present = trace_line_dict["symbol_present"] 459 symbol_name = trace_line_dict["symbol_name"] 460 build_id = trace_line_dict["build_id"] 461 462 if frame <= self.last_frame and (self.trace_lines or self.value_lines): 463 self.PrintOutput(self.trace_lines, self.value_lines) 464 self.PrintDivider() 465 self.trace_lines = [] 466 self.value_lines = [] 467 self.last_frame = frame 468 469 if area == "<unknown>" or area == "[heap]" or area == "[stack]": 470 self.trace_lines.append((code_addr, "", area)) 471 else: 472 # If this is an apk, it usually means that there is actually 473 # a shared so that was loaded directly out of it. In that case, 474 # extract the shared library and the name of the shared library. 475 lib = None 476 # The format of the map name: 477 # Some.apk!libshared.so 478 # or 479 # Some.apk 480 if so_offset: 481 # If it ends in apk, we are done. 482 apk = None 483 if area.endswith(".apk"): 484 apk = area 485 else: 486 index = area.rfind(".so!") 487 if index != -1: 488 # Sometimes we'll see something like: 489 # #01 pc abcd libart.so!libart.so (offset 0x134000) 490 # Remove everything after the ! and zero the offset value. 491 area = area[0:index + 3] 492 so_offset = 0 493 else: 494 index = area.rfind(".apk!") 495 if index != -1: 496 apk = area[0:index + 4] 497 if apk: 498 lib_name, lib = self.GetLibFromApk(apk, so_offset) 499 else: 500 # Sometimes we'll see something like: 501 # #01 pc abcd libart.so!libart.so 502 # Remove everything after the !. 503 index = area.rfind(".so!") 504 if index != -1: 505 area = area[0:index + 3] 506 if not lib: 507 lib = area 508 lib_name = None 509 510 if build_id: 511 # If we have the build_id, do a brute-force search of the symbols directory. 512 basename = os.path.basename(lib) 513 lib = self.GetLibraryByBuildId(symbol.SYMBOLS_DIR, basename, build_id) 514 if not lib: 515 print("WARNING: Cannot find {} with build id {} in symbols directory." 516 .format(basename, build_id)) 517 else: 518 # When using atest, test paths are different between the out/ directory 519 # and device. Apply fixups. 520 lib = self.GetLibPath(lib) 521 522 # If a calls b which further calls c and c is inlined to b, we want to 523 # display "a -> b -> c" in the stack trace instead of just "a -> c" 524 info = symbol.SymbolInformation(lib, code_addr) 525 nest_count = len(info) - 1 526 for (source_symbol, source_location, symbol_with_offset) in info: 527 if not source_symbol: 528 if symbol_present: 529 source_symbol = symbol.CallCppFilt(symbol_name) 530 else: 531 source_symbol = "<unknown>" 532 if not symbol.VERBOSE: 533 source_symbol = symbol.FormatSymbolWithoutParameters(source_symbol) 534 symbol_with_offset = symbol.FormatSymbolWithoutParameters(symbol_with_offset) 535 if not source_location: 536 source_location = area 537 if lib_name: 538 source_location += "(" + lib_name + ")" 539 if nest_count > 0: 540 nest_count = nest_count - 1 541 arrow = "v------>" 542 if not symbol.ARCH_IS_32BIT: 543 arrow = "v-------------->" 544 self.trace_lines.append((arrow, source_symbol, source_location)) 545 else: 546 if not symbol_with_offset: 547 symbol_with_offset = source_symbol 548 self.trace_lines.append((code_addr, symbol_with_offset, source_location)) 549 if self.code_line.match(line): 550 # Code lines should be ignored. If this were exluded the 'code around' 551 # sections would trigger value_line matches. 552 return ret 553 if self.value_line.match(line): 554 ret = True 555 match = self.value_line.match(line) 556 (unused_, addr, value, area, symbol_present, symbol_name) = match.groups() 557 if area == "<unknown>" or area == "[heap]" or area == "[stack]" or not area: 558 self.value_lines.append((addr, value, "", area)) 559 else: 560 info = symbol.SymbolInformation(area, value) 561 (source_symbol, source_location, object_symbol_with_offset) = info.pop() 562 # If there is no information, skip this. 563 if source_symbol or source_location or object_symbol_with_offset: 564 if not source_symbol: 565 if symbol_present: 566 source_symbol = symbol.CallCppFilt(symbol_name) 567 else: 568 source_symbol = "<unknown>" 569 if not source_location: 570 source_location = area 571 if not object_symbol_with_offset: 572 object_symbol_with_offset = source_symbol 573 self.value_lines.append((addr, 574 value, 575 object_symbol_with_offset, 576 source_location)) 577 578 return ret 579 580 581class RegisterPatternTests(unittest.TestCase): 582 def assert_register_matches(self, abi, example_crash, stupid_pattern): 583 tc = TraceConverter() 584 lines = example_crash.split('\n') 585 symbol.SetBitness(lines) 586 tc.UpdateBitnessRegexes() 587 for line in lines: 588 tc.ProcessLine(line) 589 is_register = (re.search(stupid_pattern, line) is not None) 590 matched = (tc.register_line.search(line) is not None) 591 self.assertEqual(matched, is_register, line) 592 tc.PrintOutput(tc.trace_lines, tc.value_lines) 593 594 def test_arm_registers(self): 595 self.assert_register_matches("arm", example_crashes.arm, '\\b(r0|r4|r8|ip|scr)\\b') 596 597 def test_arm64_registers(self): 598 self.assert_register_matches("arm64", example_crashes.arm64, '\\b(x0|x4|x8|x12|x16|x20|x24|x28|sp|v[1-3]?[0-9])\\b') 599 600 def test_x86_registers(self): 601 self.assert_register_matches("x86", example_crashes.x86, '\\b(eax|esi|xcs|eip)\\b') 602 603 def test_x86_64_registers(self): 604 self.assert_register_matches("x86_64", example_crashes.x86_64, '\\b(rax|rsi|r8|r12|cs|rip)\\b') 605 606 def test_riscv64_registers(self): 607 self.assert_register_matches("riscv64", example_crashes.riscv64, '\\b(gp|t2|t6|s3|s7|s11|a3|a7|sp)\\b') 608 609class LibmemunreachablePatternTests(unittest.TestCase): 610 def test_libmemunreachable(self): 611 tc = TraceConverter() 612 lines = example_crashes.libmemunreachable.split('\n') 613 614 symbol.SetBitness(lines) 615 self.assertTrue(symbol.ARCH_IS_32BIT) 616 tc.UpdateBitnessRegexes() 617 header_lines = 0 618 trace_lines = 0 619 for line in lines: 620 tc.ProcessLine(line) 621 if re.search(tc.unreachable_line, line) is not None: 622 header_lines += 1 623 if tc.MatchTraceLine(line) is not None: 624 trace_lines += 1 625 self.assertEqual(header_lines, 3) 626 self.assertEqual(trace_lines, 2) 627 tc.PrintOutput(tc.trace_lines, tc.value_lines) 628 629class LongASANStackTests(unittest.TestCase): 630 # Test that a long ASAN-style (non-padded frame numbers) stack trace is not split into two 631 # when the frame number becomes two digits. This happened before as the frame number was 632 # handled as a string and not converted to an integral. 633 def test_long_asan_crash(self): 634 tc = TraceConverter() 635 lines = example_crashes.long_asan_crash.splitlines() 636 symbol.SetBitness(lines) 637 tc.UpdateBitnessRegexes() 638 # Test by making sure trace_line_count is monotonically non-decreasing. If the stack trace 639 # is split, a separator is printed and trace_lines is flushed. 640 trace_line_count = 0 641 for line in lines: 642 tc.ProcessLine(line) 643 self.assertLessEqual(trace_line_count, len(tc.trace_lines)) 644 trace_line_count = len(tc.trace_lines) 645 # The split happened at transition of frame #9 -> #10. Make sure we have parsed (and stored) 646 # more than ten frames. 647 self.assertGreater(trace_line_count, 10) 648 tc.PrintOutput(tc.trace_lines, tc.value_lines) 649 650class ValueLinesTest(unittest.TestCase): 651 def test_value_line_skipped(self): 652 tc = TraceConverter() 653 symbol.ARCH_IS_32BIT = True 654 tc.UpdateBitnessRegexes() 655 tc.ProcessLine(" 12345678 00001000 .") 656 self.assertEqual([], tc.value_lines) 657 658if __name__ == '__main__': 659 unittest.main(verbosity=2) 660