(一)前言
windows端抓取dump,有一个现成的谷歌官方提供的工具:crashpad。
具体可以参考官方资料:GitHub - chromium/crashpad: A crash-reporting system · GitHub
这里编译crashpad使用的是MT的编译模式,因为不想把依赖chromium中的base库等基础库。
这样很方便多个项目很方便的使用。
crashpad的架构如下:

简单总结就是有一个CrashpadHander.exe的进程和一个CrashpadClient.dll的动态库,接入方就是Client Process,加载动态库CrashpadClient.dll,会有这个动态库启动进程CrashpadHander.exe。
但是在实际使用过程中遇到了一些问题,也就是有些类型的崩溃没有抓取到。
(二)遇到的问题
在windows平台下,抓取dump一般比较常用的方式是使用SetUnhandledExceptionFilter 设置未处理异常的回调函数,在回调函数里抓取dump。
但这种方式只能抓取到SEH可以处理的异常,比如违规访问(比如空指针),除零异常等。
还有一些异常处理不了。比如调用了纯虚函数,抛出了c++异常等。
详细的讲解在这个文章里有讲:
https://www.codeproject.com/Articles/207464/Exception-Handling-in-Visual-Cplusplus
总结下来就是这四类:
_set_purecall_handler(pureVirtualCall);
_set_invalid_parameter_handler(InvalidParameterHandler);
_set_thread_local_invalid_parameter_handler(localThreadInvalidParameterHandler);
signal(SIGABRT, signalHandler);
这四类都是在CRT里的函数,也就是C语言运行时库。
我当时遇到了一个崩溃,大概的代码可以简化如下:
cpp
struct Base
{
virtual void doWork() { }
std::string a = "he";
};
struct Sub : Base
{
std::string b = "xx";
};
void* pVoid = (void*)malloc(0);
std::shared_ptr<Base>& base = *(std::shared_ptr<Base>*)pVoid;
auto sub = std::dynamic_pointer_cast<Sub>(base);
这个代码会在dynamic_pointer_cast执行过程中崩溃,具体来说就会触发std::abort的调用,而这个行为的捕获就是要依赖 signal(SIGABRT, signalHandler); 这个函数注册的 signalHandler 来处理。
问题是我在CrashpadClient.dll里已经注册过了SIGABRT的处理函数,还是没有抓取到dump,但是我模拟一个空指针崩溃就可以抓到dump。
(三)如何解决
通过询问AI(不得不说AI对软件工程师来说,帮助很大,效率提高了不少)和自己的实际代码验证,确认了问题的根源:
我编译的CrashpadClient.dll是使用MT编译的。

/MT 和 /MD 是 MSVC 编译器中指定 C/C++ 运行时库(CRT)链接方式 的核心选项,本质区别在于静态链接 与动态链接。
核心区别
- **
/MT(Multi-threaded, Static)** :静态链接 。将运行时库代码(如libcmt.lib)直接编译进可执行文件。程序独立 ,无需外部 DLL,但文件体积较大;若项目含多个模块,需确保所有模块均用/MT,否则易引发"跨堆内存释放"崩溃。 - **
/MD(Multi-threaded DLL, Dynamic)** :动态链接 。程序仅导入msvcrt.lib,实际代码依赖系统对应的运行时 DLL(如msvcr140.dll)。文件体积小,所有模块共用同一堆内存,但部署时需保证目标机器存在对应版本的 CRT DLL。
由于CrashpadClient.dll是用/MT编译的,所以虽然调用了signal(SIGABRT, signalHandler)设置了SIGABRT异常回调函数,但是当ClientProcess(也就是接入crashpad的进程)异常的时候,他只会在自己的依赖的CRT库里查找对应的SIGABRT处理函数,这个处理函数一定不会是CrashpadClient.dll使用的。
项目刚好用的CEF(比如工程是Main.exe),所以项目都是用的/MT编译出来的,如果想要抓取CRT异常相关的崩溃,需要在Main.exe中设置上述的异常处理函数。
如果还有其他/MT编译的模块,也需要在模块的初始化相关代码中,设置一次CRT相关异常处理函数。
总结下来就是:
每个/MT的模块(无论exe还是dll)都需要设置一遍CRT相关的异常处理函数。
所有使用/MD编译的模块,只需要设置一次CRTCRT相关的异常处理函数即可,因为/MD的是动态链接的,所有的模块公用一个CRT库。
(四)总结
crashpad使用了/MT编译之后。外部如果需要使用,就需要注意使用方使用的是/MT还是/MD编译的。
如果是/MT编译的:每个/MT的模块(无论exe还是dll)都需要设置一遍CRT相关的异常处理函数。
如果是/MD编译的:所有使用/MD编译的模块,只需要设置一次CRTCRT相关的异常处理函数即可,因为/MD的是动态链接的,所有的模块公用一个CRT库。
备注:
windows的SEH是支持跨模块的,也就是说和/MT、/MD没有关系,也不论异常发生在哪个模块。
chromium代码和cef源码中,已经自动设置了上述三个handler:
_set_purecall_handler(pureVirtualCall);
_set_invalid_parameter_handler(InvalidParameterHandler);
signal(SIGABRT, signalHandler);
这个invalid_parameter_handler是和线程相关的,也就是说每个线程都需要设置,我们自己开发的项目一般也只设置一个主线程。
_set_thread_local_invalid_parameter_handler(localThreadInvalidParameterHandler);
最开始我使用_set_purecall_handler的时候,我在想如何才能拿触发这个purecall,查了下官网:
有现成的代码:
cpp
// _set_purecall_handler.cpp
// compile with: /W1
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
class CDerived;
class CBase
{
public:
CBase(CDerived *derived): m_pDerived(derived) {}
~CBase();
virtual void function(void) = 0;
CDerived * m_pDerived;
};
class CDerived : public CBase
{
public:
CDerived() : CBase(this) {} // C4355
virtual void function(void) {}
};
CBase::~CBase()
{
m_pDerived -> function();
}
void myPurecallHandler(void)
{
printf("In _purecall_handler.");
exit(0);
}
int _tmain(int argc, _TCHAR* argv[])
{
_set_purecall_handler(myPurecallHandler);
CDerived myDerived;
}
(五)号外号外,招人环节,招聘环境
我们公司在招聘c++开发工程,需要找工作的同学可以找我内推。这个岗位是我所在部门。
可以在评论区留言留下联系方式,或者私信我吧。
base:上海
薪资open,看面试情况和个人技术水平来定。界内TOP水平。大厂。
其他各种岗位也可以帮忙内推:
Java、AI工程师,算法工程师、数据库、前端、客户端(android, iOS, PC,鸿蒙等)。
产品经理,运营经理,实习生,应届生都可以。