def _fasReadOutput(oSelf): # Read until the debugger outputs an input prompt, or terminated. asLines = []; sCurrentLine = ""; while 1: sChar = oSelf._oProcess.stdout.read(1); if sChar in ["\r", "\n", ""]: if sCurrentLine: if dxCrashInfoConfig.get("bOutputIO", False): print "cdb>%s" % repr(sCurrentLine)[1:-1]; oSelf._asIO.append(sCurrentLine); asLines.append(sCurrentLine); sCurrentLine = ""; if sChar == "": break; else: sCurrentLine += sChar; # Detect the prompt. oPromptMatch = re.match("^\d+:\d+(:x86)?> $", sCurrentLine); if oPromptMatch: if dxCrashInfoConfig.get("bOutputIO", False): print "cdb>%s" % repr(sCurrentLine)[1:-1]; oSelf._asIO.append(sCurrentLine); return asLines; # Cdb stdout was closed: the process is terminating. assert oSelf._bExpectTermination, \ "Cdb terminated unexpectedly! Last output:\r\n%s" % "\r\n".join(oSelf._asIO[-20:]); oSelf._oProcess.wait(); # wait for it to terminate completely. oSelf._fFinishedCallback(oSelf._oErrorReport); return None;
def _fasReadOutput(oSelf): # Read until the debugger outputs an input prompt, or terminated. asLines = [] sCurrentLine = "" while 1: sChar = oSelf._oProcess.stdout.read(1) if sChar in ["\r", "\n", ""]: if sCurrentLine: if dxCrashInfoConfig.get("bOutputIO", False): print "cdb>%s" % repr(sCurrentLine)[1:-1] oSelf._asIO.append(sCurrentLine) asLines.append(sCurrentLine) sCurrentLine = "" if sChar == "": break else: sCurrentLine += sChar # Detect the prompt. oPromptMatch = re.match("^\d+:\d+(:x86)?> $", sCurrentLine) if oPromptMatch: if dxCrashInfoConfig.get("bOutputIO", False): print "cdb>%s" % repr(sCurrentLine)[1:-1] oSelf._asIO.append(sCurrentLine) return asLines # Cdb stdout was closed: the process is terminating. assert oSelf._bExpectTermination, \ "Cdb terminated unexpectedly! Last output:\r\n%s" % "\r\n".join(oSelf._asIO[-20:]) oSelf._oProcess.wait() # wait for it to terminate completely. oSelf._fFinishedCallback(oSelf._oErrorReport) return None
def __init__(oSelf, asApplicationCommandLine, auApplicationProcessIds, sApplicationISA, asSymbolServerURLs, \ fApplicationStartedCallback, fErrorDetectedCallback, fFinishedCallback, fInternalExceptionCallback): oSelf._fApplicationStartedCallback = fApplicationStartedCallback; oSelf._fErrorDetectedCallback = fErrorDetectedCallback; oSelf._fFinishedCallback = fFinishedCallback; oSelf._fInternalExceptionCallback = fInternalExceptionCallback; oSelf._sApplicationISA = sApplicationISA; uSymbolOptions = sum([ 0x00000001, # SYMOPT_CASE_INSENSITIVE 0x00000002, # SYMOPT_UNDNAME 0x00000004, # SYMOPT_DEFERRED_LOAD # 0x00000020, # SYMOPT_OMAP_FIND_NEAREST # 0x00000040, # SYMOPT_LOAD_ANYTHING 0x00000100, # SYMOPT_NO_UNQUALIFIED_LOADS 0x00000200, # SYMOPT_FAIL_CRITICAL_ERRORS 0x00000400, # SYMOPT_EXACT_SYMBOLS 0x00000800, # SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00010000, # SYMOPT_AUTO_PUBLICS # 0x00020000, # SYMOPT_NO_IMAGE_SEARCH 0x00080000, # SYMOPT_NO_PROMPTS 0x80000000, # SYMOPT_DEBUG ]); asCommandLine = [sCdbBinaryPath, "-o", "-sflags", "0x%08X" % uSymbolOptions]; # -o => debug child processes, -sflags 0xXXXXXXXX => symbol options: set_asSymbolServerURLs = set(asSymbolServerURLs + [sMicrosoftSymbolServerURL]); sSymbolsPath = ";".join( ["cache*%s" % sSymbolCachePath for sSymbolCachePath in dxCrashInfoConfig.get("asSymbolCachePaths", [])] + ["srv*%s" % sSymbolServerURL for sSymbolServerURL in set_asSymbolServerURLs] ); asCommandLine.extend(["-y", sSymbolsPath]); if asApplicationCommandLine is not None: asCommandLine += asApplicationCommandLine; if auApplicationProcessIds is not None and len(auApplicationProcessIds) > 0: asCommandLine += ["-p", str(auApplicationProcessIds.pop(0))]; oSelf._auAttachProcessIds = auApplicationProcessIds; oSelf._bResumeThreads = True; else: oSelf._auAttachProcessIds = []; oSelf._bResumeThreads = False; asCommandLine = [ (sArg.find(" ") == -1 or sArg[0] == '"') and sArg or '"%s"' % sArg.replace('"', '\\"') for sArg in asCommandLine ]; if dxCrashInfoConfig.get("bOutputCommandLine", False): print ",-- Cdb command line ".ljust(120, "-"); print "| %s" % asCommandLine[0]; for sArgument in asCommandLine[1:]: print "| %s" % sArgument; print "`".ljust(120, "-"); oSelf._asIO = []; oSelf._oErrorReport = None; oSelf._bExpectTermination = False; oSelf._oProcess = subprocess.Popen(args = " ".join(asCommandLine), stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE); oSelf._oDebugApplicationThread = threading.Thread(target = oSelf._fDebugApplication); oSelf._oDebugApplicationThread.start();
def foCreateFromAddress(cSelf, oCrashInfo, oProcess, pAddress, uSize): oSelf = cSelf(oProcess); uStackFramesCount = min(dxCrashInfoConfig.get("uMaxStackFramesCount", 50), uSize); # Execute twice, as the first time may trigger symbol loading, which outputs messages that make parsing harder. for x in xrange(2): asStack = oCrashInfo._fasSendCommandAndReadOutput("dps 0x%X L0x%X" % (pAddress, uStackFramesCount)); if asStack is None: return None; # Here are some lines you might expect to parse: # |TODO put something here... uFrameNumber = 0; for sLine in asStack: oMatch = re.match(r"^\s*%s\s*$" % ( r"(?:[0-9A-F`]+|\(Inline\))" r"\s+" # {stack_address || "(Inline)"} whitespace r"(?:[0-9A-F`]+|\-{8})" r"\s+" # {ret_address || "--------"} whitespace "(?:" # either { r"(0x[0-9A-F`]+)" # ("0x" address) "|" # } or { r"(\w+)" # (cdb_module_id) "(?:" # either { "(\+0x[0-9A-F`]+)" # ("+0x" offset_in_module) "|" # } or { r"!(.+?)([\+\-]0x[0-9A-F]+)?" # "!" (function_name) optional{({"+" || "-"} "0x" offset)} ")" # } ")" # } ), sLine, re.I); assert oMatch, "Unknown stack output: %s" % sLine; (sAddress, sCdbModuleId, sModuleOffset, sSymbol, sSymbolOffset) = oMatch.groups(); uAddress = sAddress and int(sAddress.replace("`", ""), 16); uModuleOffset = sModuleOffset and int(sModuleOffset.replace("`", ""), 16); uSymbolOffset = sSymbolOffset and int(sSymbolOffset.replace("`", ""), 16); assert uFrameNumber < uStackFramesCount, \ "Got more frames than requested"; oSelf._fAddStackFrame(uFrameNumber, uAddress, sCdbModuleId, uModuleOffset, sSymbol, uSymbolOffset); uFrameNumber += 1; return oSelf;
def __init__(oSelf, uNumber, uAddress, oModule, uModuleOffset, oFunction, uFunctionOffset): oSelf.uNumber = uNumber; oSelf.uAddress = uAddress; oSelf.oModule = oModule; oSelf.uModuleOffset = uModuleOffset; oSelf.oFunction = oFunction; oSelf.uFunctionOffset = uFunctionOffset; if oSelf.oFunction: if oSelf.uFunctionOffset > 0: oSelf.sAddress = "%s + 0x%X" % (oSelf.oFunction.sName, oSelf.uFunctionOffset); elif oSelf.uFunctionOffset: oSelf.sAddress = "%s - 0x%X" % (oSelf.oFunction.sName, abs(oSelf.uFunctionOffset)); else: oSelf.sAddress = oSelf.oFunction.sName; oSelf.sSimplifiedAddress = oSelf.oFunction.sSimplifiedName; if uFunctionOffset in xrange(dxCrashInfoConfig.get("uMaxFunctionOffset", 0xFFF)): oSelf.sHashAddress = oSelf.oFunction.sName; else: # The offset is negative or too large: this is the closest symbol, but probably not the correct symbol. # This probably means there are not enough symbols to distinguish different functions. The only thing that # can be done to create a unique stack hash for this frame is add the offset. Unfortunately, this offset will # likely be different in a different build of the same application, so the stack hash will be different as well. oSelf.sHashAddress = oSelf.sAddress; oSelf.sAddress += " (this symbol may not be correct)"; elif oSelf.oModule: oSelf.sAddress = "%s + 0x%X" % (oSelf.oModule.sBinaryName, oSelf.uModuleOffset); oSelf.sSimplifiedAddress = "%s!(unknown)" % oSelf.oModule.sBinaryName; oSelf.sHashAddress = oSelf.oModule.sBinaryName; else: oSelf.sAddress = "0x%X" % oSelf.uAddress; oSelf.sSimplifiedAddress = "(unknown)"; oSelf.sHashAddress = "";
def _fasSendCommandAndReadOutput(oSelf, sCommand): if dxCrashInfoConfig.get("bOutputIO", False): print "cdb<%s" % repr(sCommand)[1:-1]; oSelf._asIO[-1] += sCommand; try: oSelf._oProcess.stdin.write("%s\r\n" % sCommand); except Exception, oException: assert oSelf._bExpectTermination, \ "Cdb terminated unexpectedly! Last output:\r\n%s" % "\r\n".join(oSelf._asIO[-20:]); oSelf._oProcess.wait(); # wait for it to terminate completely. oSelf._fFinishedCallback(oSelf._oErrorReport); return None;
def foCreate(cSelf, oCrashInfo, oProcess): oSelf = cSelf(oProcess); uStackFramesCount = dxCrashInfoConfig.get("uMaxStackFramesCount", 50); # Execute twice, as the first time may trigger symbol loading, which outputs messages that make parsing harder. for x in xrange(2): asStack = oCrashInfo._fasSendCommandAndReadOutput("kn 0x%X" % uStackFramesCount); if asStack is None: return None; sHeader = asStack.pop(0); assert re.sub(r"\s+", " ", sHeader.strip()) in ["# ChildEBP RetAddr", "# Child-SP RetAddr Call Site"], \ "Unknown stack header: %s" % repr(sHeader); # Here are some lines you might expect to parse: # |00 (Inline) -------- chrome_child!WTF::RawPtr<blink::Document>::operator*+0x11 # |03 0082ec08 603e2568 chrome_child!blink::XMLDocumentParser::startElementNs+0x105 # |33 0082fb50 0030d4ba chrome!wWinMain+0xaa # |23 0a8cc578 66629c9b 0x66cf592a # |13 0a8c9cc8 63ea124e IEFRAME!Ordinal231+0xb3c83 # |36 0a19c854 77548e71 MSHTML+0x8d45e # |1b 0000008c`53b2c650 00007ffa`4631cfba ntdll!KiUserCallbackDispatcherContinue # |22 00000040`0597b770 00007ffa`36ddc0e3 0x40`90140fc3 # |WARNING: Frame IP not in any known module. Following frames may be wrong. # |WARNING: Stack unwind information not available. Following frames may be wrong. # |Could not allocate memory for stack trace uFrameNumber = 0; for sLine in asStack: if re.match(r"^%s$" % "|".join([ "WARNING: Frame IP not in any known module\. Following frames may be wrong\.", "WARNING: Stack unwind information not available\. Following frames may be wrong\.", "Could not allocate memory for stack trace", ]), sLine): continue; oMatch = re.match(r"^\s*%s\s*$" % ( r"([0-9A-F]+)" r"\s+" # frame_number whitespace r"(?:[0-9A-F`]+|\(Inline\))" r"\s+" # {stack_address || "(Inline)"} whitespace r"(?:[0-9A-F`]+|\-{8})" r"\s+" # {ret_address || "--------"} whitespace "(?:" # either { r"(0x[0-9A-F`]+)" # ("0x" address) "|" # } or { r"(\w+)" # (cdb_module_id) "(?:" # either { "(\+0x[0-9A-F]+)" # ("+0x" offset_in_module) "|" # } or { r"!(.+?)([\+\-]0x[0-9A-F]+)?" # "!" (function_name) optional{(["+" || "-"] "0x" offset)} ")" # } ")" # } ), sLine, re.I); assert oMatch, "Unknown stack output: %s" % repr(sLine); (sFrameNumber, sAddress, sCdbModuleId, sModuleOffset, sSymbol, sSymbolOffset) = oMatch.groups(); assert uFrameNumber == int(sFrameNumber, 16), "Unexpected frame number: %s vs %d" % (sFrameNumber, uFrameNumber); uAddress = sAddress and int(sAddress.replace("`", ""), 16); uModuleOffset = sModuleOffset is not None and int(sModuleOffset.replace("`", ""), 16); uSymbolOffset = sSymbolOffset is not None and int(sSymbolOffset.replace("`", ""), 16); oSelf._fAddStackFrame(uFrameNumber, uAddress, sCdbModuleId, uModuleOffset, sSymbol, uSymbolOffset); uFrameNumber += 1; return oSelf;
def _fAddStackFrame(oSelf, uNumber, uAddress, sCdbModuleId, uModuleOffset, sSymbol, uSymbolOffset): # frames must be created in order: assert uNumber == len(oSelf.aoFrames), \ "Unexpected frame number %d vs %d" % (uNumber, len(oSelf.aoFrames)); uMaxStackFramesCount = dxCrashInfoConfig.get("uMaxStackFramesCount", 50); assert uNumber < uMaxStackFramesCount, \ "Unexpected frame number %d (max %d)" % (uNumber, uMaxStackFramesCount); if uNumber == uMaxStackFramesCount - 1: oSelf.bPartialStack = True; # We leave the last one out so we can truely say there are more. else: oModule = sCdbModuleId and oSelf.oProcess.foGetModule(sCdbModuleId); oFunction = oModule and sSymbol and oModule.foGetOrCreateFunction(sSymbol); oSelf.aoFrames.append(cStackFrame(uNumber, uAddress, oModule, uModuleOffset, oFunction, uSymbolOffset));
def _fasSendCommandAndReadOutput(oSelf, sCommand): if dxCrashInfoConfig.get("bOutputIO", False): print "cdb<%s" % repr(sCommand)[1:-1] oSelf._asIO[-1] += sCommand try: oSelf._oProcess.stdin.write("%s\r\n" % sCommand) except Exception, oException: assert oSelf._bExpectTermination, \ "Cdb terminated unexpectedly! Last output:\r\n%s" % "\r\n".join(oSelf._asIO[-20:]) oSelf._oProcess.wait() # wait for it to terminate completely. oSelf._fFinishedCallback(oSelf._oErrorReport) return None
def fsGetAddressId(uAddress): for (uBaseAddress, sId) in dsId_uAddress.items(): iOffset = uAddress - uBaseAddress; if iOffset == 0: return sId; uMaxAddressOffset = dxCrashInfoConfig.get("uMaxAddressOffset", 0xFFF); if iOffset > uMaxAddressOffset: # Maybe this is wrapping: iOffset -= 0x100000000; elif iOffset < -uMaxAddressOffset: # Maybe this is wrapping: iOffset += 0x100000000; uOffset = abs(iOffset); if uOffset < uMaxAddressOffset: return "%s%s0x%X" % (sId, iOffset < 0 and "-" or "+", uOffset); return "Arbitrary";
def ftsGetAddressIdAndDescription(uAddress): for (uBaseAddress, (sId, sDescription)) in dsId_uAddress.items(): iOffset = uAddress - uBaseAddress; if iOffset == 0: return sId; uMaxAddressOffset = dxCrashInfoConfig.get("uMaxAddressOffset", 0xFFF); if iOffset > uMaxAddressOffset: # Maybe this is wrapping: iOffset -= 0x100000000; elif iOffset < -uMaxAddressOffset: # Maybe this is wrapping: iOffset += 0x100000000; uOffset = abs(iOffset); if uOffset < uMaxAddressOffset: return "%s%s0x%X" % (sId, iOffset < 0 and "-" or "+", uOffset), sDescription; return "Arbitrary", "an invalid pointer was used";
def foCreateFromAddress(cSelf, oCrashInfo, oProcess, pAddress, uSize): oSelf = cSelf(oProcess) uStackFramesCount = min( dxCrashInfoConfig.get("uMaxStackFramesCount", 50), uSize) # Execute twice, as the first time may trigger symbol loading, which outputs messages that make parsing harder. for x in xrange(2): asStack = oCrashInfo._fasSendCommandAndReadOutput( "dps 0x%X L0x%X" % (pAddress, uStackFramesCount)) if asStack is None: return None # Here are some lines you might expect to parse: # |TODO put something here... uFrameNumber = 0 for sLine in asStack: oMatch = re.match( r"^\s*%s\s*$" % ( r"(?:[0-9A-F`]+|\(Inline\))" r"\s+" # {stack_address || "(Inline)"} whitespace r"(?:[0-9A-F`]+|\-{8})" r"\s+" # {ret_address || "--------"} whitespace "(?:" # either { r"(0x[0-9A-F`]+)" # ("0x" address) "|" # } or { r"(\w+)" # (cdb_module_id) "(?:" # either { "(\+0x[0-9A-F`]+)" # ("+0x" offset_in_module) "|" # } or { r"!(.+?)([\+\-]0x[0-9A-F]+)?" # "!" (function_name) optional{({"+" || "-"} "0x" offset)} ")" # } ")" # } ), sLine, re.I) assert oMatch, "Unknown stack output: %s" % sLine (sAddress, sCdbModuleId, sModuleOffset, sSymbol, sSymbolOffset) = oMatch.groups() uAddress = sAddress and int(sAddress.replace("`", ""), 16) uModuleOffset = sModuleOffset and int( sModuleOffset.replace("`", ""), 16) uSymbolOffset = sSymbolOffset and int( sSymbolOffset.replace("`", ""), 16) assert uFrameNumber < uStackFramesCount, \ "Got more frames than requested" oSelf._fAddStackFrame(uFrameNumber, uAddress, sCdbModuleId, uModuleOffset, sSymbol, uSymbolOffset) uFrameNumber += 1 return oSelf
def _fAddStackFrame(oSelf, uNumber, uAddress, sCdbModuleId, uModuleOffset, sSymbol, uSymbolOffset): # frames must be created in order: assert uNumber == len(oSelf.aoFrames), \ "Unexpected frame number %d vs %d" % (uNumber, len(oSelf.aoFrames)) uMaxStackFramesCount = dxCrashInfoConfig.get("uMaxStackFramesCount", 50) assert uNumber < uMaxStackFramesCount, \ "Unexpected frame number %d (max %d)" % (uNumber, uMaxStackFramesCount) if uNumber == uMaxStackFramesCount - 1: oSelf.bPartialStack = True # We leave the last one out so we can truely say there are more. else: oModule = sCdbModuleId and oSelf.oProcess.foGetModule(sCdbModuleId) oFunction = oModule and sSymbol and oModule.foGetOrCreateFunction( sSymbol) oSelf.aoFrames.append( cStackFrame(uNumber, uAddress, oModule, uModuleOffset, oFunction, uSymbolOffset))
def __init__(oSelf, uNumber, uAddress, oModule, uModuleOffset, oFunction, uFunctionOffset): oSelf.uNumber = uNumber oSelf.uAddress = uAddress oSelf.oModule = oModule oSelf.uModuleOffset = uModuleOffset oSelf.oFunction = oFunction oSelf.uFunctionOffset = uFunctionOffset if oSelf.oFunction: if oSelf.uFunctionOffset > 0: oSelf.sAddress = "%s + 0x%X" % (oSelf.oFunction.sName, oSelf.uFunctionOffset) elif oSelf.uFunctionOffset: oSelf.sAddress = "%s - 0x%X" % (oSelf.oFunction.sName, abs(oSelf.uFunctionOffset)) else: oSelf.sAddress = oSelf.oFunction.sName oSelf.sSimplifiedAddress = oSelf.oFunction.sSimplifiedName if uFunctionOffset in xrange( dxCrashInfoConfig.get("uMaxFunctionOffset", 0xFFF)): oSelf.sHashAddress = oSelf.oFunction.sName else: # The offset is negative or too large: this is the closest symbol, but probably not the correct symbol. # This probably means there are not enough symbols to distinguish different functions. The only thing that # can be done to create a unique stack hash for this frame is add the offset. Unfortunately, this offset will # likely be different in a different build of the same application, so the stack hash will be different as well. oSelf.sHashAddress = oSelf.sAddress oSelf.sAddress += " (this symbol may not be correct)" elif oSelf.oModule: oSelf.sAddress = "%s + 0x%X" % (oSelf.oModule.sBinaryName, oSelf.uModuleOffset) oSelf.sSimplifiedAddress = "%s!(unknown)" % oSelf.oModule.sBinaryName oSelf.sHashAddress = oSelf.oModule.sBinaryName else: oSelf.sAddress = "0x%X" % oSelf.uAddress oSelf.sSimplifiedAddress = "(unknown)" oSelf.sHashAddress = ""
def _fDebugApplication(oSelf): try: # Read the initial cdb output if oSelf._fasReadOutput() is None: return None; # Make a list of all the cdb commands that need to be execute to initialize and start the application. asInitialCommands = []; # if requested, resume all threads in current process. asInitialCommands.append(".childdbg 1"); if oSelf._bResumeThreads: asInitialCommands.append("~*m"); # if requested, attach to additional processes and optionally resume all threads in those as well. for uAttachProcessId in oSelf._auAttachProcessIds: asInitialCommands.append(".attach 0n%d;g" % uAttachProcessId); asInitialCommands.append(".childdbg 1"); if oSelf._bResumeThreads: asInitialCommands.append("~*m"); # request second chance debugger break for certain exceptions that indicate the application has a bug. for sException in asDetectedExceptions: if dxCrashInfoConfig.get("bOutputFirstChanceExceptions", False): asInitialCommands.append("sxe %s" % sException); else: asInitialCommands.append("sxd %s" % sException); # ignore certain other exceptions for sException in asIgnoredExceptions: asInitialCommands.append("sxi %s" % sException); # if epr is disabled, the debugger will silently exit when the application terminates. # To distinguish this from other unexpected terminations of the debugger, epr is enabled and the "g" command is # executed whenever a process terminates. This will continue execution of the application until the last process # is terminated, at which point cdb outputs an error message. This error message is detected to determine that # the application has terminated without crashing. asInitialCommands.append("sxe -c \"g\" epr"); # Execute all commands in the list and stop if cdb terminates in the mean time. for sCommand in asInitialCommands: if oSelf._fasSendCommandAndReadOutput(sCommand) is None: return; oSelf._fApplicationStartedCallback(); # The application is now started, read its output until an exception is detected: while 1: asExceptionDetectedOutput = oSelf._fasSendCommandAndReadOutput("g"); if asExceptionDetectedOutput is None: return; # Scan backwards through the output to detect the exception that occured: for uIndex in xrange(len(asExceptionDetectedOutput) - 1, -1, -1): sLine = asExceptionDetectedOutput[uIndex]; # Event output looks like this: # |(16c0.11c): Access violation - code c0000005 (!!! second chance !!!) # |(273c.1f1c): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!) oEventMatch = re.match(r"^\s*%s\s*$" % ( r"\([0-9A-F]+\.[0-9A-F]+\): " # "(" process id "." thread id "): " r"(.*?)" # (exception description)" r" \- code " # " - code " r"([0-9A-F`]+)" # (exception code) r" \(!*\s*(first|second) chance\s*!*\)" # " (first chance)" or " (!!! second chance !!!)" ), sLine, re.I); if oEventMatch: sDescription, sCode, sChance = oEventMatch.groups(); uCode = int(sCode.replace("`", ""), 16); break; oTerminatedMatch = re.match(r"^\s*\^ No runnable debuggees error in '.*'\s*$", sLine); if oTerminatedMatch: oSelf._bExpectTermination = True; asDebuggerOutput = oSelf._fasSendCommandAndReadOutput("q"); assert asDebuggerOutput is None, "Debugger did not terminate"; return; else: raise AssertionError( "Could not find what caused the debugger to break into the application!\r\n%s" % "\r\n".join(asExceptionDetectedOutput) ); if sChance == "second": break; # we've found something interesting # get a stack for this first chance exception (in case it turns out to be interesting later) if oSelf._fasSendCommandAndReadOutput("kn 0x%X" % dxCrashInfoConfig.get("uMaxStackFramesCount", 50)) is None: return; # continue the application. # Report that an exception has been detected. oSelf._fErrorDetectedCallback(); # Gather exception information: oException = cException.foCreate(oSelf, uCode, sDescription); if oException is None: return None; # Sve the exception report for returning when we're finished. oSelf._oErrorReport = cErrorReport.foCreateFromException(oException, oSelf._asIO); # terminate the debugger. oSelf._bExpectTermination = True; asDebuggerOutput = oSelf._fasSendCommandAndReadOutput("q"); assert asDebuggerOutput is None, "Debugger did not terminate"; except Exception, oException: oSelf._fInternalExceptionCallback(oException); raise;
def foCreateFromException(cSelf, oException, asCdbIO): # Get initial exception type id and description oTopFrame = len(oException.oStack.aoFrames) > 0 and oException.oStack.aoFrames[0] or None; # Get application id. sApplicationId = oException.oProcess.sBinaryName; # See if its in a "special" exception and rewrite the exception type id accordingly. if oTopFrame and oTopFrame.oFunction: sTypeId = fsGetSpecialExceptionTypeId(oException.sTypeId, oTopFrame) \ or oException.sTypeId; # in case there is no special type id. else: sTypeId = oException.sTypeId; # Find out which frame should be the "main" frame and get stack id. # * Stack exhaustion can be caused by recursive function calls, where one or more functions repeatedly call # themselves. If possible, this is detected, and the alphabetically first functions is chosen as the main function # The stack hash is created using only the looping functions. # ^^^^ THIS IS NOT YET IMPLEMENTED ^^^ # * Plenty of exceptions get thrown by special functions, eg. kernel32!DebugBreak, which are not relevant to the # exception. These are ignored and the calling function is used as the "main" frame). oMainFrame = None; uIgnoredFramesHashed = 0; uFramesHashed = 0; asStack = []; sStackId = ""; oIgnoredFramesHasher = hashlib.md5(); oSkippedFramesHasher = hashlib.md5(); for oFrame in oException.oStack.aoFrames: if (uFramesHashed - uIgnoredFramesHashed == dxCrashInfoConfig.get("uStackHashFramesCount", 3)): asStack.append(" %s" % oFrame.sAddress); continue; if oMainFrame is None: if fbIsIrrelevantTopFrame(sTypeId, oException.uCode, oFrame): uIgnoredFramesHashed += 1; uFramesHashed += 1; asStack.append(" ~ %s" % oFrame.sAddress); oIgnoredFramesHasher.update(oFrame.sHashAddress); continue; # This frame is irrelevant in the context of this exception type. if uIgnoredFramesHashed > 0: sStackId += "%02X~" % ord(oIgnoredFramesHasher.digest()[0]); oMainFrame = oFrame; if oFrame.oFunction: oHasher = hashlib.md5(); oHasher.update(oFrame.sHashAddress); sStackId += "%02X" % ord(oHasher.digest()[0]); asStack.append(" * %s" % oFrame.sAddress); elif oFrame.oModule: oHasher = hashlib.md5(); oHasher.update(oFrame.sHashAddress); sStackId += "(%02X)" % ord(oHasher.digest()[0]); asStack.append(" ? %s" % oFrame.sAddress); else: sStackId += "--"; asStack.append(" - %s" % oFrame.sAddress); uFramesHashed += 1; if uFramesHashed == 0: sStackId = "#"; if oException.oStack.bPartialStack: asStack.append(" ..."); # Get the main stack frame's simplified address as the id. sFunctionId = oMainFrame and oMainFrame.sSimplifiedAddress or "(no stack)"; # Combine the various ids into a unique exception id sId = " ".join([sApplicationId, sTypeId, sStackId, sFunctionId]); # Get the description sLocationDescription = oTopFrame and oTopFrame.sAddress or "(no stack)"; sDescription = "%s in %s" % (oException.sDescription, sLocationDescription); sSecurityImpact = oException.sSecurityImpact; # Create HTML details sHTMLDetails = """ <!doctype html> <html> <head> <style> div { color: white; background: black; padding: 5px; margin-bottom: 1em; } code { margin-bottom: 1em; } </style> <title>%s</title> </head> <body> <div>%s</div> <code>%s</code> <div>Debugger input/output</div> <code>%s</code> </body> </html>""".strip() % ( fsHTMLEncode(sId), fsHTMLEncode(sDescription), "".join(["%s<br/>" % fsHTMLEncode(x) for x in asStack]), "".join(["%s<br/>" % fsHTMLEncode(x) for x in asCdbIO]) ); return cSelf(sId, sDescription, sSecurityImpact, sHTMLDetails);
def __init__(oSelf, asApplicationCommandLine, auApplicationProcessIds, sApplicationISA, asSymbolServerURLs, \ fApplicationStartedCallback, fErrorDetectedCallback, fFinishedCallback, fInternalExceptionCallback): oSelf._fApplicationStartedCallback = fApplicationStartedCallback oSelf._fErrorDetectedCallback = fErrorDetectedCallback oSelf._fFinishedCallback = fFinishedCallback oSelf._fInternalExceptionCallback = fInternalExceptionCallback oSelf._sApplicationISA = sApplicationISA uSymbolOptions = sum([ 0x00000001, # SYMOPT_CASE_INSENSITIVE 0x00000002, # SYMOPT_UNDNAME 0x00000004, # SYMOPT_DEFERRED_LOAD # 0x00000020, # SYMOPT_OMAP_FIND_NEAREST # 0x00000040, # SYMOPT_LOAD_ANYTHING 0x00000100, # SYMOPT_NO_UNQUALIFIED_LOADS 0x00000200, # SYMOPT_FAIL_CRITICAL_ERRORS 0x00000400, # SYMOPT_EXACT_SYMBOLS 0x00000800, # SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00010000, # SYMOPT_AUTO_PUBLICS # 0x00020000, # SYMOPT_NO_IMAGE_SEARCH 0x00080000, # SYMOPT_NO_PROMPTS 0x80000000, # SYMOPT_DEBUG ]) asCommandLine = [ sCdbBinaryPath, "-o", "-sflags", "0x%08X" % uSymbolOptions ] # -o => debug child processes, -sflags 0xXXXXXXXX => symbol options: set_asSymbolServerURLs = set(asSymbolServerURLs + [sMicrosoftSymbolServerURL]) sSymbolsPath = ";".join([ "cache*%s" % sSymbolCachePath for sSymbolCachePath in dxCrashInfoConfig.get( "asSymbolCachePaths", []) ] + [ "srv*%s" % sSymbolServerURL for sSymbolServerURL in set_asSymbolServerURLs ]) asCommandLine.extend(["-y", sSymbolsPath]) if asApplicationCommandLine is not None: asCommandLine += asApplicationCommandLine if auApplicationProcessIds is not None and len( auApplicationProcessIds) > 0: asCommandLine += ["-p", str(auApplicationProcessIds.pop(0))] oSelf._auAttachProcessIds = auApplicationProcessIds oSelf._bResumeThreads = True else: oSelf._auAttachProcessIds = [] oSelf._bResumeThreads = False asCommandLine = [(sArg.find(" ") == -1 or sArg[0] == '"') and sArg or '"%s"' % sArg.replace('"', '\\"') for sArg in asCommandLine] if dxCrashInfoConfig.get("bOutputCommandLine", False): print ",-- Cdb command line ".ljust(120, "-") print "| %s" % asCommandLine[0] for sArgument in asCommandLine[1:]: print "| %s" % sArgument print "`".ljust(120, "-") oSelf._asIO = [] oSelf._oErrorReport = None oSelf._bExpectTermination = False oSelf._oProcess = subprocess.Popen(args=" ".join(asCommandLine), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) oSelf._oDebugApplicationThread = threading.Thread( target=oSelf._fDebugApplication) oSelf._oDebugApplicationThread.start()
def _fDebugApplication(oSelf): try: # Read the initial cdb output if oSelf._fasReadOutput() is None: return None # Make a list of all the cdb commands that need to be execute to initialize and start the application. asInitialCommands = [] # if requested, resume all threads in current process. asInitialCommands.append(".childdbg 1") if oSelf._bResumeThreads: asInitialCommands.append("~*m") # if requested, attach to additional processes and optionally resume all threads in those as well. for uAttachProcessId in oSelf._auAttachProcessIds: asInitialCommands.append(".attach 0n%d;g" % uAttachProcessId) asInitialCommands.append(".childdbg 1") if oSelf._bResumeThreads: asInitialCommands.append("~*m") # request second chance debugger break for certain exceptions that indicate the application has a bug. for sException in asDetectedExceptions: if dxCrashInfoConfig.get("bOutputFirstChanceExceptions", False): asInitialCommands.append("sxe %s" % sException) else: asInitialCommands.append("sxd %s" % sException) # ignore certain other exceptions for sException in asIgnoredExceptions: asInitialCommands.append("sxi %s" % sException) # if epr is disabled, the debugger will silently exit when the application terminates. # To distinguish this from other unexpected terminations of the debugger, epr is enabled and the "g" command is # executed whenever a process terminates. This will continue execution of the application until the last process # is terminated, at which point cdb outputs an error message. This error message is detected to determine that # the application has terminated without crashing. asInitialCommands.append("sxe -c \"g\" epr") # Execute all commands in the list and stop if cdb terminates in the mean time. for sCommand in asInitialCommands: if oSelf._fasSendCommandAndReadOutput(sCommand) is None: return oSelf._fApplicationStartedCallback() # The application is now started, read its output until an exception is detected: while 1: asExceptionDetectedOutput = oSelf._fasSendCommandAndReadOutput( "g") if asExceptionDetectedOutput is None: return # Scan backwards through the output to detect the exception that occured: for uIndex in xrange( len(asExceptionDetectedOutput) - 1, -1, -1): sLine = asExceptionDetectedOutput[uIndex] # Event output looks like this: # |(16c0.11c): Access violation - code c0000005 (!!! second chance !!!) # |(273c.1f1c): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!) oEventMatch = re.match( r"^\s*%s\s*$" % ( r"\([0-9A-F]+\.[0-9A-F]+\): " # "(" process id "." thread id "): " r"(.*?)" # (exception description)" r" \- code " # " - code " r"([0-9A-F`]+)" # (exception code) r" \(!*\s*(first|second) chance\s*!*\)" # " (first chance)" or " (!!! second chance !!!)" ), sLine, re.I) if oEventMatch: sDescription, sCode, sChance = oEventMatch.groups() uCode = int(sCode.replace("`", ""), 16) break oTerminatedMatch = re.match( r"^\s*\^ No runnable debuggees error in '.*'\s*$", sLine) if oTerminatedMatch: oSelf._bExpectTermination = True asDebuggerOutput = oSelf._fasSendCommandAndReadOutput( "q") assert asDebuggerOutput is None, "Debugger did not terminate" return else: raise AssertionError( "Could not find what caused the debugger to break into the application!\r\n%s" % "\r\n".join(asExceptionDetectedOutput)) if sChance == "second": break # we've found something interesting # get a stack for this first chance exception (in case it turns out to be interesting later) if oSelf._fasSendCommandAndReadOutput( "kn 0x%X" % dxCrashInfoConfig.get( "uMaxStackFramesCount", 50)) is None: return # continue the application. # Report that an exception has been detected. oSelf._fErrorDetectedCallback() # Gather exception information: oException = cException.foCreate(oSelf, uCode, sDescription) if oException is None: return None # Sve the exception report for returning when we're finished. oSelf._oErrorReport = cErrorReport.foCreateFromException( oException, oSelf._asIO) # terminate the debugger. oSelf._bExpectTermination = True asDebuggerOutput = oSelf._fasSendCommandAndReadOutput("q") assert asDebuggerOutput is None, "Debugger did not terminate" except Exception, oException: oSelf._fInternalExceptionCallback(oException) raise
def foCreate(cSelf, oCrashInfo, oProcess): oSelf = cSelf(oProcess) uStackFramesCount = dxCrashInfoConfig.get("uMaxStackFramesCount", 50) # Execute twice, as the first time may trigger symbol loading, which outputs messages that make parsing harder. for x in xrange(2): asStack = oCrashInfo._fasSendCommandAndReadOutput( "kn 0x%X" % uStackFramesCount) if asStack is None: return None sHeader = asStack.pop(0) assert re.sub(r"\s+", " ", sHeader.strip()) in ["# ChildEBP RetAddr", "# Child-SP RetAddr Call Site"], \ "Unknown stack header: %s" % repr(sHeader) # Here are some lines you might expect to parse: # |00 (Inline) -------- chrome_child!WTF::RawPtr<blink::Document>::operator*+0x11 # |03 0082ec08 603e2568 chrome_child!blink::XMLDocumentParser::startElementNs+0x105 # |33 0082fb50 0030d4ba chrome!wWinMain+0xaa # |23 0a8cc578 66629c9b 0x66cf592a # |13 0a8c9cc8 63ea124e IEFRAME!Ordinal231+0xb3c83 # |36 0a19c854 77548e71 MSHTML+0x8d45e # |1b 0000008c`53b2c650 00007ffa`4631cfba ntdll!KiUserCallbackDispatcherContinue # |22 00000040`0597b770 00007ffa`36ddc0e3 0x40`90140fc3 # |WARNING: Frame IP not in any known module. Following frames may be wrong. # |WARNING: Stack unwind information not available. Following frames may be wrong. # |Could not allocate memory for stack trace uFrameNumber = 0 for sLine in asStack: if re.match( r"^%s$" % "|".join([ "WARNING: Frame IP not in any known module\. Following frames may be wrong\.", "WARNING: Stack unwind information not available\. Following frames may be wrong\.", "Could not allocate memory for stack trace", ]), sLine): continue oMatch = re.match( r"^\s*%s\s*$" % ( r"([0-9A-F]+)" r"\s+" # frame_number whitespace r"(?:[0-9A-F`]+|\(Inline\))" r"\s+" # {stack_address || "(Inline)"} whitespace r"(?:[0-9A-F`]+|\-{8})" r"\s+" # {ret_address || "--------"} whitespace "(?:" # either { r"(0x[0-9A-F`]+)" # ("0x" address) "|" # } or { r"(\w+)" # (cdb_module_id) "(?:" # either { "(\+0x[0-9A-F]+)" # ("+0x" offset_in_module) "|" # } or { r"!(.+?)([\+\-]0x[0-9A-F]+)?" # "!" (function_name) optional{(["+" || "-"] "0x" offset)} ")" # } ")" # } ), sLine, re.I) assert oMatch, "Unknown stack output: %s" % repr(sLine) (sFrameNumber, sAddress, sCdbModuleId, sModuleOffset, sSymbol, sSymbolOffset) = oMatch.groups() assert uFrameNumber == int( sFrameNumber, 16), "Unexpected frame number: %s vs %d" % (sFrameNumber, uFrameNumber) uAddress = sAddress and int(sAddress.replace("`", ""), 16) uModuleOffset = sModuleOffset is not None and int( sModuleOffset.replace("`", ""), 16) uSymbolOffset = sSymbolOffset is not None and int( sSymbolOffset.replace("`", ""), 16) oSelf._fAddStackFrame(uFrameNumber, uAddress, sCdbModuleId, uModuleOffset, sSymbol, uSymbolOffset) uFrameNumber += 1 return oSelf
def foCreateFromException(cSelf, oException, asCdbIO): # Get initial exception type id and description oTopFrame = len(oException.oStack.aoFrames ) > 0 and oException.oStack.aoFrames[0] or None # Get application id. sApplicationId = oException.oProcess.sBinaryName # See if its in a "special" exception and rewrite the exception type id accordingly. if oTopFrame and oTopFrame.oFunction: sTypeId = fsGetSpecialExceptionTypeId(oException.sTypeId, oTopFrame) \ or oException.sTypeId # in case there is no special type id. else: sTypeId = oException.sTypeId # Find out which frame should be the "main" frame and get stack id. # * Stack exhaustion can be caused by recursive function calls, where one or more functions repeatedly call # themselves. If possible, this is detected, and the alphabetically first functions is chosen as the main function # The stack hash is created using only the looping functions. # ^^^^ THIS IS NOT YET IMPLEMENTED ^^^ # * Plenty of exceptions get thrown by special functions, eg. kernel32!DebugBreak, which are not relevant to the # exception. These are ignored and the calling function is used as the "main" frame). oMainFrame = None uIgnoredFramesHashed = 0 uFramesHashed = 0 asStack = [] sStackId = "" oIgnoredFramesHasher = hashlib.md5() oSkippedFramesHasher = hashlib.md5() for oFrame in oException.oStack.aoFrames: if (uFramesHashed - uIgnoredFramesHashed == dxCrashInfoConfig.get( "uStackHashFramesCount", 3)): asStack.append(" %s" % oFrame.sAddress) continue if oMainFrame is None: if fbIsIrrelevantTopFrame(sTypeId, oException.uCode, oFrame): uIgnoredFramesHashed += 1 uFramesHashed += 1 asStack.append(" ~ %s" % oFrame.sAddress) oIgnoredFramesHasher.update(oFrame.sHashAddress) continue # This frame is irrelevant in the context of this exception type. if uIgnoredFramesHashed > 0: sStackId += "%02X~" % ord(oIgnoredFramesHasher.digest()[0]) oMainFrame = oFrame if oFrame.oFunction: oHasher = hashlib.md5() oHasher.update(oFrame.sHashAddress) sStackId += "%02X" % ord(oHasher.digest()[0]) asStack.append(" * %s" % oFrame.sAddress) elif oFrame.oModule: oHasher = hashlib.md5() oHasher.update(oFrame.sHashAddress) sStackId += "(%02X)" % ord(oHasher.digest()[0]) asStack.append(" ? %s" % oFrame.sAddress) else: sStackId += "--" asStack.append(" - %s" % oFrame.sAddress) uFramesHashed += 1 if uFramesHashed == 0: sStackId = "#" if oException.oStack.bPartialStack: asStack.append(" ...") # Get the main stack frame's simplified address as the id. sFunctionId = oMainFrame and oMainFrame.sSimplifiedAddress or "(no stack)" # Combine the various ids into a unique exception id sId = " ".join([sApplicationId, sTypeId, sStackId, sFunctionId]) # Get the description sLocationDescription = oTopFrame and oTopFrame.sAddress or "(no stack)" sDescription = "%s in %s" % (oException.sDescription, sLocationDescription) sSecurityImpact = oException.sSecurityImpact # Create HTML details sHTMLDetails = """ <!doctype html> <html> <head> <style> div { color: white; background: black; padding: 5px; margin-bottom: 1em; } code { margin-bottom: 1em; } </style> <title>%s</title> </head> <body> <div>%s</div> <code>%s</code> <div>Debugger input/output</div> <code>%s</code> </body> </html>""".strip() % (fsHTMLEncode(sId), fsHTMLEncode(sDescription), "".join([ "%s<br/>" % fsHTMLEncode(x) for x in asStack ]), "".join(["%s<br/>" % fsHTMLEncode(x) for x in asCdbIO])) return cSelf(sId, sDescription, sSecurityImpact, sHTMLDetails)