最近在分析转储文件时,遇到了一个由 throw
抛出的异常。尽管在 windbg
中使用 !analyze -v
迅速知道了异常码是 0xe06d7363
(对应的 ASCII
码是 .msc
),但是根据异常码并不能确定具体抛出来的是哪种异常。针对这种情况,确定具体的异常类型才有意义。
本篇文章会简单介绍与抛出异常相关的内容,包括关键的函数及结构体。下一篇文章会通过实例介绍几种典型情况(有调试符号 / 没有调试符号 / 32
位程序 / 64
位程序)下的定位方法。
说明: 对源码不感兴趣的小伙伴而可以直接跳到【解析方法小结】查看结论。
throw
关键字编译后对应的函数是 _CxxThrowException()
,该函数内部会通过 RaiseException()
触发异常。_CxxThrowException()
是有源码可查的,我们可以从这个函数入手,先来熟悉下这个函数以及相关的结构体。
该函数定义在 vs
自带的 throw.cpp
中,一般在 crt\src\vcruntime\
目录下。直接用 everything
搜索 throw.cpp
,然后打开即可。vs2019
中的实现代码如下,有删减:
根据源码可知, _CxxThrowException()
内部会调用 RaiseException()
,RaiseException()
的原型如下:
_CxxThrowException
调用 RaiseException()
时传递的各个参数值如下:
dwExceptionCode
的值是 EH_EXCEPTION_NUMBER
,对应的十六进制值是 0xe06d7363
,也就是 .msc
。
dwExceptionFlags
的值是 EXCEPTION_NONCONTINUABLE
,对应的十六进制值是 0x1
。
nNumberOfArguments
的值是 EH_EXCEPTION_PARAMETERS
,在 32
位程序中是 3
,在 64
位程序中是 4
。定义如下:
根据定义可知,ThisException.params
的类型是 EHExceptionRecord::EHParameters
,如果 _EH_RELATIVE_TYPEINFO
为 0
,则包含 3
个成员,否则就会包含第 4 个成员 pThrowImageBase
。
而 _EH_RELATIVE_TYPEINFO
在 32
位程序中是 0
,在 64
位程序中是 1
,定义如下:
EHExceptionRecord::EHParameters
结构体的成员数量与调用 RaiseException()
时的 nNumberOfArguments
参数值是对应的。
在 32
位程序中,nNumberOfArguments
的值是 3
,EHExceptionRecord::EHParameters
刚好有 3
个成员,在 64
位程序中 nNumberOfArguments
的值是 4
,EHExceptionRecord::EHParameters
刚好有 4
个成员。
EHExceptionRecord::EHParameters
中的 pExceptionObject
和 pThrowInfo
是查找异常类型的关键。
其中,pExceptionObject
是异常对象的地址,pThrowInfo
的类型是 ThrowInfo
,用来描述异常对象的类型信息。一起来看看 ThrowInfo
的定义。
pmfnUnwind
是处理异常时会调用的回卷函数,一般是析构函数,可以根据此值判断异常对象的类型!
pForwardCompat
一般情况下都是 0
,不用太关心
pCatchableTypeArray
非常重要,记录了类型信息
_EH_RELATIVE_TYPEINFO
在上面已经贴出来了,在 32
位程序中被定义为 0
,在 64
位程序中被定义为 1
。
所以,pForwardCompat
和 pCatchableTypeArray
在 32
位程序中是地址,在 64
位程序中是偏移。
还记得 EHExceptionRecord::EHParameters
在 64
位程序中有 4
个成员吗?第 4
个成员就是抛出异常对应的模块基址,用这个基址加上这里的偏移就得到了对应成员在内存中的位置。一定要记住这个结论,在分析 64
位程序的异常对象类型时会用到!
接下来看看关键的 CatchableTypeArray
类型的定义,摘录如下:
说明: 这里为什么使用数组呢?因为抛出的异常可能继承自某个基类。arrayOfCatchableTypes
会把继承链上的所有类型信息按照从子类到基类的顺序记录下来。拿 std::bad_alloc
举例,它继承自 std::exception
。所以,nCatchableTypes
的值为 2
,arrayOfCatchableTypes[0]
记录了 std::bad_alloc
的类型信息,arrayOfCatchableTypes[1]
记录了 std::exception
的类型信息。
再来看看结构体 CatchableType
的定义,摘录如下:
我们只需要关注 pType
成员即可。同样的,在 32
位程序中是地址,在 64
位程序中是偏移。 pType
对应的类型是 TypeDescriptor
,接下来看看 TypeDescriptor
的定义。
其中,name
成员是经过名字改编后的异常类型,它是一个以 \0
结尾的字符串,可以在 windbg
中通过 da
查看。
源码有点乱,还是在 windbg
中看的直观舒服,还可以看到偏移。以下是 32
位和 64
位程序中对应的结构体定义:
划重点: 务必记住以上结构体的定义,尤其是关键字段的偏移。这是解析的依据!
先找到 EHParameters
类型的对象(可省略此步)
可以通过 RaiseException()
的第四个参数查找。
在 32
位程序中,定位方法非常简单,可以直接查看 RaiseException()
的第 4
个参数,ebp+0x14
。
在 x64
位中可以通过 _CxxThrowException()
的 rsp + 0x28
定位。因为在调用 RaiseException()
的时候, _CxxThrowException()
会把此参数存在自己栈帧中 rsp + 0x28
的位置。
再找到 ThrowInfo
类型的对象
解析 EHParameters
中的第 3
个成员 pThrowInfo
,在 32
位程序中偏移是 0x8
,在 64
位程序中偏移是 0x10
。
说明: 还有两种查看方法:
对于 32
位程序可以通过 _CxxThrowException()
对应栈帧的第 2
个参数(ebp+c
)直接查看。
如果有 vcruntimexxx.dll
的调试符号,可以直接切到 _CxxThrowException()
对应的栈帧,windbg
会自动帮忙列出对应的值。
再找到 CatchableTypeArray
类型的对象
解析 ThrowInfo
的第 4
个成员 pCatchableTypeArray
,其偏移是 0xc
(32
位 64
位通用)。
需要注意的是,此成员在 32
位程序中是地址;在 64
位程序中是偏移,需要加上镜像基址得到最终的地址。
再找到 CatchableType
类型的对象
解析 CatchableTypeArray
的第 2
个成员 arrayOfCatchableTypes
,偏移是 0x4
(32
位 64
位通用)。
该成员记录了 CatchableType
数组的首地址或者偏移。
需要注意的是,此成员在 32
位程序中是地址;在 64
位程序中是偏移,需要加上镜像基址得到最终的地址。
说明: 第 1
个成员 nCatchableTypes
记录了 CatchableType
数组的个数。
再找到 TypeDescriptor
类型的对象
解析 CatchableType
数组中的每个对象(其实,只需要解析第一个即可)。重点关注第 2
个成员 pType
,偏移是 0x4
(32
位 64
位通用)。
需要注意的是,此成员在 32
位程序中是地址;在 64
位程序中是偏移,需要加上镜像基址得到最终的地址。
最后找到异常类型名
解析 TypeDescriptor
对象,只需要关注第 3
个成员 name
成员即可,在 32
位程序中偏移是 0x8
,在 64
位程序中偏移是 0x10
。
它是一个以 \0
结尾的字符串,可以在 windbg
用 da
显示其内容。
vs
源码
extern
"C"
__declspec
(
noreturn
)
void
__stdcall
_CxxThrowException(
void
* pExceptionObject,
_ThrowInfo* pThrowInfo
) {
EHTRACE_ENTER_FMT1(
"Throwing object @ 0x%p"
, pExceptionObject);
static
const
EHExceptionRecord ExceptionTemplate = {
EH_EXCEPTION_NUMBER,
EXCEPTION_NONCONTINUABLE,
nullptr,
nullptr,
EH_EXCEPTION_PARAMETERS,
{ EH_MAGIC_NUMBER1,
nullptr,
nullptr,
#if EH_EXCEPTION_PARAMETERS == 4
nullptr
#endif
}
};
EHExceptionRecord ThisException = ExceptionTemplate;
ThrowInfo* pTI = (ThrowInfo*)pThrowInfo;
ThisException.params.pExceptionObject = pExceptionObject;
ThisException.params.pThrowInfo = pTI;
#if _EH_RELATIVE_TYPEINFO
PVOID
ThrowImageBase = RtlPcToFileHeader((
PVOID
)pTI, &ThrowImageBase);
ThisException.params.pThrowImageBase = ThrowImageBase;
#endif
EHTRACE_EXIT;
RaiseException( ThisException.ExceptionCode,
ThisException.ExceptionFlags,
ThisException.NumberParameters,
(
PULONG_PTR
)&ThisException.params );
}
extern
"C"
__declspec
(
noreturn
)
void
__stdcall
_CxxThrowException(
void
* pExceptionObject,
_ThrowInfo* pThrowInfo
) {
EHTRACE_ENTER_FMT1(
"Throwing object @ 0x%p"
, pExceptionObject);
static
const
EHExceptionRecord ExceptionTemplate = {
EH_EXCEPTION_NUMBER,
EXCEPTION_NONCONTINUABLE,
nullptr,
nullptr,
EH_EXCEPTION_PARAMETERS,
{ EH_MAGIC_NUMBER1,
nullptr,
nullptr,
#if EH_EXCEPTION_PARAMETERS == 4
nullptr
#endif
}
};
EHExceptionRecord ThisException = ExceptionTemplate;
ThrowInfo* pTI = (ThrowInfo*)pThrowInfo;
ThisException.params.pExceptionObject = pExceptionObject;
ThisException.params.pThrowInfo = pTI;
#if _EH_RELATIVE_TYPEINFO
PVOID
ThrowImageBase = RtlPcToFileHeader((
PVOID
)pTI, &ThrowImageBase);
ThisException.params.pThrowImageBase = ThrowImageBase;
#endif
EHTRACE_EXIT;
RaiseException( ThisException.ExceptionCode,
ThisException.ExceptionFlags,
ThisException.NumberParameters,
(
PULONG_PTR
)&ThisException.params );
}
VOID
WINAPI RaiseException(
_In_
DWORD
dwExceptionCode,
_In_
DWORD
dwExceptionFlags,
_In_
DWORD
nNumberOfArguments,
_In_reads_opt_(nNumberOfArguments) CONST
ULONG_PTR
* lpArguments
);
VOID
WINAPI RaiseException(
_In_
DWORD
dwExceptionCode,
_In_
DWORD
dwExceptionFlags,
_In_
DWORD
nNumberOfArguments,
_In_reads_opt_(nNumberOfArguments) CONST
ULONG_PTR
* lpArguments
);
#if (defined(_M_AMD64) || defined(_M_ARM) || defined(_M_ARM64)) && !defined(_CHPE_X86_ARM64_EH_)
#define EH_EXCEPTION_PARAMETERS 4 // Number of parameters in exception record
#else
#define EH_EXCEPTION_PARAMETERS 3 // Number of parameters in exception record
#endif
#if (defined(_M_AMD64) || defined(_M_ARM) || defined(_M_ARM64)) && !defined(_CHPE_X86_ARM64_EH_)
#define EH_EXCEPTION_PARAMETERS 4 // Number of parameters in exception record
#else
#define EH_EXCEPTION_PARAMETERS 3 // Number of parameters in exception record
#endif
typedef
struct
EHExceptionRecord {
unsigned
long
ExceptionCode;
unsigned
long
ExceptionFlags;
struct
_EXCEPTION_RECORD* ExceptionRecord;
void
* ExceptionAddress;
unsigned
long
NumberParameters;
struct
EHParameters {
unsigned
long
magicNumber;
void
* pExceptionObject;
ThrowInfo* pThrowInfo;
#if _EH_RELATIVE_TYPEINFO
void
* pThrowImageBase;
#endif
} params;
} EHExceptionRecord;
typedef
struct
EHExceptionRecord {
unsigned
long
ExceptionCode;
unsigned
long
ExceptionFlags;
struct
_EXCEPTION_RECORD* ExceptionRecord;
void
* ExceptionAddress;
unsigned
long
NumberParameters;
struct
EHParameters {
unsigned
long
magicNumber;
void
* pExceptionObject;
ThrowInfo* pThrowInfo;
#if _EH_RELATIVE_TYPEINFO
void
* pThrowImageBase;
#endif
} params;
} EHExceptionRecord;
#if defined(_M_CEE_PURE) || defined(BUILDING_C1XX_FORCEINCLUDE)
#define _EH_RELATIVE_TYPEINFO 0 // <-----
#define _EH_RELATIVE_FUNCINFO 0
#define _RTTI_RELATIVE_TYPEINFO 0
#elif defined(_CHPE_X86_ARM64_EH_)
#define _EH_RELATIVE_TYPEINFO 0 // <-----
#define _EH_RELATIVE_FUNCINFO 1
#define _RTTI_RELATIVE_TYPEINFO 0
#elif defined(_M_ARM)
#define _EH_RELATIVE_TYPEINFO 1 // <-----
#define _EH_RELATIVE_FUNCINFO 1
#define _RTTI_RELATIVE_TYPEINFO 0
#elif defined(_M_AMD64) || defined(_M_ARM64)
#define _EH_RELATIVE_TYPEINFO 1 // <-----
#define _EH_RELATIVE_FUNCINFO 1
#define _RTTI_RELATIVE_TYPEINFO 1
#else
#define _EH_RELATIVE_TYPEINFO 0 // <-----
#define _EH_RELATIVE_FUNCINFO 0
#define _RTTI_RELATIVE_TYPEINFO 0
#endif
#if defined(_M_CEE_PURE) || defined(BUILDING_C1XX_FORCEINCLUDE)
#define _EH_RELATIVE_TYPEINFO 0 // <-----
#define _EH_RELATIVE_FUNCINFO 0
#define _RTTI_RELATIVE_TYPEINFO 0
#elif defined(_CHPE_X86_ARM64_EH_)
#define _EH_RELATIVE_TYPEINFO 0 // <-----
#define _EH_RELATIVE_FUNCINFO 1
#define _RTTI_RELATIVE_TYPEINFO 0
#elif defined(_M_ARM)
#define _EH_RELATIVE_TYPEINFO 1 // <-----
#define _EH_RELATIVE_FUNCINFO 1
#define _RTTI_RELATIVE_TYPEINFO 0
#elif defined(_M_AMD64) || defined(_M_ARM64)
#define _EH_RELATIVE_TYPEINFO 1 // <-----
#define _EH_RELATIVE_FUNCINFO 1
#define _RTTI_RELATIVE_TYPEINFO 1
#else
#define _EH_RELATIVE_TYPEINFO 0 // <-----
#define _EH_RELATIVE_FUNCINFO 0
#define _RTTI_RELATIVE_TYPEINFO 0
#endif
typedef
const
struct
_s_ThrowInfo {
unsigned
int
attributes;
PMFN pmfnUnwind;
#if _EH_RELATIVE_TYPEINFO && !defined(BUILDING_C1XX_FORCEINCLUDE)
int
pForwardCompat;
int
pCatchableTypeArray;
#else
int
(__cdecl * pForwardCompat)(...);
CatchableTypeArray* pCatchableTypeArray;
#endif
} ThrowInfo;
typedef
const
struct
_s_ThrowInfo {
unsigned
int
attributes;
PMFN pmfnUnwind;
#if _EH_RELATIVE_TYPEINFO && !defined(BUILDING_C1XX_FORCEINCLUDE)
int
pForwardCompat;
int
pCatchableTypeArray;
#else
int
(__cdecl * pForwardCompat)(...);
CatchableTypeArray* pCatchableTypeArray;
#endif
} ThrowInfo;
typedef
const
struct
_s_CatchableTypeArray {
int
nCatchableTypes;
#if _EH_RELATIVE_TYPEINFO
int
arrayOfCatchableTypes[];
#else
CatchableType* arrayOfCatchableTypes[];
#endif
} CatchableTypeArray;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!