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

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

文章目录

  • 未处理异常
  • 内核态的未处理异常
  • 用户态的未处理异常
    • BaseProcessStart中的SEH处理器
    • 编译器插入的SEH处理器
    • 基于信号的异常处理
    • BaseThreadStart中的SEH处理器
  • UnhandledExceptionFilter
    • JIT调试
    • 应用程序错误对话框
    • 源码剖析

未处理异常

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

系统只有在第一轮分发时,才会把异常分发给用户代码注册的异常处理器,第二轮分发时并不会这么做。因此,对于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  _MTif ( !_heap_init(1) ) // 初始化堆
#elseif ( !_heap_init(0) ) // 初始化堆
#endiffast_error_exit(_RT_HEAPINIT);
#ifdef  _MTif( !_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);
#endifinitret = _cinit();   // 初始化C Dataif (initret != 0)_amsg_exit(initret);
#ifdef  _WINMAIN_StartupInfo.dwFlags = 0;GetStartupInfo( &StartupInfo );
#ifdef  WPRFLAGlpszCommandLine = _wwincmdln();mainret = wWinMain(
#elselpszCommandLine = _wincmdln();mainret = WinMain(
#endifGetModuleHandleA(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 tracePrintStackTrace(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=PA115if (!(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;
}

Windows中的未处理异常相关推荐

  1. windows核心编程---未处理异常,向量化异常处理与C++异常

    -未处理异常 异常过滤返回EXCEPTION_CONTINUE_SEARCH,向上搜索,但无法搜索到处理部分,产生未处理异常. // 负责处理未处理异常 // 进程初始化时,设置处理未处理异常过滤函数 ...

  2. 无法启动此程序,因为计算机中丢失iathook.dll,通过IAT+Hook调试Windows自定义未处理异常过滤器.pdf...

    2009年第12期 计算机系统应用 Hoo n 通过IAT d k调试Wiows自定义未处理 异常过滤器① WindowsCustomFiltersforUnhandled IATHook Debug ...

  3. Windows核心编程 第2 5章 未处理异常和C ++异常(上)

    未处理异常和C + +异常(上) 前一章讨论了当一个异常过滤器返回 E X C E P T I O N _ C O N T I N U E _ S E A R C H时会发生什么事情.返回EXCEPT ...

  4. 未处理异常和C++异常——Windows核心编程学习手札之二十五

    未处理异常和C++异常 --Windows核心编程学习手札之二十五 当一个异常过滤器返回EXCEPTION_CONTINUE_SEARCH标识符时是告诉系统继续上溯调用树,寻找另外的异常过滤器,但当每 ...

  5. Windows异常学习笔记(五)—— 未处理异常

    Windows异常学习笔记(五)-- 未处理异常 要点回顾 最后一道防线 实验一:理解最后一道防线 实验二:新线程的最后一道防线 总结 UnhandledExceptionFilter 实验三:理解U ...

  6. 《Windows核心编程》读书笔记二十五章 未处理异常,向量化异常处理与C++异常

    第二十五章  未处理异常,向量化异常处理与C++异常 本章内容 25.1 UnhandledExceptionFilter函数详解 25.2 即时调试 25.3 电子表格示例程序 25.4 向量化异常 ...

  7. 了解Java中的检查与未检查异常

    约书亚·布洛赫(Joshua Bloch)在< 有效的Java >中说 将检查的异常用于可恢复的条件,将运行时异常用于编程错误(第二版中的项目58) 让我们看看我是否正确理解了这一点. 这 ...

  8. java未检查异常_Java中已检查和未检查的异常

    java未检查异常 Java有两种类型的异常-已检查和未检查. 简而言之,选中的是指开发人员可以从异常中合理恢复的情况,而未选中的异常是无法处理的编程错误. 本文介绍了何时使用哪种. 但这不是那么简单 ...

  9. odbc驱动程序管理器连接未打开_Windows 10 怎么修复 Windows 中的 Wi-Fi 连接问题,我教你...

    解决 Windows 中的网络问题 如果您无法获取电子邮件.浏览 web 或流式传输音乐,则很可能您未连接到您的网络,并且无法进入互联网. 若要解决此问题,可以尝试以下操作. Windows 10 首 ...

最新文章

  1. learnByWork
  2. 马斯克翻跟头机器人_马斯克又搞事情 用VR训练机器人模仿人类动作
  3. wx:if 与hidden
  4. 【深入理解JS核心技术】3. 调用、应用和绑定有什么区别
  5. 柔性传感器——多源信息融合
  6. STC8H开发(十四): I2C驱动RX8025T高精度实时时钟芯片
  7. 黑马程序员——Java基础--IO流(一)---File类以及其他流对象
  8. 目前以太网主要采用什么连接计算机,目前以太网主要采用______连接计算机,计算机能够独享带宽...
  9. 前端定期小复盘, 每期都有小收获(一)
  10. C/C++语言二维数组的传参方法总结
  11. EXCEl2013 创建下拉菜单
  12. 教你怎么更好的学习嵌入式开发
  13. 线段树的建树 单点修改 区间查询
  14. 蓝桥杯: Cowboys
  15. 贵州厉害的计算机学校,贵州2021年计算机学校好点的大专学校
  16. 【全志T113-S3_100ask】SD卡扩容
  17. 当代女画家扬笛人物画《怒放丝路20》赏析
  18. 开有多个门店的小老板如何记账和给员工发工资?
  19. 数码视讯Q5刷 armbian Ubuntu
  20. 一次线上运营活动实操

热门文章

  1. 细说中国富豪的9种发家类型
  2. 备战金九银十,腾讯 T4 梳理 2022 年最全 999 道 Java 岗必备面试题答案
  3. JAVA第三方工具类
  4. 一款内网横向渗透辅助工具
  5. msfvenom基本介绍
  6. ecshop数据结构
  7. sqlmap介绍和用法
  8. opencv微信二维码引擎的使用(for java)
  9. SMTP命令 发送邮件 DOS命令
  10. 全球5G市场最新进展及未来展望