分类 调试 下的文章

一、什么是PDB文件

PDB(Program DataBase),全称为“程序数据库”文件。存储程序的所有调试信息数据。在编译连接时,如果选择了/debug选项或/debug:full选项,则最新的Microsoft链接器在链接时创建程序数据库(PDB)文件。pdb文件包含有关可执行文件创建的信息,还包含最新codeview格式的符号信息。可执行文件包含本地计算机上PDB文件的路径和文件名以及标识代码,以便找到正确的PDB文件。

它包含用于引入的调试信息存储在 Visual c + + 版本 1.0 中新的格式。  格式更改为最重要的动机是允许的第一次在 Visual c + + 2.0 版中引入了更改的程序的调试版本的增量链接。现在的格式版本是7.0版本。将来可能还会变化,比如在将来.PDB 文件也将保留其他项目状态信息。

二、PDB文件存储格式

pdb文件格式与磁盘文件系统使用的格式类似。磁盘文件系统将被分为若干块数据,这些数据称为固定大小的“扇区”。文件中的数据包含在那些在文件写入磁盘时标识为备用的扇区中,但它们不一定是连续的。文件目录跟踪数据在磁盘上的位置。在pdb文件中,将数据块称为“pages”、将文件中的数据称为“stream”以及将文件目录称为“stream directory”可能更为合适。


PDB文件格式并未公开,但是Microsoft提供了API来读取PDB中的数据,可以参考CCI开源项目。虽然官方未公开格式,但是The RSDS pdb format对PDB的文件格式做了较详细的介绍。PDB的文件格式类似于磁盘的文件系统,每个磁盘会被划分成很多个大小一样的扇区,文件中的数据就存放在不同的扇区中,而且无需保证这些扇区在磁盘上是连续的。PDB文件用page进行划分,类似于扇区,stream就类似于文件,stream directory类似于文件目录。

下面我们就用winhex来看下PDB中到底存放了那些信息吧。

2.1 PDB数据组织结构

 

2.2 PDB头部结构

在本机代码中,内存由调用Malloc()/Free()的程序(或类似的api)显式管理。在托管代码中,有一个垃圾收集器(GC)。
GC并不意味着您不再有内存泄漏。它只意味着运行时在运行时检测到对象不再可访问后的某个任意点为您调用Free()。这有效地在安全方面出错(不能释放对象),以避免悬空指针问题,这通常会导致崩溃。因此,如果对象总是可访问的,即使GC也无法释放它,因此会出现内存泄漏。发生这种情况的一种常见方法是使用一些委托或静态引用。这是一个常见的关于主题的文章,所以在基本问题上我没有什么要补充的。查看Maoni或Chris Lyon的博客,了解GC的优点。

我想说的是,这是所产生的bug的一种范式转变。


本机内存管理中的常见错误:

Bug Effect
calling Free() twice on the same memory Crash, undefined behavior.
never calling Free()  (or Release()) Memory leak
Using a pointer after the original memory was freed (dangling pointer). Crash, Using undefined memory, arbitrary undefined behavior

注意:这些问题的范围已经超出了内存的使用范围,但由于这是一篇博客文章,而不是一篇论文,所以我的范围是有限的。(例如,如果我们使用C++析构函数而不是Free(),C++ dor会释放OS资源)

GC环境中的常见错误(托管代码):

Bug Effect
Not releasing a reference Memory leak
Finalizer called at random times Native resource not released when expected. This could be a resource leak. Or it could lock resources longer than expected, preventing others from using them)The finalizer can't be sure what state it's called in.

由于在GC环境中不能调用Free(),因此您不必再担心整个类的bug。

从一个角度来看,GCs基本上将bug从崩溃降级为内存泄漏。从debugger+工具的角度来看,用户代码中仍然存在bug,因此用户仍然需要工具来调试它们。但是,针对本机内存泄漏的传统工具在托管内存泄漏的世界中发挥得并不好。

malloc/free错误很难,因为罪魁祸首可能离失败点很远。例如,当Free()被调用两次时,您可能会发现第二个Free()崩溃。但是现在您可能需要找到对Free()的第一个调用,它可能发生在过去的某个任意时刻。一般来说,诊断非托管内存泄漏通常需要日志记录工具来跟踪malloc/free调用并对其进行分析;或者采用捕捉悬空指针的奇特内存页保护方案。(参见UMDH或PageHeap)

托管内存泄漏在概念上很容易,因为您可以通过检查当前堆,从单个快照中及时静态诊断它们。不幸的是,ICorDebug没有提供这个功能,所以这里没有很好的VS集成。实际上,您需要使用SOS。

总而言之:从bug的角度来看,从手动内存管理到GC的转变有两大好处:

-它降低了bug的严重性(从崩溃/未定义的行为到内存泄漏)

-它产生的bug更容易诊断,但需要改变工具范式来解决这些新的bug。

 

WinDbg的alias命令(as, aS)在script里面很有用,但是WinDbg的script不算是一种设计良好的语言,一般在写WinDbg script总会遇到各种坑,就包括alias命令的求值。

与变量相比,WinDbg的alias更像是C语言的宏。他可以把一个名字定义成指定的字符串,环境变量,给定地址的字符串,甚至表达式的值或者WinDbg命令的输出字符串。C语言的宏仅在定义点到文件结束有效(如果后面没有undef),WinDbg的alias的求值则 下面解释下这个过程。

假设已经有alias (aS foo bar), 在运行命令 .echo foo 或者 .echo ${foo} (使用后者的可以对alias name提供显示的tokenize和参数,比如${/v:foo},参考WinDbg帮助)。在运行 .echo foo 命令,WinDbg会对整个命令字符串执行alias替换,替换完成在执行整个命令,运行栈如下:


dbgeng!ReplaceAliases
dbgeng
!PreprocessExternalStrBuf
dbgeng
!PreprocessExternalString
dbgeng
!Execute
dbgeng
!DebugClient::ExecuteWide
...

一、什么是调试信息

调试信息即是符号,用于配合帮助调试器分析调试被调试程序的布局,因此调试信息能够帮助调试器定位变量、函数地址,显示变量的值(包括结构体以及类等复杂的结构),并且能够将地址映射到源代码的行。

二、调试信息分类

  • 公开函数和变量
    描述跨编译单元的可见函数何变量,调度信息存储其位置和名字
  • 私有函数和变量
    描述所有函数和变量,包括跨编译单元不可见的函数和变量,比如静态函数和变量,参数等,调试信息存储其位置、大小和名字
  • 源文件和行号
    源代码行到程序对应的映射。不包括注释
  • 类型信息
    调试信息存储类型的额外信息,使调试器能够区分变量、参数和返回值类型是int/char或用户自定义类型。对于函数来说,调试器还要知道参数个数,调用约定等
  • FPO信息
    编译时如果使用了FPO优化,调试信息使调试器能够知道函数调用栈的大小。
  • 编辑和继续信息
    主要用于VS实现编辑并继续的功能

三、调试信息的存储

调试信息在编译连接的时候产生,调试这个动作肯定是在编译连接之后,那么就需要把这些信息存储起来,为了离开开发环境也能调试,当然是要以文件的形式来永久存储。从微软的发展历史来看,主要有如下三种格式来存储调试信息:

  • COFF格式
    只包含三种调试信息类型:公开函数和变量、源代码行信息、FPO信息。直接存储在可执行文件中。
  • CodeView格式
    包含除编辑和继续信息以外的所有调试信息,既可以存储在可执行文件中,也可以存储在*.DBG文件中
  • PDB格式
    可以存储所有类型的调试信息,只存储在*.PDB文件中

*.DBG和*.PDB文件就是我们平常说的符号文件。

 

我想讨论一个我们都非常熟悉的场景。在过去的一年里你一直在拼命工作,在过去的几个月里,你甚至在晚上和周末工作。管理层对你的团队给予两周的休息,以感谢你的努力。但是现在你回到了办公室,你听到了来自你的技术支持部门的流言,说有些情况下你的应用程序因为神秘的原因在现场崩溃。你是做什么的?
应用程序恰好是用通过注册的未经处理的异常筛选器生成的AppDomain.UnhandledException事件。因此,您至少知道应用程序因InvalidCastException而失败,但是您无法想象为什么会发生这种情况。
如果您可以在受影响的系统上进行实时调试,那不是很好吗?除非您在现场为您的客户工作,或者您的软件在笔记本电脑上,并且您的客户愿意将其发送给您,否则我怀疑您是否会获得此机会。您需要的是一个工具来捕获应用程序失败时的状态。然后客户可以捕获这些信息并将其发送给您。

输入ADPlus。ADPlus是Debugging Tools for Windows包中的一个免费工具,它为CDB调试器编写脚本,允许您捕获系统上一个或多个进程的转储。它还具有以下优点:

  • ADPlus可以监视桌面应用程序、服务等。
  • ADPlus可以监视系统上的多个进程。当它收集这些进程的转储时,它会同时冻结和转储它们。这对于跟踪进程间通信的问题至关重要。
  • ADPlus支持xcopy部署,这意味着客户不需要通过Windows安装程序等来安装任何东西。这将最大限度地减少机器上的配置更改,这对客户来说是一种音乐。

注意:尽管ADPlus可以xcopy安装,但您仍然必须通过Windows安装程序安装Windows调试工具包,因为这是微软发布它的唯一方式。但是,一旦安装了一次Windows调试工具,就可以xcopy将ADPlus或整个Windows调试工具包部署到另一台计算机上。事实上,在开发过程中,我发现将开发工具签入源存储库非常方便。Windows调试工具支持这一点,因为它是xcopy可安装的。
对于熟悉Windows Installer的用户,您可以使用msi执行管理安装,以便调试Windows工具,这将允许您提取文件,而无需在计算机上实际安装软件包,例如:msiexec /a dbg_x86_6.11.1.404.msi。

综上所述,让我们看看ADPlus如何帮助您诊断.NET应用程序的问题。

示例应用程序

我将引用我放在一起的C#3.0示例应用程序来演示如何使用ADPlus捕获.NET应用程序中未经处理的异常。代码如下:

usingSystem;usingSystem.Linq;usingSystem.Runtime.Serialization;classA

{
public voidSaySomething() {

Console.WriteLine(
"Yeah, Peter....");throw newBadDesignException();

}

}
classB : A

{

}
classC

{

}
classEntryPoint

{
static voidMain() {

DoSomething();

}
static voidDoSomething() {

Func
<int, object> generatorFunc = (x) =>{if( x == 7) {return newC();

}
else{return newB();

}

};
var collection = from i in Enumerable.Range( 0, 10)selectgeneratorFunc(i);//Let's iterate over each of the items in the collection// //ASSUMING THEY ARE ALL DERIVED FROM A !!!! foreach( var item incollection ) {

A a
=(A) item;try{

a.SaySomething();

}
catch( BadDesignException ) {//Swallow these here. The programmer chose to//use exceptions for normal control flow which//is *very* poor design. }

}

}

}
public classBadDesignException : Exception

{
publicBadDesignException() { }public BadDesignException( string msg ) : base( msg ) { }public BadDesignException( string msg, Exception x ) : base( msg, x ) { }protectedBadDesignException( SerializationInfo si,

StreamingContext ctx )

:
base( si, ctx ) { }

}