def fVersionCheck(): import os, platform, sys oConsole.fLock() try: oConsole.fPrint("+ ", INFO, "Windows", NORMAL, " version: ", INFO, mWindowsAPI.oWindowsVersion.sProductName, \ NORMAL, " release ", INFO, mWindowsAPI.oWindowsVersion.sReleaseId, NORMAL, ", build ", INFO, \ mWindowsAPI.oWindowsVersion.sCurrentBuild, NORMAL, " ", INFO, mWindowsAPI.oWindowsVersion.sISA, NORMAL, ".") oConsole.fPrint("+ ", INFO, "Python", NORMAL, " version: ", INFO, str(platform.python_version()), NORMAL, " ", \ INFO, mWindowsAPI.fsGetPythonISA(), NORMAL, ".") axModules = [ ("BugId", "__main__", oVersionInformation), ("cBugId", "cBugId", cBugId.oVersionInformation), ("mFileSystem", "mFileSystem", mFileSystem.oVersionInformation), ("mWindowsAPI", "mWindowsAPI", mWindowsAPI.oVersionInformation), ("oConsole", "oConsole", oConsole.oVersionInformation), ] uCounter = 0 for (sModuleName, sSysModuleName, oModuleVersionInformation) in axModules: assert sModuleName == oModuleVersionInformation.sProjectName, \ "Module %s reports that it is called %s" % (sModuleName, oModuleVersionInformation.sProjectName) sInstallationPath = os.path.dirname( sys.modules[sSysModuleName].__file__) oConsole.fPrint("+ ", INFO, oModuleVersionInformation.sProjectName, NORMAL, " version: ", INFO, \ oModuleVersionInformation.sCurrentVersion, NORMAL, ", installed at ", INFO, sInstallationPath, NORMAL, ".") oConsole.fProgressBar(uCounter * 1.0 / len(axModules), \ "* Checking %s for updates..." % oModuleVersionInformation.sProjectName) if oModuleVersionInformation.bPreRelease: oConsole.fPrint(" You are running a ", HILITE, "pre-release", NORMAL, " version: ", "the latest release version is ", INFO, oModuleVersionInformation.sLatestVersion, NORMAL, ".") elif not oModuleVersionInformation.bUpToDate: oConsole.fPrint(" Version ", HILITE, oModuleVersionInformation.sLatestVersion, NORMAL, " is available at ", HILITE, oModuleVersionInformation.sUpdateURL, NORMAL, ".") uCounter += 1 oConsole.fPrint() finally: oConsole.fUnlock()
def __init__(oCdbWrapper, sCdbISA, # Which version of cdb should be used to debug this application? ("x86" or "x64") sApplicationBinaryPath, auApplicationProcessIds, sUWPApplicationPackageName, sUWPApplicationId, asApplicationArguments, asLocalSymbolPaths, asSymbolCachePaths, asSymbolServerURLs, dsURLTemplate_by_srSourceFilePath, bGenerateReportHTML, uProcessMaxMemoryUse, uTotalMaxMemoryUse, uMaximumNumberOfBugs, ): if sCdbISA: assert not (sCdbISA == "x64" and fsGetPythonISA() == "x86"), \ "You cannot use a 64-bit version of cdb.exe when you are using a 32-bit version of Python."; oCdbWrapper.sCdbISA = sCdbISA; else: oCdbWrapper.sCdbISA = fsGetPythonISA(); assert sApplicationBinaryPath or auApplicationProcessIds or sUWPApplicationPackageName, \ "You must provide one of the following: an application command line, a list of process ids or an application package name"; oCdbWrapper.sApplicationBinaryPath = sApplicationBinaryPath; oCdbWrapper.oUWPApplication = sUWPApplicationPackageName and cUWPApplication(sUWPApplicationPackageName, sUWPApplicationId) or None; oCdbWrapper.auApplicationProcessIds = auApplicationProcessIds or []; oCdbWrapper.auMainProcessIds = oCdbWrapper.auApplicationProcessIds[:]; oCdbWrapper.bApplicationStarted = False; oCdbWrapper.bUWPApplicationStarted = False; oCdbWrapper.bStopping = False; oCdbWrapper.auProcessIdsPendingAttach = []; oCdbWrapper.auProcessIdsThatNeedToBeResumedAfterAttaching = []; oCdbWrapper.asApplicationArguments = asApplicationArguments; oCdbWrapper.asLocalSymbolPaths = asLocalSymbolPaths or []; oCdbWrapper.asSymbolCachePaths = asSymbolCachePaths; if asSymbolCachePaths is None: oCdbWrapper.asSymbolCachePaths = dxConfig["asDefaultSymbolCachePaths"]; oCdbWrapper.asSymbolServerURLs = asSymbolServerURLs; if asSymbolServerURLs is None: oCdbWrapper.asSymbolServerURLs = dxConfig["asDefaultSymbolServerURLs"]; oCdbWrapper.dsURLTemplate_by_srSourceFilePath = dsURLTemplate_by_srSourceFilePath or {}; oCdbWrapper.bGenerateReportHTML = bGenerateReportHTML; oCdbWrapper.uProcessMaxMemoryUse = uProcessMaxMemoryUse; oCdbWrapper.uTotalMaxMemoryUse = uTotalMaxMemoryUse; oCdbWrapper.oEventCallbacksLock = threading.Lock(); oCdbWrapper.dafEventCallbacks_by_sEventName = { # These are the names of all the events that cCdbWrapper can throw. If it's not in the list, you cannot use it in # `fAddEventCallback`, `fRemoveEventCallback`, or `fbFireEvent`. The same event names are used by cBugId, but # any "Application resumed": [], # () "Application running": [], # () "Application debug output": [], # (cProcess oProcess, str[] asOutput) "Application stderr output": [], # (mWindowsAPI.cConsoleProcess oConsoleProcess, str sOutput) "Application stdout output": [], # (mWindowsAPI.cConsoleProcess oConsoleProcess, str sOutput) "Application suspended": [], # (str sReason) "Bug report": [], # (cBugReport oBugReport) "Cdb stderr output": [], # (str sOutput) "Cdb stdin input": [], # (str sInput) "Cdb stdout output": [], # (str sOutput) "Failed to apply application memory limits": [], # (cProcess oProcess) "Failed to apply process memory limits": [], # (cProcess oProcess) "Failed to debug application": [], # (str sReason) "Finished": [], # () "Internal exception": [], # (Exception oException, traceback oTraceBack) "Log message": [], # (str sDescription, dict dxData) "License errors": [], # (str[] asErrors) "License warnings": [], # (str[] asWarnings) "Page heap not enabled": [], # (cProcess oProcess, bool bPreventable) "Cdb ISA not ideal": [], # (cProcess oProcess, str sCdbISA, bool bPreventable) "Process attached": [], # (cProcess oProcess) "Process started": [], # (mWindowsAPI.cConsoleProcess oConsoleProcess) "Process terminated": [], #(cProcess oProcess) }; # This is where we keep track of the threads that are executing (for debug purposes): oCdbWrapper.aoActiveHelperThreads = []; # Get the cdb binary path oCdbWrapper.sDebuggingToolsPath = dxConfig["sDebuggingToolsPath_%s" % oCdbWrapper.sCdbISA]; assert oCdbWrapper.sDebuggingToolsPath, "No %s Debugging Tools for Windows path found" % oCdbWrapper.sCdbISA; oCdbWrapper.doProcess_by_uId = {}; oCdbWrapper.doConsoleProcess_by_uId = {}; oCdbWrapper.oCdbCurrentProcess = None; # The current process id in cdb's context oCdbWrapper.oCdbCurrentThread = None; # The current thread id in cdb's context oCdbWrapper.sCdbCurrentISA = None; # The ISA cdb is debugging the current process in (can differ from the process' ISA!) # Initialize some variables if bGenerateReportHTML: oCdbWrapper.sCdbIOHTML = ""; # Logs stdin/stdout/stderr for the cdb process, grouped by executed command. oCdbWrapper.sPromptHTML = None; # Logs cdb prompt to be adde to CdbIOHTML if a command is added. if dxConfig["bLogInReport"]: oCdbWrapper.sLogHTML = ""; # Logs various events that may be relevant oCdbWrapper.bCdbRunning = True; # Set to False after cdb terminated, used to terminate the debugger thread. oCdbWrapper.bCdbWasTerminatedOnPurpose = False; # Set to True when cdb is terminated on purpose, used to detect unexpected termination. # To make it easier to refer to cdb breakpoints by id, a mechanism must be used to allocate and release them # See fuGetBreakpointId and fReleaseBreakpointId for implementation details. oCdbWrapper.oBreakpointCounter = itertools.count(); # None have been used so far, so start at 0. # You can set a breakpoint that results in a bug being reported when it is hit. # See fuAddBugBreakpoint and fReleaseBreakpointId for implementation details. oCdbWrapper.duProcessId_by_uBreakpointId = {}; oCdbWrapper.duAddress_by_uBreakpointId = {}; oCdbWrapper.dfCallback_by_uBreakpointId = {}; oCdbWrapper.dauOldBreakpointAddresses_by_uProcessId = {}; # You can tell BugId to check for excessive CPU usage among all the threads running in the application. # See fSetCheckForExcessiveCPUUsageTimeout and cExcessiveCPUUsageDetector.py for more information oCdbWrapper.oExcessiveCPUUsageDetector = cExcessiveCPUUsageDetector(oCdbWrapper); # Keep track of timeouts that should fire at some point in the future and timeouts that should fire now. oCdbWrapper.aoTimeouts = []; oCdbWrapper.bApplicationIsRunnning = False; # Will be set to true while the application is running in cdb. oCdbWrapper.uUtilityInterruptThreadId = None; # Will be set to the thread id in which we triggered an AV to # interrupt the application. # Lock for the above four timeout and interrupt variables oCdbWrapper.oTimeoutAndInterruptLock = threading.RLock(); # Keep track of how long the application has been running, used for timeouts (see foSetTimeout, fCdbStdInOutThread # and fCdbInterruptOnTimeoutThread for details. The debugger can tell is what time it thinks it is before we start # and resume the application as well as what time it thinks it was when an exception happened. The difference is # used to calculate how long the application has been running. We cannot simply use time.clock() before start/ # resuming and at the time an event is handled as the debugger may take quite some time processing an event before # we can call time.clock(): this time would incorrectly be added to the time the application has spent running. # However, while the application is running, we cannot ask the debugger what time it thinks it is, so we have to # rely on time.clock(). Hence, both values are tracked. oCdbWrapper.oApplicationTimeLock = threading.RLock(); oCdbWrapper.nConfirmedApplicationRunTime = 0; # Total time spent running before last interruption oCdbWrapper.nApplicationResumeDebuggerTime = None; # debugger time at the moment the application was last resumed oCdbWrapper.nApplicationResumeTime = None; # time.clock() at the moment the application was last resumed oCdbWrapper.oCdbConsoleProcess = None; oCdbWrapper.oCollateralBugHandler = cCollateralBugHandler(oCdbWrapper, uMaximumNumberOfBugs); # Create VERIFIER STOP and Asan ERROR detectors. The later also needs to be called when a Breakpoint exception # happens in order to report it (the former reports the error as soon as it is detected in applicationm debug # output). cVerifierStopDetector(oCdbWrapper); oCdbWrapper.oASanErrorDetector = cASanErrorDetector(oCdbWrapper); if oCdbWrapper.bGenerateReportHTML and dxConfig["bLogInReport"]: def fWriteLogMessageToReport(sMessage, dsData = None): sData = dsData and ", ".join(["%s: %s" % (sName, sValue) for (sName, sValue) in dsData.items()]); oCdbWrapper.sLogHTML += "<span class=\"LogMessage\">%s%s</span><br/>\n" % \ (oCdbWrapper.fsHTMLEncode(sMessage), sData and " (%s)" % oCdbWrapper.fsHTMLEncode(sData) or ""); oCdbWrapper.fAddEventCallback("Log message", fWriteLogMessageToReport);
def fPrintVersionInformation(bCheckForUpdates, bCheckAndShowLicenses, bShowInstallationFolders): # Read product details for rs and all modules it uses. aoProductDetails = mProductDetails.faoGetProductDetailsForAllLoadedModules(); oMainProductDetails = mProductDetails.foGetProductDetailsForMainModule(); if bCheckForUpdates: uCheckedProductCounter = 0; for oProductDetails in aoProductDetails: oConsole.fProgressBar( uCheckedProductCounter * 1.0 / len(aoProductDetails), "Checking %s for updates..." % oProductDetails.sProductName, ); try: oProductDetails.oLatestProductDetailsFromRepository; except Exception as oException: oConsole.fPrint( ERROR, u"- Version check for ", ERROR_INFO, oProductDetails.sProductName, ERROR, " failed: ", ERROR_INFO, str(oException), ); uCheckedProductCounter += 1; oConsole.fLock(); try: if bCheckAndShowLicenses: aoLicenses = []; asLicensedProductNames = []; asProductNamesInTrial = []; asUnlicensedProductNames = []; for oProductDetails in aoProductDetails: if oProductDetails.oLicense: if oProductDetails.oLicense not in aoLicenses: aoLicenses.append(oProductDetails.oLicense); asLicensedProductNames.append(oProductDetails.sProductName); elif oProductDetails.bHasTrialPeriod and oProductDetails.bInTrialPeriod: asProductNamesInTrial.append(oProductDetails.sProductName); else: asUnlicensedProductNames.append(oProductDetails.sProductName); oLicenseCheckServer = mProductDetails.cLicenseCheckServer(oMainProductDetails.sLicenseServerURL); uCheckedLicenseCounter = 0; for oLicense in aoLicenses: oConsole.fProgressBar( uCheckedLicenseCounter * 1.0 / len(aoLicenses), "Checking license %s with server..." % oLicense.sLicenseId, ); sLicenseCheckServerError = oLicense.fsCheckWithServerAndGetError(oLicenseCheckServer, bForceCheck = True); if sLicenseCheckServerError: oConsole.fPrint( ERROR, u"- License check for ", ERROR_INFO, oLicense.sLicenseId, ERROR, " on server ", ERROR_INFO, oMainProductDetails.sLicenseServerURL, ERROR, " failed: ", ERROR_INFO, sLicenseCheckServerError, ); uCheckedLicenseCounter += 1; oConsole.fPrint( u"\u250C\u2500 ", INFO, "Version information", NORMAL, " ", sPadding = u"\u2500" ); # Output the BugId product information first, then its dependencies: fPrintProductDetails(oMainProductDetails, bIsMainProduct = True, bShowInstallationFolders = bShowInstallationFolders); for oProductDetails in aoProductDetails: if oProductDetails != oMainProductDetails: fPrintProductDetails(oProductDetails, bIsMainProduct = False, bShowInstallationFolders = bShowInstallationFolders); oConsole.fPrint( u"\u2502 \u2219 ", INFO, "Windows", NORMAL, " version: ", INFO, oSystemInfo.sOSName, NORMAL, " release ", INFO, oSystemInfo.sOSReleaseId, NORMAL, ", build ", INFO, oSystemInfo.sOSBuild, NORMAL, " ", INFO, oSystemInfo.sOSISA, NORMAL, ".", ); oConsole.fPrint( u"\u2502 \u2219 ", INFO, "Python", NORMAL, " version: ", INFO, str(platform.python_version()), NORMAL, " ", INFO, fsGetPythonISA(), NORMAL, ".", ); if bCheckAndShowLicenses: oConsole.fPrint( u"\u251C\u2500 ", INFO, "License information", NORMAL, " ", sPadding = u"\u2500", ); if aoLicenses: oConsole.fPrint( NORMAL, u"\u2502 \u2219 This system is registered with id ", INFO, mProductDetails.fsGetSystemId(), NORMAL, " on the license server", ); for oLicense in aoLicenses: oConsole.fPrint( u"\u2502 \u2219 License ", INFO, oLicense.sLicenseId, NORMAL, " for ", INFO, oLicense.asProductNames[0], NORMAL, " covers ", INFO, oLicense.sUsageTypeDescription, NORMAL, " by ", INFO, oLicense.sLicenseeName, NORMAL, " of the following products:", ); oConsole.fPrint(u"\u2502 ", faxListOutput(oLicense.asProductNames, "and", INFO, NORMAL)); if asProductNamesInTrial: oConsole.fPrint(*( [ u"\u2502 \u2219 " ] + faxListOutput(asProductNamesInTrial, "and", WARNING_INFO, WARNING) + [ WARNING, " ", len(asProductNamesInTrial) == 1 and "is" or "are", " not covered by a valid, active license but ", len(asProductNamesInTrial) == 1 and "it is in its" or "they are in their", " trial period.", ] )); if asUnlicensedProductNames: oConsole.fPrint(*( [ u"\u2502 \u2219 " ] + faxListOutput(asUnlicensedProductNames, "and", ERROR_INFO, ERROR) + [ ERROR, " ", len(asProductNamesInTrial) == 1 and "is" or "are", " not covered by a valid, active license and ", len(asProductNamesInTrial) == 1 and "has exceeded its" or "have exceeded their", " trial period.", ] )); oConsole.fPrint( u"\u2514", sPadding = u"\u2500", ); oConsole.fPrint(); finally: oConsole.fUnlock(); (asLicenseErrors, asLicenseWarnings) = mProductDetails.ftasGetLicenseErrorsAndWarnings(); if asLicenseErrors: oConsole.fLock(); try: oConsole.fPrint(ERROR, u"\u250C\u2500", ERROR_INFO, " Software license error ", ERROR, sPadding = u"\u2500"); for sLicenseError in asLicenseErrors: oConsole.fPrint(ERROR, u"\u2502 ", ERROR_INFO, sLicenseError); oConsole.fPrint(ERROR, u"\u2514", sPadding = u"\u2500"); finally: oConsole.fUnlock(); if asLicenseWarnings: oConsole.fLock(); try: oConsole.fPrint(WARNING, u"\u250C\u2500", WARNING_INFO, " Software license warning ", WARNING, sPadding = u"\u2500"); for sLicenseWarning in asLicenseWarnings: oConsole.fPrint(WARNING, u"\u2502 ", WARNING_INFO, sLicenseWarning); oConsole.fPrint(WARNING, u"\u2514", sPadding = u"\u2500"); finally: oConsole.fUnlock();
def fOutputVersionInformation(bCheckForUpdates, bShowInstallationFolders, dsAdditionalVersion_by_sName={}): # Read product details for rs and all modules it uses. aoProductDetails = mProductDetails.faoGetProductDetailsForAllLoadedModules( ) o0MainProductDetails = mProductDetails.fo0GetProductDetailsForMainModule() oConsole.fLock() try: aoProductDetailsCheckedForUpdates = [] aoProductDetailsSuccessfullyCheckedForUpdates = [] if bCheckForUpdates: for oProductDetails in aoProductDetails: if oProductDetails.o0Repository is None: continue aoProductDetailsCheckedForUpdates.append(oProductDetails) oConsole.fProgressBar( len(aoProductDetailsCheckedForUpdates) * 1.0 / len(aoProductDetails), "Checking %s for updates..." % oProductDetails.sProductName, ) try: oProductDetails.foGetLatestProductDetailsFromRepository() except mProductDetails.mExceptions.cProductDetailsException as oException: oConsole.fOutput( COLOR_ERROR, CHAR_ERROR, COLOR_NORMAL, " Version check for ", COLOR_INFO, oProductDetails.sProductName, COLOR_NORMAL, " failed: ", COLOR_INFO, str(oException), ) else: aoProductDetailsSuccessfullyCheckedForUpdates.append( oProductDetails) if len(aoProductDetailsSuccessfullyCheckedForUpdates) == 0: oConsole.fOutput( COLOR_WARNING, CHAR_WARNING, COLOR_NORMAL, "Failed to get any product version information.", ) oConsole.fOutput( " (This often indicates you are running a ", COLOR_INFO, "pre-release", COLOR_NORMAL, " version, or a version that is very ", COLOR_INFO, "out of date", COLOR_NORMAL, ").", ) oConsole.fOutput( " To try and resolve this issue, please update this product to the latest", ) oConsole.fOutput(" version and try again.", ) if f0OutputLogo: f0OutputLogo() oConsole.fOutput( "┌───[", COLOR_HILITE, " Version information ", COLOR_NORMAL, "]", sPadding="─", ) # Output the main product information first, then its dependencies alphabetically: if o0MainProductDetails: fOutputProductDetails( o0MainProductDetails, bIsMainProduct=True, bShowInstallationFolders=bShowInstallationFolders, bCheckForUpdates=bCheckForUpdates, bCheckForUpdatesSuccessful=o0MainProductDetails in aoProductDetailsSuccessfullyCheckedForUpdates, ) doRemainingProductDetails_by_sName = dict([ (oProductDetails.sProductName, oProductDetails) for oProductDetails in aoProductDetails if oProductDetails != o0MainProductDetails ]) for sProductName in sorted(doRemainingProductDetails_by_sName.keys()): oProductDetails = doRemainingProductDetails_by_sName[sProductName] fOutputProductDetails( oProductDetails, bIsMainProduct=False, bShowInstallationFolders=bShowInstallationFolders, bCheckForUpdates=bCheckForUpdates, bCheckForUpdatesSuccessful=oProductDetails in aoProductDetailsSuccessfullyCheckedForUpdates, ) asProductNames = (([o0MainProductDetails.sProductName] if o0MainProductDetails else []) + list(doRemainingProductDetails_by_sName.keys())) oConsole.fOutput( "│ ", CHAR_LIST, " ", COLOR_INFO, "Windows", COLOR_NORMAL, " version: ", COLOR_INFO, oSystemInfo.sOSName, COLOR_NORMAL, " release ", COLOR_INFO, oSystemInfo.sOSReleaseId, COLOR_NORMAL, ", build ", COLOR_INFO, oSystemInfo.sOSBuild, COLOR_NORMAL, " ", COLOR_INFO, oSystemInfo.sOSISA, COLOR_NORMAL, ".", ) oConsole.fOutput( "│ ", CHAR_LIST, " ", COLOR_INFO, "Python", COLOR_NORMAL, " version: ", COLOR_INFO, str(platform.python_version()), COLOR_NORMAL, " ", COLOR_INFO, fsGetPythonISA(), COLOR_NORMAL, ".", ) for (sName, sVersion) in dsAdditionalVersion_by_sName.items(): oConsole.fOutput( "│ ", CHAR_LIST, " ", COLOR_INFO, sName, COLOR_NORMAL, " version: ", COLOR_INFO, sVersion, COLOR_NORMAL, ".", ) oConsole.fOutput( "└", sPadding="─", ) finally: oConsole.fUnlock()
def fMain(asArguments): global \ gasAttachForProcessExecutableNames, \ gasBinaryNamesThatAreAllowedToRunWithoutPageHeap, \ gbQuiet, \ gbVerbose, \ guDetectedBugsCount, \ guMaximumNumberOfBugs; # Make sure Windows and the Python binary are up to date; we don't want our users to unknowingly run outdated # software as this is likely to cause unexpected issues. fCheckPythonVersion("BugId", asTestedPythonVersions, "https://github.com/SkyLined/BugId/issues/new") if mWindowsAPI.oSystemInfo.sOSVersion != "10.0": oConsole.fPrint(ERROR, "Error: unfortunately BugId only runs on Windows 10 at this time."); os._exit(3); if mWindowsAPI.oSystemInfo.sOSISA == "x64" and mWindowsAPI.fsGetPythonISA() == "x86": oConsole.fLock(); try: oConsole.fPrint(WARNING, u"\u250C\u2500", WARNING_INFO, " Warning ", WARNING, sPadding = u"\u2500"); oConsole.fPrint(WARNING, u"\u2502 You are running a ", WARNING_INFO, "32-bit", WARNING, " version of Python on a ", WARNING_INFO, "64-bit", WARNING, " version of Windows."); oConsole.fPrint(WARNING, u"\u2502 BugId will not be able to debug 64-bit applications unless you run it in a 64-bit " + "version of Python."); oConsole.fPrint(WARNING, u"\u2502 If you experience any issues, use a 64-bit version of Python and try again."); oConsole.fPrint(WARNING, u"\u2514", sPadding = u"\u2500"); finally: oConsole.fUnlock(); # Show usage information if no arguments are provided: if len(asArguments) == 0: fPrintLogo(); fPrintUsageInformation(ddxApplicationSettings_by_sKeyword.keys()); oConsole.fCleanup(); os._exit(0); # Parse all arguments until we encounter "--". sApplicationKeyword = None; sApplicationBinaryPath = None; auApplicationProcessIds = []; sUWPApplicationPackageName = None; sUWPApplicationId = None; asApplicationOptionalArguments = None; sApplicationISA = None; bRepeat = False; uNumberOfRepeats = None; bCheckForUpdates = False; dxUserProvidedConfigSettings = {}; asAdditionalLocalSymbolPaths = []; bFast = False; while asArguments: sArgument = asArguments.pop(0); if sArgument == "--": if len(auApplicationProcessIds) > 0: # The rest of the arguments are to be passed to the application oConsole.fPrint(ERROR, "- You cannot provide process ids and application arguments."); oConsole.fCleanup(); os._exit(2); asApplicationOptionalArguments = asArguments; break; elif sArgument in ["-q", "/q"]: gbQuiet = True; elif sArgument in ["-v", "/v"]: gbVerbose = True; elif sArgument in ["-f", "/f"]: bFast = True; elif sArgument in ["-r", "/r"]: bRepeat = True; elif sArgument in ["-c", "/c"]: guMaximumNumberOfBugs = guDefaultCollateralMaximumNumberOfBugs; elif sArgument in ["-?", "/?", "-h", "/h"]: fPrintLogo(); fPrintUsageInformation(ddxApplicationSettings_by_sKeyword.keys()); oConsole.fCleanup(); os._exit(0); elif sArgument.startswith("--"): if "=" in sArgument: sSettingName, sValue = sArgument[2:].split("=", 1); else: # "--bFlag" is an alias for "--bFlag=true" sSettingName = sArgument[2:]; sValue = None; if sSettingName in ["pid", "pids"]: if not sValue: oConsole.fPrint(ERROR, "- You must provide at least one process id."); oConsole.fCleanup(); os._exit(2); if sApplicationBinaryPath is not None: oConsole.fPrint(ERROR, "- You cannot provide an application binary and process ids."); oConsole.fCleanup(); os._exit(2); if sUWPApplicationPackageName is not None: oConsole.fPrint(ERROR, "- You cannot provide an UWP application package name and process ids."); oConsole.fCleanup(); os._exit(2); auApplicationProcessIds += [long(x) for x in sValue.split(",")]; elif sSettingName in ["uwp", "uwp-app"]: if not sValue: oConsole.fPrint(ERROR, "- You must provide UWP application details."); oConsole.fCleanup(); os._exit(2); if sUWPApplicationPackageName is not None: oConsole.fPrint(ERROR, "- You cannot provide UWP application details more than once."); oConsole.fCleanup(); os._exit(2); if sApplicationBinaryPath is not None: oConsole.fPrint(ERROR, "- You cannot provide an application binary and UWP application details."); oConsole.fCleanup(); os._exit(2); if len(auApplicationProcessIds) > 0: oConsole.fPrint(ERROR, "- You cannot provide process ids and UWP application details."); oConsole.fCleanup(); os._exit(2); if "!" not in sValue: oConsole.fPrint(ERROR, "- Please provide UWP application details in the form ", ERROR_INFO, sSettingName, \ "=<package name>!<application id>", ERROR, "."); oConsole.fCleanup(); os._exit(2); sUWPApplicationPackageName, sUWPApplicationId = sValue.split("!", 1); elif sSettingName in ["help"]: fPrintLogo(); fPrintUsageInformation(ddxApplicationSettings_by_sKeyword.keys()); oConsole.fCleanup(); os._exit(0); elif sSettingName in ["version", "check-for-updates"]: fPrintVersionInformation( bCheckForUpdates = True, bCheckAndShowLicenses = True, bShowInstallationFolders = True, ); oConsole.fCleanup(); os._exit(0); elif sSettingName in ["isa", "cpu"]: if not sValue: oConsole.fPrint(ERROR, "- You must provide an Instruction Set Architecture."); oConsole.fCleanup(); os._exit(2); if sValue not in ["x86", "x64"]: oConsole.fPrint(ERROR, "- Unknown Instruction Set Architecture ", repr(sValue)); oConsole.fCleanup(); os._exit(2); sApplicationISA = sValue; elif sSettingName in ["quiet", "silent"]: if sValue is None or sValue.lower() == "true": gbQuiet = True; elif sValue.lower() == "false": gbQuiet = False; else: oConsole.fPrint(ERROR, "- The value for ", ERROR_INFO, "--", sSettingName, ERROR, \ " must be \"true\" or \"false\"."); elif sSettingName in ["verbose", "debug"]: if sValue is None or sValue.lower() == "true": gbVerbose = True; elif sValue.lower() == "false": gbVerbose = False; else: oConsole.fPrint(ERROR, "- The value for ", ERROR_INFO, "--", sSettingName, ERROR, \ " must be \"true\" or \"false\"."); elif sSettingName in ["fast", "quick"]: if sValue is None or sValue.lower() == "true": bFast = True; elif sValue.lower() == "false": bFast = False; else: oConsole.fPrint(ERROR, "- The value for ", ERROR_INFO, "--", sSettingName, ERROR, \ " must be \"true\" or \"false\"."); elif sSettingName in ["forever"]: if sValue is None or sValue.lower() == "true": bRepeat = True; elif sValue.lower() == "false": bRepeat = False; else: oConsole.fPrint(ERROR, "- The value for ", ERROR_INFO, "--", sSettingName, ERROR, \ " must be \"true\" or \"false\"."); elif sSettingName in ["repeat"]: bRepeat = sValue is None or sValue.lower() != "false"; if bRepeat and sValue is not None: try: uNumberOfRepeats = long(sValue); if uNumberOfRepeats < 2: uNumberOfRepeats = None; elif str(uNumberOfRepeats) != sValue: uNumberOfRepeats = None; except: pass; if uNumberOfRepeats is None: oConsole.fPrint(ERROR, "- The value for ", ERROR_INFO, "--", sSettingName, ERROR, \ " must be an integer larger than 1 or \"false\"."); elif sSettingName in ["collateral"]: if sValue is None: guMaximumNumberOfBugs = guDefaultCollateralMaximumNumberOfBugs; else: # -- collateral=1 means one collateral bug in addition to the first bug. guMaximumNumberOfBugs = long(sValue) + 1; elif sSettingName in ["symbols"]: if sValue is None or not mFileSystem2.foGetFolder(sValue): oConsole.fPrint(ERROR, "- The value for ", ERROR_INFO, "--", sSettingName, ERROR, \ " must be a valid path."); asAdditionalLocalSymbolPaths.append(sValue); elif sSettingName in ["test-internal-error", "internal-error-test"]: raise Exception("Testing internal error"); else: if not sValue: oConsole.fPrint(ERROR, "- You cannot provide an argument (", ERROR_INFO, "--", sSettingName, ERROR, \ ") without a value."); oConsole.fCleanup(); os._exit(2); try: xValue = json.loads(sValue); except ValueError as oError: oConsole.fPrint(ERROR, "- Cannot decode argument JSON value --", ERROR_INFO, sSettingName, ERROR, "=", \ ERROR_INFO, sValue, ERROR, ": ", ERROR_INFO, " ".join(oError.args), ERROR, "."); oConsole.fCleanup(); os._exit(2); # User provided config settings must be applied after any keyword specific config settings: dxUserProvidedConfigSettings[sSettingName] = xValue; elif sArgument in ddxApplicationSettings_by_sKeyword: if sApplicationKeyword is not None: oConsole.fPrint(ERROR, "- You cannot provide multiple application keywords."); oConsole.fCleanup(); os._exit(2); sApplicationKeyword = sArgument; elif sArgument[-1] == "?": sApplicationKeyword = sArgument[:-1]; dxApplicationSettings = ddxApplicationSettings_by_sKeyword.get(sApplicationKeyword); if not dxApplicationSettings: oConsole.fPrint(ERROR, "- Unknown application keyword ", ERROR_INFO, sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); fPrintApplicationKeyWordHelp(sApplicationKeyword, dxApplicationSettings); oConsole.fCleanup(); os._exit(0); else: if sApplicationBinaryPath is not None: oConsole.fLock(); try: oConsole.fPrint(ERROR, "- You cannot provide multiple application binaries."); oConsole.fPrint(ERROR, " (Did you perhaps forget to put ", ERROR_INFO, "--", ERROR, \ " before the start of the application arguments?)"); finally: oConsole.fUnlock(); oConsole.fCleanup(); os._exit(2); if len(auApplicationProcessIds) > 0: oConsole.fPrint(ERROR, "- You cannot provide process ids and an application binary."); oConsole.fCleanup(); os._exit(2); if sUWPApplicationPackageName is not None: oConsole.fPrint(ERROR, "- You cannot provide an application UWP package name and a binary."); oConsole.fCleanup(); os._exit(2); sApplicationBinaryPath = sArgument; if bFast: gbQuiet = True; dxUserProvidedConfigSettings["bGenerateReportHTML"] = False; dxUserProvidedConfigSettings["asSymbolServerURLs"] = []; dxUserProvidedConfigSettings["cBugId.bUse_NT_SYMBOL_PATH"] = False; dsApplicationURLTemplate_by_srSourceFilePath = {}; fSetup = None; # Function specific to a keyword application, used to setup stuff before running. fCleanup = None; # Function specific to a keyword application, used to cleanup stuff before & after running. if sApplicationKeyword: dxApplicationSettings = ddxApplicationSettings_by_sKeyword.get(sApplicationKeyword); if not dxApplicationSettings: oConsole.fPrint(ERROR, "- Unknown application keyword ", ERROR_INFO, sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); fSetup = dxApplicationSettings.get("fSetup"); fCleanup = dxConfig["bCleanup"] and dxApplicationSettings.get("fCleanup"); # Get application binary/UWP package name/process ids as needed: if "sBinaryPath" in dxApplicationSettings: # This application is started from the command-line. if auApplicationProcessIds: oConsole.fPrint(ERROR, "- You cannot provide process ids for application keyword ", ERROR_INFO, \ sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); if sUWPApplicationPackageName: oConsole.fPrint(ERROR, "- You cannot provide an application UWP package name for application keyword ", \ ERROR_INFO, sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); if sApplicationBinaryPath is None: sApplicationBinaryPath = dxApplicationSettings["sBinaryPath"]; if sApplicationBinaryPath is None: oConsole.fPrint(ERROR, "- The main application binary for ", ERROR_INFO, sApplicationKeyword, \ ERROR, " could not be detected on your system."); oConsole.fPrint(ERROR, " Please provide the path to this binary in the arguments."); oConsole.fCleanup(); os._exit(4); elif "dxUWPApplication" in dxApplicationSettings: dxUWPApplication = dxApplicationSettings["dxUWPApplication"]; # This application is started as a Universal Windows Platform application. if sApplicationBinaryPath: oConsole.fPrint(ERROR, "- You cannot provide an application binary for application keyword ", \ ERROR_INFO, sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); if auApplicationProcessIds: oConsole.fPrint(ERROR, "- You cannot provide process ids for application keyword ", ERROR_INFO, \ sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); sUWPApplicationPackageName = dxUWPApplication["sPackageName"]; sUWPApplicationId = dxUWPApplication["sId"]; elif not auApplicationProcessIds: # This application is attached to. oConsole.fPrint(ERROR, "- You must provide process ids for application keyword ", \ ERROR_INFO, sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); elif asApplicationOptionalArguments: # Cannot provide arguments if we're attaching to processes oConsole.fPrint(ERROR, "- You cannot provide arguments for application keyword ", \ ERROR_INFO, sApplicationKeyword, ERROR, "."); oConsole.fCleanup(); os._exit(2); if "asApplicationAttachForProcessExecutableNames" in dxApplicationSettings: gasAttachForProcessExecutableNames = dxApplicationSettings["asApplicationAttachForProcessExecutableNames"]; # Get application arguments; if "fasGetStaticArguments" in dxApplicationSettings: fasGetApplicationStaticArguments = dxApplicationSettings["fasGetStaticArguments"]; asApplicationStaticArguments = fasGetApplicationStaticArguments(bForHelp = False); else: asApplicationStaticArguments = []; if asApplicationOptionalArguments is None and "fasGetOptionalArguments" in dxApplicationSettings: fasGetApplicationOptionalArguments = dxApplicationSettings["fasGetOptionalArguments"]; asApplicationOptionalArguments = fasGetApplicationOptionalArguments(bForHelp = False); asApplicationArguments = asApplicationStaticArguments + asApplicationOptionalArguments; # Apply application specific settings if dxApplicationSettings.get("dxConfigSettings"): dxApplicationConfigSettings = dxApplicationSettings["dxConfigSettings"]; if gbVerbose: oConsole.fPrint("* Applying application specific configuration for %s:" % sApplicationKeyword); for (sSettingName, xValue) in dxApplicationConfigSettings.items(): if sSettingName not in dxUserProvidedConfigSettings: # Apply and show result indented or errors. if not fbApplyConfigSetting(sSettingName, xValue, [None, " "][gbVerbose]): os._exit(2); if gbVerbose: oConsole.fPrint(); # Apply application specific source settings if "dsURLTemplate_by_srSourceFilePath" in dxApplicationSettings: dsApplicationURLTemplate_by_srSourceFilePath = dxApplicationSettings["dsURLTemplate_by_srSourceFilePath"]; # If not ISA is specified, apply the application specific ISA (if any). if not sApplicationISA and "sISA" in dxApplicationSettings: sApplicationISA = dxApplicationSettings["sISA"]; if "asBinaryNamesThatAreAllowedToRunWithoutPageHeap" in dxApplicationSettings: gasBinaryNamesThatAreAllowedToRunWithoutPageHeap += [ sBinaryName.lower() for sBinaryName in dxApplicationSettings["asBinaryNamesThatAreAllowedToRunWithoutPageHeap"] ]; elif (auApplicationProcessIds or sUWPApplicationPackageName or sApplicationBinaryPath): # There are no static arguments if there is no application keyword, only the user-supplied optional arguments # are used if they are supplied: asApplicationArguments = asApplicationOptionalArguments or []; else: oConsole.fLock(); try: oConsole.fPrint(ERROR, "- You must provide something to debug. This can be either one or more process"); oConsole.fPrint(ERROR, " ids, an application command-line or an UWP application package name."); oConsole.fPrint("Run \"", INFO, "BugId -h", NORMAL, "\" for help on command-line arguments."); finally: oConsole.fUnlock(); oConsole.fCleanup(); os._exit(2); # Apply user provided settings: for (sSettingName, xValue) in dxUserProvidedConfigSettings.items(): # Apply and show result or errors: if not fbApplyConfigSetting(sSettingName, xValue, [None, ""][gbVerbose]): os._exit(2); # Check if cdb.exe is found: sCdbISA = sApplicationISA or cBugId.sOSISA; if not cBugId.fbCdbFound(sCdbISA): oConsole.fLock(); try: oConsole.fPrint(ERROR, "- BugId depends on ", ERROR_INFO, "Debugging Tools for Windows", ERROR, " which was not found."); oConsole.fPrint(); oConsole.fPrint("To install, download the Windows 10 SDK installer at:"); oConsole.fPrint(); oConsole.fPrint(" ", INFO, "https://developer.microsoft.com/en-US/windows/downloads/windows-10-sdk"); oConsole.fPrint(); oConsole.fPrint("After downloading, run the installer. You can deselect all other features"); oConsole.fPrint("of the SDK before installation; only ", INFO, "Debugging Tools for Windows", NORMAL, " is required."); oConsole.fPrint(); oConsole.fPrint("Once you have completed these steps, please try again."); finally: oConsole.fUnlock(); oConsole.fCleanup(); os._exit(2); # Check license (asLicenseErrors, asLicenseWarnings) = mProductDetails.ftasGetLicenseErrorsAndWarnings(); if asLicenseErrors: oConsole.fLock(); try: oConsole.fPrint(ERROR, u"\u250C\u2500", ERROR_INFO, " Software license error ", ERROR, sPadding = u"\u2500"); for sLicenseError in asLicenseErrors: oConsole.fPrint(ERROR, u"\u2502 ", ERROR_INFO, sLicenseError); oConsole.fPrint(ERROR, u"\u2514", sPadding = u"\u2500"); finally: oConsole.fUnlock(); os._exit(5); if asLicenseWarnings: oConsole.fLock(); try: oConsole.fPrint(WARNING, u"\u250C\u2500", WARNING_INFO, " Software license warning ", WARNING, sPadding = u"\u2500"); for sLicenseWarning in asLicenseWarnings: oConsole.fPrint(WARNING, u"\u2502 ", WARNING_INFO, sLicenseWarning); oConsole.fPrint(WARNING, u"\u2514", sPadding = u"\u2500"); finally: oConsole.fUnlock(); if bRepeat: sValidStatisticsFileName = mFileSystem2.fsGetValidName("Reproduction statistics.txt"); uRunCounter = 0; while 1: # Will only loop if bRepeat is True nStartTimeInSeconds = time.clock(); if fSetup: # Call setup before the application is started. Argument is boolean value indicating if this is the first time # the function is being called. oConsole.fStatus("* Applying special application configuration settings..."); fSetup(bFirstRun = uRunCounter == 0); uRunCounter += 1; oConsole.fLock(); try: if sApplicationBinaryPath: # make the binary path absolute because relative paths don't work. sApplicationBinaryPath = os.path.abspath(sApplicationBinaryPath); if not gbQuiet: asCommandLine = [sApplicationBinaryPath] + asApplicationArguments; oConsole.fPrint("* Command line: ", INFO, " ".join(asCommandLine)); oConsole.fStatus("* The debugger is starting the application..."); else: if auApplicationProcessIds: asProcessIdsOutput = []; for uApplicationProcessId in auApplicationProcessIds: if asProcessIdsOutput: asProcessIdsOutput.append(", "); asProcessIdsOutput.extend([INFO, str(uApplicationProcessId), NORMAL]); oConsole.fPrint("* Running process ids: ", INFO, *asProcessIdsOutput); if sUWPApplicationPackageName: if not gbQuiet: if asApplicationArguments: oConsole.fPrint("* UWP application id: ", INFO, sUWPApplicationId, NORMAL, ", package name: ", INFO, \ sUWPApplicationPackageName, NORMAL, ", Arguments: ", INFO, " ".join(asApplicationArguments)); else: oConsole.fPrint("* UWP application id: ", INFO, sUWPApplicationId, NORMAL, ", package name: ", INFO, \ sUWPApplicationPackageName); if not sUWPApplicationPackageName: oConsole.fStatus("* The debugger is attaching to running processes of the application..."); elif auApplicationProcessIds: oConsole.fStatus("* The debugger is attaching to running processes and starting the application..."); else: oConsole.fStatus("* The debugger is starting the application..."); finally: oConsole.fUnlock(); asLocalSymbolPaths = dxConfig["asLocalSymbolPaths"] or []; if asAdditionalLocalSymbolPaths: asLocalSymbolPaths += asAdditionalLocalSymbolPaths; oBugId = cBugId( sCdbISA = sCdbISA, sApplicationBinaryPath = sApplicationBinaryPath or None, auApplicationProcessIds = auApplicationProcessIds or None, sUWPApplicationPackageName = sUWPApplicationPackageName or None, sUWPApplicationId = sUWPApplicationId or None, asApplicationArguments = asApplicationArguments, asLocalSymbolPaths = asLocalSymbolPaths or None, asSymbolCachePaths = dxConfig["asSymbolCachePaths"], asSymbolServerURLs = dxConfig["asSymbolServerURLs"], dsURLTemplate_by_srSourceFilePath = dsApplicationURLTemplate_by_srSourceFilePath, bGenerateReportHTML = dxConfig["bGenerateReportHTML"], uProcessMaxMemoryUse = dxConfig["uProcessMaxMemoryUse"], uTotalMaxMemoryUse = dxConfig["uTotalMaxMemoryUse"], uMaximumNumberOfBugs = guMaximumNumberOfBugs, ); oBugId.fAddEventCallback("Application resumed", fApplicationResumedCallback); oBugId.fAddEventCallback("Application running", fApplicationRunningCallback); oBugId.fAddEventCallback("Application suspended", fApplicationSuspendedCallback); oBugId.fAddEventCallback("Application debug output", fApplicationDebugOutputCallback); oBugId.fAddEventCallback("Application stderr output", fApplicationStdErrOutputCallback); oBugId.fAddEventCallback("Application stdout output", fApplicationStdOutOutputCallback); oBugId.fAddEventCallback("Bug report", fBugReportCallback); oBugId.fAddEventCallback("Cdb stderr output", fCdbStdErrOutputCallback); if gbVerbose: oBugId.fAddEventCallback("Cdb stdin input", fCdbStdInInputCallback); oBugId.fAddEventCallback("Cdb stdout output", fCdbStdOutOutputCallback); oBugId.fAddEventCallback("Log message", fLogMessageCallback); oBugId.fAddEventCallback("Failed to apply application memory limits", fFailedToApplyApplicationMemoryLimitsCallback); oBugId.fAddEventCallback("Failed to apply process memory limits", fFailedToApplyProcessMemoryLimitsCallback); oBugId.fAddEventCallback("Failed to debug application", fFailedToDebugApplicationCallback); oBugId.fAddEventCallback("Internal exception", fInternalExceptionCallback); oBugId.fAddEventCallback("License warnings", fLicenseWarningsCallback); oBugId.fAddEventCallback("License errors", fLicenseErrorsCallback); oBugId.fAddEventCallback("Page heap not enabled", fPageHeapNotEnabledCallback); oBugId.fAddEventCallback("Cdb ISA not ideal", fCdbISANotIdealCallback); oBugId.fAddEventCallback("Process attached", fProcessAttachedCallback); oBugId.fAddEventCallback("Process started", fProcessStartedCallback); oBugId.fAddEventCallback("Process terminated", fProcessTerminatedCallback); if dxConfig["nApplicationMaxRunTimeInSeconds"] is not None: oBugId.foSetTimeout("Maximum application runtime", dxConfig["nApplicationMaxRunTimeInSeconds"], \ fApplicationMaxRunTimeCallback); if dxConfig["bExcessiveCPUUsageCheckEnabled"] and dxConfig["nExcessiveCPUUsageCheckInitialTimeoutInSeconds"]: oBugId.fSetCheckForExcessiveCPUUsageTimeout(dxConfig["nExcessiveCPUUsageCheckInitialTimeoutInSeconds"]); guDetectedBugsCount = 0; oBugId.fStart(); oBugId.fWait(); if gbAnErrorOccured: if fCleanup: # Call cleanup after runnning the application, before exiting BugId oConsole.fStatus("* Cleaning up application state..."); fCleanup(); oConsole.fCleanup(); os._exit(3); if guDetectedBugsCount == 0: oConsole.fPrint(u"\u2500\u2500 The application terminated without a bug being detected ", sPadding = u"\u2500"); gduNumberOfRepros_by_sBugIdAndLocation.setdefault("No crash", 0); gduNumberOfRepros_by_sBugIdAndLocation["No crash"] += 1; if gbVerbose: oConsole.fPrint(" Application time: %s seconds" % (long(oBugId.fnApplicationRunTimeInSeconds() * 1000) / 1000.0)); nOverheadTimeInSeconds = time.clock() - nStartTimeInSeconds - oBugId.fnApplicationRunTimeInSeconds(); oConsole.fPrint(" BugId overhead: %s seconds" % (long(nOverheadTimeInSeconds * 1000) / 1000.0)); if uNumberOfRepeats is not None: uNumberOfRepeats -= 1; if uNumberOfRepeats == 0: bRepeat = False; if not bRepeat: if fCleanup: # Call cleanup after runnning the application, before exiting BugId oConsole.fStatus("* Cleaning up application state..."); fCleanup(); oConsole.fCleanup(); os._exit(guDetectedBugsCount > 0 and 1 or 0); sStatistics = ""; auOrderedNumberOfRepros = sorted(list(set(gduNumberOfRepros_by_sBugIdAndLocation.values()))); auOrderedNumberOfRepros.reverse(); for uNumberOfRepros in auOrderedNumberOfRepros: for sBugIdAndLocation in gduNumberOfRepros_by_sBugIdAndLocation.keys(): if gduNumberOfRepros_by_sBugIdAndLocation[sBugIdAndLocation] == uNumberOfRepros: sStatistics += "%d \xD7 %s (%d%%)\r\n" % (uNumberOfRepros, str(sBugIdAndLocation), \ round(100.0 * uNumberOfRepros / uRunCounter)); if dxConfig["sReportFolderPath"] is not None: sStatisticsFilePath = os.path.join(dxConfig["sReportFolderPath"], sValidStatisticsFileName); else: sStatisticsFilePath = sValidStatisticsFileName; oStatisticsFile = None; try: oStatisticsFile = mFileSystem2.foGetOrCreateFile(sStatisticsFilePath); oStatisticsFile.fWrite(sStatistics); except Exception as oException: oConsole.fPrint(" Statistics: ", ERROR, "Cannot be saved (", ERROR_INFO, str(oException), ERROR, ")"); else: oConsole.fPrint(" Statistics: ", INFO, sStatisticsFilePath, NORMAL, " (%d bytes)" % len(sStatistics)); if oStatisticsFile: oStatisticsFile.fClose(); oConsole.fPrint(); # and loop raise AssertionError("Not reached!");