Windbg分析程序崩溃实践

1. 项目场景

本故事纯属虚构。
初入职场的小木,负责维护一个博客系统,后端采用C++编写,部署在Windows服务器上。刚刚熟悉完产品的小木,接到了后台服务的报警,服务器后端偶尔会程序崩溃。刚开始小木还有点慌张,脑子里面浮现出各种问题,这个是程序的bug吗?茫茫的代码如何寻找问题?log能看到线索吗?当冷静下来后,小木忽然想起前几天看的两篇文章<<Windbg调试----Windbg入门>><<Windows程序Dump收集>>,还没动手过呢,正好练习练习。

2. 收集Dump

首先小木想到了要定位到问题,得收集Dump。blogserver程序是64位程序,小木决定采用procdump64去收集dump。于是在产品服务器上运行了如下的命令, 将程序产生的dump生成到C:\dumps目录下。

procdump.exe -ma blogserver.exe -t -e -o C:\dumps

接下小木就是一边review以前的代码,一边等待着Crash的出现。终于有一天出现啦,procdump64输出了如下的信息:

[15:34:17] Exception: C0000005.ACCESS_VIOLATION
[15:34:17] Unhandled: C0000005.ACCESS_VIOLATION
[15:34:17] Dump 1 initiated: C:\dumps\blogserver.exe_201115_153417.dmp
[15:34:17] Dump 1 writing: Estimated dump file size is 42 MB.
[15:34:17] Dump 1 complete: 42 MB written in 0.2 seconds
[15:34:17] Dump count reached.

ACCESS_VIOLATION 看来是访问了不可访问的内存,估计做过C++代码编写的程序员都碰到过这种内存访问问题。 小木将程序dump拷贝到了自己的办公机器上,准备用预先安装好的Windbg64位进行分析。

3. Windbg分析

小木根据之前学习的内容,先用Windbg 加载dump。用k查看crash的堆栈,因为没有加载产品的符号信息,函数调用栈,没有显示出哪个函数调用导致程序crash了。这里补充一句,默认的产品发布采用Visual Studio Release模式发布,这个模式产品的符号信息将采用.pdb文件单独保存,保证自己的符号信息不被泄露。

3.1 符号信息加载

  1. 小木先将之前产品的符号信息blogserver.pdb拷贝到调试机器的C:\blogserversymbols目录下
  2. 创建一个微软的symbols的缓存目录C:\windowssymbols, 一般windows程序会加载很多微软的dll,而在分析crash的时候,也需要加载微软的symbols
  3. 使用命令添加产品的symbols目录: .sympath c:\blogserversymbols
  4. 使用命令添加了微软的symbols,并保存到指定的目录: .sympath+ srv*C:\windowssymbols*http://msdl.microsoft.com/download/symbols
  5. 运行重新加载symbols: .reload

以上的配置也可以保存到workspace中,以便下次继续使用。

3.2 寻找程序崩溃的代码

加载完symbols后,我们来看下程序调用栈:

0:000> k
 # Child-SP          RetAddr               Call Site
00 00000001`08b2f4e8 00007ffb`62c05237     ucrtbase!strnlen+0x3c
01 00000001`08b2f4f0 00007ffb`62bebf65     ucrtbase!cgetws_s+0x3b37
02 00000001`08b2f520 00007ffb`62beaa7c     ucrtbase!_stdio_common_vfwscanf+0x3b15
03 00000001`08b2f570 00007ffb`62be9e7a     ucrtbase!_stdio_common_vfwscanf+0x262c
04 00000001`08b2f5a0 00007ffb`62be863b     ucrtbase!_stdio_common_vfwscanf+0x1a2a
05 00000001`08b2fac0 00007ffb`62beead1     ucrtbase!_stdio_common_vfwscanf+0x1eb
06 00000001`08b2faf0 00007ff6`78aa1c9f     ucrtbase!_stdio_common_vfprintf+0x81
07 00000001`08b2fb60 00007ff6`78aa1cf8     blogserver!_vfprintf_l+0x3f [c:\program files (x86)\windows kits\10\include\10.0.18362.0\ucrt\stdio.h @ 644] 
08 00000001`08b2fba0 00007ff6`78aa15b3     blogserver!fprintf+0x48 [c:\program files (x86)\windows kits\10\include\10.0.18362.0\ucrt\stdio.h @ 839] 
09 00000001`08b2fbf0 00007ff6`78aa1d59     blogserver!LogStr+0x33 [c:\personal\test\blogserver\blogserver.cpp @ 10] 
0a 00000001`08b2fc30 00007ff6`78aa2054     blogserver!main+0x39 [c:\personal\test\blogserver\blogserver.cpp @ 16] 
0b (Inline Function) --------`--------     blogserver!invoke_main+0x22 [d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78] 
0c 00000001`08b2fc90 00007ffb`653f4034     blogserver!__scrt_common_main_seh+0x10c [d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
0d 00000001`08b2fcd0 00007ffb`664a3691     kernel32!BaseThreadInitThunk+0x14
0e 00000001`08b2fd00 00000000`00000000     ntdll!RtlUserThreadStart+0x21

小木松了一口气,终于有点线索了,程序崩溃在函数LogStr,根据里面的行数提示,找到那段代码:

void LogStr(std::string strContent)
{
    
    
	fprintf(stdout, strContent.c_str());
}

刚松了一口气,小木又疑惑起来,这个函数是用来打印博客标题的log的,一直都用,也测试过,怎么会偶尔导致程序崩溃呢? 小木睁大眼睛,(也许在读文章的你已经知道到什么问题了),作为新手,就这么一行代码,还是没有找到原因。

3.3 真相大白

小木接着看,是不是这个log内容比较奇特呢,决定先看一看strContent的内容。
小木切到fprintf那个frame:

0:000> .frame 08
08 00000001`08b2fba0 00007ff6`78aa15b3     blogserver!fprintf+0x48 [c:\program files (x86)\windows kits\10\include\10.0.18362.0\ucrt\stdio.h @ 839] 

然后再查看当前的参数内容,知道了strContentHello %sWindbg!

0:000> dv /t
struct _iobuf * _Stream = 0x00007ffb`62c584b8
char * _Format = 0x00000001`08b2fc60 "Hello %sWindbg!"
char * _ArgList = 0x00000001`08b2fc00 "Hello %sbg"
int _Result = 0n145947744

当看到%s的时候,小木的编程直觉,瞬间反应过来了,这个%s 是格化式字符串。而这句话fprintf(stdout, strContent.c_str()); 却将需要打印的strContent内容直接放在fprintf_Format参数中, 这样%s这个格式化串,将认为存在一个字符串参数,其实并没有,这样读取到的地址,将可能会出现ACCESS_VIOLATION.

真相大白了,小木身心舒适,程序员的成就感涌上心头,对着空气微微一笑。小木知道以后还会碰到棘手的问题去处理,所以决定继续努力学习,所谓“台上一分钟,台下十年功”。


最后是个人微信公众号,文章CSDN和微信公众号都会发,欢迎一起讨论。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CJF_iceKing/article/details/109703832