def cCrashInfo_foCdbRunApplicationAndGetErrorReport(oCrashInfo, asIntialOutput): # cdb can either start an application, or attach to paused processes; which is it? bCdbStartedAnApplication = len(oCrashInfo._auProcessIdsPendingAttach) == 0; bDebuggerIsAttachingToProcesses = not bCdbStartedAnApplication; # If cdb was asked to attach to a process, make sure it worked. if bDebuggerIsAttachingToProcesses: fDetectFailedAttach(asIntialOutput); # If the debugger started a process, we should set up exception handling now. Otherwise wait until the debugger has # attached to all the processes. bExceptionHandlersHaveBeenSet = False; # While no error has been reported: oErrorReport = None; while oErrorReport is None: # Find out what event caused the debugger break asLastEventOutput = oCrashInfo._fasSendCommandAndReadOutput(".lastevent"); if not oCrashInfo._bCdbRunning: return None; # Sample output: # |Last event: 3d8.1348: Create process 3:3d8 # | debugger time: Tue Aug 25 00:06:07.311 2015 (UTC + 2:00) # - or - # |Last event: c74.10e8: Exit process 4:c74, code 0 # | debugger time: Tue Aug 25 00:06:07.311 2015 (UTC + 2:00) bValidLastEventOutput = len(asLastEventOutput) == 2 and re.match(r"^\s*debugger time: .*$", asLastEventOutput[1]); oEventMatch = bValidLastEventOutput and re.match( "".join([ r"^Last event: ([0-9a-f]+)\.[0-9a-f]+: ", r"(?:", r"(Create|Exit) process [0-9a-f]+\:([0-9a-f]+)(?:, code [0-9a-f]+)?", r"|", r"(.*?) \- code ([0-9a-f]+) \(!*\s*(?:first|second) chance\s*!*\)", r")\s*$", ]), asLastEventOutput[0], re.I ); assert oEventMatch, "Invalid .lastevent output:\r\n%s" % "\r\n".join(asLastEventOutput); ( sProcessIdHex, sCreateExitProcess, sCreateExitProcessIdHex, sExceptionDescription, sExceptionCode ) = oEventMatch.groups(); uProcessId = long(sProcessIdHex, 16); if sCreateExitProcess: # Make sure the created/exited process is the current process. assert sProcessIdHex == sCreateExitProcessIdHex, "%s vs %s" % (sProcessIdHex, sCreateExitProcessIdHex); oCrashInfo.fHandleCreateExitProcess(sCreateExitProcess, uProcessId); if len(oCrashInfo._auProcessIds) == 0: break; # The last process was terminated. else: uExceptionCode = int(sExceptionCode, 16); if uExceptionCode == STATUS_BREAKPOINT and uProcessId not in oCrashInfo._auProcessIds: # This is assumed to be the initial breakpoint after starting/attaching to the first process or after a new # process was created by the application. This assumption may not be correct, in which case the code needs to # be modifed to check the stack to determine if this really is the initial breakpoint. But that comes at a # performance cost, so until proven otherwise, the code is based on this assumption. oCrashInfo.fHandleCreateExitProcess("Create", uProcessId); else: # Report that analysis is starting... oCrashInfo._fExceptionDetectedCallback(uExceptionCode, sExceptionDescription); # Turn off noizy symbol loading; it can mess up the output of commands, making it unparsable. asOutput = oCrashInfo._fasSendCommandAndReadOutput("!sym quiet"); if not oCrashInfo._bCdbRunning: return None; # Gather relevant information... oProcess = cProcess.foCreate(oCrashInfo); oException = cException.foCreate(oCrashInfo, oProcess, uExceptionCode, sExceptionDescription); if not oCrashInfo._bCdbRunning: return None; # Save the exception report for returning when we're finished. oErrorReport = cErrorReport.foCreateFromException(oCrashInfo, oException); if not oCrashInfo._bCdbRunning: return None; # Stop the debugger if there was a fatal error that needs to be reported. if oErrorReport is not None: break; # Turn noizy symbol loading back on if it was enabled. if dxCrashInfoConfig["bDebugSymbolLoading"]: asOutput = oCrashInfo._fasSendCommandAndReadOutput("!sym noizy"); if not oCrashInfo._bCdbRunning: return None; # If there are more processes to attach to, do so: if len(oCrashInfo._auProcessIdsPendingAttach) > 0: asAttachToProcess = oCrashInfo._fasSendCommandAndReadOutput(".attach 0n%d" % oCrashInfo._auProcessIdsPendingAttach[0]); if not oCrashInfo._bCdbRunning: return; else: # The debugger has started the process or attached to all processes. # Set up exception handling if this has not beenm done yet. if not bExceptionHandlersHaveBeenSet: # Note to self: when rewriting the code, make sure not to set up exception handling before the debugger has # attached to all processes. But do so before resuming the threads. Otherwise one or more of the processes can # end up having only one thread that has a suspend count of 2 and no amount of resuming will cause the process # to run. The reason for this is unknown, but if things are done in the correct order, this problem is avoided. bExceptionHandlersHaveBeenSet = False; oCrashInfo._fasSendCommandAndReadOutput(sExceptionHandlingCommands); if not oCrashInfo._bCdbRunning: return; # If the debugger attached to processes, mark that as done and resume threads in all processes. if bDebuggerIsAttachingToProcesses: bDebuggerIsAttachingToProcesses = False; for uProcessId in oCrashInfo._auProcessIds: oCrashInfo._fasSendCommandAndReadOutput("|~[0n%d]s;~*m;~" % uProcessId); if not oCrashInfo._bCdbRunning: return; # Run the application asRunApplicationOutput = oCrashInfo._fasSendCommandAndReadOutput("g"); if not oCrashInfo._bCdbRunning: return; # If cdb is attaching to a process, make sure it worked. if bDebuggerIsAttachingToProcesses: fDetectFailedAttach(asRunApplicationOutput); # Terminate cdb. oCrashInfo._bCdbTerminated = True; oCrashInfo._fasSendCommandAndReadOutput("q"); assert not oCrashInfo._bCdbRunning, "Debugger did not terminate when requested"; return oErrorReport;
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 _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