现在的位置: 首页 > 综合 > 正文

转:mem

2012年08月02日 ⁄ 综合 ⁄ 共 7198字 ⁄ 字号 评论关闭

最后一种内存问题是 Stack corruption 和 Stackoverrun。stack overrun 很简单,一般是由于递
归函数缺少结束条件导致,使得函数调用过深把 stack 地址用光,比如下面的代码:
Void foo()
{
Foo();
}

只要在调试器里重现问题,调试器立刻就会收到 Stack overflow Exception。检查 callstack 就
可以立刻看出问题所在:
0:001> g
(cd0.4b0): Stack overflow - code c00000fd (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=cccccccc ebx=7ffdd000 ecx=00000000 edx=10312d18 esi=0012fe9c edi=00033130
 
eip=004116f9 esp=00032f9c ebp=0003305c iopl=0
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000
 
nv up ei pl nz na po nc
efl=00010206
 
*** WARNING: Unable to verify checksum for c:\Documents and Settings\Li Xiong\My
Documents\My code\MyTest\debug\MyTest.exe
MyTest!foo+0x9:
 
004116f9 53
 
push
 
ebx
 
0:000> k
ChildEBP RetAddr
0003305c 00411713 MyTest!foo+0x9
00033130 00411713 MyTest!foo+0x23
00033204 00411713 MyTest!foo+0x23
000332d8 00411713 MyTest!foo+0x23
000333ac 00411713 MyTest!foo+0x23
00033480 00411713 MyTest!foo+0x23
00033554 00411713 MyTest!foo+0x23
00033628 00411713 MyTest!foo+0x23
000336fc 00411713 MyTest!foo+0x23
000337d0 00411713 MyTest!foo+0x23
000338a4 00411713 MyTest!foo+0x23

00033978 00411713 MyTest!foo+0x23
00033a4c 00411713 MyTest!foo+0x23

第二种情况 stack corruption 往往是臭名昭著的 stack buffer overflow 导致的。这样的 bug 不
单单会造成程序崩溃,还会严重威胁到系统安全性:

http://search.msn.com/results.aspx?q=stack+buffer+overflow+attack&FORM=MSNH&srch_type
=0

在当前的计算机架构上, stack 是保存运行信息的地方。当 stack 损坏后,关于当前执行情况
的所有信息都丢失了。所以调试器在这种情况下没有用武之地。比如下面的代码:

void killstack()
{
char c;
char *p=&c;
for(int i=10;i<=100;i++)
*(p+i)=0;
}

int main(int, char*)
{
killstack();
return 0;
}

在 VS2005 中用下面的参数,在 debug 模式下编译:

/Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm /EHsc
/RTC1 /MDd /Gy /Fo"Debug\\" /Fd"Debug\vc80.pdb" /W3 /nologo /c /Wp64 /Zi /TP
/errorReport:prompt

在调试器中运行,看到的结果是:

0:000> g
 
ModLoad: 76290000 762ad000
ModLoad: 62d80000 62d89000
ModLoad: 75490000 754f1000
 
C:\WINDOWS\system32\IMM32.DLL
C:\WINDOWS\system32\LPK.DLL
C:\WINDOWS\system32\USP10.dll
 
(1d0.1504): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012ff5b ebx=7ffda000 ecx=fffffffb edx=0012ffbf esi=00000000 edi=00000000
 
eip=000000f8 esp=0012ff68 ebp=0012ff68 iopl=0
cs=001b  ss=0023  ds=0023 es=0023  fs=003b gs=0000

 

 
nv up ei pl zr na po nc
efl=00010246

000000f8 ??
 

???
 
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012ff64 00000000 00000000 00000000 00000000 0xf8
0:000> dds esp
0012ff68  00000000
0012ff6c  00000000
0012ff70  00000000
0012ff74  00000000
0012ff78  00000000
0012ff7c 00000000
0012ff80  00000000
0012ff84 00000000
0012ff88  00000000
0012ff8c 00000000
0012ff90 00000000
0012ff94  00000000
0012ff98  00000000
0012ff9c 00000000
0012ffa0 00000000
0012ffa4 00000000
0012ffa8  00000000
0012ffac 00000000
0012ffb0  00000000
0012ffb4 00000000
0012ffb8 00000000
0012ffbc  00000000
0012ffc0 0012fff0
0012ffc4 77e523cd kernel32!BaseProcessStart+0x23

Windbg 里面看到 EIP 非法,EBP 指向的地址全是 0,callstack 的信息已经被冲毁。找不到任
何线索。

对于 stack corruption,普遍的做法是首先对问题做大致定位,然后检查相关函数,在可疑函
数中添加代码写 log 文件。当问题发生后从 log 文件中分析线索。在第三部分,有关于如何
检查内存访问错误,stack overflow 和内存泄漏的详细说明

题外话和相关讨论:

关于下面这段代码在激活 pageheap 后不会崩溃的解决方法是使用/unaligned 参数。详细情况
请参考 KB816542 中关于/unaligned 的介绍
char *p=(char*)malloc(1023);
p[1023]=1;

同理,下面这段代码默认情况下使用 pageheap 也不会崩溃:

char *p=new char[1023];
p[-1]='c';

解决方法是使用 pageheap 的/backwards 参数。
上面两个例子说明由于 4kb 的粒度限制,哪怕使用 pageheap,也需要根据 pageheap 的原理
来调整参数,以便覆盖多种情况。

Pageheap 的另外一个作用是 trace。激活 pageheap 的 trace 功能后,Heap Manager 会在内存
中开辟一块专门的空间来记录每次 heap 的操作,把操作 heap 的 callstack 记录下来。当问题
发生后,以便通过导致问题的 heap 地址找到对应的 callstack。参考下面一个例子:

char * getmem()
{
return new char[100];
}

void free1(char *p)
{
delete p;
}

void free2(char *p)
{
delete [] p;
}

int main(int, char*)
{
char *c=getmem();
free1(c);
free2(c);
return 0;
}

该程序在 release 模式,不激活 pageheap 的情况下是不会崩溃的。当激活 pageheap 后,pageheap
默认设定会记录 trace。激活 pageheap 后,在 debugger 中运行会看到:

 

0:000> g

===========================================================
VERIFIER STOP 00000007: pid 0x1324: block already freed

015B1000 : Heap handle
003F5858 : Heap block
00000064 : Block size
00000000 :
===========================================================

(1324.538): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=015b1001 ecx=7c81b863 edx=0012fa7f esi=00000064 edi=00000000
 
eip=7c822583 esp=0012fbe8 ebp=0012fbf4 iopl=0
cs=001b  ss=0023  ds=0023  es=0023  fs=003b gs=0000
ntdll!DbgBreakPoint:
 
nv up ei pl nz na pe nc
efl=00000202
 
7c822583 cc
 
int
 
3
 

pageheap 激发一个 break point exception,使 debugger 停下来,同时 pageheap 会在 debugger
中打印出 block already freed 信息,表示这是一个 double free 问题

使用 kb 命令打印出 callstack:

0:000> kb
ChildEBP RetAddr  Args to Child
0012fbe4 7c85079b 015b1000 0012fc94 0012fc70 ntdll!DbgBreakPoint
0012fbf4 7c87204b 00000007 7c8722f8 015b1000 ntdll!RtlpPageHeapStop+0x72
0012fc70 7c873305 015b1000 00000004 003f5858
ntdll!RtlpDphReportCorruptedBlock+0x11e
0012fca0 7c8734c3 015b1000 003f0000 01001002 ntdll!RtlpDphNormalHeapFree+0x32
0012fcf8 7c8766b9 015b0000 01001002 003f5858
ntdll!RtlpDebugPageHeapFree+0x146
0012fd60 7c860386 015b0000 01001002 003f5858 ntdll!RtlDebugFreeHeap+0x1ed
0012fe38 7c81d77d 015b0000 01001002 003f5858 ntdll!RtlFreeHeapSlowly+0x37
0012ff1c 78134c3b 015b0000 01001002 003f5858 ntdll!RtlFreeHeap+0x11a
0012ff68 00401016 003f5858 003f5858 00000064 MSVCR80!free+0xcd
0012ff7c 00401198 00000001 003f57e8 003f3628 win32!main+0x16
[d:\xiongli\today\win32\win32\win32.cpp @ 77]
0012ffc0 77e523cd 00000000 00000000 7ffde000 win32!__tmainCRTStartup+0x10f
0012fff0 00000000 004012e1 00000000 78746341 kernel32!BaseProcessStart+0x23

崩溃发生在 free 函数。Free 函数的返回地址是 00401016,所以 Free 是在 00401016 的前一
行被调用的。发生问题的 heap 地址是 0x3f5858,使用!heap 命令加上-p –a 参数打印出保存
下来的 callstack:

0:000> !heap -p -a 0x3f5858

address 003f5858 found in
_HEAP @ 3f0000
in HEAP_ENTRY: Size : Prev Flags - UserPtr UserSize - state
3f5830: 0014 : N/A  [N/A] - 3f5858 (70) - (free DelayedFree)
Trace: 004f
7c860386 ntdll!RtlFreeHeapSlowly+0x00000037
7c81d77d ntdll!RtlFreeHeap+0x0000011a
78134c3b MSVCR80!free+0x000000cd
401010 win32!main+0x00000010
77e523cd kernel32!BaseProcessStart+0x00000023

从保存的 callstack 看到, 0x401010 前一行上,已经调用过一次 free 了,00401016,00401010
距离很近,看看分别是什么:

0:000> uf 00401010
win32!main [d:\xiongli\today\win32\win32\win32.cpp @ 74]:
 
74 00401000 56
75 00401001 6a64
75 00401003 e824000000
75 00401008 8bf0
76 0040100a 56
76 0040100b e828000000
77 00401010 56
77 00401011 e81c000000
77 00401016 83c40c
78 00401019 33c0
78 0040101b 5e
79 0040101c c3
 
push
push

mov
push

push

add
xor
pop
ret
 
esi
0x64
win32!operator new[] (0040102c)
esi,eax
esi
win32!operator delete (00401038)
esi
win32!operator delete[] (00401032)
esp,0xc
eax,eax
esi
 

这里可以看到,对应的 double free 是连续调用了 delete 和 delete []。对应的源代码地址大约
在 win32.cpp 的 74 行。(源代码中,delete 和 delete[]是在两个自定义函数中被调用的。这里
看不到 free1 和 free2 两个函数的原因在于 release 模式下编译器做了 inline 优化。)

如果有兴趣,可以检查一下 heap pointer 0x3f5858 前后的内容:

0:000> dd 0x3f5848
003f5848  7c88c580 0025a5f0 00412920 dcbaaaa9
 
003f5858
 
f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
 
003f5868 f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
003f5878  f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
003f5888  f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
003f5898 f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
003f58a8  f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
003f58b8  f0f0f0f0 a0a0a0a0 a0a0a0a0 00000000

这里的红色 dcba 其实是一个标志位,标致位前面的地址上保存的就是对应的 callstack:

0:000> dds 00412920
 
00412920
 
00000000
 
00412924 00000001
00412928  0005004f
0041292c  7c860386 ntdll!RtlFreeHeapSlowly+0x37
00412930  7c81d77d ntdll!RtlFreeHeap+0x11a
00412934  78134c3b MSVCR80!free+0xcd
 
00412938
 
00401010 win32!main+0x10
 
0041293c 77e523cd kernel32!BaseProcessStart+0x23

了解这个标致位的好处是,可以利用这个特点来解决 memory leak 和 fragmentation。发生泄
漏的内存往往是相同 callstack 分配的。当泄露到一个比较严重的程度的时候,程序中残留的
大多数的 heap pointer 都是泄露的内存地址。由于每一个 heap pointer 都带有标志位,通过在
程序中搜索这个标志位,就可以找到分别的 callstack。如果某些 callstack 出现得非常频繁,
往往这些 callstack 就跟 memory leak 相关。下面就是一个使用这个方法解决 memory leak 的

抱歉!评论已关闭.