Windows用户态程序高效排错–Heap和Stack[转帖]

平坦内存空间中的层次结构:Heap和Stack

本小结主要介绍Heap相关的崩溃和内存泄漏,和如何使用pageheap来排错。首先介绍heap的原理,不同层面的内存分配,接下来通过例子代码举例演示heap问题的严重性和欺骗性。最后介绍如何使用pageheap工具高效地对heap问题排错。

2.4.1 Heap是对平坦空间的高效管理和利用

内存是容纳代码和资料的空间。无论是stack,heap还是DLL,都是”生长”在内存上的。代码的执行效果其实是对内存上的资料进行转化。内存是导致问题最多的地方,比如内存不足、内存访问违例、内存泄漏等,都是常见的问题。

关于内存的详细信息,Programming Applications for Microsoft Windows书中有详细介绍。这里针对排错作一些补充。

Windows API中有两类内存分配函数。分别是:VirtualAlloc和HeapAlloc。前一种是向操作系统申请4KB为边界的整块内存,后者是分配任意大小的内存块。区别在于,后者的实现依赖于前者。换句话说,操作系统管理内存的最小单位是4KB,这个粒度是固定的(其实根据芯片是可以做调整的。这里只讨论最普遍的情况)。但用户的资料不可能恰好都是4KB大小。用4KB作单位,难免会产生很多浪费。解决办法是依靠用户态代码的薄计工作,实现比4KB单位更小的分配粒度。换句话说,用户态的程序需要实现一个Memory Manager,通过自身的管理,在4KB为粒度的基础上,提供以字节为粒度的内存分配,释放功能,并且能够平衡好时间利用率和空间利用率。

Windows提供了Heap Manager完成上述功能。HeapAlloc函数是Heap Manager的分配函数。Heap Manager的工作方式大概是这样:首先分配足够的大的4 KB倍数的连续内存空间;然后在这块内存上开辟一小块区域用来做薄计;接下来把这连续的大块内存分割成很多尺寸不等的小块,把每一小块的信息记录到薄计里面。薄计记录了每一小块的起始地址和长度,以及是否已经分配。

当内存请求发生的时候,HeapManager根据请求的长度,在薄计信息里面找到大小最合适的一块空间,把这块空间标记成已经分配,然后把这块空间的地址返回,这样就完成了一次内存分配。如果找不到长度足够大的空闲小块,Heap Manager继续以4 KB为粒度向系统申请更多的内存。

当用户需要释放内存的时候,调用HeapFree,同时传入起始地址。HeapManager在薄计信息中找到这块地址,把这块地址的信息由已经分配改回没有分配。当Heap Manager发现有大量的连续空闲空间的时候,也会调用VirtualFree来把这些内存归还给操作系统。在实现上面这些基本功能的情况下,HeapManager还需要考虑到:

  1. 分配的内存最好是在4字节边界上,这样可以提高内存访问效率。

  2. 做好线程同步,保证多个线程同时分配内存的时候不会出现错误。

  3. 尽可能节省维护薄计的开销,提高性能,避免不必要的计算和检查。所以HeapManager假设用户的代码没有bug,比如用户代码永远不会越界对内存块进行存取,这样就可以省去检查核对的开销。

  4. 优化内部的内存块管理。比如灵活地合并连续的内存小块以便满足长尺寸的内存申请,或者拆分连续内存块提高小尺寸的内存使用率。

有了上面的理解后,看下面一些情况:

  1. 如果首先用HeapAlloc分配了一块空间,然后用HeapFree释放了这块空间。但是在释放后,继续对这块空间做操作,程序会发生访问违例错误吗?答案是不会,除非HeapManager恰好把那块地址用VirtualFree返还给操作系统了。但是带来的结果是什么?是”非预期结果”。也就是说,谁都无法保证最后会产生什么情况。程序可能不会有什么问题,也可能会格式化整个硬盘。出现得最多的情况是,这块内存后来被Heap Manager重新分配出去。导致两个本应指向不同地址的指针,指向同一个地址。伴随而来的是资料损坏或者访问违例等等。

  2. 如果用HeapAlloc分配了100KB的空间,但是访问的长度超过了100KB,会怎么样?如果100KB恰好在4KB内存边界上,而且恰好后面的内存地址并没有被映像上来,程序不会崩溃的。这时,越界的写操作,要么写到别的内存块上,要么就写入薄计信息中,破坏了薄计。导致的结果是HeapManager维护的数据损坏,导致”非预期结果”。

  3. 其他错误的代码,比如对同一个地址HeapFree了两次,多线程访问的时候忘记在调用HeapAllocate的第二个参数中传入SERIALIZE bit等等,都会导致”非预期结果”。

总的来说,上面这些情况都会导致非预期结果。如果问题发生后程序立刻崩溃,或者抛出异常,则可以在第一时间截获这个错误。但是,现实的情况是,这些错误不会有及时的效果,错误带来的后果会暂时隐藏起来,在程序继续执行几个小时后,突然在一些看起来绝对不可能出现错误的地方崩溃。比如在调用HeapAllocate/HeapFree的时候崩溃。比如访问一个刚刚分配好的地址的时候崩溃。这个时候哪怕抓到了崩溃的详细信息也无济于事,因为问题根源潜伏在很久以前。这种根源在前现象在后的情况会给调试带来极大的困难。

仔细考虑这种难于调试的情况,错误之所以没有在第一时间暴露,在于下面两点:

  1. Heap每一块内存的界限是Heap Manager定义的,而内存访问无效的界限,是操作系统定义的。哪怕访问越界,如果越界的地方已经有映像上来的4KB为粒度的内存页,程序就不会立刻崩溃。

  2. 为了提高效率,Heap Manager不会主动检查自身的数据结构是否被破坏。

所以,为了方便检查Heap上的错误,让现象尽早表现出来,Heap Manager应该这样管理内存:

  1. 把所有的Heap内存都分配到4KB页的结尾,然后把下一个4KB页面标记为不可访问。越界访问发生时候,就会访问到无效地址,程序就立刻崩溃。

  2. 每次调用Heap相关函数的时候,Heap Manager主动去检查自身的数据结构是否被破坏。如果检查到这样的情况,就主动报告出来。

接下来我们会分析如何使用Pageheap帮忙做到上面两点。如果想了解更多Windows上Heap的知识,以及如何用Windbg中的!heap命令检查Heap,请参考:

Debug Tutorial Part 3: The Heap

2.4.2 PageHeap,调试Heap问题的工具

幸运的是,Heap Manager的确提供了主动检查错误的功能。只需要在注册表里面做对应的修改,操作系统就会根据设置来改变Heap Manager的行为。Pageheap是用来配置该注册表的工具。关于heap的详细信息和原理请参考:

How to use Pageheap.exe in Windows XP and Windows 2000

Pageheap,Gflag和后面介绍的Application Verifier工具一样,都是方便修改对应注册表的工具。如果不使用这两个工具,直接修改注册表也可以达到一样的效果。3个工具里面Application Verifier是目前的主流,Gflag是老牌。除了heap问题外,这两个工具还可以修改其他的调试选项,后面都有说明。Pageheap.exe工具主要针对heap问题,使用起来简单方便。目前gflag.exe包含在调试器的安装包中,Application Verifier可以单独下载安装。如果调试安装包中没有包含pageheap.exe,可以从这里下载:

看几个简单的但是却很有意义的例子:

用release模式编译运行下面的代码:

这里往分配的空间多写一个字节。但是在release模式下运行,程序不会崩溃。

假设上面的代码编译成mytest.exe,用下面的方法可以对mytest.exe激活pageheap:

C:\Debuggers\pageheap>pageheap /enable mytest.exe /full

直接运行pageheap可以查看当前pageheap的激活状态:

C:\Debuggers\pageheap>pageheap

mytest.exe: page heap enabled with flags (full traces )

当激活pageheap后,重新运行一次上面的代码,程序就崩溃了。

(直接双击运行程序和在Windbg中用调试模式运行程序,观察到的崩溃有差别。在Windbg中运行,pageheap会首先触发break point异常,同时pageheap还会在调试器中输出额外的调试信息方便调试。)

上面的例子说明了pageheap能够让错误尽快暴露出来。接下来我们稍微修改一下代码:

试试看,修改后的代码还会导致程序崩溃吗?

根据我的测试,分配1023字节的情况下,哪怕激活pageheap,也不会崩溃。你能说明原因吗?如果看不出来,可以检查一下每次malloc返回的地址的数值,注意对这个数值在二进制上敏感一点,然后结合Heap Manager和pageheap的原理思考一下,看看有没有发现。

对于上面两种代码,如果用debug模式编译,激活pageheap,程序会崩溃吗?根据我的测试,无论是否激活pageheap,debug模式都不会崩溃的。你能想到原因吗?

再来看下面一段代码:

这里显然有double free的问题。

如果没有激活pageheap,分别在debug和release模式下运行,根据我的测试,debug模式下会崩溃,release模式下运行正常。

如果激活pageheap,同样在debug/release模式下运行。根据我的测试,在两种模式下都会崩溃。如果细心观察,会发现两种模式下,崩溃后弹出的提示各自不同。你能想到原因吗?

从上面的例子,可以很清楚地看到pageheap对于检查这类问题的帮助。同时也可以看到,pageheap无法保证检查出所有潜在问题,比如分配1023个字节,但是写1024个字节这种情况。只有理解pageheap的工作原理,同时对问题作认真的思考和测试后,才会理解其中的差别。

除了Heap使用不当导致崩溃外,还有一类问题是内存泄漏。内存泄漏是指随着程序的运行,内存消耗越来越多,最后发生内存不足,或者整体性能下降。从代码上看,这类问题是由于内存使用后没有及时释放导致的。这里的内存,可以是VirtualAlloc分配的,也有可能是HeapAllocate分配的。

这里只讨论Heap相关的内存泄漏。检查内存泄漏是一个比较大的题目,第4章会作详细讨论。

举个例子,客户开发一个cd刻录程序。每次把盘片中所有内容写入内存,然后开始刻录。如果每次刻录完成后都忘记去释放分配的空间,那么最多能够刻3张CD。因为3张CD,每一张600MB,加在一起就是1.8GB,濒临2GB的上限。

另外还有一种跟内存泄漏相关的问题,是内存碎片(Fragmentation)。内存碎片是指内存被分割成很多的小块,以至于很难找到连续的内存来满足比较大的内存申请。导致内存碎片常见原因有两种,一种是加载了过多DLL,还有一种是小块Heap的频繁使用。

DLL分割内存空间最常见的情况是ASP.NET中的batch compilation没有打开,导致每一个ASP.NET页面都会被编译成一个单独的DLL文件。运行一段时间后,就可以看到几千个DLL文件加载到进程中。一个极端的例子是5000个DLL把2GB内存平均分成5000份,导致每一份的大小在400KB左右(假设DLL本身只占用1个字节),于是无法申请大于400KB的内存,哪怕总的内存还是接近2GB。对于这种情况的检查很简单,列一下当前进程中所有加载起来的DLL就可以看出问题来。

对于小块Heap的频繁使用导致的内存分片,可以参考下面的解释:

Heap fragmentation is often caused by one of the following two reasons

  1. Small heap memory blocks that are leaked (allocated but never freed) over time

  2. Mixing long lived small allocations with short lived long allocations

Both of these reasons can prevent the NT heap manager from using free memory efficiently since they are spread as small fragments that cannot be used as a single large allocation

为了更好地理解上面的解释,考虑这样的情况。假设开发人员设计了一个数据结构来描述一首歌曲,数据结构分成两部分,第一部分是歌曲的名字、作者和其他相关的描述性信息,第二部分是歌曲的二进制内容。显然第一部分比第二部分小得多。假设第一部分长度1KB,第二部分399KB。每处理一首歌需要调用两次内存分配函数,分别分配数据结构第一部分和第二部分需要的空间。

假设每次处理完成后,只释放了数据结构的第二部分,忘记释放第一部分,这样每处理一次,就会留下1个1KB的数据块没有释放。程序长时间运行后,留下的1KB数据块就会很多,虽然HeapManager的薄计信息中可能记录了有很多399KB的数据块可以分配,但是如果要申请500KB的内存,就会因为找不到连续的内存块而失败。对于内存碎片的调试,可以参考最后的案例讨论。在Windows 2000上,可以用下面的方法来缓解问题:

The Windows XP Low Fragmentation Heap Algorithm Feature Is Available for Windows 2000

关于 CLR上内存碎片的讨论和图文详解,请参考:

.NET Memory usage – A restaurant analogy

2.4.3 Stack overrun/corruption

另外一种内存问题是Stack overrun和Stack corruption。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 nv up ei pl nz na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 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不单单会造成程序崩溃,还会严重威胁到系统安全性。在网上搜索Stack buffer overflow,可以看到无数用Stack buffer进行攻击的例子。

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

在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 C:\WINDOWS\system32\IMM32.DLL

ModLoad: 62d80000 62d89000 C:\WINDOWS\system32\LPK.DLL

ModLoad: 75490000 754f1000 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 nv up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 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都指向非法地址,callstack的信息已经被冲毁,根本找不到任何线索进行调试。对于Stack corruption,行之有效的方法是首先对问题作大致定位,然后检查相关函数,在可疑函数中添加代码写log文件。当问题发生后从log文件中找到线索。

2.4.4 题外话和相关讨论

前面提到了分配1023个字节的问题。在激活pageheap后,同时使用/unaligned参数,才可以检测到这类问题。详细情况请参考KB816542中关于/unaligned的介绍。

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

解决方法是使用pageheap的/backwards参数。

Pageheap的另外一个功能是trace,作用是记录Heap的历史操作。激活pageheap的trace功能后,Heap Manager会在内存中开辟一块专门的空间来记录每次Heap的操作,比如Heap的分配和释放,把操作Heap的callstack记录下来。当问题发生后,在Windbg中可以检查Heap操作的历史记录,方便调试。参考下面一个例子:

该程序在release模式下,不激活pageheap是不会崩溃的。激活pageheap后,在Windbg中运行会看到:

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 nv up ei pl nz na pe nc

cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

ntdll!DbgBreakPoint:

7c822583 cc int 3

激活pageheap后,当Heap Manager检测到错误,就会激发一个break point exception,使debugger停下来,同时pageheap会在debugger中打印出block already freed信息,表示这是一个double free问题。

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的前一行被调用的。接下来分析第一次Free调用发生的地方。发生问题的Heap地址是Free函数的参数0x3f5858,在Windbg中使用!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就是Heap Manager保存的,这是Heap地址的历史操作。从保存的callstack看到,在0x401010地址是MSVCR80!free调用的返回地址,所以00401016和00401010两个地址,就是对同一个Heap地址两次调用Free后的两个返回地址。检查这两个地址的前一条汇编语句,就能找到对应的Free调用:

0:000> uf 00401010

win32!main [d:\xiongli\today\win32\win32\win32.cpp @ 74]:

74 00401000 56 push esi

75 00401001 6a64 push 0x64

75 00401003 e824000000 call win32!operator new[] (0040102c)

75 00401008 8bf0 mov esi,eax

76 0040100a 56 push esi

76 0040100b e828000000 call win32!operator delete (00401038)

77 00401010 56 push esi

77 00401011 e81c000000 call win32!operator delete[] (00401032)

77 00401016 83c40c add esp,0xc

78 00401019 33c0 xor eax,eax

78 0040101b 5e pop esi

79 0040101c c3 ret

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

同时可以检查一下Heap 指针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其实是一个标志位,标志位前面的地址保存的其实就是这个Heap地址的历史操作记录。通过Windbg的dds命令,可以直接检查保存下来的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

客户程序在内存占用只有300MB左右的时候,对malloc的调用就会失败。通过检查问题发生时候的dump文件,发现问题是由heap fragmentation导致的。客户的程序有大量的小块内存没有及时释放,导致分片严重。

激活pageheap后,再次抓取问题发生时的dump,然后使用下面命令在内存空间搜索dcba标志位:

0:044> s -w 0 L?60030000 0xdcba

00115e9e dcba 0000 0000 ef98 0012 893d 0047 efc8 ……….=.G…

19b90fe6 dcba cfe8 02d8 afe8 2ca3 cfe8 02d8 b22a ………,….*.

19b92fe6 dcba cfe8 1a52 8fe8 1dff cfe8 1af6 f44f ….R………O.

19b9cfce dcba efd0 23d8 cfd0 1c58 8fd0 15ac c0c0 …..#..X…….

2b06efe6 dcba cfe8 02d8 8fe8 258b cfe8 02d8 a6d2 ………%……

2b074fce dcba 2fd0 1c0f afd0 1c4d dfd0 0e69 c0c0 …/….M…i…

2e860fe6 dcba afe8 02d8 2fe8 2ef3 afe8 02d8 0a0b ……./……..

2e868fce dcba afd0 0881 2fd0 2e92 afd0 0881 c0c0 ……./……..

根据搜索结果,使用下面的命令来随机打印callstack,看到:

0:044> dds poi(19b92fe6 -6)

005bba0c 005cbe90

005bba10 00031c49

005bba14 00122ddb

005bba18 77fa8468 ntdll!RtlpDebugPageHeapAllocate+0x2f7

005bba1c 77faa27a ntdll!RtlDebugAllocateHeap+0x2d

005bba20 77f60e22 ntdll!RtlAllocateHeapSlowly+0x41

005bba24 77f46f5c ntdll!RtlAllocateHeap+0xe3a

005bba28 0046b404 Customer_App+0x6b404

005bba2c 0046b426 Customer_App+0x6b426

005bba30 00427612 Customer_App+0x27612

0:044> dds poi(19b9cfce -6)

005bba0c 005cbe90

005bba10 00031c49

005bba14 00122ddb

005bba18 77fa8468 ntdll!RtlpDebugPageHeapAllocate+0x2f7

005bba1c 77faa27a ntdll!RtlDebugAllocateHeap+0x2d

005bba20 77f60e22 ntdll!RtlAllocateHeapSlowly+0x41

005bba24 77f46f5c ntdll!RtlAllocateHeap+0xe3a

005b8024 0046b404 Customer_App+0x6b404

005b8028 0046b426 Customer_App+0x6b426

005b802c 00427a82 Customer_App+0x27a82

0:044> dds poi(2b06efe6 -6)

005bba0c 005cbe90

005bba10 00031c49

005bba14 00122ddb

005bba18 77fa8468 ntdll!RtlpDebugPageHeapAllocate+0x2f7

005bba1c 77faa27a ntdll!RtlDebugAllocateHeap+0x2d

005bba20 77f60e22 ntdll!RtlAllocateHeapSlowly+0x41

005bba24 77f46f5c ntdll!RtlAllocateHeap+0xe3a

005bd5d4 0046b404 Customer_App+0x6b404

005bd5d8 0046b426 Customer_App+0x6b426

005bd5dc 00427612 Customer_App+0x27612

Original: https://www.cnblogs.com/chengxin1985/archive/2009/08/14/1546196.html
Author: 程鑫
Title: Windows用户态程序高效排错–Heap和Stack[转帖]

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/711603/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Python3 字典浅析

    字典是一个无序、可变和有索引的集合。在 Python 中,字典用花括号编写,拥有键和值。 创建并打印字典: thisdict = { "brand": &quo…

    技术杂谈 2023年6月21日
    081
  • xftp 上传文件到CentOS 出现 Permission is not allowed解决方案

    今天试了以下好久没用的xftp,结果连接虚拟机中的CentOS出现了错误:Permission is not allowed.根据提示感觉应该是文件夹权限问题。 查看一下文件夹权限…

    技术杂谈 2023年5月31日
    073
  • 比较 Windows 10 的不同版本

    转自 比较 Windows 10 的不同版本 win10 家庭版、专业版、专业工作站版、企业版的区别比较。 更多信息,可以通过比较完整的列表: Windows10_Commerci…

    技术杂谈 2023年5月31日
    067
  • C# 可视化与自定义控件开发

    和上篇文章一样,基本上以后不会再去弄C#了,所以2年前的资料,都拿出来无偿贡献了。 有两篇整理的资料,目录如下图所示,可以点击此处(C_Sharp_可视化控件开发.rar)下载。 …

    技术杂谈 2023年5月30日
    0103
  • 搭建简单JavaWeb项目

    参考:(17条消息) 手把手搭建一个完整的javaweb项目(适合新手)_心歌技术的博客-CSDN博客_javaweb项目完整案例 补充项目结构的细节,进行了一点修改,修改为学生信…

    技术杂谈 2023年6月21日
    088
  • pytest-xdist–分布式执行用例

    pytest-xdist基本的介绍 声明:在介绍pytest-xdist时,本人不讲任何原理,需要看原理的请移至官方:当我们自动化测试用例非常多的时候, 一条条按顺序执行会非常慢,…

    技术杂谈 2023年7月25日
    072
  • spring中bean的生命周期

    bean生命周期简易版 生命周期:1、创建对象(实例化) 生命周期:2、依赖注入(为属性赋值) 生命周期:3、初始化(需要通过bean中init-method属性指定初始化方法) …

    技术杂谈 2023年7月11日
    052
  • innosetup安装之前关闭进程

    InnoSetup覆盖安装的时候可能会因为源程序正在运行而安装失败,以下脚本能够关闭原运行进程。 [code]// 安装前检查关闭**进程function InitializeSe…

    技术杂谈 2023年5月31日
    081
  • ArcGIS Pro创建注记

    You can check out some of these Annotation tools: Annotate Selected Features Convert Label…

    技术杂谈 2023年5月30日
    085
  • 生成符合chrome要求的自签名HTTPS证书

    按照文章给Nginx配置一个自签名的SSL证书中给出的代码生成自签名证书后,发现使用chrome浏览器访问会报 Invalid self signed SSL cert &#821…

    技术杂谈 2023年6月21日
    089
  • Caddy一个强大的web服务器和代理服务器

    什么是Caddy Caddy是一个强大的、可扩展的平台,可以为您的站点、服务和应用程序提供服务,它是用Go编写的。虽然大多数人使用它作为web服务器或代理,但其实他支持更多的功能:…

    技术杂谈 2023年5月31日
    088
  • 开发环境的搭建

    ​ 一:ubuntu14.04 LTS开发环境搭建 1.1、参考博客 参考的教程如下: ubuntu老版本下载地址 VMware下Ubuntu Server 14.04安装教程(最…

    技术杂谈 2023年6月21日
    086
  • 15. 合并二叉树

    title: 合并二叉树 📃 题目描述 题目链接: 合并二叉树 🔔 解题思路 递归法:采用前序遍历方式进行简单的构造即可,下面是优化的代码; class Solution { pu…

    技术杂谈 2023年7月24日
    096
  • jQuery快速入门

    jQuery介绍 jQuery是一个轻量级的、兼容多浏览器的JavaScript库。 jQuery使用户能够更方便地处理HTML Document、Events、实现动画效果、方便…

    技术杂谈 2023年6月21日
    081
  • 踏实型的TOGAF企业架构学习者要学什么?

    下载:本文PDF版 昨天有帮友在IT帮大本营微信群了问到 我打开ea网站的常见问题,截图回复了一下 如果让我再简单说一下该学什么,我想用下面这张图来说明 实操型的看 企业架构,从理…

    技术杂谈 2023年5月31日
    078
  • Clickhouse入门及实践

    ClickHouse安装 采用doker安装测试: 拉取服务端 docker pull yandex/clickhouse-server 拉取客户端 docker pull yan…

    技术杂谈 2023年7月10日
    072
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球