Dump文件数据存储格式(八)
十、已卸载模块列表流(UnloadedModuleListStream
)
UnloadedModuleListStream
流包含卸载模块的模块信息。跟随在ModuleListStream
的后面。ModuleListStream
相关信息如下:
0x29c0+0n26680=0x91f8
而UnloadedModuleListStream
相关信息如下:
可知,UnloadedModuleListStream
的RVA为0x91f8,所以它紧随在ModuleListStream
的后面,大小为324字节。数据如下:
这些数据按如下结构组织:
首先是相当于头结构的_MINIDUMP_UNLOADED_MODULE_LIST,包含流的一些数据量
typedef struct_MINIDUMP_UNLOADED_MODULE_LIST {
ULONG32 SizeOfHeader;
ULONG32 SizeOfEntry;
ULONG32 NumberOfEntries;
} MINIDUMP_UNLOADED_MODULE_LIST,*PMINIDUMP_UNLOADED_MODULE_LIST;
软件调试前的准备工作
在我们对某个问题进行调试前一定要做足准备工作,不然后面的调试工作会面临极大的困难,甚至都无法开展调试工作。
必须要做的准备工作
不管我们是在开发期调试,还是在发布后调试,都要做好如下准备工作:
- 充足的心里准备
这个太重要了,在实际工作中,我见到太多被问题吓怕的人。在还没整清楚问题是什么时就已经打了退堂鼓,把工作和问题给别人。面对任何问题,我们首先要做的就是树立起信心。特别是在计算机的世界里,事出必有因,且一定具有事情的发生的必然逻辑。所以,我们只要有信心就肯定能解决问题。 - 编写高质量代码
程序开发者应该提供高质量的程序代码,包括规 范的代码和必要的注释,对开发的代码进行单元测试, 经过同行严格的代码评审。 这样一是减少问题发生,二是对调试定位问题和问题修改有很大的帮助。 - 了解软件的设计和算法,熟悉软件代码
调试一个软件模块,需要了解它的设计和实现算 法,了解各个函数之间的调用关系,该模块与其他模块 之间的接口关系。 - 熟悉软件运行环境
首先要明白我们软件要求的运行环境。了解用户机的环境,是否满足软件运行要求,排除一些运行环境引起的软件异常。同时随着硬件、操作系统、网络技术、云技术、大数据技术的发展,软件 运行环境越来越复杂,调试者只有熟悉这些环境和环 境配置,才能保证软件正常运行和调试。 - 熟悉调试工具
调试工具提供很多功能来帮助我们调试分析程序,只有熟练掌握调试工具,才能开展我们的调试工作。 - 足够的日志输出
日志的作用不用我多说,如果日志有足够的信息,我们甚至都可以不用调试器,都能定位问题和解决问题。 - 知道用户的操作流程
某些问题更用户的独特使用习惯和操作步骤有关,如果我们不知道,很有可能复现不了问题从而无法解决。
开发过程中的调试准备工作
还在软件的开发过程中,我们就会遇到许多问题和异常,为了解决这些问题,我们需要做如下准备工作:
- 了解我们的工程属性配置
要知道我们的工程属性做了哪些配置,特别是一些特殊的配置,最好知道没一个配置选项对我们程序行为的影响。要知道发生问题前配置有没有做修改,为什么修改。 - 知道代码组织结构
只有知道代码组织结构,我们才能快速定位代码 - 准备充分必要的数据
相当一部分问题可能是数据引起,我们只有有这样的问题数据,才能引发问题和调试定位问题。 - 了解代码里使用的第三方库的原理和使用要求
如果不知道,可能会发生一些莫名其妙的问题,让我们无从下手。
软件发布后问题的调试准备工作
我们在开发时,严格尊搜了代码规范,进行了单元测试,也进行了组织内部的严格测试,但是,当我们的软件发布,功能上线后,用户还是会反馈很多问题,有的问题还非常致命。为了调试这些问题,我们要做如下准备工作:
- 做好代码发布分支管理
- 准被好符号文件
如果是Windows上C++/.net开发的,一定要生成好符号文件,甚至是Map文件,且要做好符号版本管理 - 做好发布包版本管理
- 充分收集用户的相关信息
应用版本、操作系统、硬件信息,使用流程、日志、注册表等一切相关信息
关于bad_typeid异常
什么是bad_typeid异常?
当typeid运算符应用于多态类型的已取消引用的空指针值时,将引发此类型的异常。
继承关系:
class bad_typeid : public exception
例子:
//expre_bad_typeid.cpp//compile with: /EHsc /GR #include <typeinfo>#include<iostream> classA{public://object for class needs vtable//for RTTI virtual ~A();
};using namespacestd;intmain() {
A* a =NULL;try{
cout<< typeid(*a).name() << endl; //Error condition }catch(bad_typeid){
cout<< "Object is NULL" <<endl;
}
}
软件调试原则和策略
在日常工作中,我们还是有些原则要坚守,也有些策略可用:
1、调试的本质:确认原则
修正充满错误的程序,就是逐个确认,你自认为正确的许多事情所对应的代码确实是正确的。当你发现其中某个假设不成立时,就表示已经找到了关于程序错误的位置的线索了,可能并不时准确的位置。
换一种表达方式来说:惊讶是好事。当你认为关于程序的某件事情是正确的,而在确认它的过程中却失败了,你就会感到惊讶,但这是好事,因为这种发现会引导你找到程序错误所在的位置。
2、熟悉软件设计和编码
让熟悉软件设计和编码的人参与调试工作,修改 错误也是程序设计的一种形式。 在程序设计阶段使用 的方法都可以应用到程序错误的修正工作中。
3、从简单工作开始调试
在调试过程的开始阶段,应该从容易、简单的情况开始运行程序。这样做也许无法揭示所有程序错误,但是很有可能发现其中的部分错误。例如,如果代码由大型循环组成,则最容易发现程序错误的是在第一次或第二次迭代期间引发错误。又比如多线程程序改为单线程或减少几个线程。
4、从软件模块的最小集成包开始
在增量式软件开发过程中,软件模块最初的起始 可能是一个最低功能限度的集成包,随后新的代码不 断加入到系统中。 调试工作可以从最小的集成包开 始,不断增加代码和模块来查找、定位问题。
5、分而治之
每次只处理一个问题,把被调试组件从其上下文 组件之中孤立出来,通过设计驱动模块和桩模块进行 调试。
6、发现问题及时反馈和处理
检测到的错误越早,就越容易找到原因。 如果等 到问题症状出现在客户端接口,那么可能很难缩小发 生问题的原因列表。
7、兼顾全局
程序代码错误修改兼顾全局,确保修改错误没有 影响软件的其他部分。
8、彻底修改
如果提出的修改方案不能解释与该错误有关的全 部线索,那就表明只修改了错误的一部分,必须对错误 进行彻底修改。
9、关注相关问题
在查找问题根源时,对可能发现的一些相关问题 也要做处理。 暂时不能处理的相关问题应该记录在案,为以后的调试工作保留相关信息。】
10、自顶向下的方法
通常采用自顶向下或模块化方法来编写代码。 采 用自顶向下或模块化的方法来调试代码,可以缩小软件问题定位的范围,提高调试效率。
关于std::bad_cast异常
什么是bad_cast异常?
当对引用类型的动态转换未通过运行时检查(例如,因为类型与继承无关)时,将引发此类型的异常。
继承关系
class bad_cast : public exception
例子:
//expre_bad_cast_Exception.cpp//compile with: /EHsc /GR #include <typeinfo>#include<iostream> classShape {public:virtual void virtualfunc() const{}
};class Circle: publicShape {public:virtual void virtualfunc() const{}
};using namespacestd;intmain() {
Shape shape_instance;
Shape& ref_shape =shape_instance;try{
Circle& ref_circle = dynamic_cast<Circle&>(ref_shape);
}catch(bad_cast b) {
cout<< "Caught:" <<b.what();
}
}