目录
1、尝试将Windbg附加到目标进程上进行动态调试,但Windbg并没有捕获到
2、在系统应用程序日志中找到了系统在程序发生异常时自动生成的dump文件
2.1、查看应用程序日志的入口
2.2、在应用程序日志中找到系统自动生成的dump文件
3、使用Windbg静态分析dump文件
3.1、找到函数调用堆栈中相关模块的pdb文件,将pdb文件路径设置到Windbg中
3.2、查看详细的函数调用堆栈,对照着C++源码进行分析
4、总结
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html 近日某个用户反馈,在其Win10的电脑上运行我们的软件,会频繁地出现崩溃闪退的情况,系统弹出程序已经停止运行的提示。我们尝试将Windbg附加到目标进程上分析,但Windbg并没有感知到,后来查看系统中应用程序日志找到系统自动生成的dump文件,然后分析这个dump文件才排查出问题。本文详细讲解这个问题的完整排查过程。
1、尝试将Windbg附加到目标进程上进行动态调试,但Windbg并没有捕获到
用户反馈,在其Win10的电脑上运行我们的软件,会频繁地出现崩溃闪退问题,系统会弹出程序已经停止运行的提示,如下所示:
程序中安装的异常捕获模块没有感知到崩溃,没有生成包含异常上下文的dump文件。
既然没有生成dump文件,那我们就尝试将Windbg附加到目标进程上进行动态调试,看看程序发声异常时Windbg能否捕捉到。于是让用户在启动程序时将Windbg附加到程序上,然后按照之前的问题场景去复现问题,一般使用Windbg动态调试时如果程序发声异常,Windbg都会感知到并中断下来,这样就能地问题进行分析了。
但本问题有点不太一样,复现问题时Windbg并没有第一时间感知到,系统立即弹出了程序已经停止运行的提示。弹出该提示窗口时,Windbg也被冻结住了,没法进行操作了。这种场景还是第一次遇到,很是奇怪!
2、在系统应用程序日志中找到了系统在程序发生异常时自动生成的dump文件
在日常分析软件异常崩溃问题时,基本都是使用Windbg分析的,要么使用Windbg静态分析dump文件,要么将Windbg附加到进程上进行动态调试。但目前这个问题,静态分析和动态调试都行不通,这就比较棘手了!后来想到,我们可以到系统的应用程序日志中查看一下,看看能不能找到一些线索。
2.1、查看应用程序日志的入口
我们通过远程软件远程到用户的电脑上,在Win10系统的桌面上,右键点击“此电脑”,在弹出的右键菜单中点击“管理”菜单项,如下所示:
打开计算机管理窗口后,在系统工具节点下,展开事件查看器节点,在该节点下继续展开Windows日志节点,然后点击应用程序节点,这样右边就显示应用程序相关的系统日志了,如下所示:
一般程序在发生异常时,系统感知到,会自动生成与之相关的日志。于是按照程序出问题时的时间点,在应用程序日志列表中找对应时间点的日志记录。出问题的时间点大概为2023/07/28 14:55,在应用程序日志列表中果然找到了这个时间的日志记录,如上所示。
2.2、在应用程序日志中找到系统自动生成的dump文件
于是点击上述时间点的记录,在下方的详细信息中看到了问题的相关描述:
错误存储段 ,类型 0
事件名称: BEX
响应: 不可用
Cab Id: 0
问题签名:
P1: XXXXXXX.exe(哈哈,这个地方把程序名称做匿名处理)
P2: 7.0.0.3
P3: 648a1cec
P4: ucrtbase.dll
P5: 10.0.10586.0
P6: 5632d166
P7: 00083472
P8: c0000409
P9: 00000005
P10:
附加文件:
C:\Users\Kvs\AppData\Local\Temp\WERF6BC.tmp.WERInternalMetadata.xml
C:\Users\Kvs\AppData\Local\Temp\WER53E1.tmp.appcompat.txt
C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_XXXXXXX.exe_a57edd93ecda26986177f6523e6d19d6506eb2_d05b1441_cab_15ff59bc\memory.hdmp
C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_XXXXXXX.exe_a57edd93ecda26986177f6523e6d19d6506eb2_d05b1441_cab_15ff59bc\triagedump.dmp
WERGenerationLog.txt
可在此处获取这些文件:
C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_XXXXXXX.exe_a57edd93ecda26986177f6523e6d19d6506eb2_d05b1441_cab_15ff59bc
分析符号:
重新检查解决方案: 0
报告 Id: 08632728-2da3-493f-a30c-ffd617076fc3
报告状态: 96
哈希存储段:
在日志描述信息中,首先看到发生的段错误,然后看到了系统自动生成的dump文件的完整路径:
C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_XXXXXXX.exe_a57edd93ecda26986177f6523e6d19d6506eb2_d05b1441_cab_15ff59bc\triagedump.dmp
然后到这个路径中将dump文件拷贝出来。这个应该是系统在检测到程序异常时自动生成的,应该是程序发生异常时包含异常上下文信息的dump文件,使用Windbg静态分析这个dump文件应该就能分析出问题的。
3、使用Windbg静态分析dump文件
使用Windbg打开dump文件,先输入.ecxr命令切换到发生异常的那个线程中,然后输入kn命令查看该线程的函数调用堆栈,如下所示:
这个函数调用堆栈中看不到具体的函数名和代码的行号,是因为没有加载函数调用堆栈中模块的pdb符号文件的原因。
3.1、找到函数调用堆栈中相关模块的pdb文件,将pdb文件路径设置到Windbg中
从当前的函数调用堆栈中,可以看到涉及到三个模块:xxlogdll.dll、xxxpsdll.dll和microblogdll.dll,所以我们需要去拿这三个模块的pdb符号文件。使用lm命令查看这三个模块二进制文件的时间戳,通过时间戳到文件服务器上去找pdb符号文件。以microblogdll.dll为例,查看该模块信息的命令为:lm vm microblogdll*,打印出来的模块信息如下:
从图中可以看出,当前的microblogdll.dll文件的时间戳(生成时间)为2023年06月13日09时56分21秒,于是以这个时间点,到文件服务器上找到对应的文件夹,找到对应时间点的pdb符号文件。
将上述三个模块的pdb符号文件找到,统一拷贝到桌面路径C:\Users\Administrator\Desktop\pdbdir中,然后将pdb文件路径设置到Windbg中,设置的路径如下所示:
C:\Users\Administrator\Desktop\pdbdir;srv*f:\mss0616*http://msdl.microsoft.com/download/symbols
其中,C:\Users\Administrator\Desktop\pdbdir是业务库的pdb符号文件路径;srv*f:\mss0616*http://msdl.microsoft.com/download/symbols为微软系统库pdb在线下载服务器地址,其中http://msdl.microsoft.com/download/symbols是在线下载服务器地址,f:\mss0616为从在线服务器上下载pdb文件的临时保存地址。
之所以要设置Windows系统库的pdb在线下载服务器地址,是因为上述函数调用堆栈中包含系统库的模块ucrtbase.dll,我们要查看系统模块中的具体函数调用。有时如果能看到系统模块中的具体函数调用,能更有利于我们分析问题。
3.2、查看详细的函数调用堆栈,对照着C++源码进行分析
加载pdb文件后,就能看到完整的函数调用堆栈了(能看到具体的函数名及C++代码的行号),如下所示:
从最上面调用的系统库的接口ucrtbase!_invalid_parameter来看,是程序中产生了无效的参数,无效参数引发了程序异常。再沿着函数调用堆栈向上看,是调用C函数vsnprintf函数去格式化数据引发的无效参数,当然肯定不是系统C函数vsnprintf中有问题。还要继续向上看,上面几个是打印日志的接口,然后继续向上,最终找到了调用打印日志接口的上层函数lohttp::CloHttp::OnHttpStackCb,其相关代码片如下:
上述代码中,要将一个char型buffer中的字符串作为日志打印出来,调用的是MiCROBLOG_LOG接口,使用的格式化符%s,要待格式化的参数p,应该也是吻合的,这里似乎并没有问题。如果是有问题,则可能是p指向的内存中的数据有问题,引发底层函数格式化时产生了异常。
这个代码片段位于组件组维护的模块中,于是将上述函数调用堆栈及相关信息发给组件组的同事,让他们继续排查。其实这个地方有个简单的规避方法,直接在lohttp::CloHttp::OnHttpStackCb函数中将调用MiCROBLOG_LOG接口的那行代码注释掉即可。但这只是规避的办法,还是要搞清楚为什么会引发崩溃的。
4、总结
这个问题现象比较少见,所以在此详细记录一下。一般情况下,将Windbg附加到进程上进行动态调试时,如果程序发生异常,Windbg应该能感知到并中断下来。结果这个问题中,直接弹出程序停止运行的系统提示框,Windbg并没有感知到。遇到这类情况时,可以尝试到系统的应用程序日志中查看相关记录,可能能找到程序发生异常时系统自动生成的包含异常上下文的dump文件,然后使用Windbg静态分析该dump文件就能分析出问题了。