分类 调试 下的文章

十、已卸载模块列表流(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;

在我们对某个问题进行调试前一定要做足准备工作,不然后面的调试工作会面临极大的困难,甚至都无法开展调试工作。

必须要做的准备工作

不管我们是在开发期调试,还是在发布后调试,都要做好如下准备工作:

  1. 充足的心里准备
    这个太重要了,在实际工作中,我见到太多被问题吓怕的人。在还没整清楚问题是什么时就已经打了退堂鼓,把工作和问题给别人。面对任何问题,我们首先要做的就是树立起信心。特别是在计算机的世界里,事出必有因,且一定具有事情的发生的必然逻辑。所以,我们只要有信心就肯定能解决问题。
  2. 编写高质量代码
    程序开发者应该提供高质量的程序代码,包括规 范的代码和必要的注释,对开发的代码进行单元测试, 经过同行严格的代码评审。 这样一是减少问题发生,二是对调试定位问题和问题修改有很大的帮助。
  3. 了解软件的设计和算法,熟悉软件代码
    调试一个软件模块,需要了解它的设计和实现算 法,了解各个函数之间的调用关系,该模块与其他模块 之间的接口关系。
  4. 熟悉软件运行环境
    首先要明白我们软件要求的运行环境。了解用户机的环境,是否满足软件运行要求,排除一些运行环境引起的软件异常。同时随着硬件、操作系统、网络技术、云技术、大数据技术的发展,软件 运行环境越来越复杂,调试者只有熟悉这些环境和环 境配置,才能保证软件正常运行和调试。
  5. 熟悉调试工具
    调试工具提供很多功能来帮助我们调试分析程序,只有熟练掌握调试工具,才能开展我们的调试工作。
  6. 足够的日志输出
    日志的作用不用我多说,如果日志有足够的信息,我们甚至都可以不用调试器,都能定位问题和解决问题。
  7. 知道用户的操作流程
    某些问题更用户的独特使用习惯和操作步骤有关,如果我们不知道,很有可能复现不了问题从而无法解决。

开发过程中的调试准备工作

还在软件的开发过程中,我们就会遇到许多问题和异常,为了解决这些问题,我们需要做如下准备工作:

  1. 了解我们的工程属性配置
    要知道我们的工程属性做了哪些配置,特别是一些特殊的配置,最好知道没一个配置选项对我们程序行为的影响。要知道发生问题前配置有没有做修改,为什么修改。
  2. 知道代码组织结构
    只有知道代码组织结构,我们才能快速定位代码
  3. 准备充分必要的数据
    相当一部分问题可能是数据引起,我们只有有这样的问题数据,才能引发问题和调试定位问题。
  4. 了解代码里使用的第三方库的原理和使用要求
    如果不知道,可能会发生一些莫名其妙的问题,让我们无从下手。

软件发布后问题的调试准备工作

我们在开发时,严格尊搜了代码规范,进行了单元测试,也进行了组织内部的严格测试,但是,当我们的软件发布,功能上线后,用户还是会反馈很多问题,有的问题还非常致命。为了调试这些问题,我们要做如下准备工作:

  1. 做好代码发布分支管理
  2. 准被好符号文件
    如果是Windows上C++/.net开发的,一定要生成好符号文件,甚至是Map文件,且要做好符号版本管理
  3. 做好发布包版本管理
  4. 充分收集用户的相关信息
    应用版本、操作系统、硬件信息,使用流程、日志、注册表等一切相关信息

什么是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、自顶向下的方法

通常采用自顶向下或模块化方法来编写代码。 采 用自顶向下或模块化的方法来调试代码,可以缩小软件问题定位的范围,提高调试效率。

什么是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();
}
}