25.1 UnhandledExceptionFilter函数详解

25.1.1 BaseProcessStart伪代码(Kernel32内部)

  1. void BaseProcessStart(PVOID lpfnEntryPoint) //参数为线程函数的入口地址
  2. {
  3. DWORD retValue;
  4. DWORD currentESP;
  5. DWORD exceptionCode;
  6. currentESP = ESP;
  7.  
  8. //lpfnEntryPoint被try/except封装着,这是系统安装的默认的异常处理程序,也是SEH链上最后一个异常处理程序
  9. __try
  10. {
  11. NtSetInformationThread(GetCurrentThread(),
  12. ThreadQuerySetWin32StartAddress,
  13. &lpfnEntryPoint,
  14. sizeof(lpfnEntryPoint));
  15.  
  16. retValue = lpfnEntryPoint();
  17. ExitThread(retValue); //如果异常,线程从这里退出!
  18. }
  19. __except ( //过滤器表达式代码
  20. exceptionCode = GetExceptionInformation(),
  21. UnhandledExceptionFilter(GetExceptionInformation())) //出现异常会调用Unhandled...这个函数,该函数内部会调用
    //用户通过SetUnhandledFilter设置的全局异常处理函数。
  22. {
  23. //如果UnhandledExceptionFilter返回EXCEPTION_EXECUTE_HANDLER,则会控制流会执行到这里
  24. ESP = currentESP;
  25. if (!_BaseRunningInServerProcess) //普通进程,则退出进程
  26. ExitProcess(exceptionCode);
  27. else // 线程是作为服务来运行的,只退出线程并不终止整个服务
  28. ExitThread(exceptionCode);
  29. }
  30. }

(1)如果异常过滤程序返回EXCEPTION_CONTINUE_SEARCH时,系统会继续向外层寻找异常过滤程序。但如果每个异常过滤程序都返回EXCEPTION_CONTINUE_SEARCH时,会未到遇处理异常。

(2)调用SetUnhandledExceptionFilter安装用户提供的全局(顶层)异常过滤回调函数(为所有线程共享)。如果顶层异常回调函数返回EXCEPTION_EXECUTE_HANDLER或EXCEPTION_CONTINUE_SEARCH则直接传递给UnhandledExceptionFilter函数,UnhandledExceptionFilter根据这个返回值判断是终止进程还是重新执行异常代码。如果顶层异常回调函数返回EXCEPTION_CONTINUE_SEARCH,则接下来的要发生的事情就比较复杂(可参考后面的《UnhandledExceptionFilter内部工作流程》)

(3)SetUnhandledExceptionFilter返回值为上次安装的异常过滤程序的地址。如果使用C/C++运行库,则会默认安装一个__CxxUnhandledExceptionFilter过滤程序。该函数首先检查异常是不是C++异常,如果是则在结束时执行abort函数(该函数内部调用了UnhandledExceptionFilter函数,注意这可能会造成循环调用,因为UnhandledExceptionFilter内部调用了我们安装的全局异常过滤函数_CxxUnhandledExceptionFilter,而这个函数的内部又调用UnhandledExceptionFilter,为了防止无限递归调用,_CxxUnhandledExceptionFilter在调用UnhandledExceptionFilter之前会调用SetUnhandledExceptionFilter(NULL))。如果不是C++异常则返回EXCEPTION_CONTINUE_SEARCH。所以当我们调用SetUnhandled*函数,返回的地址为_CxxUnhandledExceptionFilter的地址。

(4)注意,在我们的顶层异常过滤函数里,在返回EXCEPTION_CONTINUE_SEARCH前,不应调用之前的全局异常过滤函数(即我们通过SetUnhandledExceptionFilter的返回值取得的那个函数)。因为如果这个函数是在某个动态库里,那它随时都可能被卸载了。

(5)如果SetUnhandledExceptionFilter(NULL),则取消我们设置的全局异常过滤函数。

【UnhandledExceptionFilter程序】演示设置顶层异常过滤函数

  1. #include <tchar.h>
  2. #include <windows.h>
  3. #include <locale.h>
  4.  
  5. LONG WINAPI MyUnhandledExceptionFilter(
  6. struct _EXCEPTION_POINTERS *lpTopLevelExceptionFilter)
  7. {
  8. _tprintf(_T("发生未处理异常\n"));
  9. _tsystem(_T("PAUSE"));
  10. return EXCEPTION_EXECUTE_HANDLER; //这样返回,进程将被终止。
  11. }
  12.  
  13. int _tmain()
  14. {
  15. SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); //安装用户自定义的未处理异常
  16.  
  17. _tsetlocale(LC_ALL, _T("chs"));
  18. __try{
  19. //SetErrorMode(SEM_NOGPFAULTERRORBOX);
  20.  
  21. *(int*) = ;//引发异常
  22. }
  23. __except (EXCEPTION_CONTINUE_SEARCH){ //这里返回EXCEPTION_CONTINUE_SEARCH,异常就会到达MyUnhandled*
  24.  
  25. }
  26.  
  27. _tsystem(_T("PAUSE"));//这行不会被执行!
  28. return ;
  29. }

25.1.2 UnhandledExceptionFilter内部工作流程

  ①判断是否因为对资源进行写入操作引发的异常。如果是,将资源的只读属性改为可写入,并返回EXCEPTION_CONTINUE_EXECUTION以允许失败的指令再次执行。

  ②确定进程是否被调试。如果被调试,就返回EXCEPTION_CONTINUE_SEARCH给调试器,通知调试器定位异常指令,并告知我们出了什么样的异常。

  ③调用我们设置的顶层异常过滤函数(如果存在的话)。如果顶层过滤函数返回EXCEPT_EXECUTE_HANDLER或EXCEPTION_CONTINUE_EXECUTION,将直接传递给UnhandledExceptionFilter,由它将返回值给系统。如果返回EXCEPT_CONTINUE_SEARCH,则跳到第④步。

  ④再次将未处理异常报告给调试器

  ⑤终止进程:如果线程调用SetErrorMode并设置SEM_NOGPFAULTERRORBOX标志,那么UnhandledExceptFilter会返回EXCEPTION_EXECUTE_HANDLER,在未处理异常的情况下进行全局展开并执行未执行的finally块,然后进程终止。

如果没有调用SetErrorMode函数,UnhandledExceptionFilter会返回EXCEPTION_CONTINUE_SEARCH。于是系统内核得到程序控制,它将通过ALPC(高级本地过程调用)机制将异常通知给WerSvc(Windows错误报告专用服务),然后ALPC先阻塞自己的线程,直到WerSvc执行完毕。

  ⑥UnhandledExceptionFilter与WER的交互

      

当WerSvc接到通知时,会先调用CreateProcess来启动WerFault.exe,然后 WerSvc会等待这个新进程的结束。而WerFault.exe会向我们创建上面的两个对话框以报告错误的发生。当第1个对话框出现时,可以选择“取消”来终止我们的应用程序,否则过一会儿,会弹出第2个对话框,如果我们选择“关闭程序”,则WerFault.exe会调用TerminateProcess来结束我们的应用程序。如果选择“调试”,WerFault.exe会创建一个子进程(调试器),让他附着在出错的程序上进行“即时调试”

25.2 即时调试

(1)默认调试器:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug子项下有一个名为Debugger的值,系统通过个值找到调试器。

(2)WerFault.exe会给这个调试器传入两个参数:要调试的进程ID和继承过来的事件句柄(这个句柄由WerSvc服务创建用于通知被调试进程调试也结束)

(3)通过将调试器附着到被调试进程,可以查看全局、局部和静态变量的值,也可以设置断点,检查函数调用树等调试工作。

【Spreadsheet程序】通过SEH向预订的地址空间稀疏调拨存储器

  1. /************************************************************************
  2. Module: Spreadsheet.cpp
  3. Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
  4. ************************************************************************/
  5. #include "../../CommonFiles/CmnHdr.h"
  6. #include "resource.h"
  7. #include "VMArray.h"
  8. #include <tchar.h>
  9. #include <strsafe.h>
  10.  
  11. //////////////////////////////////////////////////////////////////////////
  12. HWND g_hWnd; //全局的窗口句柄,SEH报告中会用到
  13.  
  14. const int g_nNumRows = ;
  15. const int g_nNumCols = ;
  16.  
  17. //声明单个单元格内容的结构体,每个单元格大小为1024字节
  18. typedef struct{
  19. DWORD dwValue;
  20. BYTE bDummy[];
  21. }CELL,*PCELL;
  22.  
  23. //声明全个电子表格的数据
  24. ////SPREADSHEET类型为一个数组类型,元素类型为CELL及g_nNumRows行g_nNumCols列。
  25. //判读时,可去掉typedef来看。
  26. typedef CELL SPREADSHEET[g_nNumRows][g_nNumCols];
  27. typedef SPREADSHEET* PSPREADSHEET;
  28.  
  29. //////////////////////////////////////////////////////////////////////////
  30. //一个电子表格是一个二维数组的CELLs
  31. class CVMSpreadsheet :public CVMArray<CELL>{
  32. public:
  33. CVMSpreadsheet() :CVMArray<CELL>(g_nNumRows*g_nNumCols){}
  34.  
  35. private:
  36. LONG OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful);
  37. };
  38.  
  39. //////////////////////////////////////////////////////////////////////////
  40. LONG CVMSpreadsheet::OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful){
  41.  
  42. TCHAR sz[];
  43. StringCchPrintf(sz, _countof(sz), TEXT("非法访问:试图在0x%8X进行%s操作!"),pvAddressTouched,
  44. bAttemptedRead ? TEXT("读取") : TEXT("写入"));
  45.  
  46. SetDlgItemText(g_hWnd, IDC_LOG, sz);
  47.  
  48. LONG lDispostion = EXCEPTION_EXECUTE_HANDLER;
  49.  
  50. //只有写入操作发生异常时才会提交物理存储器,读取操作则不会
  51. if (!bAttemptedRead){
  52. //返回基类的返回值
  53. lDispostion = CVMArray<CELL>::OnAccessViolation(pvAddressTouched,
  54. bAttemptedRead, pep, bRetryUntilSuccessful);
  55. }
  56. return (lDispostion);
  57. }
  58.  
  59. //////////////////////////////////////////////////////////////////////////
  60. //产生一个全局CVMSpreadsheet对象
  61. static CVMSpreadsheet g_ssObject;
  62.  
  63. //创建一个全局指针,指向电子表格的区域
  64. SPREADSHEET& g_ss = *(PSPREADSHEET)(PCELL)g_ssObject;
  65.  
  66. //////////////////////////////////////////////////////////////////////////
  67. BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam){
  68.  
  69. chSETDLGICONS(hWnd, IDI_SPREADSHEET);
  70.  
  71. g_hWnd = hWnd; //保存句柄(For SEH错误报告)
  72.  
  73. //设置对话框上面控件的默认值
  74. Edit_LimitText(GetDlgItem(hWnd, IDC_ROW), );
  75. Edit_LimitText(GetDlgItem(hWnd, IDC_COLUMN), );
  76. Edit_LimitText(GetDlgItem(hWnd, IDC_VALUE), );
  77.  
  78. SetDlgItemInt(hWnd, IDC_ROW, , FALSE);
  79. SetDlgItemInt(hWnd, IDC_COLUMN, , FALSE);
  80. SetDlgItemInt(hWnd, IDC_VALUE, , FALSE);
  81.  
  82. return (TRUE);
  83. }
  84.  
  85. //////////////////////////////////////////////////////////////////////////
  86. void Dlg_OnCommand(HWND hWnd, int id, HWND hwndCtrl, UINT codeNotity){
  87. int nRow, nCol;
  88.  
  89. switch (id)
  90. {
  91. case IDCANCEL:
  92. EndDialog(hWnd, id);
  93. break;
  94.  
  95. case IDC_ROW:
  96. //用户修改了行数,更新UI
  97. nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
  98. EnableWindow(GetDlgItem(hWnd, IDC_READCELL), chINRANGE(, nRow, g_nNumRows - ));
  99. EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL), chINRANGE(, nRow, g_nNumRows - ));
  100. break;
  101.  
  102. case IDC_COLUMN:
  103. //用户修改了行数,更新UI
  104. nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);
  105. EnableWindow(GetDlgItem(hWnd, IDC_READCELL), chINRANGE(, nCol, g_nNumCols - ));
  106. EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL), chINRANGE(, nCol, g_nNumCols - ));
  107. break;
  108.  
  109. case IDC_READCELL:
  110. //尝试从用户选择的单元格中读取一个数据
  111. SetDlgItemText(hWnd, IDC_LOG, TEXT("没有发生异常!"));
  112. nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
  113. nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);
  114. __try{
  115. SetDlgItemInt(hWnd, IDC_VALUE, g_ss[nRow][nCol].dwValue, FALSE);
  116. }
  117. //ExceptionFilter返回EXECUTION_CONTINUE_EXECUTE或EXCEPTION_EXECUTE_HANDLER
  118. //如果提交成功,返回前者;失败,返回后者
  119. __except (g_ssObject.ExceptionFilter(GetExceptionInformation(),FALSE)){
  120. //单元格不支持存储,里面不含内容
  121. SetDlgItemText(hWnd, IDC_VALUE, TEXT(""));
  122.  
  123. }
  124. break;
  125.  
  126. case IDC_WRITECELL:
  127. //尝试向用户选择的单元格中写入数据
  128. SetDlgItemText(g_hWnd, IDC_LOG, TEXT("没有发生异常!"));
  129. nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
  130. nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);
  131.  
  132. //假如单元格不支持存储,将抛出非法内存访问,这时将导致自动提交存储器
  133. //这里不设置try/except,则异常会让全局异常(未处理)过滤函数捕获
  134. g_ss[nRow][nCol].dwValue = GetDlgItemInt(hWnd, IDC_VALUE, NULL, FALSE);
  135.  
  136. break;
  137. }
  138. }
  139.  
  140. //////////////////////////////////////////////////////////////////////////
  141. INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
  142. switch (uMsg)
  143. {
  144. chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
  145. chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
  146. }
  147. return (FALSE);
  148. }
  149.  
  150. //////////////////////////////////////////////////////////////////////////
  151. int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd){
  152. DialogBox(hInstance, MAKEINTRESOURCE(IDD_SPREADSHEET), NULL, Dlg_Proc);
  153. return ();
  154. }

//VMArray.h

  1. /************************************************************************
  2. Module: VMArray.h
  3. Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
  4. ************************************************************************/
  5. #pragma once
  6. #include "../../CommonFiles/CmnHdr.h"
  7. #include <tchar.h>
  8.  
  9. #ifndef _M_IX86
  10. #error "The following code only works for x86!"
  11. #endif
  12.  
  13. //////////////////////////////////////////////////////////////////////////
  14. //注意:这个C++类是线程不安全的。不能在多线程下同时创建和销毁该类的实例
  15.  
  16. //但是一旦创建,多线程可同时访问不同的CVMArray对象,也可以通过自己同步的
  17. //方法在多线程下访问同一个CVMArray对象
  18. //////////////////////////////////////////////////////////////////////////
  19.  
  20. template <class TYPE>
  21. class CVMArray{
  22. public:
  23. //为数组各元素预订稀疏的地址空间
  24. CVMArray(DWORD dwreservElements);
  25.  
  26. //释放
  27. virtual ~CVMArray();
  28.  
  29. //允许访问数组中的一个元素
  30. operator TYPE*(){ return (m_pArray); }
  31. operator const TYPE*()const { return (m_pArray); }
  32.  
  33. //若提交失败,可以被优雅的处理
  34. LONG ExceptionFilter(PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful = FALSE);
  35.  
  36. protected:
  37. //虚函数,当非法访问内存时,可优雅的处理
  38. virtual LONG OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead,
  39. PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful);
  40. private:
  41. static CVMArray* sm_pHead; //第一个CVMArray对象
  42. CVMArray* m_pNext; //下一个VCMArray对象
  43. TYPE* m_pArray; //指向一个预订的区域数组
  44. DWORD m_cbReserve; //预订的数组空间的大小
  45.  
  46. private:
  47. //访问前一个未处理异常过滤函数
  48. static PTOP_LEVEL_EXCEPTION_FILTER sm_pfnUnhandledExceptionFilterPrev;
  49.  
  50. //当这个类发生异常,调用我们自己的全局异常过滤函数(VS2005以后微软让
  51. // 对CRT (C 运行时库)的一些与安全相关的代码做了些改动,使得许多错误
  52. //都不能在SetUnhandledExceptionFilter 捕获到。
  53. static LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pep);
  54. void DisableSetUnhandledExceptionFilter();//使SetUnhandledExceptionFilter函数失效
  55.  
  56. //为了达到设置全局异常过滤函数的目的,用向量化
  57. //AddVectoredContinueHandler来达到设置全局异常过滤函数相同的功能
  58. //static LONG WINAPI LastVEHandler(PEXCEPTION_POINTERS pep);
  59. //static PVOID sm_pVEH;
  60.  
  61. };
  62.  
  63. //////////////////////////////////////////////////////////////////////////
  64. //向量化异常过滤函数句柄
  65. //template <class TYPE>
  66. //PVOID CVMArray<TYPE>::sm_pVEH = NULL;
  67.  
  68. //CVMArray对象链表的头
  69. template <class TYPE>
  70. CVMArray<TYPE>* CVMArray<TYPE>::sm_pHead = NULL;
  71.  
  72. //前一个全局异常过滤函数
  73. template <class TYPE>
  74. PTOP_LEVEL_EXCEPTION_FILTER CVMArray<TYPE>::sm_pfnUnhandledExceptionFilterPrev;
  75.  
  76. //////////////////////////////////////////////////////////////////////////
  77. template <class TYPE>
  78. CVMArray<TYPE>::CVMArray(DWORD dwreservElements){
  79. if (sm_pHead == NULL){
  80. //在创建第1个对象前,安装我们的全局异常过滤函数
  81. sm_pfnUnhandledExceptionFilterPrev =
  82. SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
  83. DisableSetUnhandledExceptionFilter();//使SetUnhandledExceptionFilter失效
  84.  
  85. //sm_pVEH = AddVectoredContinueHandler(0, LastVEHandler);
  86. }
  87.  
  88. m_pNext = sm_pHead; //下一次节点初始化为链表头部
  89. sm_pHead = this; //本对象为链表头
  90.  
  91. m_cbReserve = sizeof(TYPE)*dwreservElements;
  92.  
  93. //预订整个数组大小的一块区域
  94. m_pArray = (TYPE*)VirtualAlloc(NULL, m_cbReserve, MEM_RESERVE | MEM_TOP_DOWN,
  95. PAGE_READWRITE);
  96. chASSERT(m_pArray != NULL);
  97. }
  98.  
  99. //////////////////////////////////////////////////////////////////////////
  100. template <class TYPE>
  101. CVMArray<TYPE>::~CVMArray(){
  102. //释放数组所占的空间
  103. VirtualFree(m_pArray, , MEM_RELEASE);
  104.  
  105. //删除链表
  106. CVMArray* p = sm_pHead;
  107. if (p == this){
  108. sm_pHead = p->m_pNext; //删除链表头
  109. }else{
  110. BOOL bFound = FALSE;
  111.  
  112. //遍历链头,并修复指针
  113. for (; !bFound && (p->m_pNext != NULL);p= p->m_pNext){
  114. if (p->m_pNext == this){
  115. p->m_pNext = p->m_pNext->m_pNext;
  116. bFound = TRUE;
  117. break;
  118. }
  119. }
  120. chASSERT(bFound);
  121. }
  122.  
  123. //if (sm_pVEH != NULL)
  124. // RemoveVectoredExceptionHandler(sm_pVEH);
  125. }
  126.  
  127. //////////////////////////////////////////////////////////////////////////
  128. //当非法访问时,默认的异常处理
  129. template <class TYPE>
  130. LONG CVMArray<TYPE>::OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful)
  131. {
  132. BOOL bCommittedStorage = FALSE; //假定提交失败
  133. do{
  134. //提交存储器
  135. bCommittedStorage = (NULL != VirtualAlloc(pvAddressTouched,
  136. sizeof(TYPE),MEM_COMMIT,PAGE_READWRITE));
  137.  
  138. //假如无法提交而我们又试图重试,提醒用户释放内存
  139. if (!bCommittedStorage && bRetryUntilSuccessful)
  140. MessageBox(NULL, TEXT("请关闭一些其他应用,然后按“确定”!"),
  141. TEXT("内存空间不足"),MB_ICONWARNING | MB_OK);
  142.  
  143. } while (!bCommittedStorage && bRetryUntilSuccessful);
  144.  
  145. //当提交存储器,重新执行出错代码。否则执行异常处理程序
  146. return (bCommittedStorage
  147. ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER);
  148. }
  149.  
  150. //////////////////////////////////////////////////////////////////////////
  151. //过滤函数被关联到单一的CVMArray对象
  152. template <class TYPE>
  153. LONG CVMArray<TYPE>::ExceptionFilter(PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful /* = FALSE */){
  154. //默认,提交给其它过滤函数处理(这是一个安全的选择)
  155. LONG lDispostion = EXCEPTION_CONTINUE_SEARCH;
  156.  
  157. //只修改非法访问内存的异常
  158. if (pep->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
  159. return (lDispostion);
  160.  
  161. //获取试图访问的地址,以及读或写异常
  162. //对于EXCEPTION_ACCESS_VIOLATION异常,ExceptionInformation[0]指出非法访问的类型
  163. //0表示线程试图读取不能访问的数据;1表示写入不能访问的数据
  164. PVOID pvAddrTouched = (PVOID)pep->ExceptionRecord->ExceptionInformation[];//非法访问的地址
  165. BOOL bAttempedRead = (pep->ExceptionRecord->ExceptionInformation[] == ); //非法访问的类型
  166.  
  167. //如果试图访问的地址在VMArray的预订的地址空间内
  168. if ((m_pArray <=pvAddrTouched) && (pvAddrTouched<((PBYTE)m_pArray + m_cbReserve))){
  169. //访问这个数组,并尝试解决问题
  170. lDispostion = OnAccessViolation(pvAddrTouched, bAttempedRead, pep, bRetryUntilSuccessful);
  171. }
  172. return (lDispostion);
  173. }
  174.  
  175. //////////////////////////////////////////////////////////////////////////
  176. //template <class TYPE>
  177. //LONG CVMArray<TYPE>::LastVEHandler(PEXCEPTION_POINTERS pep){
  178. // //默认为让其他过滤器处理
  179. // LONG lDispostion = EXCEPTION_CONTINUE_SEARCH;
  180. //
  181. // MessageBox(NULL, TEXT("发生未处理异常"), TEXT("提示"), MB_OK);
  182. //
  183. // //只修改非法访问内存
  184. // if (pep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION){
  185. // //遍历所有链表节点
  186. // for (CVMArray* p = sm_pHead; p != NULL; p = p->m_pNext){
  187. // //询问该节点是否可以修复错误
  188. // //注意:这个错误必须被修复,否进程会被终止
  189. // lDispostion = p->ExceptionFilter(pep, TRUE);
  190. //
  191. // //如果修复了错误,就停止循环
  192. // if (lDispostion != EXCEPTION_CONTINUE_SEARCH)
  193. // break;
  194. // }
  195. // }
  196. // return (lDispostion);
  197. //}
  198.  
  199. //新版本的CRT 实现在异常处理中强制删除所有应用程序先前设置的捕获函数,如下所示:
  200. ///* Make sure any filter already in place is deleted. */
  201. //SetUnhandledExceptionFilter(NULL);
  202. //UnhandledExceptionFilter(&ExceptionPointers);
  203. //解决方法是拦截CRT 调用SetUnhandledExceptionFilter 函数,使之无效
  204. template <class TYPE>
  205. void CVMArray<TYPE>::DisableSetUnhandledExceptionFilter()
  206. {
  207. void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),
  208. "SetUnhandledExceptionFilter");
  209. if (addr)
  210. {
  211. unsigned char code[];
  212. int size = ;
  213. code[size++] = 0x33;
  214. code[size++] = 0xC0;
  215. code[size++] = 0xC2;
  216. code[size++] = 0x04;
  217. code[size++] = 0x00;
  218.  
  219. DWORD dwOldFlag, dwTempFlag;
  220. VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
  221. WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
  222. VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
  223. }
  224. }
  225.  
  226. //////////////////////////////////////////////////////////////////////////
  227. //全局异常过滤函数,为所有CVMArray对象共用
  228. //这个未处理异常很有必须,如果用户忘记用try/except来处理此类对象发生的异常,可以在这里
  229. //进行最后的处理!
  230. template <class TYPE>
  231. LONG WINAPI CVMArray<TYPE>::MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pep){
  232. //默认为让其他过滤器处理
  233. LONG lDispostion = EXCEPTION_CONTINUE_SEARCH;
  234.  
  235. //只修改非法访问内存
  236. if (pep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION){
  237. //遍历所有链表节点
  238. for (CVMArray* p = sm_pHead; p != NULL;p=p->m_pNext){
  239. //询问该节点是否可以修复错误
  240. //注意:这个错误必须被修复,否进程会被终止
  241. lDispostion = p->ExceptionFilter(pep, TRUE);
  242.  
  243. //如果修复了错误,就停止循环
  244. if (lDispostion != EXCEPTION_CONTINUE_SEARCH)
  245. break;
  246. }
  247. }
  248.  
  249. //如果节点修复错误,试图调用前一个异常处理来处理
  250. if (lDispostion == EXCEPTION_CONTINUE_SEARCH)
  251. lDispostion = sm_pfnUnhandledExceptionFilterPrev(pep);
  252.  
  253. return (lDispostion);
  254. }
  255.  
  256. ///////////////////////////////////文件结束///////////////////////////////

//resource.h

  1. //{{NO_DEPENDENCIES}}
  2. // Microsoft Visual C++ 生成的包含文件。
  3. // 供 25_Spreadsheet.rc 使用
  4. //
  5. #define IDD_SPREADSHEET 1
  6. #define IDC_LOG 101
  7. #define IDI_SPREADSHEET 102
  8. #define IDI_ICON1 102
  9. #define IDC_ROW 1001
  10. #define IDC_COLUMN 1002
  11. #define IDC_COLUMN2 1003
  12. #define IDC_VALUE 1003
  13. #define IDC_READCELL 1004
  14. #define IDC_WRITECELL 1005
  15.  
  16. // Next default values for new objects
  17. //
  18. #ifdef APSTUDIO_INVOKED
  19. #ifndef APSTUDIO_READONLY_SYMBOLS
  20. #define _APS_NEXT_RESOURCE_VALUE 103
  21. #define _APS_NEXT_COMMAND_VALUE 40001
  22. #define _APS_NEXT_CONTROL_VALUE 1001
  23. #define _APS_NEXT_SYMED_VALUE 101
  24. #endif
  25. #endif

//Spreadsheet.rc

  1. // Microsoft Visual C++ generated resource script.
  2. //
  3. #include "resource.h"
  4.  
  5. #define APSTUDIO_READONLY_SYMBOLS
  6. /////////////////////////////////////////////////////////////////////////////
  7. //
  8. // Generated from the TEXTINCLUDE 2 resource.
  9. //
  10. #include "winres.h"
  11.  
  12. /////////////////////////////////////////////////////////////////////////////
  13. #undef APSTUDIO_READONLY_SYMBOLS
  14.  
  15. /////////////////////////////////////////////////////////////////////////////
  16. // 中文(简体,中国) resources
  17.  
  18. #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
  19. LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
  20.  
  21. #ifdef APSTUDIO_INVOKED
  22. /////////////////////////////////////////////////////////////////////////////
  23. //
  24. // TEXTINCLUDE
  25. //
  26.  
  27. TEXTINCLUDE
  28. BEGIN
  29. "resource.h\0"
  30. END
  31.  
  32. TEXTINCLUDE
  33. BEGIN
  34. "#include ""winres.h""\r\n"
  35. "\0"
  36. END
  37.  
  38. TEXTINCLUDE
  39. BEGIN
  40. "\r\n"
  41. "\0"
  42. END
  43.  
  44. #endif // APSTUDIO_INVOKED
  45.  
  46. /////////////////////////////////////////////////////////////////////////////
  47. //
  48. // Dialog
  49. //
  50.  
  51. IDD_SPREADSHEET DIALOGEX , , ,
  52. STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
  53. CAPTION "Spreadsheet"
  54. FONT , "宋体", , , 0x86
  55. BEGIN
  56. LTEXT "单元格大小:\n行数:\n列数:\n总大小:",IDC_STATIC,,,,
  57. LTEXT "1024 字节\n256\n1024\n256 MB (268,435,456 字节)",IDC_STATIC,,,,
  58. LTEXT "行 (0-255):",IDC_STATIC,,,,
  59. EDITTEXT IDC_ROW,,,,,ES_AUTOHSCROLL | ES_NUMBER
  60. LTEXT "列(0-1023):",IDC_STATIC,,,,
  61. EDITTEXT IDC_COLUMN,,,,,ES_AUTOHSCROLL | ES_NUMBER
  62. PUSHBUTTON "&读取单元格",IDC_READCELL,,,,
  63. LTEXT "值:",IDC_STATIC,,,,
  64. EDITTEXT IDC_VALUE,,,,,ES_AUTOHSCROLL | ES_NUMBER
  65. PUSHBUTTON "写入单元格",IDC_WRITECELL,,,,
  66. LTEXT "操作日志:",IDC_STATIC,,,,
  67. EDITTEXT IDC_LOG,,,,,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY
  68. END
  69.  
  70. /////////////////////////////////////////////////////////////////////////////
  71. //
  72. // DESIGNINFO
  73. //
  74.  
  75. #ifdef APSTUDIO_INVOKED
  76. GUIDELINES DESIGNINFO
  77. BEGIN
  78. IDD_SPREADSHEET, DIALOG
  79. BEGIN
  80. END
  81. END
  82. #endif // APSTUDIO_INVOKED
  83.  
  84. /////////////////////////////////////////////////////////////////////////////
  85. //
  86. // Icon
  87. //
  88.  
  89. // Icon with lowest ID value placed first to ensure application icon
  90. // remains consistent on all systems.
  91. IDI_SPREADSHEET ICON "Spreadsheet.ico"
  92. #endif // 中文(简体,中国) resources
  93. /////////////////////////////////////////////////////////////////////////////
  94.  
  95. #ifndef APSTUDIO_INVOKED
  96. /////////////////////////////////////////////////////////////////////////////
  97. //
  98. // Generated from the TEXTINCLUDE 3 resource.
  99. //
  100.  
  101. /////////////////////////////////////////////////////////////////////////////
  102. #endif // not APSTUDIO_INVOKED

25.3 向量化异常和继续处理程序

25.3.1向量化异常(vectored exception handing,VEH)——在SEH前被调用

  ①对于多层嵌套的SEH来说,外层的__try_except语句块可能没有机会处理被内层嵌套拦截的异常。对于一般软件来说,这不是太大的问题,但是当内层嵌套的软件是第三方的库函数,并且内部以不友好的方式处理了异常,比如:异常退出进程了事,这对整个程序将造成很不利的影响。

  ②此时可以利用向量化异常处理,在正常的SEH之前以合适的方式拦截和处理异常。

  ③当异常发生时,系统在执行SEH过滤程序之前,会先依次调用VEH列表中的每个VEH异常处理函数。

(2)注册VEH异常处理程序:AddVectoredExceptionHandler(bFirstInTheList,pfnHandler)

  ①参数bFirstInTheList为0表示pfnHandler被添加到列表尾端,非0在列表头部。

  ②pfnHandler:异常处理函数,如果返回EXCEPTION_CONTINUE_SEARCH,则重新执行导致异常的指令,如果返回EXCEPTION_CONTINUE_SEARCH表示让VEH链表中的其他函数去处理异常,如果所有函数都返回EXCEPTION_CONTINUE_SEARCH,SEH过滤函数就会被执行。

(3)删除VEH异常处理函数:RemoveVectoredExceptionHandler(pHandler),其中pHandler这个句柄为AddVectoredExceptionHandler的返回值。

25.3.2 继续处理程序:——用于实现程序的诊断和跟踪

(1)安装:PVOID AddVectoredContinueHandler(bFirstInTheList,pfnHandler);

  ①参数bFirstInTheList为0,表示安装在继续处理程序列头的尾部,非0在头部。

  ②通过该函数安装的异常处理程序是在SetUnhandledExceptionFilter安装的异常处理程序返回EXCEPTION_CONTINUE_SEARCH之后才被调用。

  ③如果pfnHandler函数返回EXCEPTION_CONTINUE_EXECUTION重新执行导致异常的指令,EXCEPTION_CONTINUE_SEARCH让系统执行它后面的异常处理程序。

(2)删除:RemoveVectoredContinueHandler(pHandler);

【VectoredExceptionFilter】演示向量化异常过滤函数的调用

  1. #include <windows.h>
  2. #include <tchar.h>
  3. #include <locale.h>
  4.  
  5. int g_iVal = ;
  6.  
  7. //VEH1异常过滤函数
  8. LONG CALLBACK VEH1(struct _EXCEPTION_POINTERS* pEP){
  9. _tprintf(_T("VEH1\n"));
  10. return EXCEPTION_CONTINUE_SEARCH;
  11. }
  12.  
  13. //VEH2异常过滤函数
  14. LONG CALLBACK VEH2(struct _EXCEPTION_POINTERS* pEP){
  15. _tprintf(_T("VEH2\n"));
  16. //以下的注释,可以取消以观察不同的输出结果
  17. //if ((EXCEPTION_INT_DIVIDE_BY_ZERO == pEP->ExceptionRecord->ExceptionCode)){
  18. // g_iVal = 25;
  19. // return EXCEPTION_CONTINUE_EXECUTION;
  20. //}
  21. return EXCEPTION_CONTINUE_SEARCH;
  22. }
  23.  
  24. //VEH3异常过滤函数
  25. LONG CALLBACK VEH3(struct _EXCEPTION_POINTERS* pEP){
  26. _tprintf(_T("VEH3\n"));
  27. return EXCEPTION_CONTINUE_SEARCH;
  28. }
  29.  
  30. //SEH异常过滤函数
  31. LONG SEHFilter(PEXCEPTION_POINTERS pEP){
  32. _tprintf(_T("SEH\n"));
  33. if ((EXCEPTION_INT_DIVIDE_BY_ZERO == pEP->ExceptionRecord->ExceptionCode)){
  34. g_iVal = ;
  35. return EXCEPTION_CONTINUE_EXECUTION;
  36. }
  37. return EXCEPTION_CONTINUE_SEARCH;
  38. }
  39.  
  40. void Fun1(int iVal){
  41. __try{
  42. _tprintf(_T("Fun1 g_iVal = %d iVal = %d\n"), g_iVal, iVal);
  43. iVal /= g_iVal; //这里发生异常,VEH异常会先被调用!
  44. _tprintf(_T("Fun1 g_iVal = %d iVal = %d\n"), g_iVal, iVal);
  45. }
  46. __except (EXCEPTION_EXECUTE_HANDLER){
  47. _tprintf(_T("Func1 _except块执行,程序将退出!\n"));
  48. _tsystem(_T("PAUSE"));
  49. ExitProcess();
  50. }
  51. }
  52.  
  53. int _tmain(){
  54. _tsetlocale(LC_ALL, _T("chs"));
  55.  
  56. PVOID pVEH1 = AddVectoredExceptionHandler(, VEH1);//安装到VEH链表尾部
  57. PVOID pVEH2 = AddVectoredExceptionHandler(, VEH2);//安装到VEH链表尾部
  58. PVOID pVEH3 = AddVectoredExceptionHandler(, VEH3);//安装到VEH链表尾部
  59.  
  60. __try{
  61. Fun1(g_iVal);
  62. }
  63. __except (SEHFilter(GetExceptionInformation())){
  64. _tprintf(_T("main _except excuted!\n"));
  65. }
  66.  
  67. RemoveVectoredExceptionHandler(pVEH1);
  68. RemoveVectoredExceptionHandler(pVEH2);
  69. RemoveVectoredExceptionHandler(pVEH3);
  70. _tsetlocale(LC_ALL, _T("chs"));
  71.  
  72. _tsystem(_T("PAUSE"));
  73. return ;
  74. }

25.4 C++异常与结构化异常的比较

(1)框架的差别

//C++异常

void ChunkyFunky(){

try{

//try块

...

throw 5;

}

catch (int x){

//catch块

...

}

...

}

//SEH异常

void ChunkyFunky(){

__try{

//try块

//...

RaiseException(Code = 0xE06D7363,//ASCII的“msc”

Flag = EXECEPTION_NONCONTINUABLE,

 Args = 5);

}

__except ((ArgType == Integer)?

               EXCEPTION_EXECUTE_HANDLER:

               EXCEPTION_CONTINUE_SEARCH){

//Catch块

...

}

...

}

(2)SEH和C++异常的比较

  ①SEH是操作系统提供的,它在任何语言中都可以使用,而C++异常只有在编写C++代码时才可以使用。

  ②如果开发C++应用程序,应该使用C++异常,而不是SEH异常,因为C++异常是语言的一部分,编译器会自动生成代码来调用C++对象的析构函数,保证对象的释放。

  ③C++异常也是利用操作系统的SEH来实现的,所以在创建一个C++try块时,编译器也会生成一个SEH的__try块。C++的catch语句对应SEH异常过滤程序,catch块中的代码对应SEH __except块中的代码。C++的throw语句也是对RaiseException函数的调用。

  ④C++调用throw抛出异常时,都会自动带EXCEPT_NONCONTINUEABLE标志。这意味着C++不能再次执行错误代码。

  ⑤__except通过比较throw变量的数据类型与C++ catch语句中所用到的变量的数据类型,如果一致,返回EXCEPTION_EXECUTE_HANDLER,让__except块执行。如果不同,返回EXCEPTION_CONTINUE_SEARCH继续向搜索外层的__try/__except。

25.5 异常与调试器

(1)首次机会通知和最后机会通知:

当某个线程抛出异常里,操作系统会马上通知调试器(如果调试器己经附着),这个通知被称为“首次机会通知(First-Chance Notification)”调试器将响应这个通知,促使线程寻找异常过滤程序。如果所有的异常过滤程序都返回EXCEPTION_CONTINUE_SEARCH,操作系统会给调试器一个“最后机会通知(Last-Chance Notification)”

(2)每个解决方案(.sln),可以从主菜单“调试”→“异常(x)…”找出“异常”对话框。每个异常代码为32位,如果勾选“引发”表示每当被调试线程引发异常时,调试器都会收到首次机会通知,此时异常刚刚发生,线程还没有得到机会执行异常过滤程序。调试器会弹出相应的对话框让我们选择操作,我们可以选择调试,然后在代码里设置断点,查看变量的值或线程的函数调用栈,如果不勾选“引发”这项,则不会弹出对话框,但调试器收到通知时,会在输出窗口中显示一行文字,以表示它收到了这个异常通知,然后允许线程寻找异常过滤程序。 “用户未处理的”表示异常过滤函数返回EXCEPTION_CONTINUE_SEARCH,调试器收到最后机会通知,也会弹出相应的对话框让用户来选择操作。

(3)可以自定义软件异常:只需单击“添加”按钮,然后输入异常名称和异常代码(不能与己有的重复)

第25章 SEH结构化异常处理_未处理异常及向量化异常的更多相关文章

  1. 第24章 SEH结构化异常处理_异常处理及软件异常

    24.1  程序的结构 (1)try/except框架 __try{ //被保护的代码块 …… } __except(except fileter/*异常过滤程序*/){ //异常处理程序 } (2) ...

  2. 第23章 SEH结构化异常处理(2)_编译器对系统SEH机制的封装

    23.2 编译器层面对系统SEH机制的封装 23.2.1 扩展的EXCEPTION_REGISTRATION级相关结构:VC_EXCEPTION_REGISTRATION (1)VC_EXCEPTIO ...

  3. 第23章 SEH结构化异常处理(1)_系统SEH机制

    23.1 基础知识 23.1.1 Windows下的软件异常 (1)中断和异常 ①中断是由外部硬件设备或异步事件产生的 ②异常是由内部事件产生的,可分为故障.陷阱和终止三类. (2)两种异常处理机制: ...

  4. 第23章 SEH结构化异常处理(3)_终止处理程序

    23.3 终止处理程序 23.3.1 程序的结构 (1)框架 __try{ //被保护的代码块 …… } __finally{ //终止处理 } (2)__try/__finally的特点 ①fina ...

  5. 异常处理第三讲,SEH(结构化异常处理),异常展开问题

    异常处理第三讲,SEH(结构化异常处理),异常展开问题 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 不知道昨天有木有 ...

  6. Windows内核读书笔记——SEH结构化异常处理

    SEH是对windows系统中的异常分发和处理机制的总称,其实现分布在很多不同的模块中. SEH提供了终结处理和异常处理两种功能. 终结处理保证终结处理块中的程序一定会被执行 __try { //要保 ...

  7. C#如何使用结构化异常处理

    Knowledge Base: Chinese (Simplified) 如何使用 Visual C# .NET 和 Visual C# 2005 中的结构化异常处理文章ID: 816157 最近更新 ...

  8. 深入研究 Win32 结构化异常处理(作者博客有许多SEH的研究文章)

    摘要 就像人们常说的那样,Win32 结构化异常处理(SEH)是一个操作系统提供的服务.你能找到的所有关于 SEH 的文档讲的都是针对某个特定编译器的.建立在操作系统层之上的封装库.我将从 SEH 的 ...

  9. [C++]深入解析结构化异常处理(SEH)

    http://www.cppblog.com/weiym/archive/2015/02/27/209884.html 尽管以前写过一篇SEH相关的文章<关于SEH的简单总结>, 但那真的 ...

随机推荐

  1. ahjesus fstab修改错误了如何修复

    fstab修改错误了如何修复   当你不小心把磁盘表输入错误以后,系统总是让你按ctrl+D重新启动或者输入密 码进入shell,你输入密码登陆后,   编辑文件是只读的,执行下面的命令后就可以编辑了 ...

  2. [Tips] Useful link ... on going

    1. CSS Bootstrap http://getbootstrap.com/ Bootstrap 中文文档 http://getbootstrap.com/2.3.2/ 最全的 Twitter ...

  3. setTimeout实现动画的黄金优化法则

    1.使用递归思想实现setTimeout的轮询动画:在每一次执行方法的时候都重新的设置一个定时器,然后在指定时间内重新的执行当前的方法 问题:每一次设置的定时器,虽然不执行了,但是还存在呢,浪费性能 ...

  4. DevExpress.XtraGrid.Views 设置指定行的背景颜色 .

    如需要将指定行的背景设置颜色,可参考以下示例 1.事件:CustomDrawCell 2.示例: private void gridView1_CustomDrawCell(object sender ...

  5. SharePoint 2013: Search Architecture in SPC202

    http://social.technet.microsoft.com/wiki/contents/articles/15989.sharepoint-2013-search-architecture ...

  6. Force.com微信开发系列(五)自定义菜单进阶及语音识别

    在上文里我们介绍了如何通过Force.com平台里为微信账号添加自定义菜单,本文里我们将进一步介绍如何查询菜单以及删除菜单的相关知识,最后会介绍微信平台如何进行语音识别的相关技术. 查询菜单 与创建菜 ...

  7. 换iphone5屏幕你花了多少钱?不防我们看下市场的批发价格

    看来人家批发价也不便宜啊,你修一块花了多少米 免费b2b平台  US $1 - 79.99 / Piece Factory Price for iphone 5" lcd alibaba   ...

  8. STL--迭代器(iterator)

    指针与数组 指针与其它数据结构呢?比如说链表? 存储空间是非连续的.不能通过对指向这种数据结构的指针做累加来遍历. 能不能提供一个行为类似指针的类,来对非数组的数据结构进行遍历呢?这样我们就能够以同样 ...

  9. 关于Block Formatting Context--BFC和IE的hasLayout

    转文请标明 --- 出处:穆乙 http://www.cnblogs.com/pigtail/ 一.BFC是什么? BFC(Block Formatting Context)直译为"块级格式 ...

  10. Dev Grid拖拽移动行

    效果图 源码下载 拖拽时带行截图效果实现代码 /// <summary> /// 拖拽帮助类 /// </summary> public static class DragHe ...