问题描述
曾经碰到一种奇怪的Crash场景:Windows程序Crash,每次用windbg
attach或者ntsd/cdb
产生dump,总是不能捕获到程序出错时候的栈,而且crash的时候只能看到少数甚至只剩一个线程的信息,而这个仅有的一些线程函数调用栈,也并不是导致程序Crash的地方。
问题分析
首先确认Dump选项是进程的全部信息;也检查了写Dump的时候系统的资源充足(CPU,Memory等)。大家平时碰到的Crash,大多数都是非法资源的访问,实际上还有一种可能存在的场景,就是进程被Kill/Terminate掉,此时捕获的Dump信息不一定是程序出错时候的栈。
那么程序被Kill/Terminate掉有两种可能性一种是外部程序(包含Taskmanager),还有一种是程序内部调用TerminateProcess等API。可以先通过笔者之前写过的文章<<我的程序被谁干掉了?>>,用gflag配置查找到程序退出的原因,如果是外部程序Kill了当前的进程,那么找到程序名称,也便有了线索;如果是当前进程调用了API自动退出呢? 本文便是讲述这种场景下的分析方法。
程序自己调用退出进程API,有以下几种可能性:
- 当前程序显式地调用了exit, TerminateProcess, ExitProcess等API。对于这种API的调用,一般产品中会很少,也可以通过搜索代码查找到可能的地方。
- 比较隐晦的一些场景,并不是自己编写的程序代码显示的调用退出进程API,而是由于一些API调用或者异常处理导致的:
- 比如微软的安全函数,strcpy_s在VS2005中比如当目标buffer空间不够就会调用TerminateProcess. (笔者此时查看VS2015版本,默认行为已经不会调用了TerminateProcess,而是返回错误,微软也是在各位程序员采坑的情况下不断的优化自己的CRT库)
- 在抛出异常Unwind过程中,会调用一些局部变量的析构函数,如果此时再次抛出异常,也会调用TerminateProcess. (所以不建议在析构函数中抛出异常)
- 等等......
既然明确了这个场景后,有个麻烦的事情,程序中有很多地方,包括第三方库都会调用strcpy_s等这类函数,而且异常处理的地方也有很多,很难通过代码审查找到问题所在,更有可能的是,还有其他的退出进程的调用场景没有列出来。下一章节将分享一种常见的分析方法。
Windbg TerminateProcess断点分析问题
本案例以VS2005中strcpy_s目标buffer不足为例触发TerminateProcess。使用Windbg调试器附加到进程之后,在TerminateProcess
处设置断点,并且运行程序(有时候也设置断点在ntdll!ZwTerminateProcess
,kernel32!TerminateProcess
,kernelbase!TerminateProcess
,kernel32!ExitProcess
,或者kernelbase!ExitProcess
):
0:000> bp KERNELBASE!TerminateProcess
0:000> g
Breakpoint 0 hit
eax=ffffffff ebx=00000000 ecx=779877e4 edx=002de8b8 esi=00000001 edi=00000000
eip=7790f210 esp=053ffc14 ebp=053ffca4 iopl=0 nv up ei ng nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000286
KERNELBASE!TerminateProcess:
7790f210 8bff mov edi,edi
当程序运行到断点TerminateProcess
处,查看函数调用栈,就可以找到程序出错的地方了。如下,可以找到函数调用关系为fun->strcpy_s
0:001> kv
ChildEBP RetAddr Args to Child
0537fc00 7790f23c ffffffff c000000d 0537fca4 ntdll!ZwTerminateProcess (FPO: [2,0,0])
0537fc10 00401410 ffffffff c000000d 00000022 KERNELBASE!TerminateProcess 0x2c (FPO: [Non-Fpo])
0537ff4c 0040108d 00000000 00000000 00000000 testforiceking!_invoke_watson 0xe6 (FPO: [Non-Fpo]) (CONV: cdecl) [f:spvctoolscrt_bldself_x86crtsrcinvarg.c @ 185]
0537ff70 00401014 0537ff87 00000001 0041218c testforiceking!strcpy_s 0x29 (FPO: [3,0,4]) (CONV: cdecl) [f:spvctoolscrt_bldself_x86crtsrctcscpy_s.inl @ 18]
0537ff88 7796336a 00000000 0537ffd4 77e99f72 testforiceking!Fun 0x14 (FPO: [Non-Fpo]) (CONV: stdcall) [d:vsprojecttestformetestforicekingtest.cpp @ 11]
0537ff94 77e99f72 00000000 7dfe5d2b 00000000 kernel32!BaseThreadInitThunk 0xe (FPO: [Non-Fpo])
0537ffd4 77e99f45 00401000 00000000 ffffffff ntdll!__RtlUserThreadStart 0x70 (FPO: [Non-Fpo])
0537ffec 00000000 00401000 00000000 00000000 ntdll!_RtlUserThreadStart 0x1b (FPO: [Non-Fpo])