The Release version Qt program under Windows generates logs and dump files (used for abnormal program crash detection)


foreword

In actual project development, the program that is usually packaged and released to the customer is the release version of the Qt program. However, in the customer environment, the program may crash abnormally. To solve this problem, a running log is generally added to the program, or a dump is generated. Files to detect and locate anomalies. The following methods are summarized here for program abnormal crash detection and positioning:
1. Generate output logs based on qInstallMessageHandler
2. Generate dump files based on qBreakpad
3. Generate dump files based on DbgHelp and SetUnhandledExceptionFilter

Compilation environment: QT:5.14.1
Compiler: MSVC_64_bit_Release

Project effect


提示:以下是本篇文章正文内容,下面案例可供参考

1. Generate output logs based on qInstallMessageHandler

In QT, we can call the function qInstallMessageHandler to process the message. Here, the custom message handler can save the printout added when you debug the code as a file to the specified path.
1. Add in 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. Generate a dump file based on qBreakpad

qBreakpad is the encapsulation of Breakpad. For a detailed introduction, please refer to this article Qt generates dump files and locates bugs under Windows (based on qBreakpad) .

Breakpad Introduction
Breakpad is an open source multi-platform C++ crash detection library developed by Google. Breakpad can capture the crashes of applications released to users, and record the debug information of software crashes into "minidump" files, namely *.dmp. In addition, Breakpad can also debug information including error line numbers, error details, and stack traces. When the software crashes, you can upload the generated dump file to your own server to get the details of the crash easily.

Working principle of BreakPad
1. When we compile, we need to generate debugging information in the Release version program.
2. Use the dump_syms tool provided by Breakpad to export the symbol file from the release version program.
3. When the program crashes, breakpad will capture the crash and generate a dump file.
4. The dump file can be sent directly to the specified server, or manually sent by the user to the developer.
5. After receiving the dump file, combined with the symbol file, the stack call information file can be generated through the minidump_stackwalk tool. This file can be read directly to locate bugs.

In my example, the relevant libraries have been compiled, and the libraries and header files are integrated into the same level folder of the project for easy use:
Please add a picture description
Please add a picture description
Please add a picture description

Next, let’s talk about the use of qBreakpad:
1. Added in 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. Add in 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. Generate dump files based on DbgHelp and SetUnhandledExceptionFilter

SetUnhandledExceptionFilter sets the unhandled exception filter function. For the detailed introduction of this function, you can directly refer to the official windows document . Here is also a direct description of its use under Qt:
1.pro added

#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. Add in 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. Example complete code

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.widget.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.widget.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.widget.ui
Please add a picture description

5. Download link

My sample Baidu network disk link: https://pan.baidu.com/s/19eGJH2OnLACxc_C4VDMtWg
Extraction code: xxcj


Summarize

It is more convenient to log here, but in many cases it is impossible to locate the specific error line of code. You can’t add output after each line of code, right? Therefore, using the dump file together with the pdb file generated when compiling the program can accurately locate the call stack and code lines, which can solve bugs very well!
The dump file, with the suffix *.dmp, is the memory dump file when the program crashes;
the pdb file, with the suffix *.pdb, is the symbol file of the program.
Regarding the use of dump files and pdb files, I will not give a specific introduction here. The introduction in the reference blog is very detailed. For more information, please refer to the reference blog.


hello:
Learn together and make progress together. If you still have related questions, you can leave a message in the comment area for discussion.

Reference blog:
qInstallMessageHandler of Qt (output detailed log)
Qt generates dump files and locates bugs under Windows (based on qBreakpad)
Use DbgHelp and SetUnhandledExceptionFilter to obtain Crash log/dump files under Qt

Guess you like

Origin blog.csdn.net/XCJandLL/article/details/129925470