Windows下定位Mingw编译的Qt程序崩溃堆栈
一、dump和pdb是什么
在Windows系统下,当我们写的程序跑在客户的机器上,因为一个bug,导致程序崩溃,我们该如何定位并修复这个bug呢?
有人会说记录日志,即便有日志,也是不好定位的,因为你只能推测出大概的模块或者位置,无法定位到具体出错的代码行。
此时,我们可以让程序崩溃后,自动生成一个*.dmp文件,并配合在编译该程序时生成的pdb文件,来准确定位到调用堆栈、代码行上。这样很轻易就可以找到该bug。
-
dump文件,后缀*.dmp,是程序崩溃时的内存转储文件;
-
pdb文件,后缀*.pdb,是程序的符号文件。
二、Breakpad与qbreakpad简介
Breakpad是由Google开发的开源跨平台崩溃报告系统,用于捕获程序崩溃时的内存状态并生成轻量级minidump文件(.dmp)。其核心功能包括崩溃拦截、堆栈记录和寄存器状态捕获,适用于C++应用的多平台部署(Windows/Linux/macOS)。
QBreakpad是Breakpad的Qt专用封装库,它将Breakpad的复杂集成简化为Qt模块,提供更便捷的API和跨平台兼容性,专门服务于Qt应用程序的崩溃管理。
BreakPad工作原理示意图:
表达的意思就是:
-
我们在编译的时候,需要在Release版程序中生成调试信息。
-
使用Breakpad提供的dump_syms工具或者cv2pdb工具,从release版本程序导出符号文件。
-
当程序崩溃时,breakpad会捕捉崩溃,并生成dump文件。
-
dump文件可以直接发送到指定服务器,或者由用户手动发给开发者。
-
收到dump文件后,结合符号文件,可通过minidump_stackwalk工具或Visual Studio工具生成堆栈调用信息文件,这个文件可以直接阅读,定位bug。
三、开发环境说明
本人使用的开发环境如下:
操作系统:Windows10
IDE:Qt Creator4.4.1
编译器:mingw53_32
Qt库:Qt5.9.3
四、源码准备
我们知道qBreakpad是对Breakpad的封装,所以qBreakpad的编译,还依赖2套源码Breakpad、LSS。
(1)下载Breakpad源码
下载地址:https://github.com/google/breakpad
(2)下载LSS源码
下载地址:https://github.com/ithaibo/linux-syscall-support
(3)下载qBreakpad源码
下载地址:https://github.com/buzzySmile/qBreakpad
(4)下载cv2pdb工具
下载地址:
注意:这个工具最好下载最新版本的,作者发布本文时,最新版本是cv2pdb 0.53
五、编译qBreakpad
(1)将Breakpad、LSS源码放入third_party目录
解压qBreakpad源码后,在qBreakpad-master\third_party目录下,有如下2个目录,如下:
分别解压Breakpad、LSS源码至breakpad和lss目录,此2个目录下源码需要参与qBreakpad的编译。放置好后,如下所示:
(2)qBreakpad工程介绍
在qBreakpad源码目录下,使用QtCreator打开qBreakpad.pro工程,如下:
-
demo工程下,有2个演示程序program和reporter,分别实现了演示生成dump文件,上报dump文件的功能。
-
handler为静态库工程,该工程封装了Breakpad,直接编译此工程,可生成qBreakpad.lib。
-
tests为一个简单的测试工程。
(3)编译生成qBreakpad.lib
分别在Debug、Release模式下,编译handler工程,生成2个版本的qBreakpad.lib静态库。
因为程序调用qBreakpad.lib时,只能debug版程序链接debug版库,release版程序链接release版库。debug版程序链接release版库会报错。
(4)编译生成demo
在program.pro文件添加下图所示内容,目的是编译release版程序时生成调试信息:
############ for qBreakpad ############
# qBreakpad中需要使用到network模块
QT += network# 启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl# without c++11 & AppKit library compiler can't solve address for symbols
CONFIG += c++11
macx: LIBS += -framework AppKit# 启用调试信息(关键!)
QMAKE_CXXFLAGS += -g
QMAKE_CXXFLAGS_RELEASE += -g
QMAKE_CFLAGS_RELEASE += -g
#release在最后link时默认有"-s”参数,表示"Omit all symbol information from the output file",因此要去掉该参数
QMAKE_LFLAGS_RELEASE = -mthreads -Wl,# 配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include
CONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}
############ for qBreakpad ############
在main函数添加以下内容,启用崩溃时生成.dmp文件的功能:
六、生成.pdb文件
将program这个demo程序编译出release版本:
可见这个demo程序编译出来后体积是比较大的:
然后将前面下载的cv2pdb工具解压放到目标程序同一个目录:
在这个目录打开终端,执行命令生成.pdb文件:
cv2pdb.exe 目标程序.exe
执行成功后,会在目标程序的同级目录下生成.pdb文件,同时也可以看到目标程序的体积变小了:
七、通过.dmp文件追踪程序崩溃的堆栈信息
运行前面生成的test.exe文件,程序崩溃后会生成.dmp文件:
使用Visual Studio打开.dmp文件,这里以Visual Studio2022为例:
点击上图中的“设置符号路径”,将.pdb文件所在路径添加进去:
击下图中的“使用仅限本机进行调试”:
如下图所示,可以看到程序崩溃时的堆栈调用情况:
双击堆栈调用的行内容,将源码文件路径设置一下,便可查看源码及变量的实时值。
八、总结
在Windows系统下,使用Mingw编译器编译出来的程序,重点和难点是如何生成.pdb文件。而生成.pdb文件的重点有两方面,一是在.pro文件添加-g编译参数,二是使用cv2pdb工具的最新版本,旧版本的cv2pdb工具可能无法生成.pdb文件(这一点困扰了我好久)。
如果应用程序调用了很多自己开发的动态库,那么动态库的.pro文件也需要添加-g编译参数,并且动态库也需要用cv2pdb工具生成.pdb文件。
最后,为了方便提取多个文件的.pdb文件,本人写了2个批处理脚本,一个用于提取.exe文件,另一个用于提取.dll文件,我将脚本给出,供大家参考:
批量提取D:\package\bin目录下所有exe文件的pdb:
@echo off
echo Hello,I am processing pdb files...set OBJECT_HOME=D:\packageset CV2PDB_HOME=%~dp0
CHDIR /D %OBJECT_HOME%set COMPILE_BIN=%OBJECT_HOME%\binecho "step1: del *.pdb..."
del *.pdbecho "step2: create *.pdb..."set FILE_TYPE_EXE=*.exe
for /r "%COMPILE_BIN%" %%i in (%FILE_TYPE_EXE%)do ( if exist %%i %CV2PDB_HOME%/cv2pdb.exe "%%i" )
批量提取D:\package\lib目录下所有dll文件的pdb:
@echo off
echo Hello,I am processing pdb files...set OBJECT_HOME=D:\packageset CV2PDB_HOME=%~dp0
CHDIR /D %OBJECT_HOME%set COMPILE_LIB=%OBJECT_HOME%\libecho "step1: del *.pdb..."
del *.pdbecho "step2: create *.pdb..."set FILE_TYPE_EXE=*.dll
for /r "%COMPILE_LIB%" %%i in (%FILE_TYPE_EXE%)do ( if exist %%i %CV2PDB_HOME%/cv2pdb.exe "%%i" )