Package FuzzManager :: Package FTB :: Package Signatures :: Module CrashInfo
[hide private]
[frames] | no frames]

Source Code for Module FuzzManager.FTB.Signatures.CrashInfo

  1  ''' 
  2  Crash Information 
  3   
  4  Represents information about a crash. Specific subclasses implement 
  5  different crash data supported by the implementation. 
  6   
  7  @author:     Christian Holler (:decoder) 
  8   
  9  @license: 
 10   
 11  This Source Code Form is subject to the terms of the Mozilla Public 
 12  License, v. 2.0. If a copy of the MPL was not distributed with this 
 13  file, You can obtain one at http://mozilla.org/MPL/2.0/. 
 14   
 15  @contact:    choller@mozilla.com 
 16  ''' 
 17   
 18  # Ensure print() compatibility with Python 3 
 19  from __future__ import print_function 
 20   
 21  from abc import ABCMeta 
 22  import json 
 23  from numpy import int32, int64, uint32, uint64 
 24  import os 
 25  import re 
 26  import sys 
 27   
 28  from FTB import AssertionHelper 
 29  from FTB.ProgramConfiguration import ProgramConfiguration 
 30  from FTB.Signatures import RegisterHelper 
 31  from FTB.Signatures.CrashSignature import CrashSignature 
32 33 34 -class CrashInfo():
35 ''' 36 Abstract base class that provides a method to instantiate the right sub class. 37 It also supports generating a CrashSignature based on the stored information. 38 ''' 39 __metaclass__ = ABCMeta 40
41 - def __init__(self):
42 # Store the raw data 43 self.rawStdout = [] 44 self.rawStderr = [] 45 self.rawCrashData = [] 46 47 # Store processed data 48 self.backtrace = [] 49 self.registers = {} 50 self.crashAddress = None 51 self.crashInstruction = None 52 53 # Store configuration data (platform, product, os, etc.) 54 self.configuration = None 55 56 # This is an optional testcase that is not stored with the crashInfo but 57 # can be "attached" before matching signatures that might require the 58 # testcase. 59 self.testcase = None 60 61 # This can be used to record failures during signature creation 62 self.failureReason = None
63
64 - def __str__(self):
65 buf = [] 66 buf.append("Crash trace:") 67 buf.append("") 68 for idx, frame in enumerate(self.backtrace): 69 buf.append("# %02d %s" % (idx, frame)) 70 buf.append("") 71 72 if self.crashAddress: 73 buf.append("Crash address: %s" % self.crashAddress) 74 75 if self.crashInstruction: 76 buf.append("Crash instruction: %s" % self.crashInstruction) 77 78 if self.crashAddress or self.crashInstruction: 79 buf.append("") 80 81 buf.append("Last 5 lines on stderr:") 82 buf.extend(self.rawStderr[-5:]) 83 84 return "\n".join(buf)
85
86 - def toCacheObject(self):
87 ''' 88 Create a cache object for restoring the class instance later on without parsing 89 the crash data again. This object includes all class fields except for the 90 storage heavy raw objects like stdout, stderr and raw crashdata. 91 92 @rtype: dict 93 @return: Dictionary containing expensive class fields 94 ''' 95 cacheObject = {} 96 cacheObject['backtrace'] = self.backtrace 97 cacheObject['registers'] = self.registers 98 99 if self.crashAddress is not None: 100 cacheObject['crashAddress'] = long(self.crashAddress) 101 else: 102 cacheObject['crashAddress'] = None 103 104 cacheObject['crashInstruction'] = self.crashInstruction 105 cacheObject['failureReason'] = self.failureReason 106 107 return cacheObject
108 109 @staticmethod
110 - def fromRawCrashData(stdout, stderr, configuration, auxCrashData=None, cacheObject=None):
111 ''' 112 Create appropriate CrashInfo instance from raw crash data 113 114 @type stdout: List of strings 115 @param stdout: List of lines as they appeared on stdout 116 @type stderr: List of strings 117 @param stderr: List of lines as they appeared on stderr 118 @type configuration: ProgramConfiguration 119 @param configuration: Exact program configuration that is associated with the crash 120 @type auxCrashData: List of strings 121 @param auxCrashData: Optional additional crash output (e.g. GDB). If not specified, stderr is used. 122 @type cacheObject: Dictionary 123 @param cacheObject: The cache object that should be used to restore the class fields 124 instead of parsing the crash data. The appropriate object can be 125 created by calling the toCacheObject method. 126 127 @rtype: CrashInfo 128 @return: Crash information object 129 ''' 130 131 assert stdout == None or isinstance(stdout, list) or isinstance(stdout, basestring) 132 assert stderr == None or isinstance(stderr, list) or isinstance(stderr, basestring) 133 assert auxCrashData == None or isinstance(auxCrashData, list) or isinstance(auxCrashData, basestring) 134 135 assert isinstance(configuration, ProgramConfiguration) 136 137 if isinstance(stdout, basestring): 138 stdout = stdout.splitlines() 139 140 if isinstance(stderr, basestring): 141 stderr = stderr.splitlines() 142 143 if isinstance(auxCrashData, basestring): 144 auxCrashData = auxCrashData.splitlines() 145 146 if cacheObject is not None: 147 c = CrashInfo() 148 149 if stdout != None: 150 c.rawStdout.extend(stdout) 151 152 if stderr != None: 153 c.rawStderr.extend(stderr) 154 155 if auxCrashData != None: 156 c.rawCrashData.extend(auxCrashData) 157 158 c.configuration = configuration 159 c.backtrace = cacheObject['backtrace'] 160 c.registers = cacheObject['registers'] 161 c.crashAddress = cacheObject['crashAddress'] 162 c.crashInstruction = cacheObject['crashInstruction'] 163 c.failureReason = cacheObject['failureReason'] 164 165 return c 166 167 asanString = "ERROR: AddressSanitizer:" 168 gdbString = "Program received signal " 169 gdbCoreString = "Program terminated with signal " 170 ubsanString = "SUMMARY: AddressSanitizer: undefined-behavior" 171 appleString = "OS Version: Mac OS X" 172 173 # Use two strings for detecting Minidumps to avoid false positives 174 minidumpFirstString = "OS|" 175 minidumpSecondString = "CPU|" 176 minidumpFirstDetected = False 177 178 # Search both crashData and stderr, but prefer crashData 179 lines = [] 180 if auxCrashData != None: 181 lines.extend(auxCrashData) 182 if stderr != None: 183 lines.extend(stderr) 184 185 for line in lines: 186 if ubsanString in line: 187 return UBSanCrashInfo(stdout, stderr, configuration, auxCrashData) 188 elif asanString in line: 189 return ASanCrashInfo(stdout, stderr, configuration, auxCrashData) 190 elif appleString in line: 191 return AppleCrashInfo(stdout, stderr, configuration, auxCrashData) 192 elif gdbString in line or gdbCoreString in line: 193 return GDBCrashInfo(stdout, stderr, configuration, auxCrashData) 194 elif not minidumpFirstDetected and minidumpFirstString in line: 195 # Only match Minidump output if the *next* line also contains 196 # the second search string defined above. 197 minidumpFirstDetected = True 198 elif minidumpFirstDetected and minidumpSecondString in line: 199 return MinidumpCrashInfo(stdout, stderr, configuration, auxCrashData) 200 elif minidumpFirstDetected: 201 minidumpFirstDetected = False 202 203 # Default fallback to be used if there is neither ASan nor GDB output. 204 # This is still useful in case there is no crash but we want to match 205 # e.g. stdout/stderr output with signatures. 206 return NoCrashInfo(stdout, stderr, configuration, auxCrashData)
207
208 - def createShortSignature(self):
209 ''' 210 @rtype: String 211 @return: A string representing this crash (short signature) 212 ''' 213 # See if we have an abort message and if so, use that as short signature 214 abortMsg = AssertionHelper.getAssertion(self.rawStderr, True) 215 if abortMsg != None: 216 return abortMsg 217 218 # See if we have an abort message in our crash data maybe, e.g. for UBSan 219 if self.rawCrashData: 220 abortMsg = AssertionHelper.getAssertion(self.rawCrashData, True) 221 if abortMsg != None: 222 return abortMsg 223 224 if not len(self.backtrace): 225 return "No crash detected" 226 227 return "[@ %s]" % self.backtrace[0]
228
229 - def createCrashSignature(self, forceCrashAddress=False, forceCrashInstruction=False, maxFrames=8, minimumSupportedVersion=13):
230 ''' 231 @param forceCrashAddress: If True, the crash address will be included in any case 232 @type forceCrashAddress: bool 233 @param forceCrashInstruction: If True, the crash instruction will be included in any case 234 @type forceCrashInstruction: bool 235 @param maxFrames: How many frames (at most) should be included in the signature 236 @type maxFrames: int 237 238 @param minimumSupportedVersion: The minimum crash signature standard version that the 239 generated signature should be valid for (10 => 1.0, 13 => 1.3) 240 @type minimumSupportedVersion: int 241 242 @rtype: CrashSignature 243 @return: A crash signature object 244 ''' 245 # Determine the actual number of frames based on how many we got 246 if len(self.backtrace) > maxFrames: 247 numFrames = maxFrames 248 else: 249 numFrames = len(self.backtrace) 250 251 # See if we have an abort message and if so, get a sanitized version of it 252 abortMsg = AssertionHelper.getAssertion(self.rawStderr, True) 253 abortMsgInCrashdata = False 254 255 if abortMsg is None and minimumSupportedVersion >= 13: 256 # Look for abort messages also inside crashdata (e.g. UBSan) 257 # only on version 1.3 or higher, because the "crashdata" source 258 # type for output matching was added in that version. 259 abortMsg = AssertionHelper.getAssertion(self.rawCrashData, True) 260 abortMsgInCrashdata = True 261 262 if abortMsg != None: 263 abortMsg = AssertionHelper.getSanitizedAssertionPattern(abortMsg) 264 265 # Consider the first four frames as top stack 266 topStackLimit = 4 267 268 symptomArr = [] 269 270 if abortMsg != None: 271 abortMsgSrc = "stderr" 272 if abortMsgInCrashdata: 273 abortMsgSrc = "crashdata" 274 275 # Compose StringMatch object with PCRE pattern. 276 # Versions below 1.2 only support the full object PCRE style, 277 # for anything newer, use the short form with forward slashes 278 # to increase the readability of the signatures. 279 if minimumSupportedVersion < 12: 280 stringObj = { "value" : abortMsg, "matchType" : "pcre" } 281 symptomObj = { "type" : "output", "src" : abortMsgSrc, "value" : stringObj } 282 else: 283 symptomObj = { "type" : "output", "src" : abortMsgSrc, "value" : "/%s/" % abortMsg } 284 symptomArr.append(symptomObj) 285 # If we have less than topStackLimit frames available anyway, count the difference 286 # between topStackLimit and the available frames already as missing. 287 # E.g. if the trace has only three entries anyway, one will be considered missing 288 # right from the start. This should prevent that very short stack frames are used 289 # for signatures without additional crash information that narrows the signature. 290 291 if numFrames >= topStackLimit: 292 topStackMissCount = 0 293 else: 294 topStackMissCount = topStackLimit - numFrames 295 296 # StackFramesSymptom is only supported in 1.2 and higher, 297 # for everything else, use multiple stackFrame symptoms 298 if minimumSupportedVersion < 12: 299 for idx in range(0, numFrames): 300 functionName = self.backtrace[idx] 301 if not functionName == "??": 302 symptomObj = { "type" : "stackFrame", "frameNumber" : idx, "functionName" : functionName } 303 symptomArr.append(symptomObj) 304 elif idx < 4: 305 # If we're in the top 4, we count this as a miss 306 topStackMissCount += 1 307 else: 308 framesArray = [] 309 310 for idx in range(0, numFrames): 311 functionName = self.backtrace[idx] 312 if not functionName == "??": 313 framesArray.append(functionName) 314 else: 315 framesArray.append("?") 316 if idx < 4: 317 # If we're in the top 4, we count this as a miss 318 topStackMissCount += 1 319 320 lastSymbolizedFrame = None 321 for frameIdx in range(0, len(framesArray)): 322 if str(framesArray[frameIdx]) != '?': 323 lastSymbolizedFrame = frameIdx 324 325 if lastSymbolizedFrame != None: 326 # Remove all elements behind the last symbolized frame 327 framesArray = framesArray[:lastSymbolizedFrame + 1] 328 else: 329 # We don't have a single symbolized frame, so it doesn't make sense 330 # to keep any wildcards in case we added some for unsymbolized frames. 331 framesArray = [] 332 333 if framesArray: 334 symptomArr.append({ "type" : "stackFrames", "functionNames" : framesArray }) 335 336 # Missing too much of the top stack frames, add additional crash information 337 stackIsInsufficient = topStackMissCount >= 2 and abortMsg == None 338 339 includeCrashAddress = stackIsInsufficient or forceCrashAddress 340 includeCrashInstruction = (stackIsInsufficient and self.crashInstruction != None) or forceCrashInstruction 341 342 if includeCrashAddress: 343 if self.crashAddress == None: 344 crashAddress = "" 345 elif self.crashAddress != 0L and self.crashAddress < 0x100L: 346 # Try to match crash addresses that are small but non-zero 347 # with a generic range that is likely associated with null-deref. 348 crashAddress = "< 0x100" 349 else: 350 crashAddress = hex(self.crashAddress).rstrip("L") 351 352 crashAddressSymptomObj = { "type" : "crashAddress", "address" : crashAddress } 353 symptomArr.append(crashAddressSymptomObj) 354 355 if includeCrashInstruction: 356 if self.crashInstruction == None: 357 failureReason = self.failureReason 358 self.failureReason = "No crash instruction available from crash data. Reason: %s" % failureReason 359 return None 360 361 crashInstructionSymptomObj = { "type" : "instruction", "instructionName" : self.crashInstruction } 362 symptomArr.append(crashInstructionSymptomObj) 363 364 sigObj = { "symptoms" : symptomArr } 365 366 return CrashSignature(json.dumps(sigObj, indent=2))
367 368 @staticmethod
369 - def sanitizeStackFrame(frame):
370 ''' 371 This function removes function arguments and other non-generic parts 372 of the function frame, returning a (hopefully) generic function name. 373 374 @param frame: The stack frame to sanitize 375 @type forceCrashAddress: str 376 377 @rtype: str 378 @return: Sanitized stack frame 379 ''' 380 381 # Remove any trailing const declaration 382 if frame.endswith(" const"): 383 frame = frame[0:-len(" const")] 384 385 # Strip the last ( ) segment, which might also contain more parentheses 386 if frame.endswith(")"): 387 idx = len(frame) - 2 388 parlevel = 0 389 while(idx > 0): 390 if frame[idx] == "(": 391 if parlevel > 0: 392 parlevel -= 1 393 else: 394 # Done 395 break 396 elif frame[idx] == ")": 397 parlevel += 1 398 399 idx -= 1 400 401 # Only strip if we actually found the ( ) before we reached the 402 # beginning of our string. 403 if idx: 404 frame = frame[:idx] 405 406 if "lambda" in frame: 407 frame = re.sub("<lambda at .+?:\d+:\d+>", "", frame) 408 409 return frame
410
411 -class NoCrashInfo(CrashInfo):
412 - def __init__(self, stdout, stderr, configuration, crashData=None):
413 ''' 414 Private constructor, called by L{CrashInfo.fromRawCrashData}. Do not use directly. 415 ''' 416 CrashInfo.__init__(self) 417 418 if stdout != None: 419 self.rawStdout.extend(stdout) 420 421 if stderr != None: 422 self.rawStderr.extend(stderr) 423 424 if crashData != None: 425 self.rawCrashData.extend(crashData) 426 427 self.configuration = configuration
428
429 430 -class ASanCrashInfo(CrashInfo):
431 - def __init__(self, stdout, stderr, configuration, crashData=None):
432 ''' 433 Private constructor, called by L{CrashInfo.fromRawCrashData}. Do not use directly. 434 ''' 435 CrashInfo.__init__(self) 436 437 if stdout != None: 438 self.rawStdout.extend(stdout) 439 440 if stderr != None: 441 self.rawStderr.extend(stderr) 442 443 if crashData != None: 444 self.rawCrashData.extend(crashData) 445 446 self.configuration = configuration 447 448 # If crashData is given, use that to find the ASan trace, otherwise use stderr 449 if crashData: 450 asanOutput = crashData 451 else: 452 asanOutput = stderr 453 454 # For better readability, list all the formats here, then join them into the regular expression 455 asanMessages = [ 456 "on address", # The most common format, used for all overflows 457 "on unknown address", # Used in case of a SIGSEGV 458 "double-free on", # Used in case of a double-free 459 "not malloc\\(\\)\\-ed:", # Used in case of a wild free (on unallocated memory) 460 "not owned:" # Used when calling __asan_get_allocated_size() on a pointer that isn't owned 461 ] 462 463 # TODO: Support "memory ranges [%p,%p) and [%p, %p) overlap" ? 464 465 asanCrashAddressPattern = " AddressSanitizer:.+ (?:" + "|".join(asanMessages) + ")\\s+0x([0-9a-f]+)" 466 asanRegisterPattern = "(?:\\s+|\\()pc\\s+0x([0-9a-f]+)\\s+(sp|bp)\\s+0x([0-9a-f]+)\\s+(sp|bp)\\s+0x([0-9a-f]+)" 467 468 expectedIndex = 0 469 for traceLine in asanOutput: 470 if self.crashAddress == None: 471 match = re.search(asanCrashAddressPattern, traceLine) 472 473 if match != None: 474 self.crashAddress = long(match.group(1), 16) 475 476 # Crash Address and Registers are in the same line for ASan 477 match = re.search(asanRegisterPattern, traceLine) 478 if match != None: 479 self.registers["pc"] = long(match.group(1), 16) 480 self.registers[match.group(2)] = long(match.group(3), 16) 481 self.registers[match.group(4)] = long(match.group(5), 16) 482 else: 483 raise RuntimeError("Fatal error parsing ASan trace: Failed to isolate registers in line: %s" % traceLine) 484 485 parts = traceLine.strip().split() 486 487 # We only want stack frames 488 if not parts or not parts[0].startswith("#"): 489 continue 490 491 index = int(parts[0][1:]) 492 493 # We may see multiple traces in ASAN 494 if index == 0: 495 expectedIndex = 0 496 497 if not expectedIndex == index: 498 raise RuntimeError("Fatal error parsing ASan trace (Index mismatch, got index %s but expected %s)" % (index, expectedIndex)) 499 500 component = None 501 if len(parts) > 2: 502 if parts[2] == "in": 503 component = " ".join(parts[3:-1]) 504 else: 505 # Remove parentheses around component 506 component = parts[2][1:-1] 507 else: 508 print("Warning: Missing component in this line: %s" % traceLine, file=sys.stderr) 509 component = "<missing>" 510 511 self.backtrace.append(CrashInfo.sanitizeStackFrame(component)) 512 expectedIndex += 1 513 514 if not self.backtrace and self.crashAddress != None: 515 # We've seen the crash address but no frames, so this is likely 516 # a crash on the heap with no symbols available. Add one artificial 517 # frame so it doesn't show up as "No crash detected" 518 self.backtrace.append("??")
519
520 -class UBSanCrashInfo(CrashInfo):
521 - def __init__(self, stdout, stderr, configuration, crashData=None):
522 ''' 523 Private constructor, called by L{CrashInfo.fromRawCrashData}. Do not use directly. 524 ''' 525 CrashInfo.__init__(self) 526 527 if stdout != None: 528 self.rawStdout.extend(stdout) 529 530 if stderr != None: 531 self.rawStderr.extend(stderr) 532 533 if crashData != None: 534 self.rawCrashData.extend(crashData) 535 536 self.configuration = configuration 537 538 # If crashData is given, use that to find the UBSan trace, otherwise use stderr 539 if crashData: 540 ubsanOutput = crashData 541 else: 542 ubsanOutput = stderr 543 544 ubsanErrorPattern = ":\\d+:\\d+: runtime error:" 545 ubsanPatternSeen = False 546 547 expectedIndex = 0 548 for traceLine in ubsanOutput: 549 if re.match(ubsanErrorPattern, traceLine) != None: 550 ubsanPatternSeen = True 551 552 parts = traceLine.strip().split() 553 554 # We only want stack frames 555 if not parts or not parts[0].startswith("#"): 556 continue 557 558 index = int(parts[0][1:]) 559 560 if not expectedIndex == index: 561 raise RuntimeError("Fatal error parsing UBSan trace (Index mismatch, got index %s but expected %s)" % (index, expectedIndex)) 562 563 component = None 564 if len(parts) > 2: 565 if parts[2] == "in": 566 component = " ".join(parts[3:-1]) 567 else: 568 # Remove parentheses around component 569 component = parts[2][1:-1] 570 else: 571 print("Warning: Missing component in this line: %s" % traceLine, file=sys.stderr) 572 component = "<missing>" 573 574 self.backtrace.append(CrashInfo.sanitizeStackFrame(component)) 575 expectedIndex += 1 576 577 if not self.backtrace and ubsanPatternSeen: 578 # We've seen the crash address but no frames, so this is likely 579 # a crash on the heap with no symbols available. Add one artificial 580 # frame so it doesn't show up as "No crash detected" 581 self.backtrace.append("??")
582
583 -class GDBCrashInfo(CrashInfo):
584 - def __init__(self, stdout, stderr, configuration, crashData=None):
585 ''' 586 Private constructor, called by L{CrashInfo.fromRawCrashData}. Do not use directly. 587 ''' 588 CrashInfo.__init__(self) 589 590 if stdout != None: 591 self.rawStdout.extend(stdout) 592 593 if stderr != None: 594 self.rawStderr.extend(stderr) 595 596 if crashData != None: 597 self.rawCrashData.extend(crashData) 598 599 self.configuration = configuration 600 601 # If crashData is given, use that to find the GDB trace, otherwise use stderr 602 if crashData: 603 gdbOutput = crashData 604 else: 605 gdbOutput = stderr 606 607 gdbFramePatterns = [ 608 "\\s*#(\\d+)\\s+(0x[0-9a-f]+) in (.+?) \\(.*?\\)( at .+)?", 609 "\\s*#(\\d+)\\s+()(.+?) \\(.*?\\)( at .+)?" 610 ] 611 612 gdbRegisterPattern = RegisterHelper.getRegisterPattern() + "\\s+0x([0-9a-f]+)" 613 gdbCrashAddressPattern = "Crash Address:\\s+0x([0-9a-f]+)" 614 gdbCrashInstructionPattern = "=> 0x[0-9a-f]+(?: <.+>)?:\\s+(.*)" 615 616 lastLineBuf = "" 617 618 pastFrames = False 619 620 for traceLine in gdbOutput: 621 # Do a very simple check for a frame number in combination with pending 622 # buffer content. If we detect this constellation, then it's highly likely 623 # that we have a valid trace line but no pattern that fits it. We need 624 # to make sure that we report this. 625 if not pastFrames and re.match("\\s*#\\d+.+", lastLineBuf) != None and re.match("\\s*#\\d+.+", traceLine) != None: 626 print("Fatal error parsing this GDB trace line:", file=sys.stderr) 627 print(lastLineBuf, file=sys.stderr) 628 raise RuntimeError("Fatal error parsing GDB trace") 629 630 if not len(lastLineBuf): 631 match = re.search(gdbRegisterPattern, traceLine) 632 if match != None: 633 pastFrames = True 634 register = match.group(1) 635 value = long(match.group(2), 16) 636 self.registers[register] = value 637 else: 638 match = re.search(gdbCrashAddressPattern, traceLine) 639 if match != None: 640 self.crashAddress = long(match.group(1), 16) 641 else: 642 match = re.search(gdbCrashInstructionPattern, traceLine) 643 if match != None: 644 self.crashInstruction = match.group(1) 645 646 if not pastFrames: 647 if not len(lastLineBuf) and re.match("\\s*#\\d+.+", traceLine) == None: 648 # Skip additional lines 649 continue 650 651 lastLineBuf += traceLine 652 653 functionName = None 654 frameIndex = None 655 656 for gdbPattern in gdbFramePatterns: 657 match = re.search(gdbPattern, lastLineBuf) 658 if match != None: 659 frameIndex = int(match.group(1)) 660 functionName = match.group(3) 661 break 662 663 if frameIndex == None: 664 # Line might not be complete yet, try adding the next 665 continue 666 else: 667 # Successfully parsed line, reset last line buffer 668 lastLineBuf = "" 669 670 # Allow #0 to appear twice in the beginning, GDB does this for core dumps ... 671 if len(self.backtrace) != frameIndex and frameIndex == 0: 672 self.backtrace.pop(0) 673 elif len(self.backtrace) != frameIndex: 674 print("Fatal error parsing this GDB trace (Index mismatch, wanted %s got %s ): " % (len(self.backtrace), frameIndex), file=sys.stderr) 675 print(os.linesep.join(gdbOutput), file=sys.stderr) 676 raise RuntimeError("Fatal error parsing GDB trace") 677 678 # This is a workaround for GDB throwing an error while resolving function arguments 679 # in the trace and aborting. We try to remove the error message to at least recover 680 # the function name properly. 681 gdbErrorIdx = functionName.find(" (/build/buildd/gdb") 682 if gdbErrorIdx > 0: 683 functionName = functionName[:gdbErrorIdx] 684 685 self.backtrace.append(functionName) 686 687 # If we have no crash address but the instruction, try to calculate the crash address 688 if self.crashAddress == None and self.crashInstruction != None: 689 crashAddress = GDBCrashInfo.calculateCrashAddress(self.crashInstruction, self.registers) 690 691 if isinstance(crashAddress, basestring): 692 self.failureReason = crashAddress 693 return 694 695 self.crashAddress = crashAddress 696 697 if self.crashAddress != None and self.crashAddress < 0: 698 if RegisterHelper.getBitWidth(self.registers) == 32: 699 self.crashAddress = uint32(self.crashAddress) 700 else: 701 # Assume 64 bit width 702 self.crashAddress = uint64(self.crashAddress)
703 704 @staticmethod
705 - def calculateCrashAddress(crashInstruction, registerMap):
706 ''' 707 Calculate the crash address given the crash instruction and register contents 708 709 @type crashInstruction: string 710 @param crashInstruction: Crash instruction string as provided by GDB 711 @type registerMap: Map from string to long 712 @param registerMap: Map of register names to values 713 714 @rtype: long 715 @return The calculated crash address 716 717 On error, a string containing the failure message is returned instead. 718 ''' 719 720 if (len(crashInstruction) == 0): 721 # GDB shows us no instruction, so the memory at the instruction 722 # pointer address must be inaccessible and we should assume 723 # that this caused our crash. 724 return RegisterHelper.getInstructionPointer(registerMap) 725 726 parts = crashInstruction.split(None, 1) 727 728 if len(parts) != 2: 729 raise RuntimeError("Failed to split instruction and operands apart: %s" % crashInstruction) 730 731 instruction = parts[0] 732 operands = parts[1] 733 734 if not re.match("[a-z\\.]+", instruction): 735 raise RuntimeError("Invalid instruction: %s" % instruction) 736 737 parts = operands.split(",") 738 739 # We now have four possibilities: 740 # 1. Length of parts is 1, that means we have one operand 741 # 2. Length of parts is 2, that means we have two simple operands 742 # 3. Length of parts is 4 and 743 # a) First part contains '(' but not ')', meaning the first operand is complex 744 # b) First part contains no '(' or ')', meaning the last operand is complex 745 # e.g. mov %ecx,0x500094(%r15,%rdx,4) 746 # 747 # 4. Length of parts is 3, just one complex operand. 748 # e.g. shrb -0x69(%rdx,%rbx,8) 749 750 # When we fail, try storing a reason here 751 failureReason = "Unknown failure." 752 753 if RegisterHelper.isX86Compatible(registerMap): 754 if len(parts) == 1: 755 if instruction == "callq" or instruction == "push" or instruction == "pop": 756 return RegisterHelper.getStackPointer(registerMap) 757 else: 758 failureReason = "Unsupported single-operand instruction." 759 elif len(parts) == 2: 760 failureReason = "Unknown failure with two-operand instruction." 761 derefOp = None 762 if "(" in parts[0] and ")" in parts[0]: 763 derefOp = parts[0] 764 765 if "(" in parts[1] and ")" in parts[1]: 766 if derefOp != None: 767 if ":(" in parts[1]: 768 # This can be an instruction using multiple segments, like: 769 # 770 # movsq %ds:(%rsi),%es:(%rdi) 771 # 772 # (gdb) p $_siginfo._sifields._sigfault.si_addr 773 # $1 = (void *) 0x7ff846e64d28 774 # (gdb) x /i $pc 775 # => 0x876b40 <js::ArgumentsObject::create<CopyFrameArgs>(JSContext*, JS::HandleScript, JS::HandleFunction, unsigned int, CopyFrameArgs&)+528>: movsq %ds:(%rsi),%es:(%rdi) 776 # (gdb) info reg $ds 777 # ds 0x0 0 778 # (gdb) info reg $es 779 # es 0x0 0 780 # (gdb) info reg $rsi 781 # rsi 0x7ff846e64d28 140704318115112 782 # (gdb) info reg $rdi 783 # rdi 0x7fff27fac030 140733864132656 784 # 785 # 786 # We don't support this right now, so return None. 787 # 788 return None 789 790 raise RuntimeError("Instruction operands have multiple loads? %s" % crashInstruction) 791 792 derefOp = parts[1] 793 794 if derefOp != None: 795 match = re.match("((?:\\-?0x[0-9a-f]+)?)\\(%([a-z0-9]+)\\)", derefOp) 796 if match != None: 797 offset = 0L 798 if len(match.group(1)): 799 offset = long(match.group(1), 16) 800 801 val = RegisterHelper.getRegisterValue(match.group(2), registerMap) 802 803 # If we don't have the value, return None 804 if val == None: 805 failureReason = "Missing value for register %s " % match.group(2) 806 else: 807 if RegisterHelper.getBitWidth(registerMap) == 32: 808 return long(int32(uint32(offset)) + int32(uint32(val))) 809 else: 810 # Assume 64 bit width 811 return long(int64(uint64(offset)) + int64(uint64(val))) 812 else: 813 failureReason = "Failed to decode two-operand instruction: No dereference operation or hardcoded address detected." 814 # We might still be reading from/writing to a hardcoded address. 815 # Note that it's not possible to have two hardcoded addresses 816 # in one instruction, one operand must be a register or immediate 817 # constant (denoted by leading $). In some cases, like a movabs 818 # instruction, the immediate constant however is dereferenced 819 # and is the first operator. So we first check parts[1] then 820 # parts[0] in case it's a dereferencing operation. 821 822 for x in (parts[1], parts[0]): 823 result = re.match("\\$?(\\-?0x[0-9a-f]+)", x) 824 if result != None: 825 return long(result.group(1), 16) 826 elif len(parts) == 3: 827 # Example instruction: shrb -0x69(%rdx,%rbx,8) 828 if "(" in parts[0] and ")" in parts[2]: 829 complexDerefOp = parts[0] + "," + parts[1] + "," + parts[2] 830 831 (result, reason) = GDBCrashInfo.calculateComplexDerefOpAddress(complexDerefOp, registerMap) 832 833 if result == None: 834 failureReason = reason 835 else: 836 return result 837 else: 838 raise RuntimeError("Unexpected instruction pattern: %s" % crashInstruction) 839 elif len(parts) == 4: 840 if "(" in parts[0] and not ")" in parts[0]: 841 complexDerefOp = parts[0] + "," + parts[1] + "," + parts[2] 842 elif not "(" in parts[0] and not ")" in parts[0]: 843 complexDerefOp = parts[1] + "," + parts[2] + "," + parts[3] 844 845 (result, reason) = GDBCrashInfo.calculateComplexDerefOpAddress(complexDerefOp, registerMap) 846 847 if result == None: 848 failureReason = reason 849 else: 850 return result 851 else: 852 raise RuntimeError("Unexpected length after splitting operands of this instruction: %s" % crashInstruction) 853 else: 854 failureReason = "Architecture is not supported." 855 856 print("Unable to calculate crash address from instruction: %s " % crashInstruction, file=sys.stderr) 857 print("Reason: %s" % failureReason, file=sys.stderr) 858 return failureReason
859 860 @staticmethod
861 - def calculateComplexDerefOpAddress(complexDerefOp, registerMap):
862 863 match = re.match("((?:\\-?0x[0-9a-f]+)?)\\(%([a-z0-9]+),%([a-z0-9]+),([0-9]+)\\)", complexDerefOp) 864 if match != None: 865 offset = 0L 866 if len(match.group(1)) > 0: 867 offset = long(match.group(1), 16) 868 869 regA = RegisterHelper.getRegisterValue(match.group(2), registerMap) 870 regB = RegisterHelper.getRegisterValue(match.group(3), registerMap) 871 872 mult = long(match.group(4), 16) 873 874 # If we're missing any of the two register values, return None 875 if regA == None or regB == None: 876 if regA == None: 877 return (None, "Missing value for register %s" % match.group(2)) 878 else: 879 return (None, "Missing value for register %s" % match.group(3)) 880 881 if RegisterHelper.getBitWidth(registerMap) == 32: 882 val = int32(uint32(regA)) + int32(uint32(offset)) + (int32(uint32(regB)) * int32(uint32(mult))) 883 else: 884 # Assume 64 bit width 885 val = int64(uint64(regA)) + int64(uint64(offset)) + (int64(uint64(regB)) * int64(uint64(mult))) 886 return (long(val), None) 887 888 return (None, "Unknown failure.")
889
890 891 -class MinidumpCrashInfo(CrashInfo):
892 - def __init__(self, stdout, stderr, configuration, crashData=None):
893 ''' 894 Private constructor, called by L{CrashInfo.fromRawCrashData}. Do not use directly. 895 ''' 896 CrashInfo.__init__(self) 897 898 if stdout != None: 899 self.rawStdout.extend(stdout) 900 901 if stderr != None: 902 self.rawStderr.extend(stderr) 903 904 if crashData != None: 905 self.rawCrashData.extend(crashData) 906 907 self.configuration = configuration 908 909 # If crashData is given, use that to find the Minidump trace, otherwise use stderr 910 if crashData: 911 minidumpOuput = crashData 912 else: 913 minidumpOuput = stderr 914 915 crashThread = None 916 for traceLine in minidumpOuput: 917 if crashThread != None: 918 if traceLine.startswith("%s|" % crashThread): 919 components = traceLine.split("|") 920 921 # If we have no symbols, don't use addresses for now (they are not portable for signature generation) 922 if not len(components[3]): 923 self.backtrace.append("??") 924 else: 925 self.backtrace.append(CrashInfo.sanitizeStackFrame(components[3])) 926 elif traceLine.startswith("Crash|"): 927 components = traceLine.split("|") 928 crashThread = int(components[3]) 929 self.crashAddress = long(components[2], 16)
930
931 932 -class AppleCrashInfo(CrashInfo):
933 - def __init__(self, stdout, stderr, configuration, crashData=None):
934 ''' 935 Private constructor, called by L{CrashInfo.fromRawCrashData}. Do not use directly. 936 ''' 937 CrashInfo.__init__(self) 938 939 if stdout != None: 940 self.rawStdout.extend(stdout) 941 942 if stderr != None: 943 self.rawStderr.extend(stderr) 944 945 if crashData != None: 946 self.rawCrashData.extend(crashData) 947 948 self.configuration = configuration 949 950 inCrashingThread = False 951 for line in crashData: 952 # Crash address 953 if line.startswith("Exception Codes:"): 954 # Example: 955 # Exception Type: EXC_BAD_ACCESS (SIGABRT) 956 # Exception Codes: KERN_INVALID_ADDRESS at 0x00000001374b893e 957 address = line.split(" ")[-1] 958 if address.startswith('0x'): 959 self.crashAddress = long(address, 16) 960 961 # Start of stack for crashing thread 962 if re.match(r'Thread \d+ Crashed:', line): 963 inCrashingThread = True 964 continue 965 966 if line.strip() == "": 967 inCrashingThread = False 968 continue 969 970 if inCrashingThread: 971 # Example: 972 # 0 js-dbg-64-dm-darwin-a523d4c7efe2 0x00000001004b04c4 js::jit::MacroAssembler::Pop(js::jit::Register) + 180 (MacroAssembler-inl.h:50) 973 components = line.split(None, 3) 974 stackEntry = components[3] 975 if stackEntry.startswith('0'): 976 self.backtrace.append("??") 977 else: 978 stackEntry = AppleCrashInfo.removeFilename(stackEntry) 979 stackEntry = AppleCrashInfo.removeOffset(stackEntry) 980 stackEntry = CrashInfo.sanitizeStackFrame(stackEntry) 981 self.backtrace.append(stackEntry)
982 983 @staticmethod
984 - def removeFilename(stackEntry):
985 match = re.match(r'(.*) \(\S+\)', stackEntry) 986 if match: 987 return match.group(1) 988 return stackEntry
989 990 @staticmethod
991 - def removeOffset(stackEntry):
992 match = re.match(r'(.*) \+ \d+', stackEntry) 993 if match: 994 return match.group(1) 995 return stackEntry
996