2023年1月

当我们通过各种手段获取到一个Windows错误码后,如何获取对应的错误消息呢?有两种方法:一是用编程的手段(FormatMessage);其二是用现成的工具。第一种方法一般在我们编码的时候有用,而更多情况下我们进行调试得到了错误码,不可能还要先写一段代码,而是直接用工具去快速获取了。

一、VS自带工具---errlook.exe

当我们安装了VS开发工具就有,一般放在..\Microsoft Visual Studio XX\Common7\Tools目录下

运行的界面如下:

如果是Win32 Error Code,直接输入错误码就行,可以是十进制也可以是十六进制

注意,当我们输入一个值查不到时,比如0xC0000005

如果我们确定这是一个错误值的话,可以添加对应的模块,在进行查找

 二、Error Lookup

Error Lookup是一款国外的系统错误或蓝屏问查询工具,平时使用电脑中偶尔会有出错提示,在出错的时候会有一串你看不明白的代码,同理电脑蓝屏的时候也会有一串错误代码,只要把这些代码粘贴到软件中即可查询到是因为什么原因而出错,找到原因才可以解决错误的问题。下载地址http://www.jisuxz.com/down/66543.html

运行界面如下:

它显示了更多的错误信息,不仅仅时错误消息

 

这个工具不用自己去选择错误代码对应的模块,它会默认加载很多常用模块

 还可以切换语言

三、Windows Error Lookup Tool

是一款非常不催的错误代码查询软件。如何查询错误的代码呢?那么不妨来看看这款Windows Error Lookup Tool。该款工具能够帮助您在编程时间最快找出错误代码,提高您的编程效率。使用方法也很简单,用户只需在软件界面中输入错误代码,它会自动显示这个错误代码的含义或类型等详细信息。下载地址http://www.jz5u.com/Soft/system/Other/206870.html

运行界面如下:

也是自动加载了常用模块,不用用户自己去加载了

四、网络错误查找命令

net helpmsg #

是将Windows错误代码转换为字符串的一种快速的方法。只能输入十进制的错误代码,它只在Windows错误代码上运行。它不解码hresult或ntstatus值,不支持带有插入的消息,大概是因为它没有传递FORMAT_MESSAGE_IGNORE_INSERTS标识。

使用举例

当输入16进制代码时

 

 

总之,可以根据情况合理选择工具。

这三种码其实都是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文件就是我们平常说的符号文件。