这三种码其实都是Windows系统错误码,只是对应不同API和使用场景。它们既有区别,又相互有联系。

一、区别和联系

  • 都是32位值
  • Win32 Error Code和NTSTATUS位域组成相同,但Win32 Error Code的取值范围只能在0x00000000---0x0000FFFF
  • Win32 Error Code和COM Error Code,在高2位定义不同,设备来源值可能一样,但代表的设备不一样,设备来源值位数也不一样,但它们又可以互相转换。它们实际上的定义都是LONG型的,都是通过相关的API函数返回值返回来的,小于0时都代表着失败了。
  • 具有相同格式的ntstatus和win32错误代码可能会使用相同的设施代码。然而,情况并非如此——相反,win32错误代码(根据winerrror.h)使用的是hresults的功能值!因此,交换ntstatus和win32错误代码在语法上是正常的,但由于设施代码不匹配而改变了它们的语义。

二、相互转换

1 Facility may need to be adapted
2 Holds for ‘real’ Win32 error codes. For compatibility error codes, use HRESULT_FROM_WIN32
3 As long as you have a ‘real’ HRESULT (i.e. not one from
HRESULT_FROM_WIN32) and want to get a ‘real’ Win32 error code (i.e. not a
compaitibility one) — otherwise it can get tricky
4 Note that HRESULT_FROM_NT does not take the NT Status to Win32 Error Code conversion table
into account, thus the result may not be what one would expect. Using
LsaNtStatusToWinError takes this table into account, but yields
‘compatibility’ Win32 error code.

参考:

https://jpassing.com/2007/08/20/error-codes-win32-vs-hresult-vs-ntstatus/?like=1&_wpnonce=9495153bae


 

一、什么是符号文件

符号文件(Symbol Files)是一个数据信息文件,它包含了应用程序二进制文件(比如:EXE、DLL等)调试信息,专门用来作调试之用,最终生成的可执行文件在运行时并不需要这个符号文件,但你的程序中所有的变量信息都记录在这个文件中。所以调试应用程序时,这个文件是非常重要的。用 Visual C++ 和 WinDbg 调试程序时都要用到这个文件。  在 Windows 系统中,符号文件以 .pdb 为扩展名,比如:每个 Windows 操作系统下有一个 GDI32.dll 文件,编译器在编译该 DLL 的时候会产生一个 GDI32.pdb 文件,一旦你拥有了这个 PDB 文件,那么便可以用它来调试并跟踪到 GDI32.dll 内部。该文件和二进制文件的编译版本密切相关,比如修改了 DLL 的输出函数,再编译该 DLL,那么原先的 PDB 文件就过时了,不能再用老的 PDB 文件来做调试工作,而必须使用最新的 PDB 文件版本。

二、符号文件的生成和获取

  • 自己编写的代码
    在链接配置里做如下配置

  • Windows系统相关程序的符号
    从微软服务器下载http://msdl.microsoft.com/download/symbols下载,在vs里配置

 三、符号文件包含哪些信息

3.1、Native C++ PDB包含了如下的信息:

  • public,private 和static函数地址;
  • 全局变量的名字和地址;
  • 参数和局部变量的名字和在堆栈的偏移量;
  • class,structure 和数据的类型定义;
  • Frame Pointer Omission 数据,用来在x86上的native堆栈的遍历;
  • 源代码文件的名字和行数;


3.2、.NET PDB只包含了2部分信息:

  •  源代码文件名字和行数;
  • 和局部变量的名字;
  • 所有的其他的数据都已经包含在了.NET Metadata中了; 

四、符号文件分类

  •  私有符号文件
    包含所有程序调试信息,如第三节讲到的信息,VC默认生成的就是私有符号文件

  • 公有符号文件
    在编译链接时,设置/PDBSTRIPPED 选项将创建第二个程序数据库 (PDB) 文件。 此 PDB 文件省略您不希望交付给客户的符号。 第二个 PDB 文件仅包含公共符号:对象文件的列表和可执行文件中由它们参与构成的部分;用于遍历堆栈的帧指针优化 (FPO) 调试记录。去除了如下信息:类型信息,行号信息、基于对象文件的 CodeView 符号,如函数、局部变量和静态数据的符号。

五、符号文件系统

有两个常见的符号文件系统。 在本文档中,这些将称为当前系统旧系统

  • 当前符号文件系统
    在当前系统中,始终有两个文件: 可执行文件和.pdb 文件。 .Pdb 文件包含所有符号。 可执行文件包含.pdb 文件的指针。

  • 旧的符号文件系统
    在旧系统中,可执行文件和符号文件可以排列两个不同的方式:
    1、可执行文件和.pdb 文件。 在这种方案,大多数符号信息是.pdb 文件中。 可执行文件中包含的符号信息的其余部分。 可执行文件还包含指向.pdb 文件的指针。
    2、可执行文件、.pdb 文件和.dbg 文件。 .Pdb 文件是两个文件排列方式相同: 它包含的符号的大多数。 符号信息的其余部分是.dbg 文件中。 在可执行文件是没有对应符号信息。 可执行文件包含一个指向.dbg 文件,并且.dbg 文件包含.pdb 文件的指针。

我们知道符号文件对我们调试是非常重要的,如果没有符号文件,我们在调试器里看到的要么是偏移地址,要么看到的是错误符号,这会导致我们定不了位或错误定位,如果有了匹配的符号文件,这一切都不是问题了。

一、调试器寻找符号文件

首先在我们编译我们的程序时,如果设置了符号选项,那么在编译连接时,除了产生我们需要的符号文件外,还会在我们的PE文件(exe,dll)里记录下对应的符号文件信息。

这里的路径其实是开发编译机上生成的路径,当我们在这台机上调试时,就会到这个目录下寻找符号文件。当在别的机器上调试时,调试器就会会拿路径里的文件名去查找。

当调试器加载一个PE模块时,第一个搜索的路径就是这个模块所在的路径,如果不在模块所在的路径,则查找模块中记录的build目录,就时上图里的路径, 如果以上两个路径都没有找到PDB,则根据symbol server的设置,在本地的symbol server的cache中查找,如果在本地的symbol server的cache中没有对应的PDB,则最后才到远程的symbol server中查找。

当在某个路径找到了文件名匹配的符号文件,就要看符合文件根模块是否匹配。根据上图,我们知道编译器在生成PE模块时,调试信息里面会生成一个GUID值,同时符号文件里也会记录这个GUID值,每一次编译连接,这个值都会发生变化。

当两个文件里的GUID值相等时,符号文件才跟模块文件匹配,这时才能进行正常的调试,获得正确的符号信息。所以我们在调试时,一定要把正确的符号文件跟模块文件放在同一目录下才行。

二、调试器定位

当有了正确的符号文件后,调试器是如何定位源代码、行号等信息的呢?我们以VS来大概讲一下,默认情况下,在pdb文件中,保存了可执行文件中所有的符号(函数名、变量名等)所在源文件、行号、OFFSET(文件中的偏移)等信息。但是这些信息,是在编译阶段得到的,编译器在编译每个cpp的过程中,就可以把这些符号的相关信息收集起来,存放在各个cpp所生成的obj文件中,然后在链接的时候,提取每个obj中的这些信息,生成一个单独的pdb文件。这样,以后调试程序的时候,调试器只要找得到这个pdb,就可以知道可执行文件中,所有符号所在的源文件、行号和OFFSET了。反过来说,当给出一个源文件和行号,就可以拿到对应的OFFSET了,所以在还没有启动调试的时候,我们下的断点,实际上调试器是知道这个断点应该在哪个OFFSET上了,等启动调试的时候,用这个OFFSET加上这个模块所加载到的基地址值,就可以得到这个断点所在的VA(程序加载到内存后的一个虚拟地址)了,然后在这个VA处强行写上int 3指令,并继续执行,当执行到这里,便中断下来给我们一个调试机会了。当我们在VS里鼠标放在某个变量上时,调试器可以拿到这个变量的名称,根据我们前面说的,用这个名称去pdb中查找,自然就可以找到pdb文件中保存的OFFSET了,加上这个模块的基地址,就找到了这个变量所在内存的VA,剩下的就是读一下这个VA内存中的内容了。这样也就实现了观察变量值得功能。

一、什么是调试信息

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

二、调试信息分类

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

三、调试信息的存储

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

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

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

 

一、什么是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头部结构