`
aigo
  • 浏览: 2733318 次
  • 性别: Icon_minigender_1
  • 来自: 宜昌
社区版块
存档分类
最新评论

Windows 异常处理的研究的总结

阅读更多

原文:

http://www.cnblogs.com/yangchaobj/archive/2013/02/23/2923416.html

http://www.cnblogs.com/yangchaobj/archive/2013/02/23.html

 

最近半年单位的项目集中上线,出了一些 crash 的问题。

程序crash 后,会自动生成一个 dump 文件,这是通过调用系统API MiniDumpWriteDump 实现的。

 

通过事后对 dump 文件的分析,工程师可以找到出问题的点。

不方便的是,每次打开dump文件后,堆栈总是停在类似 ntdll!KiFastSystemCallRet 的地方,通过这个 callstack 完全找不到任何程序的蛛丝马迹。而是用 .ecxr 则报错,说没有 ContextRecord. 工程师需要执行 windbg 的 s 命令,在堆栈中寻找 ExceptionContext 去恢复现场,对于做过的人稍有些麻烦,对于生手就比较困难了。

*具体搜索思路是:根据ContextRecord 的定义,第一个成员是异常号,比如 0xc0000005,那就在堆栈上搜这个异常号:s -d esp L1000 0xc0000005

如果是 x64平台,用 s -d rsp L1000 0xc0000005

一般会找到2-3个匹配的堆栈地址,再逐个去试,就很容易找到。

还有一个问题,有时候 dump 文件可以生成,有时候程序直接退出,没有任何 dump。

 

基于这些现象,我有了一些问题:

1. 能否不用每次手动寻找 ExceptionContext ,而让 windbg 直接重现现场。

2. 能否保证所有的异常都被捕获并生成 dump (当然是在外围条件都满足的情况下)

由这个 Dump 文件引发了我一系列的研究和学习。现在将成果总结如下。

 

在windbg 定位的时候,需要用到 .cxr 指令,通过查windbg帮助手册,了解到这个指令是 Display Context Record 的缩写。在异常发生时,windows 会将异常发生时的各个寄存器数值、堆栈位置等记录下来,存在一个 ContextRecord 结构里,并把这个结构压入堆栈。.cxr 命令还带一个地址参数,用来指定 ContextRecord 的位置。这个结构对于能否恢复堆栈起着决定性的作用。

通过读《Windows核心编程(第5版)》,了解到windows会在异常发生时,向堆栈上压入3个结构体:ExceptionRecord, ContextRecord, EXCEPTION_POINTERS。

只要生成 dump 文件时这三个结构体还没有出栈,则通过搜索堆栈是一定可以找到ContextRecord,就一定可以恢复堆栈。

但为什么每次要手动搜索 ContextRecord 呢?经过研究MiniDumpWriteDump,有了答案:

BOOL WINAPI MiniDumpWriteDump(
__in HANDLE hProcess,
__in DWORD ProcessId,
__in HANDLE hFile,
__in MINIDUMP_TYPE DumpType,
__in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
__in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
__in PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);

第5个参数 ExceptionParam,是一个结构地址,这个结构的定义如下:

typedef struct _MINIDUMP_EXCEPTION_INFORMATION { 
    DWORD ThreadId; 
    PEXCEPTION_POINTERS ExceptionPointers; 
    BOOL ClientPointers;
} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;

结构体的第二个参数就是 EXCEPTION_POINTERS,而这个指针的第二个成员就是 ContextRecord。

之前生成 dump 的时候,并没有将这个结构填好传给 MiniDumpWriteDump,所以才造成直接用 .ecxr 命令找不到 ContextRecord 的错误。

通过修改代码,在异常发生时将 EXCEPTION_POINTER 保存下来(如果能确保生成 dump 时,堆栈上的 ContextRecord 依然有效,则仅保留EXCEPTION_POINTER的地址即可)。

调用 MiniDumpWriteDump 时将这个 EXCEPTION_POINTER 给传进去,就可以做到 .ecxr 指令自动恢复堆栈定位错误行了。

 

问题:有些异常可以捕获,生成 dump,有些则不能。

要解决这个问题,必须对 windows 和 C++ 的异常机制有一个了解。

 

平时我们写程序都这样捕获异常:

 1 try
 2 {
 3     fire_access_violation();
 4 }
 5 catch (exception& ex)
 6 {
 7     // ......
 8 }
 9 catch (...)
10 {
11     // ......
12 }

第3行的函数会触发 ACCESS_VIOLATION (0xC0000005) 异常,第5行用来catch 普通的 C++异常,第9行用来捕获其他异常,ACCESS_VIOLATION 就属于这种异常。

但运行后发现,第9行的 catch (...) 什么也抓不到。要让它起作用,还需要调整默认的编译参数,找到 C++ -> Code Generation -> Enable C++ Exceptions 选项,设为 Yes with SEH Exceptions (/EHa),即通过SEH机制,让C++的 catch (...) 能够捕获所有的异常

这一步做下来,我们已经可以捕获try 块上的所有异常了。但虽然捕获了异常,我们还无法知道异常类型。也没有前文所说的 ContextRecord 信息。要更精确的得到这些信息,还需要多做一步:把 SEH 映射成C++异常。

 

C 提供了一个函数 _set_se_translator

_se_translator_function _set_se_translator(
    _se_translator_function seTransFunction
);

typedef void (*_se_translator_function)(unsigned int, struct _EXCEPTION_POINTERS* );

 

这个函数要尽量早的注册到系统,所以在线程一开始的时候,就要调用这个函数。

它给本线程设置一个异常转换函数,当有 SEH 异常发生时,会调到用户注册的 _se_translator_function,传入 exception code 和 EXCEPTION_POINTERS,这时我们需要自己生成一个结构体,把这些非常有用的信息存在结构体里。还是那句话:如果能确保异常处理还在本堆栈上,并且异常信息还没有出栈,则存一个指针即可,否则要把 EXCEPTION_POINTERS 的整个结构复制过来,在遇到嵌套异常时,他的成员 ExceptionRecord 有可能是一个链表,如何保存这个链表还是件比较麻烦的事儿,所以能在原地处理异常时最好

转换函数看起来可以是这样:

 1 struct StructuredException : public exception
 2 {
 3     PEXCEPTION_POINTERS  exp_ptr;
 4 
 5     StructuredException() : exception() {}
 6     StructuredException(const char* text) : exception(text) {}
 7 };
 8 
 9 void trans_func(unsigned int code, EXCEPTION_POINTERS* p)
10 {
11     cout << "Exception Record: " << p->ExceptionRecord << endl;
12     StructuredException se;
13     se.exp_ptr = p;
14     throw se;
15 }

 

StructuredException 从 C++ 异常 exception 继承。只要安装了 tranlator 的线程内发生了结构化异常,windows 就会在该线程上调用次函数

最后一句即第14行,抛出StructuredException,即抛出一个C++异常。通过 catch (exception& ex) 就可以捕获它。当然为了区别处理结构化异常,也可以这也写 catch (StructuredException& se) {...},毕竟结构化异常的出现时超出预期的。

 

到了这一步,可以保证每个线程都在我们的保护之下。但还有一些线程不是我们自己写的,可能是一些库自己创建的,这些线程有可能没有try块的保护,如果这些线程发生了异常,还是会造成程序悄悄退出,没有 dump。windows 也提供了方法来抓住这种漏网之鱼。

 

Windows 提供了 UnhandledExceptionFilter 来处理那些没被 catch 住的异常。

 1 LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
 2   __in  LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
 3 );
 4 
 5 LONG WINAPI UnhandledExceptionFilter(
 6   __in  struct _EXCEPTION_POINTERS* ExceptionInfo
 7 );
 8 
 9 LONG WINAPI unhandled_exception_filter(__in struct _EXCEPTION_POINTERS *ExceptionInfo)
10 {
11     // do something
12     return EXCEPTION_CONTINUE_SEARCH; // EXCEPTION_EXECUTE_HANDLER
13 }

 

整个进程设置一个 filter 就可以拦住进程内任何线程抛出的漏网的异常。如果你用 debugger 调试 filter 代码,会发现它从来不会被调到,因为异常已经被 debugger 拦下来了,于是就不是“漏网之鱼”,所以这个 filter 不会被调到。

可以看到 filter 的参数里也有 EXCEPTION_POINTERS,所以在这个 filter 函数里我们可以调用 MiniDumpWriteDump 生成我们需要的 dump 文件。filter被调到的线程就是发生异常的线程。

 

做到这一步,进程内几乎所有的异常都会被我们捕获到并通过 mini dump 文件把现场保存下来以供事后分析。

为什么说是“几乎所有”呢,还是有一些特殊情况下的异常无法被捕获到:

当遇到 Heap Corrupt 异常时(两次 delete 同一块内存地址就会出现这种异常),windows有两种处理策略:杀掉进程或者不杀。在64位的 windows server 下,当堆发生错误时,系统会直接杀死进程,不会有全局展开,异常捕获函数什么都不活不到,这种异常代码是:0xc0000374,这时需要外挂 windbg 或 cdb 来帮助我们 dump。

如果 mini dump 的参数设的过于“求全”,既要保存堆栈信息又要保持堆信息,系统很可能没有足够的硬盘空间来保持 dump 文件,这样 mini dump 也会失败。

但无论如何,只要进程非正常退出,总会在系统事件中记上一笔,我们在 Event Viewer 里就可以看到。

 

总结:

通过 /Eha 编译参数,我们可以既捕获C++异常,又捕获结构化异常。

通过 _set_se_tranlator 可以把结构化异常转换成C++异常,并保存异常发生的现场(需要 /Eha 参数)。

通过 SetUnhandledExceptionFilter,可以捕获没有 try-catch/__try __except 保护的线程上抛出的异常。

通过 MiniDumpWriteDump 加上我们保存的异常现场信息,我们可以在生成 mini dump 时把 进程id、线程id、发生异常的时间、ContextRecord 都写进 mini dump(前三项通过文件名的形式),极大的方便后期调试。

通过以上努力,可以保证绝大多数异常都会被捕获并保存下现场信息。

分享到:
评论

相关推荐

    55links友情链接网址跟踪器

    55links友情链接网址跟踪器,放在桌面,每次直接打开就可以访问55links友情链接交易平台,方便快捷。

    [AB PLC例程源码][MMS_046180]CompactFlash Data Storage.zip

    AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!

    moore_01_0909.pdf

    moore_01_0909

    FIBR English learning

    FIBR English learning

    [AB PLC例程源码][MMS_042350]How to send-receive SMS text messages using Westermo modem.zip

    AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!

    OIF_IEEE802.3_liaison_19OCt09.pdf

    OIF_IEEE802.3_liaison_19OCt09

    SerU,做网络安全FTP内容的实验必备

    做网络安全FTP内容的实验必备

    nagarajan_01_1107.pdf

    nagarajan_01_1107

    [AB PLC例程源码][MMS_043879]Programming in SFC and ST Language.zip

    AB PLC例程代码项目案例 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。 3、用途:项目具有较高的学习借鉴价值,不仅适用于小白学习入门进阶。也可作为毕设项目、课程设计、大作业、初期项目立项演示等。 4、如果基础还行,或热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载!欢迎交流学习!不清楚的可以私信问我!

    mellitz_3cd_01_0318.pdf

    mellitz_3cd_01_0318

    PyQt6实战派 配套代码

    PyQt6实战派 配套代码

    陕西省省级非物质文化遗产民俗经纬度数据统计表

    陕西省省级非物质文化遗产经纬度数据统计表 统计内容包含以下字段: 1. 项目名称 2. 遗产类别 3. 入选批次 4. 所属地区 5. 申报地区/单位 6. 地理经度 7. 地理纬度 该统计表系统记录了陕西省省级非物质文化遗产的地理空间信息,为文化遗产的数字化保护与研究工作提供了重要的数据支撑。

    ran_3ck_02a_0918.pdf

    ran_3ck_02a_0918

    毕业设计-基于springboot+vue开发的汽车租赁管理系统【源码+sql+可运行】50308.zip

    毕业设计_基于springboot+vue开发的汽车租赁管理系统【源码+sql+可运行】【50308】.zip 全部代码均可运行,亲测可用,尽我所能,为你服务; 1.代码压缩包内容 代码:springboo后端代码+vue前端页面代码; 脚本:数据库SQL脚本 效果图:运行结果请看资源详情效果图 2.环境准备: - JDK1.8+ - maven3.6+ - nodejs14+ - mysql5.6+ - redis 3.技术栈 - 后台:springboot+mybatisPlus+Shiro - 前台:vue+iview+Vuex+Axios - 开发工具: idea、navicate 4.功能列表 - 系统设置:用户管理、角色管理、资源管理、系统日志 - 业务管理:汽车管理、客户管理、租赁订单 3.运行步骤: 步骤一:修改数据库连接信息(ip、port修改) 步骤二:找到启动类xxxApplication启动 4.若不会,可私信博主!!!

    Runcorder - 跑步训练管理系统

    # Runcorder - 跑步训练管理系统 Runcorder 是一款专为跑步爱好者、马拉松运动员及高校体育生设计的本地化跑步训练管理工具,基于 Python 开发,结合 Tkinter 图形界面与强大的数据处理能力,为用户提供从训练记录到数据分析的全方位支持。无论是初学者还是专业跑者,Runcorder 都能帮助你科学规划训练、精准追踪进度,并通过可视化图表直观呈现训练成果,让你的跑步训练更智能、更高效! - **多用户管理**:支持创建、加载和删除用户档案,每个用户的数据独立存储,确保隐私与安全。 - **科学训练记录**:全维度记录跑步数据,包括日期、里程、配速、自评和晨跑标记,支持智能输入校验,避免数据错误。 - **多维数据分析**:通过动态可视化图表展示跑步里程趋势、平均配速曲线,支持自定义 Y 轴范围,帮助用户深入理解训练效果。 - **高阶功能**:提供 4 种科学训练模式(有氧/无氧/混合),支持历史记录修改与删除,数据以 JSON 格式持久化存储,跨平台兼容。

    paatzsch_01_0708.pdf

    paatzsch_01_0708

    开源AI工具下载——AnythingLLMDesktop1.7.3-r2 windows版

    AnythingLLM是一个全栈应用程序,您可以使用流行的开源大语言模型,再结合向量数据库解决方案构建个人本地AI大模型知识库

    mellitz_3ck_02_0519.pdf

    mellitz_3ck_02_0519

    petrilla_01_0708.pdf

    petrilla_01_0708

    ran_3ck_01_0918.pdf

    ran_3ck_01_0918

Global site tag (gtag.js) - Google Analytics