Qt使用qBreakpad定位崩溃位置(2)
软件调试
Qt使用qBreakpad定位崩溃位置(2)
更多精彩内容 |
---|
👉个人内容分类汇总 👈 |
👉C++软件调试、异常定位 👈 |
前言
1、Google - Breakpad
- Breakpad 是 Google 公司开发的开源 跨平台C++崩溃检测库。
- Breakpad可以捕获发布给用户的应用程序的崩溃,并记录软件崩溃的调试信息到 minidump 文件中。
- 调试信息包括错误行号,报错详情,堆栈错误(stack traces)。
2、qBreakpad
-
qBreakpad 是 Qt 库,用于使用 google-breakpad 崩溃报告工具(并方便地使用它)。支持
- Windows(但故障转储解码不适用于 MinGW 编译器)
- Linux
- macOS X
3、crashpad
- Crashpad 是 Google 构建的最新开源崩溃报告工具,是流行的 Breakpad 崩溃报告器的继任者。 Crashpad 允许您在产品发生崩溃后将小型转储提交到配置的 URL。
- 这里没有使用crashpad,以后再慢慢了解。
4、注意
- dmp文件、pdb文件、可执行程序、源码 版本一定要一致,否则可能无法调试。
Linux下
个人观点:
- Linux下Breakpad使用麻烦,而且Qt中有些异常无法定位到,不如GDB + Core好用;
- 但是这个和Core使用不冲突,也可以一起用。
1、环境
- 系统:ubuntu20.04.1
- Qt版本:Qt5.14.2
2、qBreakpad源码准备
-
可用下载我打包修改好的源码,包含了依赖文件
git clone https://gitee.com/mahuifa/q-breakpad.git
-
下载qBreakpad
git clone https://github.com/buzzySmile/qBreakpad.git
-
进入依赖文件夹third_party
cd qBreakpad/third_party
- 打开README文件,可以看见我们还需要下载breakpad和linux-syscall-support两个仓库,由于这两个仓库地址不方便访问,但是在github上也有,所以直接在Github下载就可以
-
下载依赖仓库,注意:是在
qBreakpad/third_party
文件夹下下载,或者下载后移动到qBreakpad/third_party
文件夹下# breakpad git clone https://github.com/google/breakpad.git # linux-syscall-support git clone https://github.com/adelshokhy112/linux-syscall-support.git # 然后将 breakpad 放至qBreakpad/third_party/beakpad # 然后将 linux-syscall-support下的lss 放至qBreakpad/third_party
3、qBreakpad编译
-
进入
qBreakpad/handler
文件夹cd qBreakpad/handler
-
使用qtcreator 打开handler.pro
-
选择Release模式,点击锤子开始编译
-
编译完成后会在handler 文件夹下生成一个
libqBreakpad.a
文件,这就编译完成了
4、测试qBreakpad
-
打
qBreakpad/demo/program/program.pro
工程 -
编译后发现报了如下3个错误信息,错误信息大概是
google_breakpad::PEFile::TryGetDebugInfo
未定义 -
打开
minidump_writer.cc
文件,搜索TryGetDebugInfo,找到如下位置 -
按着Ctrl键,鼠标点击TryGetDebugInfo,进入到
pe_file.h
头文件,这里是声明,那没定义大概是没有包含cc文件或者cpp文件 -
将鼠标放到下列位置就可以知道头文件位置了
-
发现
pe_file.h
头文件在qBreakpad/third_party/breakpad/src/client/linux/minidump_writer
文件夹下,进入该文件夹后发现了pe_file.cc
文件 -
在breakpad.pri文件夹中如下位置加入
$$BREAKPAD_PATH/client/linux/minidump_writer/pe_file.cc \
,然后将build-handler-Desktop_Qt_5_14_2_GCC_64bit-Release
文件夹删除后重新编译handler.pro -
编译完成后再去编译program.pro 就不会报错了,program.pro编译运行后会崩溃(正常情况,特意写的异常用于生成dmp文件),program.pro生成的可执行程序为test
-
进入可执行程序路径下,会发现生成了一个crashes 文件夹,dmp 文件就放在这个文件夹下
这个文件夹名称是在main.cpp文件中由
QBreakpadInstance.setDumpPath("crashes");
指定的。
5、dump文件调试
- 由于dmp文件不能直接查看,定义崩溃位置,这里需要使用到2个工具;
- dump_syms:由beakpad编译生成,使用
dump_syms
读取调试信息并生成Breakpad
符号文件; - minidump_stackwalk:由beakpad编译生成,minidump_stackwalk
可以获取一个
minidump及其相应的符号,并产生一个符号化的
崩溃位置信息;
- dump_syms:由beakpad编译生成,使用
5.1 编译breakpad
- 想要生成dump_syms、minidump_stackwalk 两个文件就需要编译breakpad,由于之前编译qBreakpad已经下载breakpad源码,这里就不需要再下载了;
- 将lss文件夹移动到 breakpad/src/third_party/ 路径下
cd breakpad/ # 进入breakpad文件夹
mkdir build # 创建一个build文件夹
sudo chmod -R 777 ./ # 修改所有文件的权限
cd build/ # 进入build文件夹
../configure # 开始配置
sudo make # 使用make构建编译
-
使用make 编译时可能会出现下列错误,根据错误提示打开
breakpad/src/third_party/lss/linux_syscall_support.h:
文件,定位到2408行,将"rsp"删除../src/third_party/lss/linux_syscall_support.h: In member function ‘bool google_breakpad::ExceptionHandler::GenerateDump(google_breakpad::ExceptionHandler::CrashContext*)’: ../src/third_party/lss/linux_syscall_support.h:2408:75: error: listing the stack pointer register ‘rsp’ in a clobber list is deprecated [-Werror=deprecated] 2408 | : "rsp", "memory", "r8", "r10", "r11", "rcx");
-
删除后如下所示,然后保存修改,重新使用
sudo make
构建 -
重新编译后又出现了新的错误,错误信息提示
strcmp
不是std成员,strcmp
位于string.h
文件夹中../src/common/dwarf/elf_reader.cc: In constructor ‘google_breakpad::ElfSectionReader<ElfArch>::ElfSectionReader(const char*, const string&, int, const typename ElfArch::Shdr&)’: ../src/common/dwarf/elf_reader.cc:200:15: error: ‘strcmp’ is not a member of ‘std’; did you mean ‘strcmp’? 200 | if ((std::strcmp(name, ".strtab") == 0 || | ^~~~~~ In file included from ../src/common/dwarf/elf_reader.cc:35: /usr/include/string.h:137:12: note: ‘strcmp’ declared here 137 | extern int strcmp (const char *__s1, const char *__s2) | ^~~~~~ ../src/common/dwarf/elf_reader.cc:201:15: error: ‘strcmp’ is not a member of ‘std’; did you mean ‘strcmp’? 201 | std::strcmp(name, ".shstrtab") == 0) && | ^~~~~~ In file included from ../src/common/dwarf/elf_reader.cc:35: /usr/include/string.h:137:12: note: ‘strcmp’ declared here 137 | extern int strcmp (const char *__s1, const char *__s2)
-
根据错误提示打开
breakpad/src/common/dwarf/elf_reader.cc
文件,将所有std::strcmp
替换成strcmp
,然后保存重新编译就可以编译通过了 -
执行
sudo make install
安装编译后的breakpad。
5.2 开始分析dmp文件
-
进入可执行程序当前文件夹,使用dump_syms生成 符号文件,test 是program工程生成的可执行程序。
dump_syms test > test.sym
-
在program工程源码当前路径下创建symbols/test/B84C09F0458DE588E280087B600FDCC80三级文件夹
- 第一级目录,固定为
symbols
; - 第二级目录,为即将放入的
符号文件名称
,如test.sym
,则目录名为test
; - 第三级目录,在sym文件中第一行内容,有一串
16进制
编号,将其作为目录名
。如下图所示:
- 第一级目录,固定为
-
将test.sym文件移动到symbols/test/B84C09F0458DE588E280087B600FDCC80文件夹下
-
将包含dmp 文件的crashes文件夹也移动到program工程源码路径下,如下图所示
-
使用minidump_stackwalk 生成堆栈跟踪信息
minidump_stackwalk crashes/470c3ed4-d8f5-470c-17786f91-4280144e.dmp symbols/ > error.log
-
打开error.log文件,根据可执行程序名称test 找到奔溃位置位于TestThread类crash()函数中,具体在TestThread.cpp文件的第34行。
-
注意:目前测试来看除零异常可用定位到文件、函数、行号,但是空指针异常很难定位到(不知道是不是我操作问题)
- 使用QWidget的空指针异常无法定位到,使用int空指针异常可用定位到。
Windows下
windows下如果不想这么麻烦的编译qBreakPad或者不考虑跨平台,可以使用我封装的crashhandler模块。
1、环境
-
Windows10
-
Qt版本:Qt5.12.5
-
编译器版本:msvc2017_64
2、源码准备
-
使用到的源码和Linux下一样
- qBreakpad
- breakpad 放至qBreakpad/third_party/beakpad
- linux-syscall-support下的lss 放至qBreakpad/third_party
3、qBreakpad编译
-
进入qBreakpad/handler文件夹,双击打开handler.pro
-
注意编译器一定要选择MSVC,不能使用MinGW,这里演示就使用Release,如果需要Debug也是差不多的;
- 打开handler.pro后选择Release,然后点击左下角的锤子,等待编译。
-
编译成功后会在qBreakpad\handler文件夹下生成一个qBreakpad.lib 文件
-
因为后续调用qBreakpad需要使用到qBreakpad.pri模块,这里使用文本编辑器打开qBreakpad.pri文件,加入下列代码,否则Release程序编译时不会生成.pdb符号文件;
# Windows下在Release生成用于调试dump的信息,包括【禁用release编译优化】、【生成PDB符号表】,但是这些设置会降低程序性能,和debug差不多 CONFIG(release, debug|release) { QMAKE_CXXFLAGS_RELEASE -= -O2 QMAKE_CXXFLAGS_RELEASE += -O0 QMAKE_CXXFLAGS_RELEASE += /Zi QMAKE_LFLAGS_RELEASE += /DEBUG /OPT:REF /OPT:ICF # 生成 PDB符号文件,功能和下一行一样,但是最好用这一行,显示指定编译选项 # QMAKE_LFLAGS_RELEASE += $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO # 打印变量参数值 message(QMAKE_CXXFLAGS_RELEASE变量值:$$QMAKE_CXXFLAGS_RELEASE) message(QMAKE_LFLAGS_RELEASE变量值:$$QMAKE_LFLAGS_RELEASE) }
4、测试qBreakpad
-
打
qBreakpad/demo/program/program.pro
工程,如下图所示 -
直接点击左下角开始编译运行,编译完成后程序运行就会崩溃,然后打开可执行程序路径,会发现生成
- crashes文件夹:存放dump文件
- test.pdb:符号文件
-
只要生成pdb、dmp两个文件就说明已经成功使用qBreakpad捕获了崩溃信息。
5、dump文件调试
- 打开Visual Studio,将生成的dmp文件直接拖进vs中
-
点击1️⃣【设置符号路径】,进行如下设置;
- 如果是第一次调试需要勾选Microsoft符号服务器2️⃣:会从网络下载调试使用的符号文件,然后将符号文件下载到5️⃣位置,以后就不需要勾选符号服务器了;
- 点击“+”号3️⃣添加【test.pdb】6️⃣文件所在路径4️⃣
-
设置完成后点击【确认】,然后点击【使用仅限本机进行调试】
-
如下图所示成功得到崩溃位置
6、打包qBreakpad模块
-
新建一个qBreakpad文件夹,将qBreakpad.pri文件和handler文件夹移动到qBreakpad文件夹中,handler文件夹中包含qBreakpad.lib
7、测试打包的qBreakpad模块
-
新建工程TestCrashes,编译器选择和编译qBreakpad一样,使用Release
-
将打包的qBreakpad模块文件夹复制到新建的工程路径下
-
在工程的TestCrashes.pro文件中添加下列两行代码,加载qBreakpad模块
include(./qBreakpad/qBreakpad.pri) INCLUDEPATH += ./qBreakpad
-
由于qBreakpad有使用到网络模块,所以在qBreakpad.pri文件中添加
QT += network
-
在main.cpp文件中添加头文件
#include "QBreakpadHandler.h"
和QBreakpadInstance.setDumpPath("crashes");
-
在widget.cpp文件中设置一个除0异常 ,点击按键后程序就会异常崩溃
-
进入编译后的文件夹下,可以看见成功生成pdb符号文件和dmp文件。
-
通过VS成功定位崩溃位置。