Qt生成日志与以及捕获崩溃文件(mingw64位,winDbg)————附带详细解说

news2025/6/7 16:18:07

文章目录

  • Qt生成日志与以及报错文件(mingw64位,winDbg)
    • 0 背景与结果
    • 0.1 背景
    • 0.2 结果
    • 1 WinDbg
      • 1.1 安装
      • 1.2 使用
    • 2 编写代码
      • 2.1 ccrashstack类
      • 2.2 编写输出捕获异常的dmp文件
      • 2.2 编写输出日志文件
      • 2.3 调用生成日志和dmp文件
    • 参考

Qt生成日志与以及报错文件(mingw64位,winDbg)

0 背景与结果

0.1 背景

因为编写的Qt软件,编译成运用程序发布后,会遇到莫名的崩溃的问题。为了找到问题所在,解决的方法就是记录日志和捕获崩溃错误的信息。

因为作者使用mingw64位的编译器,所以这里使用ccrashstack类(专为Qt-Mingw32环境设计的工具类),生成.dmp 文件,来帮助开发者调试程序中的bug。

除了使用ccrashstack类,也可以使用Google breakpadqBreakpad(适用MSVC编译器(而mingw编译器,不支持生成Microsoft PDB格式)。需要编译源代码,会稍微麻烦一些,点击可以查看操作方法)。

0.2 结果

使用项目生成dmp文件,
在这里插入图片描述
使用winDbg打开文件,调试获得的结果如下:
在这里插入图片描述
在这里插入图片描述

可以看出结果非常nice,可以知道错误类型 NULL_POINTER_READ_NULL_INSTRUCTION_PTR_INVALID_POINTER_READ,错误的行数在哪里。非常适合测试发布版本,出现的异常闪退的问题。

生成的日志信息也非常方便,程序运行的状况。

在这里插入图片描述

1 WinDbg

1.1 安装

捕获生成的dmp异常文件,需要使用WinDbg来进行解析。因此需要先下载和安装WinDbg。

官方推荐的安装方式:

若要使用 Windows 包管理器安装 WinDbg,请从命令行/PowerShell运行以下命令:

winget install Microsoft.WinDbg

安装完成后,可以在搜索中找到安装好的调试软件。

1.2 使用

1,设置源代码路径(如果源代码修改太多,定位的时候可以会出错);
在这里插入图片描述
如果存在符号路径,也可以填写,不填写的时候,会联网下载;

在这里插入图片描述
2,打开dmp文件;
在这里插入图片描述

如果是发布的程序,则需要把exe、所有库都放在一起,如下图所示:
在这里插入图片描述

3,按照图片上的先点击.ecxr,再点击!analyze -v,就可以得到最开头的结果;
在这里插入图片描述
4,如果熟悉WinDbg的,可以在这个框中,输入调试指令;

在这里插入图片描述

2 编写代码

2.1 ccrashstack类

因为编译的环境为mingw64位,因此需要对32位的CCrashStack类进行修改,修改的地方如下:
请添加图片描述
1,按照提示把32位的Eax、Ebx、Exc、Edx、Esi、Edi、Esp、Ebp、Eip的首字母改成R开头的64位的接口;
2, error: cast from 'PVOID' {aka 'void*'} to 'int' loses precision [-fpermissive] sprintf(buffer, "Exception Addr: %08X ", (int)E.ExceptionAddress);。根据报错信息得知,void*类型转int会遗失精度(在64位系统中,内存使用64位地址,所以指针的大小也是64位。 void* 指针是64位的,而 int 通常是32位的,因此转换时可能会丢失高位信息。),使用reinterpret_castvoid*指针转换为 uintptr_t 类型,这是一个无符号整数类型(在 64 位机器上,uintptr_t 被定义为 unsigned long int),其大小与指针相同,然后使用static_cast再将 uintptr_t类型的整数转换为int类型;
3, error: cast from 'PBYTE' {aka 'unsigned char*'} to 'unsigned int' loses precision。根据报错信息得知,unsigned char*unsigned int会损失精度。错误原因同上,64位的指针地址,转为32位的无符号整型,会遗失精度报错。解决方法为把unsigned char*Ebp->Ret_Addr转换为unsigned long long类型,并使用 %016llX 来确保能完整输出64位十六进制值;

这里只展示头文件,具体文件见附录:

#ifndef CCRASHSTACK_H
#define CCRASHSTACK_H

#include <windows.h>
#include <QString>

class CCrashStack
{
private:
    PEXCEPTION_POINTERS m_pException;

private:
    QString GetModuleByRetAddr(PBYTE Ret_Addr, PBYTE & Module_Addr);
    QString GetCallStack(PEXCEPTION_POINTERS pException);
    QString GetVersionStr();
    bool GetHardwareInaformation(QString &graphics_card, QString &sound_deivce);

public:
    CCrashStack(PEXCEPTION_POINTERS pException);

    QString GetExceptionInfo();
};

#endif // CCRASHSTACK_H

2.2 编写输出捕获异常的dmp文件

SetErrorMode函数的用法如下:

在这里插入图片描述

#ifndef OUTPUTDUMP_H
#define OUTPUTDUMP_H
//outputDump.h

//#pragma once         //避免同一个文件只会被包含一次
#include "ccrashstack.h"
#include <shlobj.h>
#include <QDebug>
#include <DbgHelp.h>
#include <QDateTime>
//#pragma comment(lib, "dbghelp.lib")  //使用注释方式引入库dbghelp.lib或编译目录。

/*------------------生成dump文件------------------------*/
LONG crashHandler(EXCEPTION_POINTERS *pException)
{
    qDebug()<<"LONG crashHandler(EXCEPTION_POINTERS *pException)";

    QString curDataTime = QDateTime::currentDateTime().toString("yyyyMMddhhmmss");
    QString dumpName = curDataTime + ".dmp";

    HANDLE dumpFile = CreateFile((LPCWSTR)QString("./" + dumpName).utf16(),GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(dumpFile != INVALID_HANDLE_VALUE)
    {
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;

        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),dumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
        CloseHandle(dumpFile);
    }
    else
    {
        qDebug() << "dumpFile not vaild";
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

//防止CRT(C runtime)函数报错可能捕捉不到(64位会报错)
void DisableSetUnhandledExceptionFilter()
{
    
//原理:https://learn.microsoft.com/zh-cn/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode
       SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
/*
在64位系统中运行该函数闪退,可能是由于代码尝试修改系统函数 SetUnhandledExceptionFilter 的内存,而64位系统有更严格的内存保护机制,导致内存写入失败。

*/
    // void* addr = (void*)GetProcAddress(LoadLibrary(L"kernel32.dll"), "SetUnhandledExceptionFilter");
    // if(addr)
    // {
    //     unsigned char code[16];
    //     int size = 0;

    //     code[size++] = 0x33;
    //     code[size++] = 0xC0;
    //     code[size++] = 0xC2;
    //     code[size++] = 0x04;
    //     code[size++] = 0x00;

    //     DWORD dwOldFlag, dwTempFlag;
    //     VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
    //     WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
    //     VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
    // }


}

#endif // OUTPUTDUMP_H

2.2 编写输出日志文件

把Qt5个类型的日志消息,附加上产生时间、文件名、行数、函数名称的信息,写入到txt文件中。控制日志的大小,每当写入5000次时,检查日志大小是否超过15M,如果超过,则清空重新写入。

对于日志内容,放入到QQueue中,进行异步上锁操作。

#ifndef OUTPUT_LOG_H
#define OUTPUT_LOG_H
//outputLog.h

//#pragma once       //避免同一个文件不会被包含多次
#include <QDir>
#include <QFile>
#include <QMutex>
#include <QTextStream>
#include <QTime>

// #include"ccrashstack.h"
#include <QQueue>

#include <QFileInfo>
#include <fstream>
std::ofstream g_OutputDebug;
// 使用异步日志队列
QQueue<QString> logQueue;
QMutex queueMutex;


void enqueueLogMessage(const QString &message) {
    QMutexLocker locker(&queueMutex);
    logQueue.enqueue(message);
    // 可以在此处触发异步写入操作
}

/*---------------打日志文件----------------------*/
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // static QMutex mutex;
    // mutex.lock();



    enqueueLogMessage(msg); // 将消息加入队列,而非直接写入

    static int count = 0;

    QString text;
    switch(type)
    {
    case QtDebugMsg:
        text = QString("[Debug]");
        break;
    case QtWarningMsg:
        text = QString("[Warning]");
        break;
    case QtCriticalMsg:
        text = QString("[Critical]");
        break;
    case QtInfoMsg:
        text = QString("[Information]");
        break;
        break;
    case QtFatalMsg:
        text = QString("[Fatal]");
    }

    // 获取纯文件名
    QFileInfo fileInfo(context.file);
    QString filename = fileInfo.baseName();
    // QString context_info = QString("File:(%1) Line:(%2) Function:(%3)").arg(filename).arg(context.line).arg(context.function);
    QString context_info = QString("( %1:%2, %3 )").arg(filename).arg(context.line).arg(context.function);
    QString current_date_time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
    QString current_date = QString("[%1]").arg(current_date_time);
    QString message = QString("%1 %2 %3 \r\n%4").arg(current_date).arg(text).arg(context_info).arg(msg);

    //判断文件夹是否存在,不存在新建
    QString aFile = QDir::currentPath() + "/LogFile";
    QDir dir(aFile);
    if(!dir.exists())
    {
        dir.mkdir(aFile);//只创建一级子目录,即必须保证上级目录存在
    }

    QString current_time = QDateTime::currentDateTime().toString("yyyyMMdd");

    QFile file(aFile+"/log"+current_time+".txt");
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream text_stream(&file);
    text_stream << message << "\r\n \r\n";

    file.flush();
    file.close();
    // mutex.unlock();

    count ++;
    //每打印5000次判断一次文件大小,超过5M就清空重新写入
    QFileInfo info(aFile+"/log"+current_time+".txt");
    if(count > 5000)
    {
        count = 0;
        if(info.size() > 1024*1024*15)//1024*1024*5
        {

            g_OutputDebug.open(qPrintable(aFile+"/log"+current_time+".txt"), std::ios::out | std::ios::trunc);
            g_OutputDebug.close();
        }
    }

}

#endif // OUTPUT_LOG_H

2.3 调用生成日志和dmp文件

调用Windows的系统SetUnhandledExceptionFilter函数(该函数有个设置回调函数,软件崩溃时会回调该系统函数,并传回崩溃地址信息等,),给该函数传递编写的crashHandler函数中,此函数的MiniDumpWriteDump方法(MiniDumpWriteDump 是一个 Windows API 函数,用于将用户模式小型转储信息写入指定的文件。这个函数在调试和分析程序崩溃时非常有用,可以帮助开发人员收集异常信息并进行故障排除)来把产生的错误内容写入到.dmp文件中。

pro文件中加入如下配置:

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_MESSAGELOGCONTEXT
DEFINES += QT_DEPRECATED_WARNINGS


# # 添加DUMP文件
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO,
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG

# test crash
QMAKE_CFLAGS_RELEASE += -g
QMAKE_CXXFLAGS_RELEASE += -g
QMAKE_CFLAGS_RELEASE -= -O2
QMAKE_CXXFLAGS_RELEASE -= -O2
QMAKE_LFLAGS_RELEASE = -mthreads -W

# 方便生成DUMP调试
LIBS += -lDbgHelp
QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG

QMAKE_CXXFLAGS += -g
QMAKE_CFLAGS += -g

# 调试信息以及pdb文件
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_CFLAGS_RELEASE = $$QMAKE_CLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

LIBS += -lpsapi

调用方法如下:

#include"outputDump.h" //生成dump头文件
#include"outputLog.h"  //生成日志头文件

int main(int argc, char *argv[])
{
   //  // /*-------1、注冊异常捕获函数 生成.dmp文件--------*/
      SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)crashHandler);
     DisableSetUnhandledExceptionFilter();

      QApplication a(argc, argv);

   //  // /*-------2、注册MessageHandler 生成日志文件--------*/
      qInstallMessageHandler(outputMessage);

     MainInterface w;
     w.show();

    return a.exec();
}

参考

Qt环境生成dump解决异常崩溃

Qt-生成dump文件

# Qt环境生成dump文件解决程序异常崩溃以及生成日志文件

【Qt 应用开发 日志处理 】学习自定义消息处理的艺术

Get started with WinDbg (user mode)

Qt Windows系统使用QBreakpad实战

SetErrorMode 函数

QT运行日志保存和对日志大小进行监控

Qt开发 之 抓取崩溃信息(读这一篇就够了)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能手表健康监测系统的PSRAM存储芯片CSS6404LS-LI—高带宽、耐高温、微尺寸的三重突破

一、直击智能手表三大核心痛点 痛点场景风险传统方案缺陷连续生物数据流存储100Hz PPG信号产生82MB/s数据洪峰SPI NOR Flash带宽不足(≤50MB/s)高温环境稳定性腕表表面温度达50℃&#xff08;烈日/运动场景&#xff09;商用级存储器件(85℃)易触发数据错误极限空间约束PCB面积…

蓝桥杯国赛题2022

首先这个题应该是一个01背包&#xff0c;背包容量为2022&#xff0c;有2022个物品&#xff0c;第i个物品的体积为i&#xff0c;只不过这里有两个限制条件&#xff0c;一个限制条件是和为2022&#xff0c;另一个限制条件为10个数&#xff0c;两个限制条件那就把加一维&#xff0…

关于如何使用VScode编译下载keil工程的步骤演示

1、vscode的插件市场下载keil Assistant 2 、点设置 3、复制keil的地址 4、粘贴到第…

Redis底层数据结构之深入理解跳表(2)

上一篇文章中我们详细讲述了跳表的增添、查找和修改的操作&#xff0c;这篇文章我们来讲解一下跳表在多线程并发时的安全问题。在Redis中&#xff0c;除了网络IO部分和大文件的后台复制涉及到多线程外&#xff0c;其余任务执行时全部都是单线程&#xff0c;这也就意味着在Redis…

[蓝桥杯]兰顿蚂蚁

兰顿蚂蚁 题目描述 兰顿蚂蚁&#xff0c;是于 1986 年&#xff0c;由克里斯兰顿提出来的&#xff0c;属于细胞自动机的一种。 平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只"蚂蚁"。 蚂蚁的头部朝向为&#xff1a;上下左右其中一方。 蚂蚁的移…

使用 Python 构建并调用 ComfyUI 图像生成 API:完整实战指南

快速打造你自己的本地 AI 图像生成服务&#xff0c;支持 Web 前端一键调用&#xff01; &#x1f4cc; 前言 在 AIGC 快速发展的今天&#xff0c;ComfyUI 作为一款模块化、节点式的图像生成界面&#xff0c;备受开发者青睐。但默认情况下&#xff0c;ComfyUI 主要通过界面交互…

嵌入式学习笔记-freeRTOS taskENTER_CRITICAL(_FROM_ISR)跟taskEXIT_CRITICAL(_FROM_ISR)函数解析

一 函数taskENTER_CRITICAL&#xff0c;taskEXIT_CRITICAL 函数taskENTER_CRITICAL最终实现如下&#xff1a; 第①处按照系统设定的configMAX_SYSCALL_INTERRUPT_PRIORITY值对中断进行屏蔽 第②处调用一次自增一次 第③处检查中断状态寄存器位&#xff0c;如果有任何中断位置…

1panel面板中部署SpringBoot和Vue前后端分离系统 【图文教程】

1panel面板中部署SpringBoot和Vue前后端分离系统 一&#xff0c;1panel面板部署二&#xff0c;安装OpenResty三&#xff0c;安装MySQL&#xff0c;Redis等Spring boot 运行依赖环境四&#xff0c;SpringBoot 应用配置及打包部署配置打包部署 五 &#xff0c;前端VUE应用配置打包…

【Android基础回顾】二:handler消息机制

Android 的 Handler 机制 是 Android 应用中实现线程间通信、任务调度、消息分发的核心机制之一&#xff0c;它基于 消息队列&#xff08;MessageQueue&#xff09; 消息循环&#xff08;Looper&#xff09; 消息处理器&#xff08;Handler&#xff09; 组成。 1 handler的使用…

每日Prompt:每天上班的状态

提示词 一个穿着清朝官服的僵尸脸上贴着符纸&#xff0c;在电脑面前办公&#xff0c;房间阴暗&#xff0c;电脑桌面很乱&#xff0c;烟灰缸里面满是烟头

C++11 右值引用:从入门到精通

文章目录 一、引言二、左值和右值&#xff08;一&#xff09;概念&#xff08;二&#xff09;区别和判断方法 三、左值引用和右值引用&#xff08;一&#xff09;左值引用&#xff08;二&#xff09;右值引用 四、移动语义&#xff08;一&#xff09;概念和必要性&#xff08;二…

.net 使用MQTT订阅消息

在nuGet下载M2Mqtt V4.3.0版本。&#xff08;支持.net framework&#xff09; 订阅主题 public void LoadMQQCData() {string enpoint "xxx.xxx.x.x";//ip地址int port 1883;//端口string user "usrname";//用户名string pwd "pwd";//密码…

【递归、搜索与回溯】综合练习(四)

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人递归&#xff0c;搜索与回溯算法的学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码…

强化学习入门:Gym实现CartPole随机智能体

前言 最近想开一个关于强化学习专栏&#xff0c;因为DeepSeek-R1很火&#xff0c;但本人对于LLM连门都没入。因此&#xff0c;只是记录一些类似的读书笔记&#xff0c;内容不深&#xff0c;大多数只是一些概念的东西&#xff0c;数学公式也不会太多&#xff0c;还望读者多多指教…

STM32:CAN总线精髓:特性、电路、帧格式与波形分析详解

声明&#xff1a;此博客是我的学习笔记&#xff0c;所看课程是江协科技的CAN总线课程&#xff0c;知识点都大同小异&#xff0c;我仅进行总结并加上了我自己的理解&#xff0c;所引案例也都是课程中的案例&#xff0c;希望对你的理解有所帮助&#xff01; 知识点1【CAN总线的概…

贝叶斯深度学习!华科大《Nat. Commun.》发表BNN重大突破!

华科大提出基于贝叶斯深度学习的超分辨率成像&#xff0c;成功被Nat. Commun.收录。可以说&#xff0c;这是贝叶斯神经网络BNN近期最值得关注的成果之一了。另外还有AAAI 2025上的Bella新框架&#xff0c;计算成本降低了99.7%&#xff0c;也非常值得研读。 显然鉴于BNN“不确定…

【大模型LLM学习】Flash-Attention的学习记录

【大模型LLM学习】Flash-Attention的学习记录 0. 前言1. flash-attention原理简述2. 从softmax到online softmax2.1 safe-softmax2.2 3-pass safe softmax2.3 Online softmax2.4 Flash-attention2.5 Flash-attention tiling 0. 前言 Flash Attention可以节约模型训练和推理时间…

物联网数据归档之数据存储方案选择分析

在上一篇文章中《物联网数据归档方案选择分析》中凯哥分析了归档设计的两种方案,并对两种方案进行了对比。这篇文章咱们就来分析分析,归档后数据应该存储在哪里?及存储方案对比。 这里就选择常用的mysql及taos数据库来存储归档后的数据吧。 你在处理设备归档表存储方案时对…

【C语言】C语言经典小游戏:贪吃蛇(上)

文章目录 一、游戏背景及其功能二、Win32 API介绍1、Win32 API2、控制台程序3、定位坐标&#xff08;COORD&#xff09;4、获得句柄&#xff08;GetStdHandle&#xff09;5、获得光标属性&#xff08;GetConsoleCursorInfo&#xff09;1&#xff09;描述光标属性&#xff08;CO…

vue2中使用jspdf插件实现页面自定义块pdf下载

pdf下载 实现pdf下载的环境安装jspdf插件在项目中使用 实现pdf下载的环境 项目需求案例背景&#xff0c;点击【pdf下载】按钮&#xff0c;弹出pdf下载弹窗&#xff0c;显示需要下载四个模块的下载进度&#xff0c;下载完成后&#xff0c;关闭弹窗即可&#xff01; 项目使用的是…