Программа Release версии Qt под Windows генерирует журналы и файлы дампа (используются для обнаружения ненормального сбоя программы)


предисловие

В реальной разработке проекта программа, которая обычно упаковывается и выпускается заказчику, представляет собой окончательную версию программы 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 — это многоплатформенная библиотека обнаружения сбоев C++ с открытым исходным кодом, разработанная Google. Breakpad может фиксировать сбои приложений, выпущенных для пользователей, и записывать отладочную информацию о сбоях программного обеспечения в файлы «минидампа», а именно *.dmp. Кроме того, Breakpad также может отлаживать информацию, включая номера строк ошибок, сведения об ошибках и трассировку стека. В случае сбоя программного обеспечения вы можете загрузить сгенерированный файл дампа на свой сервер, чтобы легко получить подробную информацию о сбое.

Принцип работы BreakPad
1. Когда мы компилируем, нам нужно генерировать отладочную информацию в программе Release version.
2. Используйте инструмент dump_syms, предоставленный Breakpad, для экспорта файла символов из версии программы.
3. При сбое программы breakpad зафиксирует сбой и сгенерирует файл дампа.
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.widget.ui
Пожалуйста, добавьте описание изображения

5. Ссылка для скачивания

Ссылка на мой образец сетевого диска Baidu: https://pan.baidu.com/s/19eGJH2OnLACxc_C4VDMtWg
Код извлечения: xxcj


Подведем итог

Здесь удобнее логировать, но во многих случаях невозможно найти конкретную строчку кода с ошибкой, ведь нельзя после каждой строчки кода выводить вывод, верно? Следовательно, использование файла дампа вместе с файлом pdb, сгенерированным при компиляции программы, может точно определить местонахождение стека вызовов и строк кода, что может очень хорошо устранять ошибки!
Файл дампа с расширением *.dmp — это файл дампа памяти при сбое программы,
файл pdb с расширением *.pdb — это файл символов программы.
Что касается использования файлов дампа и файлов pdb, я не буду давать здесь конкретных сведений. Введение в справочном блоге очень подробное. Дополнительную информацию см. в справочном блоге.


привет:
Учитесь вместе и добивайтесь прогресса вместе. Если у вас все еще есть связанные вопросы, вы можете оставить сообщение в области комментариев для обсуждения.

Справочный блог:
qInstallMessageHandler of Qt (вывод подробного журнала)
Qt создает файлы дампа и находит ошибки в Windows (на основе qBreakpad)
Используйте DbgHelp и SetUnhandledExceptionFilter для получения файлов журнала сбоев/дампа в Qt

Supongo que te gusta

Origin blog.csdn.net/XCJandLL/article/details/129925470
Recomendado
Clasificación