Windows中的未处理异常

上一篇文章Windows对异常的管理详细介绍了Windows中对异常的分发和处理过程。
我们知道,Windows会给每个异常最多两轮被处理的机会:

  • 第一轮:先尝试发给调试器,如果调试器没有处理,则遍历并依次调用异常处理器。如果一个处理器返回EXCEPTION_CONTINUE_EXECUTION,则表示它已经处理了异常,并让系统恢复执行因为异常而中断的代码;如果返回EXCEPTION_CONTINUE_SEARCH,则表示它不能处理该异常,并让系统继续寻找其他的异常处理器。
  • 第二轮:再次发给调试器,如果还没有处理,则通过KeBugCheckEx触发蓝屏。

未处理异常

未处理异常:系统遍历了所有的异常处理器都处理不了的异常。

系统只有在第一轮分发时,才会把异常分发给用户代码注册的异常处理器,第二轮分发时并不会这么做。因此,对于SEH或VEH异常处理器来说,只有一轮处理异常的机会。如果在第一轮异常分发的过程中一个异常没有被处理,那么它便称为未处理异常,进入到第二轮分发的异常都属于未处理异常。

根据程序的运行模式,把发生在驱动程序等内核态模块中的未处理异常称为内核态的未处理异常,把发生在应用程序中的未处理异常称为用户态的未处理异常

内核态的未处理异常

Windows对它的处理很简单:

  • 若有内核调试器存在:系统(KiExceptionDispatch)会给调试器第二轮处理机会;
  • 若调试器没有处理该异常或者根本没有内核调试器:系统便会调用KeBugCheckEx发起蓝屏机制,报告错误并停止整个系统,其停止码为KMODE_EXCEPTION_NOT_HANDLED

Windows这么做的理由是,它把内核态执行的代码看作是系统信任的代码,这些代码应该是经过缜密设计和认真测试过的,因此,一旦在信任代码中发生未处理异常,那么一定是发生了事先没有估计到的严重问题,蓝屏终止可让系统以可控的方式停止工作,防止其继续运行造成更大的损失。

用户态的未处理异常

对于用户态的未处理异常,Windows使用系统登记的默认异常处理器来处理。
Windows为应用程序的每个线程都设置了默认的SEH异常处理器,此外编译器在编译时插入的启动函数通常也会注册一个SEH处理器。对于使用C运行库的程序,C运行库包含了基于信号的异常处理器机制。
当应用程序内的代码没有处理异常时,系统会使用这些默认的异常处理器来处理异常。

BaseProcessStart中的SEH处理器

首先介绍一下Windows启动一个程序的大体过程:
不论是通过双击程序文件、键入程序命令,还是在程序中调用CreateProcess来启动一个程序,其内部过程都是类似的。

  1. 打开要执行的程序映像文件,创建section对象用于将文件映射到内存中。
  2. 建立进程运行所需的各种数据结构(EPROCESSKPROCESSPEB)和地址空间。
  3. 调用NtCreateThread创建处于挂起状态的初始线程,将用户态的初始地址存储在ETHREAD结构中。
  4. 通知Windows子系统注册新的进程。
  5. 开始执行初始线程。
  6. 在新进程的上下文中对进程做最后的初始化工作。

3点决定了初始线程的起始地址,也就是新线程开始在用户态正式运行时的起始地址。Windows程序的PE文件中登记了程序的入口地址,即IMAGE_OPTIONAL_HEADER->AddressOfEntryPoint字段。但是在创建Windows子系统进程时,系统通常并不把这个地址用作新线程的起始地址,而是把起始地址指向kernel32.dll中的进程启动函数BaseProcessStart

原因就是要注册一个默认的SEH处理器,它是初始化线程中注册的第一个异常处理器,但它是最后得到处理机会的。只有当应用程序自己设计的代码没有处理异常时,这个默认的SEH处理器才会得到处理机会。
在这里插入图片描述

编译器插入的SEH处理器

BaseProcessStart的参数lpStartAddress指向的入口地址并不是mainWinMain函数的地址,因为在执行这些函数前,还有很多准备工作要做,比如初始化C运行库、初始化全局变量、初始化C运行库所使用的堆、准备命令行参数等。为了做这些准备工作,编译器通常会把自身提供的一个启动函数登记为程序的入口,让系统先执行这个启动函数,这个启动函数内部再调用用户编写的main或WinMain函数。
通常是:mainCRTStartupwmainCRTStartupWinMainCRTStartupwWinMainCRTStartup

我们看一下这些启动函数的源码实现,太长了不好截图,就直接粘过来了。
可以发现在它里面也注册了一个SEH处理器,这是应用程序初始线程的第二个异常处理器。

#ifdef  _WINMAIN_
#ifdef  WPRFLAG
int wWinMainCRTStartup(
#else
int WinMainCRTStartup(
#endif
#else   /* ndef _WINMAIN_ */
#ifdef  WPRFLAG
int wmainCRTStartup(
#else
int mainCRTStartup(
#endif
#endif  /* _WINMAIN_ */
        void)
{
    
    
        int initret;
        int mainret;
        OSVERSIONINFOA *posvi;
        int managedapp;
#ifdef  _WINMAIN_
        _TUCHAR *lpszCommandLine;
        STARTUPINFO StartupInfo;
#endif
		// 动态申请一块缓冲区,避免触发/GS缓冲区溢出检测
        posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));
        posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
        // 获得Win32版本信息
        (void)GetVersionExA(posvi);
        _osplatform = posvi->dwPlatformId;
        _winmajor = posvi->dwMajorVersion;
        _winminor = posvi->dwMinorVersion;

        _osver = (posvi->dwBuildNumber) & 0x07fff;
        if ( _osplatform != VER_PLATFORM_WIN32_NT )
            _osver |= 0x08000;
        _winver = (_winmajor << 8) + _winminor;

        // 确定是否是管理员程序
        managedapp = check_managed_app();
#ifdef  _MT
        if ( !_heap_init(1) ) // 初始化堆
#else
        if ( !_heap_init(0) ) // 初始化堆
#endif
            fast_error_exit(_RT_HEAPINIT);
#ifdef  _MT
        if( !_mtinit() )  // 初始化多线程
            fast_error_exit(_RT_THREAD);
#endif
#ifdef  _RTC
        _RTC_Initialize();	// 初始化运行时检查
#endif
        // 注册一个SEH处理器,保护初始化代码的剩余部分,以及调用main或WinMain
        __try {
    
    
            if ( _ioinit() < 0 )            // 初始化lowio
                _amsg_exit(_RT_LOWIOINIT);
#ifdef  WPRFLAG
            // 获得命令行信息(双字)
            _wcmdln = (wchar_t *)__crtGetCommandLineW();
            // 获得环境变量信息(双字)
            _wenvptr = (wchar_t *)__crtGetEnvironmentStringsW();
            if ( _wsetargv() < 0 )
                _amsg_exit(_RT_SPACEARG);
            if ( _wsetenvp() < 0 )
                _amsg_exit(_RT_SPACEENV);
#else
            // 获得命令行信息(单字)
            _acmdln = (char *)GetCommandLineA();
			// 获得环境变量信息(单字)
            _aenvptr = (char *)__crtGetEnvironmentStringsA();
            if ( _setargv() < 0 )
                _amsg_exit(_RT_SPACEARG);
            if ( _setenvp() < 0 )
                _amsg_exit(_RT_SPACEENV);
#endif
			initret = _cinit();   // 初始化C Data
            if (initret != 0)
                _amsg_exit(initret);
#ifdef  _WINMAIN_
            StartupInfo.dwFlags = 0;
            GetStartupInfo( &StartupInfo );
#ifdef  WPRFLAG
            lpszCommandLine = _wwincmdln();
            mainret = wWinMain(
#else
            lpszCommandLine = _wincmdln();
            mainret = WinMain(
#endif
                               GetModuleHandleA(NULL),
                               NULL,
                               lpszCommandLine,
                               StartupInfo.dwFlags & STARTF_USESHOWWINDOW
                                    ? StartupInfo.wShowWindow
                                    : SW_SHOWDEFAULT
                             );
#else   /* _WINMAIN_ */
#ifdef  WPRFLAG
            __winitenv = _wenviron;
            mainret = wmain(__argc, __wargv, _wenviron);
#else
            __initenv = _environ;
            mainret = main(__argc, __argv, _environ);
#endif
#endif  /* _WINMAIN_ */
            if ( !managedapp )
                exit(mainret);
            _cexit();
        }
        __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
        {
    
    
            mainret = GetExceptionCode();
            if ( !managedapp )
                _exit(mainret);
            _c_exit();
        } /* end of try - except */
        return mainret;
}

基于信号的异常处理

在编译器入口函数的SEH处理器的__except中,过滤表达式的内容是调用_XcptFilter函数。它内部调用了UnhandledExceptionFilter
在这里插入图片描述
这里涉及到的是C运行库中基于信号的异常处理器,主要是为了兼容来自UNIX系统的软件才存在的,对于Win32程序不起什么作用,我们暂不做详细介绍了。

BaseThreadStart中的SEH处理器

对于初始线程之外的其他线程,其用户态的起始地址是系统提供的另一个位于kernel32.dll中的函数BaseThreadStart,它内部也注册了一个SEH处理器。
在这里插入图片描述

UnhandledExceptionFilter

我们总结这几个默认的SEH处理器的过滤表达式,发现它们最终都调用了UnhandledExceptionFilter函数。

函数 过滤表达式 内部调用
BaseProcessStartup BaseExceptionFilter UnhandledExceptionFilter
mainCRTStartup _XcptFilter UnhandledExceptionFilter
BaseThreadStartup BaseThreadExceptionFilter UnhandledExceptionFilter

UnhandledExceptionFilter函数才是真正的大BOSS!!!它的源码也很长,在看源码之前,先介绍几个概念。

JIT调试

JIT调试:在应用程序出现严重错误后而启动的紧急调试。
因为JIT调试建立时,被调试的应用程序内已经发生了严重的错误。通常都无法再恢复正常运行,所以JIT调试又被称为事后调试(Postmortem Debugging)。

JIT调试的主要目的是分析和定位错误原因,或者收集和记录错误发生时的现场数据供事后分析。很多调试器都可以作为JIT调试器来使用,Dr.Watson是Windows系统中默认的JIT调试器。

关于JIT调试器的配置信息被保存在注册表的如下键中:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
AeDebug(AE指Application Error)键下通常包括三个键值:AutoDebuggerUserDebugger-HotKey
在这里插入图片描述

  • Auto:决定是否自动启动JIT调试器。也就是当有未处理异常发生时,是先询问用户还是直接启动JIT调试器。它的有效值只有"0""1"两种,如果该值为"1"Debugger非空,那么系统就会直接启动JIT调试器;否则系统会先显示应用程序错误对话框,等用户选择调试选项后,再启动JIT调试器。
  • Debugger:用来定义启动JIT调试器的命令行。如果该项存在,那么在应用程序错误对话框内就会包含调试取消按钮,供用户选择是否调试发生错误的程序;如果不存在或为空,那么弹出的对话框中就不包含调试或取消按钮。
  • UserDebuggerHotKey:用来定义中断到调试器的热键(默认为F12)。

应用程序错误对话框

当Windows检测到应用程序内发生了未处理异常或其他严重错误时,系统的策略是将其终止。在终止前,系统通常会弹出一个对话框来通知用户这个程序即将被关闭,这个对话框称为应用程序错误对话框(Application Fault Dialog)或GPF错误框(General Protection Fault)。

源码剖析

有了这两个概念之后,我们来看看UnhandledExceptionFilter函数的源码实现,详情见注释:

LONG
WINAPI
UnhandledExceptionFilter(IN PEXCEPTION_POINTERS ExceptionInfo)
{
    
    
    static UNICODE_STRING AeDebugKey =
        RTL_CONSTANT_STRING(L"\\Registry\\Machine\\" REGSTR_PATH_AEDEBUG);
    static BOOLEAN IsSecondChance = FALSE;
    /* Exception data */
    NTSTATUS Status;
    PEXCEPTION_RECORD ExceptionRecord = ExceptionInfo->ExceptionRecord;
    LPTOP_LEVEL_EXCEPTION_FILTER RealFilter;
    LONG RetValue
    /* Debugger and hard error parameters */
    HANDLE DebugPort = NULL;
    ULONG_PTR ErrorParameters[4];
    ULONG DebugResponse, ErrorResponse;
    /* Post-Mortem "Auto-Execute" (AE) Debugger registry data */
    HANDLE KeyHandle;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING ValueString;
    ULONG Length;
    UCHAR Buffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
    PKEY_VALUE_PARTIAL_INFORMATION PartialInfo = (PVOID)Buffer;
    BOOLEAN AeDebugAuto = FALSE;
    PWCHAR AeDebugPath = NULL;
    WCHAR AeDebugCmdLine[MAX_PATH];
    /* Debugger process data */
    BOOL Success;
    HRESULT hr;
    ULONG PrependLength;
    HANDLE hDebugEvent;
    HANDLE WaitHandles[2];
    STARTUPINFOW StartupInfo;
    PROCESS_INFORMATION ProcessInfo;

    // 如果是一个嵌套异常,直接结束进程
    if (ExceptionRecord->ExceptionFlags & EXCEPTION_NESTED_CALL)
    {
    
    
        NtTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);
        return EXCEPTION_EXECUTE_HANDLER;
    }
	// 处理非法访问异常
    if ((ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) &&
        (ExceptionRecord->NumberParameters >= 2))
    {
    
    
        switch (ExceptionRecord->ExceptionInformation[0])
        {
    
    
            case EXCEPTION_WRITE_FAULT:	// 写错误
            {
    
    
                // 更改一些写保护,一些InstallShield 设置会有这个错
                RetValue = BasepCheckForReadOnlyResource(
                    (PVOID)ExceptionRecord->ExceptionInformation[1]);
                if (RetValue == EXCEPTION_CONTINUE_EXECUTION)
                    return EXCEPTION_CONTINUE_EXECUTION;
                break;
            }
            case EXCEPTION_EXECUTE_FAULT:
                /* FIXME */
                break;
        }
    }
    // 如果该进程正在被调试(DebugPort不为空),则不处理,让系统继续分发(发给调试器)
    Status = NtQueryInformationProcess(NtCurrentProcess(),
                                       ProcessDebugPort,
                                       &DebugPort,
                                       sizeof(DebugPort),
                                       NULL);
    if (NT_SUCCESS(Status) && DebugPort)
    {
    
    
        DPRINT("Passing exception to debugger\n");
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
    RealFilter = RtlDecodePointer(GlobalTopLevelExceptionFilter);
    if (RealFilter)
    {
    
    
        RetValue = RealFilter(ExceptionInfo);
        if (RetValue != EXCEPTION_CONTINUE_SEARCH)
            return RetValue;
    }

    // ReactOS-specific: DPRINT a stack trace
    PrintStackTrace(ExceptionInfo);

    /*
     * Now pop up an error if needed. Check both the process-wide (Win32)
     * and per-thread error-mode flags (NT).
     */
    if ((GetErrorMode() & SEM_NOGPFAULTERRORBOX) ||
        (RtlGetThreadErrorMode() & RTL_SEM_NOGPFAULTERRORBOX))
    {
    
    
        // 不显示弹出的错误框,只是将控制转移到异常处理程序
        return EXCEPTION_EXECUTE_HANDLER;
    }
    
    // 保存异常代码和异常地址
    ErrorParameters[0] = (ULONG_PTR)ExceptionRecord->ExceptionCode;
    ErrorParameters[1] = (ULONG_PTR)ExceptionRecord->ExceptionAddress;
    if (ExceptionRecord->ExceptionCode == STATUS_IN_PAGE_ERROR)	// 页错误
    {
    
    
        // 获取导致异常的底层状态代码,暂时不考虑导致异常的操作类型(读/写)
        ErrorParameters[2] = ExceptionRecord->ExceptionInformation[2];
    }
    else // if (ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) or others...
    {
    
    
        // 获得导致非法访问的操作类型
        ErrorParameters[2] = ExceptionRecord->ExceptionInformation[0];
    }
    // 保存发生错误的地址
    ErrorParameters[3] = ExceptionRecord->ExceptionInformation[1];
    
    // 准备硬错误对话框:默认只有OK,以防我们没有任何调试器可用
    DebugResponse = OptionOk;
    AeDebugAuto = FALSE;

    /*
     * Retrieve Post-Mortem Debugger settings from the registry, under:
     * HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
     * (REGSTR_PATH_AEDEBUG).
     */
    // 准备JIT调试(事后调试)相关事项
    InitializeObjectAttributes(&ObjectAttributes,
                               &AeDebugKey,
                               OBJ_CASE_INSENSITIVE,
                               NULL,
                               NULL);
    Status = NtOpenKey(&KeyHandle, KEY_QUERY_VALUE, &ObjectAttributes);
    if (NT_SUCCESS(Status))
    {
    
    
        /*
         * 读'Auto'的键值:
         * "0" (or any other value): 提示用户启动调试器.
         * "1": 不提示,直接启动调试器.
         */
        RtlInitUnicodeString(&ValueString, REGSTR_VAL_AEDEBUG_AUTO);
        Status = NtQueryValueKey(KeyHandle,
                                 &ValueString,
                                 KeyValuePartialInformation,
                                 PartialInfo,
                                 sizeof(Buffer),
                                 &Length);
        if (NT_SUCCESS(Status) && (PartialInfo->Type == REG_SZ))
        {
    
    
            AeDebugAuto = (*(PWCHAR)PartialInfo->Data == L'1');
        }
        else
        {
    
    
            AeDebugAuto = FALSE;
        }
        // 读'Debugger'的键值, 其格式一般为: C:\dbgtools\ntsd.exe -p %ld -e %ld -g
        // 第一参数是进程ID, 第二参数是通知事件句柄
        RtlInitUnicodeString(&ValueString, REGSTR_VAL_AEDEBUG_DEBUGGER);
        Status = NtQueryValueKey(KeyHandle,
                                 &ValueString,
                                 KeyValuePartialInformation,
                                 PartialInfo,
                                 sizeof(Buffer),
                                 &Length);
        if (NT_SUCCESS(Status) && (PartialInfo->Type == REG_SZ))
        {
    
    
            AeDebugPath = (PWCHAR)PartialInfo->Data;
            // 跳过任何前置空格
            while (  *AeDebugPath &&
                   ((*AeDebugPath == L' ') ||
                    (*AeDebugPath == L'\t')) ) // iswspace(*AeDebugPath)
            {
    
    
                ++AeDebugPath;
            }
            if (*AeDebugPath)
            {
    
    
                // 我们有调试器路径,则弹出应用错误对话框,让用户选择是否调试程序
                DebugResponse = OptionOkCancel;
            }
            else
            {
    
    
                AeDebugPath = NULL;
            }
        }
        else
        {
    
    
            AeDebugPath = NULL;
        }
        NtClose(KeyHandle);
    }
    TODO: Start a ReactOS Fault Reporter (unimplemented!)
    //
    // For now we are doing the "old way" (aka Win2k), that is also the fallback
    // case for Windows XP/2003 in case it does not find faultrep.dll to display
    // the nice "Application Error" dialog box: We use a hard error to communicate
    // the problem and prompt the user to continue debugging the application or
    // to terminate it.
    //
    // Since Windows XP/2003, we have the ReportFault API available.
    // See http://www.clausbrod.de/twiki/pub/Blog/DefinePrivatePublic20070616/reportfault.cpp
    // and https://msdn.microsoft.com/en-us/library/windows/desktop/bb513616(v=vs.85).aspx
    // and the legacy ReportFault API: https://msdn.microsoft.com/en-us/library/windows/desktop/bb513615(v=vs.85).aspx
    //
    // NOTE: Starting Vista+, the fault API is constituted of the WerXXX functions.
    //
    // Also see Vostokov's book "Memory Dump Analysis Anthology Collector's Edition, Volume 1"
    // at: https://books.google.fr/books?id=9w2x6NHljg4C&pg=PA115&lpg=PA115
	
    if (!(AeDebugPath && AeDebugAuto))
    {
    
    
        // Debugger值为NULL, 或者Auto值为"0", 则显示硬错误, 不启动JIT调试
        // 对于第一次机会,允许继续或调试; 对于第二次机会,调试或终止进程。 
        Status = NtRaiseHardError(STATUS_UNHANDLED_EXCEPTION | HARDERROR_OVERRIDE_ERRORMODE,
                                  4,
                                  0,
                                  ErrorParameters,
                                  (!IsSecondChance ? DebugResponse : OptionOk),
                                  &ErrorResponse);
    }
    else
    {
    
    
        Status = STATUS_SUCCESS;
        ErrorResponse = (AeDebugPath ? ResponseCancel : ResponseOk);
    }
    // 如果用户选择不调试,或者是第二次机会,则直接结束进程
    if (!NT_SUCCESS(Status) || (ErrorResponse != ResponseCancel) || IsSecondChance)
        goto Quit;
    // 如果异常来自CSR服务器,则终止它
    if (BaseRunningInServerProcess)
    {
    
    
        IsSecondChance = TRUE;
        goto Quit;
    }
    /*
     * Attach a debugger to this process. The debugger process is given
     * the process ID of the current process to be debugged, as well as
     * a notification event handle. After being spawned, the debugger
     * initializes and attaches to the process by calling DebugActiveProcess.
     * When the debugger is ready, it signals the notification event,
     * so that we can give it control over the process being debugged,
     * by passing it the exception.
     *
     * See https://msdn.microsoft.com/en-us/library/ms809754.aspx
     * and http://www.debuginfo.com/articles/ntsdwatson.html
     * and https://sourceware.org/ml/gdb-patches/2012-08/msg00893.html
     * for more details.
     */
	// 走到这里代表自动开启调试 或 用户选择调试, 下面为开启调试做准备
	
    // 为调试器创建一个可继承的通知调试事件对象
    InitializeObjectAttributes(&ObjectAttributes,
                               NULL,
                               OBJ_INHERIT,
                               NULL,
                               NULL);
    Status = NtCreateEvent(&hDebugEvent,
                           EVENT_ALL_ACCESS,
                           &ObjectAttributes,
                           NotificationEvent,
                           FALSE);
    if (!NT_SUCCESS(Status))
        hDebugEvent = NULL;
    Success = FALSE;

    /*
     * We will add two longs (process ID and event handle) to the command
     * line. The biggest 32-bit unsigned int (0xFFFFFFFF == 4.294.967.295)
     * takes 10 decimal digits. We then count the terminating NULL.
     */
    // 将进程ID和事件句柄加到调试器命令行中
    Length = wcslen(AeDebugPath) + 2*10 + 1;
	// 检查Debugger路径是不是一个相对路径
    if ((*AeDebugPath != L'"') &&
        (RtlDetermineDosPathNameType_U(AeDebugPath) == RtlPathTypeRelative))
    {
    
    
        // 如果是相对路径, 则在前面追加"SystemRoot\System32"
        PrependLength = wcslen(SharedUserData->NtSystemRoot) + 10 // == wcslen(L"\\System32\\");
        if (PrependLength + Length <= ARRAYSIZE(AeDebugCmdLine))
        {
    
    
            hr = StringCchPrintfW(AeDebugCmdLine,
                                  PrependLength + 1,
                                  L"%s\\System32\\",
                                  SharedUserData->NtSystemRoot);
            Success = SUCCEEDED(hr);
        }
    }
    else
    {
    
    
        // 全路径
        PrependLength = 0;
        if (Length <= ARRAYSIZE(AeDebugCmdLine))
            Success = TRUE;
    }

    // 格式化命令行,组成真正的启动路径
    if (Success)
    {
    
    
        hr = StringCchPrintfW(&AeDebugCmdLine[PrependLength],
                              Length,
                              AeDebugPath,
                              HandleToUlong(NtCurrentTeb()->ClientId.UniqueProcess), // GetCurrentProcessId()
                              hDebugEvent);
        Success = SUCCEEDED(hr);
    }

    // 启动调试器
    if (Success)
    {
    
    
        DPRINT1("\nStarting debugger: '%S'\n", AeDebugCmdLine);
        RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
        RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
        StartupInfo.cb = sizeof(StartupInfo);
        StartupInfo.lpDesktop = L"WinSta0\\Default";
        Success = CreateProcessW(NULL,
                                 AeDebugCmdLine,
                                 NULL, NULL,
                                 TRUE, 0,
                                 NULL, NULL,
                                 &StartupInfo, &ProcessInfo);
    }
    if (Success)
    {
    
    
        WaitHandles[0] = hDebugEvent;
        WaitHandles[1] = ProcessInfo.hProcess;

        // 循环等待,给调试器足够的时间进行初始化和完成各种准备工作
        do
        {
    
    
            // 可提醒的等待,当JIT调试器成功附加到目标进程并准备就绪后,JIT调试器就会SetEvent,等待函数就会返回
            Status = NtWaitForMultipleObjects(ARRAYSIZE(WaitHandles),
                                              WaitHandles,
                                              WaitAny,
                                              TRUE, NULL);
        } while ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC));

        // 调试器意外终止或无法将调试器附加到目标进程,则终止进程
        if (Status == STATUS_WAIT_1)
        {
    
    
            // 确定没有其他调试器正在调试目标进程
            Status = NtQueryInformationProcess(NtCurrentProcess(),
                                               ProcessDebugPort,
                                               &DebugPort,
                                               sizeof(DebugPort),
                                               NULL);
            if (!NT_SUCCESS(Status) || !DebugPort)
            {
    
    
                /* No debugger is attached, kill the process at next round */
                IsSecondChance = TRUE;
            }
        }
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);
        if (hDebugEvent)
            NtClose(hDebugEvent);
        // 返回EXCEPTION_CONTINUE_SEARCH,让系统继续搜索其他异常处理器,如果当前的SEH不是最后一个,那么系统评估另一个SEH的过滤函数
        // 通常也是UnhandledExceptionFIlter函数,而且也会返回EXCEPTION_CONTINUE_SEARCH,这样第一轮分发就结束了,会发起第二轮。
        // 而此时,JIT调试器已经准备好了,所以第二轮分发时会发给JIT调试器。
        return EXCEPTION_CONTINUE_SEARCH;
    }
    
	// 若无法启动调试器,则关闭事件句柄,终止目标进程
    if (hDebugEvent)
        NtClose(hDebugEvent);
    IsSecondChance = TRUE;
    
Quit:
    // 如果是第二次机会,直接终止进程
    if (IsSecondChance)
        NtTerminateProcess(NtCurrentProcess(), ExceptionRecord->ExceptionCode);

    /* Otherwise allow handling exceptions in first chance */

	// 返回EXCEPTION_EXECUTE_HANDLER 意味着__except块将被执行,通常这将在Terminate进程中结束
    return EXCEPTION_EXECUTE_HANDLER;
}

猜你喜欢

转载自blog.csdn.net/qq_42814021/article/details/121407358