記事ディレクトリ
序文
実際のプロジェクト開発において、通常パッケージ化されて顧客にリリースされるプログラムはリリース版の Qt プログラムですが、顧客の環境ではプログラムが異常終了する場合があり、これを解決するために実行ログをファイルに追加することが一般的です。プログラム、またはダンプが生成される 異常を検出して特定するためのファイル。ここでは、プログラムの異常クラッシュ検出と位置特定のための次の方法を要約します。
1. qInstallMessageHandler に基づいて出力ログを生成します。
2. qBreakpad に基づいてダンプ ファイルを生成します
。 3. DbgHelp および SetUnhandledExceptionFilter に基づいてダンプ ファイルを生成します。
コンパイル環境: QT:5.14.1
コンパイラー: MSVC_64_bit_Release
事業効果
提示:以下是本篇文章正文内容,下面案例可供参考
1. qInstallMessageHandler に基づいて出力ログを生成します。
QT では、関数 qInstallMessageHandler を呼び出してメッセージを処理できます。ここで、カスタム メッセージ ハンドラーは、コードのデバッグ時に追加された出力を指定されたパスにファイルとして保存できます。
1. main.cppを追加します
//自定义消息处理
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{
static QMutex mutex;
mutex.lock();
//初始化log文件夹
QString logFilePath = QCoreApplication::applicationDirPath() + "/DebugLog/";
QDir dstDir(logFilePath);
if(!dstDir.exists())
{
if(!dstDir.mkpath(logFilePath))
{
qDebug()<<__FILE__<<__LINE__<<"创建DebugLog文件夹失败!";
}
}
//获取输出内容
QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
QString debugMsg = QString("%1 \r\n%2").arg(debugDateTime).arg(msg);
//保存文件
QString logFileName = logFilePath + "log_" + QDate::currentDate().toString("yyyyMMdd") + ".txt";
QFile file(logFileName);
file.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream textStream(&file);
textStream << debugMsg << "\r\n \r\n";
file.flush();
file.close();
mutex.unlock();
}
//使用
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//生成输出日志
qInstallMessageHandler(outputMessage);
Widget w;
w.show();
return a.exec();
}
2. qBreakpad に基づいてダンプ ファイルを生成します
qBreakpad は Breakpad のカプセル化です。詳細については、「Qt は Windows でダンプ ファイルを生成し、バグを見つける (qBreakpad に基づく)」という記事を参照してください。
Breakpad の概要
Breakpad は、Google が開発したオープンソースのマルチプラットフォーム C++ クラッシュ検出ライブラリです。Breakpad は、ユーザーにリリースされたアプリケーションのクラッシュをキャプチャし、ソフトウェア クラッシュのデバッグ情報を「ミニダンプ」ファイル (*.dmp) に記録できます。さらに、Breakpad は、エラー行番号、エラーの詳細、スタック トレースなどの情報をデバッグすることもできます。ソフトウェアがクラッシュした場合、生成されたダンプ ファイルを自分のサーバーにアップロードして、クラッシュの詳細を簡単に取得できます。
BreakPad の動作原理
1. コンパイル時に、リリース版プログラムでデバッグ情報を生成する必要があります。
2. Breakpad が提供する dump_syms ツールを使用して、リリース バージョンのプログラムからシンボル ファイルをエクスポートします。
3. プログラムがクラッシュすると、ブレークパッドがクラッシュをキャプチャし、ダンプ ファイルを生成します。
4. ダンプ ファイルは、指定したサーバーに直接送信することも、ユーザーが開発者に手動で送信することもできます。
5. ダンプ ファイルを受信した後、シンボル ファイルと組み合わせて、minidump_stackwalk ツールを使用してスタック コール情報ファイルを生成し、このファイルを直接読み取ってバグを特定できます。
私の例では、関連するライブラリがコンパイルされており、ライブラリとヘッダー ファイルは使いやすいようにプロジェクトの同じレベルのフォルダーに統合されています。
次に、qBreakpad の使用方法について説明します。
1. pro に追加
#for qBreakpad
#qBreakpad中需要使用到network模块
QT += network
#启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl
#没有c++11和AppKit库编译器不能解决符号的地址
CONFIG += c++11
macx: LIBS += -framework AppKit
#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO
#配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include
CONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}
2. main.cppを追加します
//包含头文件
//qBreakpad
#include "qBreakpad/include/QBreakpadHandler.h"
//使用
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//设置生成dump文件路径
QBreakpadInstance.setDumpPath("BreakCrashes"); //捕获构造时的异常
Widget w;
w.show();
QBreakpadInstance.setDumpPath("BreakCrashes"); //捕获构造完成之后的异常
return a.exec();
}
3. DbgHelp および SetUnhandledExceptionFilter に基づいてダンプ ファイルを生成します。
SetUnhandledExceptionFilter は、ハンドルされない例外フィルター関数を設定します。この関数の詳細については、Windows の公式ドキュメントを直接参照してください。ここでは、Qt での使用方法についても直接説明しています:
1.pro が追加されました
#for DbgHelp
#方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG
#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO
2. main.cppを追加します
//包含头文件
//DbgHelp
#include <windows.h>
#include <DbgHelp.h>
#include <QDateTime>
#include <QMessageBox>
//程序异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{
//初始化dump文件夹
QString logFilePath = QCoreApplication::applicationDirPath() + "/DumpCrashes/";
QDir dstDir(logFilePath);
if(!dstDir.exists())
{
if(!dstDir.mkpath(logFilePath))
{
qDebug()<<__FILE__<<__LINE__<<"创建DumpCrashes文件夹失败!";
}
}
//创建Dump文件
QString dumpFileName = logFilePath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".dmp";
HANDLE hDumpFile = CreateFile((LPCWSTR)(dumpFileName.toStdWString().c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hDumpFile != INVALID_HANDLE_VALUE)
{
//Dump信息
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
//写入Dump文件内容
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules), &dumpInfo, NULL, NULL);
}
//这里弹出一个错误对话框并退出程序
EXCEPTION_RECORD* record = pException->ExceptionRecord;
QString errCode(QString::number(record->ExceptionCode,16));
QString errAddr(QString::number((uint)record->ExceptionAddress,16));
QMessageBox::critical(NULL,"错误",QString("程序异常崩溃捕获!\nerrCode:%1 \nerrAddr:%2").arg(errCode.toStdString().c_str()).arg(errAddr.toStdString().c_str()));
return EXCEPTION_EXECUTE_HANDLER;
}
//使用
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//注冊异常捕获函数
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler); //捕获构造时的异常
Widget w;
w.show();
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler); //捕获构造完成之后的异常
return a.exec();
}
4. 完全なコードの例
1.MyDump.pro
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
widget.cpp
HEADERS += \
widget.h
FORMS += \
widget.ui
# Default rules for deployment.
qnx: target.path = /tmp/$${
TARGET}/bin
else: unix:!android: target.path = /opt/$${
TARGET}/bin
!isEmpty(target.path): INSTALLS += target
#设置字符
contains( CONFIG,"msvc" ):QMAKE_CXXFLAGS += /source-charset:utf-8 /execution-charset:utf-8
contains( CONFIG,"msvc" ):QMAKE_CFLAGS +=/source-charset:utf-8 /execution-charset:utf-8
#使用其中之一的话,将另外一个屏蔽即可
#for qBreakpad
#qBreakpad中需要使用到network模块
QT += network
#启用多线程、异常、RTTI、STL支持
CONFIG += thread exceptions rtti stl
#没有c++11和AppKit库编译器不能解决符号的地址
CONFIG += c++11
macx: LIBS += -framework AppKit
#release版程序带上调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO
#配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include
CONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}
#for DbgHelp
#方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG
#release版程序带上调试信息
#QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
#QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO
2.main.cpp
#include "widget.h"
#include <QApplication>
//logfile
#include <QDir>
#include <QDebug>
//qBreakpad
#include "qBreakpad/include/QBreakpadHandler.h"
//DbgHelp
#include <windows.h>
#include <DbgHelp.h>
#include <QDateTime>
#include <QMessageBox>
//自定义消息处理
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{
static QMutex mutex;
mutex.lock();
//初始化log文件夹
QString logFilePath = QCoreApplication::applicationDirPath() + "/DebugLog/";
QDir dstDir(logFilePath);
if(!dstDir.exists())
{
if(!dstDir.mkpath(logFilePath))
{
qDebug()<<__FILE__<<__LINE__<<"创建DebugLog文件夹失败!";
}
}
//获取输出内容
QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
QString debugMsg = QString("%1 \r\n%2").arg(debugDateTime).arg(msg);
//保存文件
QString logFileName = logFilePath + "log_" + QDate::currentDate().toString("yyyyMMdd") + ".txt";
QFile file(logFileName);
file.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream textStream(&file);
textStream << debugMsg << "\r\n \r\n";
file.flush();
file.close();
mutex.unlock();
}
//程序异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException)
{
//初始化dump文件夹
QString logFilePath = QCoreApplication::applicationDirPath() + "/DumpCrashes/";
QDir dstDir(logFilePath);
if(!dstDir.exists())
{
if(!dstDir.mkpath(logFilePath))
{
qDebug()<<__FILE__<<__LINE__<<"创建DumpCrashes文件夹失败!";
}
}
//创建Dump文件
QString dumpFileName = logFilePath + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".dmp";
HANDLE hDumpFile = CreateFile((LPCWSTR)(dumpFileName.toStdWString().c_str()), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hDumpFile != INVALID_HANDLE_VALUE)
{
//Dump信息
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
//写入Dump文件内容
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, (MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithProcessThreadData | MiniDumpWithUnloadedModules), &dumpInfo, NULL, NULL);
}
//这里弹出一个错误对话框并退出程序
EXCEPTION_RECORD* record = pException->ExceptionRecord;
QString errCode(QString::number(record->ExceptionCode,16));
QString errAddr(QString::number((uint)record->ExceptionAddress,16));
QMessageBox::critical(NULL,"错误",QString("程序异常崩溃捕获!\nerrCode:%1 \nerrAddr:%2").arg(errCode.toStdString().c_str()).arg(errAddr.toStdString().c_str()));
return EXCEPTION_EXECUTE_HANDLER;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//生成输出日志
qInstallMessageHandler(outputMessage);
//二选一,将异常捕获函数放在此处可捕获构造时的异常
//QBreakpadInstance.setDumpPath("BreakCrashes"); //设置生成dump文件路径
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler); //注冊异常捕获函数
Widget w;
w.show();
//二选一,将异常捕获函数放在此处可捕获构造完成之后的异常
//QBreakpadInstance.setDumpPath("BreakCrashes");
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
return a.exec();
}
3.ウィジェット.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QLabel>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pb_crash_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
4.ウィジェット.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
qDebug()<<__FILE__<<__LINE__<<"widget structure!";
//异常测试1
//QLabel *label_1;
//label_1->setText("hello world!");
//label_1->show();
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pb_crash_clicked()
{
qDebug()<<__FILE__<<__LINE__<<"pb_crash clicked!";
//异常测试2
QLabel *label_2;
label_2->setText("hello world!");
label_2->show();
}
5.ウィジェット.ui
5. ダウンロードリンク
私のサンプル Baidu ネットワーク ディスク リンク: https://pan.baidu.com/s/19eGJH2OnLACxc_C4VDMtWg
抽出コード: xxcj
要約する
ここにログを記録すると便利ですが、多くの場合、コードの特定のエラー行を見つけることは不可能です。コードの各行の後に出力を追加することはできません。したがって、プログラムのコンパイル時に生成される pdb ファイルとダンプ ファイルを併用すると、コール スタックとコード行を正確に特定でき、バグをうまく解決できます。
接尾辞 *.dmp の付いたダンプ ファイルは、プログラムがクラッシュしたときのメモリ ダンプ ファイルであり、
接尾辞 *.pdb の付いた pdb ファイルは、プログラムのシンボル ファイルです。
ダンプファイルやpdbファイルの使い方については、ここでは具体的な紹介はしませんが、参考ブログに詳しく紹介されていますので、詳しくは参考ブログをご覧ください。
こんにちは:
一緒に学び、一緒に進歩しましょう。関連する質問がまだある場合は、ディスカッションのためにコメント領域にメッセージを残すことができます。
参考ブログ:
QtのqInstallMessageHandler(詳細ログの出力)
QtはWindowsでダンプファイルを生成しバグを見つける(qBreakpadベース)
Qtでクラッシュログ/ダンプファイルを取得するにはDbgHelpとSetUnhandledExceptionFilterを使用