在上一篇文章<<栈溢出的场景分析和建议>>中,本人分享了如何查找程序Crash的函数调用栈,然后通过代码审查找到栈溢出的原因。但是却有一些场景通过代码审查不易找到问题,比如如下两点:
- 函数的调用逻辑复杂,且触发逻辑依赖于输入样例。这样通过代码审查是很难看出问题所在的。
- 当触发的栈溢出问题在非自己公司开发的第三方库中,无法获取源代码,也不易看出问题。
那么针对上面这两点,都需要一个东西去做辅助分析,那就是触发栈溢出的输入内容(这的所谓输入内容不是指用户在交互界面输入,而是指触发这个栈溢出的数据),无论是自己用这个输入内容来调试栈溢出的触发逻辑,或者是交给第三方库的支持方,都是不可或缺的。
由于这种场景分析距今时间较长,本该在上一篇介绍的内容,便忘记介绍,好记性不如烂笔头。而今日正好又碰到了这种场景,遂记录于此,也与大家一起分享。
程序样例
为了将故事完整性,我重新编写了一段样例代码。记住我们的目标,是根据Crash后收集的dump内容,找到触发这个栈溢出的输入数据。
这个程序是如何触发栈溢出的:
- 调用的函数是
TriggerStackOverFlow
- 导致栈溢出的递归调用的函数是
Func
- 这里特意设置的触发条件是当输入的数据为
Data Trigger StackOverFlow
,只有这个输入的时候才会触发递归调用。实际真实的工程代码也是类似,并不是栈溢出问题必现,而是在特定的情况下才会触发,这也是为什么本文强调的是如何获取触发栈溢出的输入数据如此重要,因为调试问题离不开它。
#include <iostream>
int a = 1;
void Func(const char* pcsPara)
{
std::cout << pcsPara << " " << ++a << std::endl;
if (0 == strcmp(pcsPara, "Data Trigger StackOverFlow"))
Func(pcsPara);
}
void TriggerStackOverFlow()
{
char csTmpStr[1000] = "Data Trigger StackOverFlow";
Func(csTmpStr);
}
int main()
{
TriggerStackOverFlow();
return 0;
}
找出函数调用栈
一般碰到Crash的Dump,习惯性的都是先用Windbg
中k
命令看下函数调用栈。但是对于这个命令默认只能显示0xff
个栈帧,那么我们其实还可以调用k [FrameCount]
, 这里的FrameCount
就是设置为你想显示的栈帧数量,但是查看了最新的Windbg Preview
版本最多也只能显示0xffff
个栈帧,那么对于超过0xffff
的栈帧无法显示。那么本人的就刚好碰到了这种场景(那也是因为我们把默认的栈空间调整到了更大),这个时候就要用到上一篇文章讲解的方法<<栈溢出的场景分析和建议>>, 把整个函数调用栈的空间用dps
打印出来。不麻烦大家再去看原先的文章,我把这个步骤再做一次,此文不再重新说明。直接说结果,对于上述案例打印出来如下所示。0000004ea1401000
和0000004ea1500000
根据!teb
得来的栈空间地址范围。
0:000> dps 0000004ea1401000 0000004ea1500000
......
0000004e`a14059a0 00007fff`d1059570 ucrtbase!_argc
0000004e`a14059a8 00007ff7`190d1cd6 StackOverflowFindData!Func+0x46 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 5]
0000004e`a14059b0 00007fff`c98c5080 MSVCP140!std::cout
0000004e`a14059b8 00007ff7`190d7bb0 StackOverflowFindData!__xt_z+0x110
0000004e`a14059c0 0000004e`00000000
0000004e`a14059c8 00007ff7`190d7bf3 StackOverflowFindData!__xt_z+0x153
0000004e`a14059d0 00007fff`d1059570 ucrtbase!_argc
0000004e`a14059d8 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0000004e`a14059e0 0000004e`a14ff8d0
......
0000004e`a14ff3b8 00007ff7`190d7bf3 StackOverflowFindData!__xt_z+0x153
0000004e`a14ff3c0 00007fff`d1059570 ucrtbase!_argc
0000004e`a14ff3c8 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0000004e`a14ff3d0 0000004e`a14ff8d0
0000004e`a14ff3d8 00007ff7`190d7bb0 StackOverflowFindData!__xt_z+0x110
0000004e`a14ff3e0 0000004e`00000000
0000004e`a14ff3e8 00007ff7`190d7bf3 StackOverflowFindData!__xt_z+0x153
0000004e`a14ff3f0 00007fff`d1059570 ucrtbase!_argc
0000004e`a14ff3f8 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0000004e`a14ff400 0000004e`a14ff8d0
0000004e`a14ff408 00007ff7`190d7bb0 StackOverflowFindData!__xt_z+0x110
0000004e`a14ff410 0000004e`00000000
0000004e`a14ff418 00007ff7`190d7bf3 StackOverflowFindData!__xt_z+0x153
0000004e`a14ff420 00007fff`d1059570 ucrtbase!_argc
0000004e`a14ff428 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0000004e`a14ff430 0000004e`a14ff8d0
0000004e`a14ff438 00007ff7`190d7bb0 StackOverflowFindData!__xt_z+0x110
......
0000004e`a14ff860 0000004e`00000000
0000004e`a14ff868 00007ff7`190d7bf3 StackOverflowFindData!__xt_z+0x153
0000004e`a14ff870 00007fff`d1059570 ucrtbase!_argc
0000004e`a14ff878 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0000004e`a14ff880 0000004e`a14ff8d0
0000004e`a14ff888 00007ff7`190d7bb0 StackOverflowFindData!__xt_z+0x110
0000004e`a14ff890 00000000`00000000
0000004e`a14ff898 00000000`4000006a
0000004e`a14ff8a0 00000000`00000001
0000004e`a14ff8a8 00007ff7`190d1d80 StackOverflowFindData!TriggerStackOverFlow+0x50 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 14]
......
0000004e`a14ffcc0 00002372`af0f3fac
0000004e`a14ffcc8 00000000`00000000
0000004e`a14ffcd0 00000000`00000044
0000004e`a14ffcd8 0000015e`fd10d100
0000004e`a14ffce0 00000000`00000000
0000004e`a14ffce8 00007ff7`190d1e49 StackOverflowFindData!main+0x9 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 18]
0000004e`a14ffcf0 00000000`0000001f
......
因为是样例程序,可能看不出问题的重点。这里不妨假设一下Func
是一个第三方库的函数,如果通过k [FrameCount]
最多打印的函数调用栈也只能看到Func
调用,却看不到是自己编写的程序哪里触发调用了Func
,尤其是多处调用了第三方库的Func
,甚至是调用了第三方库接口后,间接再调用了第三方库里的Func
。那么通过上述方法就能够确定到,原来是我们自己编写的代码TriggerStackOverFlow
调用了这个函数。
找到了触发的函数,那么这个时候没有栈帧编号,也无法用.frame [FrameNumber]
去切换到TriggerStackOverFlow
去直接查看函数调用中的一些参数或者局部变量的值了。 那么我们接着下一章,讲一讲如何获取。
查找触发栈溢出的输入数据
在上一章的提到的情况下,可以设置BasePtr
来设置栈帧: .frame /c /r = BasePtr
。
这个笔者测试过,并不是所有的地址都可以获取到正确的函数调用栈,在不确定的时候,可以在栈底附近的多个函数调用栈帧的地址附近做尝试。比如笔者测试的地址位置如下:
0:000> .frame /c /r = 0000004e`a14ff400
00 0000004e`a14ff400 00007ff7`190d7bb0 KERNELBASE!WriteFile+0x73
rax=0000004ea1404040 rbx=0000004ea1404090 rcx=0000000000000058
rdx=0000000000000000 rsi=0000000000000058 rdi=0000000000000000
rip=00007fffd10a2573 rsp=0000004ea14ff400 rbp=0000004ea14055c1
r8=0000000000000000 r9=0000000000000000 r10=0000000000000000
r11=0000004ea14040a0 r12=0000000000000040 r13=0000000000000000
r14=0000000000000058 r15=0000000000000001
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
KERNELBASE!WriteFile+0x73:
00007fff`d10a2573 ff152f0a1500 call qword ptr [KERNELBASE!_imp_NtWriteFile (00007fff`d11f2fa8)] ds:00007fff`d11f2fa8={ntdll!NtWriteFile (00007fff`d4a5abc0)}
紧接着查看函数调用栈,查看新的栈帧。栈帧0x1b
对应了函数调用TriggerStackOverFlow
。
0:000> k
*** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 0000004e`a14ff400 00007ff7`190d7bb0 KERNELBASE!WriteFile+0x73
01 0000004e`a14ff470 0000004e`00000000 StackOverflowFindData!__xt_z+0x110
02 0000004e`a14ff478 00007ff7`190d7bf3 0x0000004e`00000000
03 0000004e`a14ff480 00007fff`d1059570 StackOverflowFindData!__xt_z+0x153
04 0000004e`a14ff488 00007ff7`190d1d05 ucrtbase!_argc
05 0000004e`a14ff490 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
06 0000004e`a14ff4c0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
07 0000004e`a14ff4f0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
08 0000004e`a14ff520 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
09 0000004e`a14ff550 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0a 0000004e`a14ff580 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0b 0000004e`a14ff5b0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0c 0000004e`a14ff5e0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0d 0000004e`a14ff610 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0e 0000004e`a14ff640 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
0f 0000004e`a14ff670 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
10 0000004e`a14ff6a0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
11 0000004e`a14ff6d0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
12 0000004e`a14ff700 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
13 0000004e`a14ff730 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
14 0000004e`a14ff760 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
15 0000004e`a14ff790 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
16 0000004e`a14ff7c0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
17 0000004e`a14ff7f0 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
18 0000004e`a14ff820 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
19 0000004e`a14ff850 00007ff7`190d1d05 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
1a 0000004e`a14ff880 00007ff7`190d1d80 StackOverflowFindData!Func+0x75 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 8]
1b 0000004e`a14ff8b0 00007ff7`190d1e49 StackOverflowFindData!TriggerStackOverFlow+0x50 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 14]
1c 0000004e`a14ffcf0 00007ff7`190d227c StackOverflowFindData!main+0x9 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 18]
1d (Inline Function) --------`-------- StackOverflowFindData!invoke_main+0x22 [d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
1e 0000004e`a14ffd20 00007fff`d41c4034 StackOverflowFindData!__scrt_common_main_seh+0x10c [d:\agent\_work\2\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
1f 0000004e`a14ffd60 00007fff`d4a33691 KERNEL32!BaseThreadInitThunk+0x14
20 0000004e`a14ffd90 00000000`00000000 ntdll!RtlUserThreadStart+0x21
然后我们切换到栈帧TriggerStackOverFlow
,并且查看局部变量的信息:
0:000> .frame 1b; dv /v /t
1b 0000004e`a14ff8b0 00007ff7`190d1e49 StackOverflowFindData!TriggerStackOverFlow+0x50 [c:\personal\sync\beyourbest\cpp\windbgsample\stackoverflowfinddata\source.cpp @ 14]
0000004e`a14ff8d0 char [1000] csTmpStr = char [1000] "Data Trigger StackOverFlow"
此时我们终于查找到了触发栈溢出的输入数据。这个例子是一个字符串,但是也有可能你的输入数据是一段二进制,比如一个读入到内存的文件。那么这个时候并不会像字符串一样直观,需要导出来。这个时候可以利用.writemem
这个命令,以这个字符串为例,我们可以操作如下,便把这1000个字节的内容全部导出到文件sample.txt
中了。
0:000> .writemem c:\\test\\sample.txt 0000004e`a14ff8d0 L?0n1000
Writing 3e8 bytes.
既然触发栈溢出的数据已经导出,那么根据这个数据重现、调试,便比较容易找出逻辑bug所在了。