我在上一篇文章中介绍了定位抛出异常的理论知识,本文会通过几个实例介绍各种情况下的定位方法。有调试符号如何定位?没有调试符号如何定位?32
位程序如何定位?64
位程序又该如何定位?
其实,32
位程序和 64
位程序定位过程大同小异,只不过在解析过程中需要注意,很多关键字段在 64
位程序中是偏移,需要加上模块基址得到虚拟地址后才能使用,而在 32
位程序中对应的字段就是虚拟地址,可以直接使用。
没有调试符号的时候定位异常类型会比较困难,需要根据上一篇文章中总结的步骤一步步的找到异常类型。有调试符号的情况会比较容易,有很多简便的查看方法。
一起来实战吧!
在开始实战之前,把相关结构体再贴一下,方便参考。
接下来,先介绍无调试符号时的定位方法,然后再介绍有调试符号时的定位方法。
如果没有导致异常的模块的调试符号,定位过程会比较复杂,需要根据上一篇文章中总结的方法一步步定位。此方法的坏处是麻烦,好处是比较通用,任何情况下都可以使用。
获取 EHParameters
的地址。_CxxThrowException
栈帧的 rsp+0x28
指向了 EHParameters
。
_CxxThrowException
对应栈帧的 rsp
是 000000b9f2effb80
。在 windbg
中输入 ? 000000b9f2effb80 + 0x28
,如下图:
由上图红色高亮部分可知 EHParameters
的地址是 000000b9f2effba8
。
获取 ThrowInfo
的地址。EHParameters + 0x10
的位置保存了 ThrowInfo
的地址,EHParameters + 0x18
的位置保存了异常模块基址。
在 windbg
中输入 dq 000000b9f2effba8
,如下图:
由上图红色高亮部分可知 ThrowInfo
的地址是 00007ff71c542a20
,异常模块基址是 00007ff71c540000
。
说明: 如果有 vcruntimexxx.dll
的调试符号,可以跳过前两步,直接切换到 _CxxThrowException
对应的栈帧即可得到 ThrowInfo
的地址和异常模块基址。
获取 CatchableTypeArray
的地址。ThrowInfo + 0xc
保存了 CatchableTypeArray
的偏移。
在 windbg
中输入 dd 00007ff71c542a20
,如下图:
由上图红色高亮部分可知 CatchableTypeArray
的偏移是 000029b8
。
异常模块基址是 00007ff71c540000
,所以 CatchableTypeArray
的地址是 00007ff71c540000 + 000029b8 = 00007ff71c5429b8
。
获取 CatchableType
的地址。CatchableTypeArray + 0x04
保存了第一个 CatchableType
对象的偏移,CatchableTypeArray + 0x08
保存了第二个 CatchableType
对象的偏移,以此类推。
在 windbg
中输入 dd 00007ff71c5429b8
,如下图:
由上图可知,一共有两个 CatchableType
类型的对象,第一个偏移是 000029d0
,第二个偏移是 000029f8
。
异常模块基址是 00007ff71c540000
,所以第一个 CatchableType
对象的地址是 00007ff71c540000 + 000029d0 = 00007ff71c5429d0
。
获取 TypeDescriptor
的地址。CatchableType + 0x04
保存了 TypeDescriptor
的偏移。
在 windbg
中输入 dd 00007ff71c5429d0
,如下图:
由上图红色高亮部分可知 TypeDescriptor
的偏移是 00004058
。
异常模块基址是 00007ff71c540000
,所以 TypeDescriptor
的地址是 00007ff71c540000 + 00004058 = 00007ff71c544058
。
获取异常类型名。TypeDescriptor + 0x10
保存了编码后的异常类型名。
在 windbg
中输入 da 00007ff71c544058 + 0x10
,如下图:
从上图可知,异常类型是 .?AVbad_alloc@std@@
,也就是 std::bad_alloc
。
32
位程序和 64
位程序定位过程大同小异,只需要把 64
位程序定位过程中的偏移值当成地址使用即可。这里就不赘述了,参考下图:
其实,对于 32
位程序,如果有 vcruntimexxx.dll
对应的符号,还有一种极其简单的方法,在 windbg
中输入 dt -r3 ThrowInfo address
,如下图:
对于有调试符号的情况,不仅可以使用无调试符号的定位方法,还可以使用更简单的方法查看——通过查看 pExceptionObject
对象的虚函数表来推断对应的对象类型。
在测试程序中,pExceptionObject
的地址是 0x000000b9f2effc00
,可以在 windbg
中执行 dps 0x000000b9f2effc00
即可查看异常对象对应的虚表,如下图:
从上图可知,异常类型是 std::bad_alloc
,其虚表内容都是其基类(std::exception
)的虚函数,因为 std::bad_alloc
没重写任何虚函数,也没新增任何虚函数。
为什么没调试符号的时候不能用这个方法呢?因为没有调试符号的情况下,从 dps
的输出结果中看不到关键的虚表名称,也就不能推断出具体的异常类型了。
对应的程序源码工程文件及对应的转储文件已经上传到我的个人仓库了,感兴趣的小伙伴儿可以从以下链接自行下载:
https://gitee.com/bianchengnan/my-blog-stuff/tree/master/search-throwing-exception-from-dump-file-part2/TestThrowException
还有一个更真实的转储文件,可以实战一把。因为比较大,我传到百度云了,可以到这里下载:
https://pan.baidu.com/s/1K7FzsseMlU6kmrMwm3jn4Q?pwd=8t47
vs
源码
0:000> dt EHExceptionRecord
TestThrowException!EHExceptionRecord
+0x000 ExceptionCode : Uint4B
+0x004 ExceptionFlags : Uint4B
+0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD
+0x00c ExceptionAddress : Ptr32 Void
+0x010 NumberParameters : Uint4B
+0x014 params : EHExceptionRecord::EHParameters
0:000> dt EHExceptionRecord::EHParameters
TestThrowException!EHExceptionRecord::EHParameters
+0x000 magicNumber : Uint4B
+0x004 pExceptionObject : Ptr32 Void
+0x008 pThrowInfo : Ptr32 _s_ThrowInfo
0:000> dt _s_ThrowInfo
TestThrowException!_s_ThrowInfo
+0x000 attributes : Uint4B
+0x004 pmfnUnwind : Ptr32
void
+0x008 pForwardCompat : Ptr32
int
+0x00c pCatchableTypeArray : Ptr32 _s_CatchableTypeArray
0:000> dt _s_CatchableTypeArray
TestThrowException!_s_CatchableTypeArray
+0x000 nCatchableTypes : Int4B
+0x004 arrayOfCatchableTypes : [0] Ptr32 _s_CatchableType
0:000> dt _s_CatchableType
TestThrowException!_s_CatchableType
+0x000 properties : Uint4B
+0x004 pType : Ptr32 TypeDescriptor
+0x008 thisDisplacement : PMD
+0x014 sizeOrOffset : Int4B
+0x018 copyFunction : Ptr32
void
0:000> dt TypeDescriptor
TestThrowException!TypeDescriptor
+0x000 hash : Uint4B
+0x004 spare : Ptr32 Void
+0x008 name : [0] Char
0:000> dt EHExceptionRecord
TestThrowException!EHExceptionRecord
+0x000 ExceptionCode : Uint4B
+0x004 ExceptionFlags : Uint4B
+0x008 ExceptionRecord : Ptr32 _EXCEPTION_RECORD
+0x00c ExceptionAddress : Ptr32 Void
+0x010 NumberParameters : Uint4B
+0x014 params : EHExceptionRecord::EHParameters
0:000> dt EHExceptionRecord::EHParameters
TestThrowException!EHExceptionRecord::EHParameters
+0x000 magicNumber : Uint4B
+0x004 pExceptionObject : Ptr32 Void
+0x008 pThrowInfo : Ptr32 _s_ThrowInfo
0:000> dt _s_ThrowInfo
TestThrowException!_s_ThrowInfo
+0x000 attributes : Uint4B
+0x004 pmfnUnwind : Ptr32
void
+0x008 pForwardCompat : Ptr32
int
+0x00c pCatchableTypeArray : Ptr32 _s_CatchableTypeArray
0:000> dt _s_CatchableTypeArray
TestThrowException!_s_CatchableTypeArray
+0x000 nCatchableTypes : Int4B
+0x004 arrayOfCatchableTypes : [0] Ptr32 _s_CatchableType
0:000> dt _s_CatchableType
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!