C++异常问题排查调试策略

问题描述

常见代码编写Bug引起的程序崩溃异常解决起来比较简单不在此文的讨论范围内。此文主要讨论的问题是那些偶现的、没有特别征兆的、排查起来比较困难的问题的排查和解决思路。
这类问题的主要特点有以下几个:
1.运行一段时间才能出现(偶现)
2.并不崩溃到某个特定的点
3.出现资源消耗增加(CPU或内存)

解决策略和思路

1.重现bug: 记录问题发生的步骤,记录用户的输入信息,问题发生的软硬件信息,包括操作系统类型、软件版本信息以及各种配套组件的版本等
2.记录bug: 记录和收集 bug 的详细信息。包括崩溃日志、堆栈信息、环境变量等

  • 通过初始日志定位问题发生的模块和大概范围,缩小排查的范围
  • 在对应模块添加额外的补充日志然后针对崩溃点进行二分查找,定位到问题发生的具体函数并打印函数或过程的输入和输出,通过输入和输出来定位函数的异常原因

3.兼容性检查: 检查客户端运行的操作系统、硬件、浏览器或移动设备型号等信息,并确定是否有兼容性问题
4.调试工具和技术: Windows下可以使用Windbg; Linux下使用gdb和perf

问题排查工具

WinDbg排查工具

WinDbg是Windows操作系统上的一款强大的调试工具,可用于分析和调试应用程序、内核和驱动程序的崩溃和错误。这里用它主要是用来排查客户端的dump文件;

客户端生成dmp文件的三种方式:
1.通过修改注册表让所有程序都生成dump文件

@echo off
rem 开启生成dump文件   
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d "D:\Dump" /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpCount /t REG_DWORD /d 10 /f

rem 关闭生成dump文件
reg delete "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /f
@echo on

2.通过资源管理器生成dump文件
在程序还没有退出的时候,通过资源管理器手动生成可执行程序的dump文件
在这里插入图片描述

3.通过程序生成dump文件

#include <DbgHelp.h>

LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException){

    //创建 Dump 文件
    QString dumpFilename = QString("D:\\DumpFile\\") + QDateTime::currentDateTime().toString("yyyy-MM-dd-hhmmss") + ".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);
        CloseHandle(hFile);
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
    //*****
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);//注冊异常捕获函数
    //****
}

程序生成dump文件之后我们就可以使用WinDbg分析dump信息来确定程序的崩溃信息了,WinDbg使用流程如下:
1.启动WinDbg,默认Windows系统是安装了WinDbg的,用Everything全局搜一下就行了
2.通过File -> Symbol File Path设置符号路径,以便能够正确解析代码及调试信息,对应的配置图如下图所示:
在这里插入图片描述

3.选择调试目标
通过(File -> Attach to a Process)或者File -> Open Crash Dump选择你要调试的目标。
如果是调试正在运行的进程, 选择"Attach to a Process"并选择要调试的进程名称。如果是调试崩溃后生成的dump文件,选择"Open Crash Dump"并加载对应的dump文件。
4.分析调试信息
一旦成功连接到调试目标,你可以查看各种信息以了解程序崩溃的原因。WinDbg提供了许多窗口和命令用于分析调试信息,常用的窗口包括"Command"、“Locals”、“Registers”、“Call Stack”、"Threads"等。你可以通过窗口或命令查看变量的值、寄存器的状态、调用栈、线程状态等
5.运行调试命令
WinDbg提供了丰富的调试命令,可用于执行特定的操作和分析

!analyze -v     # 查看堆栈信息,自动分析并提供崩溃信息  
!exchain        # 查看异常信息   
k               # 查看代码  
g               # 继续执行程序
.symfix         # 修复符号路径,以确保WinDbg可以正确找到符号文件
.reload         # 重新加载符号和模块  
!threads        # 显示所有线程的信息,包括线程ID、优先级、堆栈基址等  
!thread         # 显示有关当前线程或指定线程的信息,包括线程的状态、调用栈等
!peb            # 显示进程环境块(Process Environment Block)的内容  
!process        # 显示有关当前进程或指定进程的信息  
!stacks         # 显示所有线程的堆栈信息  
k               # 显示当前堆栈的调用堆栈信息  
bp              # 设置断点。例如,在某个函数地址上设置断点  
u Address       # 反汇编指定的代码地址  
lm              # 显示已加载的模块列表
.printf         # 打印指定格式的信息  
.cls            # 清屏  
!address        # 显示特定地址的详细信息,包括所属模块、页属性等  
!heap           # 显示堆信息,包括堆的基本信息、块列表等  
!htrace         # 跟踪堆分配和释放的历史  
!handle         # 显示当前进程的句柄信息  
!lmi            # 显示指定模块的详细信息,包括版本、调试路径等  
!analyze -hang  # 用于分析程序的挂起状态
.frame          # 在调用堆栈中切换到指定的帧  
.dvalloc        # 动态分配虚拟内存  
!locks          # 显示线程锁定信息  
.reload /f      # 强制重新加载指定模块的符号  

!wmitrace       # 启用和配置WMI(Windows Management Instrumentation)跟踪  
!objsize        # 显示指定类型的对象大小  
!gle            # 获取最后的错误代码(GetLastError)
.lastevent      # 显示最后一个调试事件的信息  
!runaway        # 显示线程执行时间的统计信息,帮助识别执行时间较长的线程  
.formats        # 显示和设置数值的显示格式  
!dh             # 显示指定模块的导出函数  
!sym noisy      # 增加符号查找的详细信息,有助于调试符号加载问题  
!gflag          # 显示或修改全局调试标志  
.chain          # 显示当前进程的模块加载链。  
!gch            # 显示和修改全局堆设置  

6.分析异常信息
如果程序出现异常,WinDbg将提供有关异常发生的详细信息。异常信息可以帮助你定位错误的源头。
WinDbg将显示异常类型、异常代码、异常地址等信息。你可以在异常发生时查看相关信息,并在需要时向上或向下遍历调用堆栈。

gdb调试工具

gdb是一个强大的命令行调试工具,这里不详细介绍gdb的基本用法,只介绍一下如何通过gdb来捕获程序崩溃时的堆栈信息,用来分析崩溃原因:

# 启动gdb调试器  
gdb 

# 挂载某个进程  
gdb attach  <pid>  

# 程序崩溃的时候 调用对应的命令分析堆栈信息  
(gdb) bt   # 显示当前的函数调用栈
(gdb) thread apply all bt  # 显示当前程序的所有线程信息,包括线程编号、状态等

#up 和 down:在函数调用栈之间移动,可以用来查看上下文  
(gdb) up
(gdb) down

(gdb) info threads  # info threads:显示当前程序的所有线程信息,包括线程编号、状态等
(gdb) info registers # 显示当前 CPU 寄存器的值,可以帮助你了解程序崩溃时 CPU 的状态

(gdb) info locals        # 显示当前函数的局部变量信息
(gdb) info args          # 显示当前函数的参数信息
(gdb) info variables     # 显示当前作用域内的所有变量信息
(gdb) info sharedlibrary # 显示已加载的共享库信息
(gdb) set logging on     # 开启日志记录,将 gdb 会话中的所有输出保存到文件中

perf,strace调试工具

有些时候程序的异常状态可能不是崩溃,而是资源的异常占用,这时候我们就需要使用一些工具来分析程序的性能瓶颈,然后针对程序的瓶颈进行优化和修复;
这里介绍一下常用的性能分析和优化工具包括:perf和strace

# 监视指定 PID 进程  
sudo perf top -p <PID>   

# 用来记录指定PID进程的事件  
sudo perf record -p <PID>  
# 使用perf report 来生成分析报告  
sudo perf report  

# 显示系统中当前所有进程的资源占用情况  
top 

# htop 是 top 命令的一个增强版本,提供了更加友好的界面和更多的功能
htop  

# strace: strace 命令可以跟踪进程的系统调用,显示进程正在执行的系统调用以及参数。这可以帮助你了解进程在执行时具体在做什么
strace -p <PID>  
  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农飞飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值