[老文章搬家] [翻译] 深入解析win32 crt 调试堆

09 年翻译的东西。

原文见:  http://www.nobugs.org/developer/win32/debug_crt_heap.html

在DeviceStudio的Debug编译模式下, crt中的堆内存分配操作----包括malloc()和free()----使用一个特殊的, 便于调试的版本, 我们称之为crt debug堆(译注: 下面简称CDH). 相比于电光火石(译注: 原文blazingly, 我想不出更确切的说法)的运行效率, 调试版本更关注对于堆错误的定位, 它通过以下三种手法实现以上诉求:

1.用守护内存块包围新分配的内存, 这样就可以侦测到缓冲过载和欠载. 所谓守护内存块就是一系列被填充为0xfd的内存字节, 又被称为”无主之地”.

2.用一个特殊的值(0xcd)初始化新申请的内存.

3.同时用一个特殊的值填充(0xdd) 被释放的内存.

为了便于记忆, 可以这样理解以上用于填充的值:

1.0xcd意为Clean Memory.

2.0xdd意为Dead Memory.

3.0xfd意为Fences(译注: 栅栏).

CDH将大部分工作交由win32 api中的堆函数HeapAlloc()和HeapFree()完成, 每进程4Gb的虚地址空间的分块和管理是由kernel32.dl中的Win32堆自己完成的.

当你调用malloc(8)分配8字节的内存时, CDH会调用HeapAlloc()申请48字节的内存, 额外的40字节被用来存放内存块的额外信息----比如调用malloc()的源文件和行号, 以及指向上/下一个内存块的指针.在后面的列表中, 所有的crt调试信息均被标记为红色.

HeaoAlloc()本身也需要记录簿记(译注: 原文bookkeeping)信息, 事实上,一个HeaoAlloc()调用会在进程地址空间里保留80字节内存, 其中8字节的簿记信息出现在真正使用的40字节之前, 剩下的32字节在真正使用的40字节之后.在下面的列表里, Win32堆簿记信息被标记为灰色.最后, HeapAlloc()返回的内存总是被一4字节对齐初始化为0xBAADF00D.顺道说一句: 当你使用经由VirtualAlloc()管理内存的虚拟内存管理器申请内存页时, 获取的内存页会被初始化为0, 所以可以断定HeapAlloc()按照上述模式执行了额外的初始化工作.

crt取得40字节的内存块后会填入自己的簿记信息. 头两个字(译注: 即WORD)用来存放直向”前一个”和”后一个”crt堆内存块的指针. 这里的前后不能从字面去理解, 因为所谓指向”后一个”内存块的指针事实上指向的是时间顺序上紧邻本内存块之前分配的内存块, 相应的, 指向“前一个”的指针指向的是下一个将被分配的内存块. 之所以这样命名, 是因为内存块链表是从最后分配的内存块开始的. 同时, 为了使堆检查代码能遍历每个内存块, CDH还保存着第一块和最后一块内存的地址(_pFirstBlock和_pLastBlock).

如果调用malloc()代码所在的文件名和行号是已知的, 它们将被被保存在第三第四个字中, 紧接着下面一个字表示本块申请了多少字节内存. 再下面一个字是类型域, 等于1表示new或malloc()分配的普通块, 2表示crt分配的供内部使用的块. 0表示已经被用户释放但是还未归还给win32堆的块(详见下文). 通常来说, 新申请的内存块本位置等于1. 最后一块是计数器, 每执行一次内存分配计数器加1.

通过malloc()得到的8字节内存无用内存包围. 这些空内存被填充为0xfd, 当整个内存块被free()时, crt会检查这些空内存存放的值是否仍然是0xfd. 如果值改变了, 说明程序有错误存在. 不幸的是, 错误可能发生的远比检查到的早, 如果需要更精确的检查, 可以使用Purify或者Boundschecker, 他们能让程序停止在内存崩溃的点上, 如果你不想花钱, 你可以等我写篇文章告诉你怎样巧妙的实现这个功能.

真正被使用的8字节内存被初始化为0xcd, 如果你的对象中间出现连续的0xcd, 那么你一定是忘记了初始化一些东西.

当你调用free()释放上述8字节的内存时, crt首先会用0xdddddddd填充全部48字节的内存块(包括簿记信息), 这样就可以通过检查这块内存的值获知这块内存在释放后是否右被写入过(比如使用野指针写内存).

接下来, crt通常会调用HeapFree()函数将本内存块归还给win32堆, win32堆会将本内存块填充为0xFEEEFEEE. 注意, crt并不维护”空闲块列表”, 这些都由HeapFree()来做(译注: 也就是说空闲列表是由win32 堆来维护的).

但是, 你可以让crt不把被释放的内存块归还给win32堆(译注: 也就是不调用HeapFree()), 方法是将_CRTDBG_DELAY_FREE_MEM_DF传递给_CrtSetDbgFlag(), 这在你跟踪野指针错误时将会派上用场,在这种情况下, 内存不会被复用, 所以释放过的内存的值必然是0xDDDDDDDD, 处非你对释放过的内存执行了写操作. 你可以调用_CrtCheckMemory()检查释放过的内存是否被篡改.(译注: 这个函数缺省情况下需要显式调用, 你也可以将_CRTDBG_CHECK_ALWAYS_DF传递给_CrtSetDbgFlag(), 这样每次分配和释放内存时都会调用_CrtCheckMemory())

一个例子:

下面是调用p = malloc(8)然后调用free(p)的过程中内存的变化表, malloc(8)返回的指针为0x00321000, 我列出了偏移后的内存值, 以便你找到你自己分配的内存信息.


Address


Offset


After HeapAlloc()


After malloc()


During free()


After HeapFree()


Comments


0x00320FD8


-40


0x01090009


0x01090009


0x01090009


0x0109005A


Win32 heap info


0x00320FDC


-36


0x01090009


0x00180700


0x01090009


0x00180400


Win32 heap info


0x00320FE0


-32


0xBAADF00D


0x00320798


0xDDDDDDDD


0x00320448


Ptr to next CRT heap block (allocated earlier in time)


0x00320FE4


-28


0xBAADF00D


0x00000000


0xDDDDDDDD


0x00320448


Ptr to prev CRT heap block (allocated later in time)


0x00320FE8


-24


0xBAADF00D


0x00000000


0xDDDDDDDD


0xFEEEFEEE


Filename of malloc() call


0x00320FEC


-20


0xBAADF00D


0x00000000


0xDDDDDDDD


0xFEEEFEEE


Line number of malloc() call


0x00320FF0


-16


0xBAADF00D


0x00000008


0xDDDDDDDD


0xFEEEFEEE


Number of bytes to malloc()


0x00320FF4


-12


0xBAADF00D


0x00000001


0xDDDDDDDD


0xFEEEFEEE


Type (0=Freed, 1=Normal, 2=CRT use, etc)


0x00320FF8


-8


0xBAADF00D


0x00000031


0xDDDDDDDD


0xFEEEFEEE


Request #, increases from 0


0x00320FFC


-4


0xBAADF00D


0xFDFDFDFD


0xDDDDDDDD


0xFEEEFEEE


No mans land


0x00321000


+0


0xBAADF00D


0xCDCDCDCD


0xDDDDDDDD


0xFEEEFEEE


The 8 bytes you wanted


0x00321004


+4


0xBAADF00D


0xCDCDCDCD


0xDDDDDDDD


0xFEEEFEEE


The 8 bytes you wanted


0x00321008


+8


0xBAADF00D


0xFDFDFDFD


0xDDDDDDDD


0xFEEEFEEE


No mans land


0x0032100C


+12


0xBAADF00D


0xBAADF00D


0xDDDDDDDD


0xFEEEFEEE


Win32 heap allocations are rounded up to 16 bytes


0x00321010


+16


0xABABABAB


0xABABABAB


0xABABABAB


0xFEEEFEEE


Win32 heap bookkeeping


0x00321014


+20


0xABABABAB


0xABABABAB


0xABABABAB


0xFEEEFEEE


Win32 heap bookkeeping


0x00321018


+24


0x00000010


0x00000010


0x00000010


0xFEEEFEEE


Win32 heap bookkeeping


0x0032101C


+28


0x00000000


0x00000000


0x00000000


0xFEEEFEEE


Win32 heap bookkeeping


0x00321020


+32


0x00090051


0x00090051


0x00090051


0xFEEEFEEE


Win32 heap bookkeeping


0x00321024


+36


0xFEEE0400


0xFEEE0400


0xFEEE0400


0xFEEEFEEE


Win32 heap bookkeeping


0x00321028


+40


0x00320400


0x00320400


0x00320400


0xFEEEFEEE


Win32 heap bookkeeping


0x0032102C


+44


0x00320400


0x00320400


0x00320400


0xFEEEFEEE


Win32 heap bookkeeping

时间: 2024-10-10 02:47:39

[老文章搬家] [翻译] 深入解析win32 crt 调试堆的相关文章

[老文章搬家] 关于屏蔽优酷视频广告的一个方法

11年的老文章,稳重那个插件让我爽了很久,不过后来就买会员了.代码我已经遗失了,不过无所谓,思路还是明确的,我后来在 Chrome 上测试过,一样能用. ==== 正文 ==== 需求:优酷最近搞了一个广告防屏蔽,导致修改host的方法不能用了.我们需要一个新方法来绕过这个防屏蔽机制. 基本思想:新版的优酷flv player下载不到广告会罢工30秒,既然不能屏蔽广告,那我们就替换广告文件,用一个假广告文件让flv player播放. 实现:首先抓一下包,或者用Fiddle2拦截请求,会发现优酷

[老文章搬家] 插件化软件设计的头疼问题以及可能的解决思路

11年的文章,当时在做系统集成,实际上当时的思路到现在我还在琢磨,只不过后来就不做系统集成了,也一直没机会深入下去解决这个问题. ==== 正文 ==== 一直以来做的项目中有很大一部分工作量都是有关集成设备的工作.为了方便扩展以支持更多厂家的设备,但在这个过程中遇到了非常头疼的问题,在这里我把问题描述一下,欢迎大家来探讨. 问题描述:所谓设备集成,多数情况下可以简化为SDK的集成(这个比较方便,有时候我们宁愿直接集成协议,但是厂家有厂家的考虑).针对这种情况,我们的系统采用了比较典型的插件式设

[老文章搬家] 关于 Huffman 编码

按:去年接手一个项目,涉及到一个一个叫做Mxpeg的非主流视频编码格式,编解码器是厂商以源代码形式提供的,但是可能代码写的不算健壮,以至于我们tcp直连设备很正常,但是经过一个UDP数据分发服务器之后,在偶尔有丢包的情况下解码器会偶发崩溃,翻了翻他们的代码觉得可能问题出在Huffman这一块.水平有限也没有看太懂他们的源码,而且我也不是科班出身当时对Huffman编码算法只是知道这么个名字,还好服务端软件那边做了修改,解决了丢包的问题.在回家过年的火车上想起这件事,阅读了一些关于Huffman编

再来一篇装逼老文章:屏幕传输算法

仍然是以前写的一篇老文章,从其它站点拷贝回来的.此文写于07年,思想幼稚,特别是后期说教味道特别严重,仅供参考.另外,从Vista后,实际上操作系统已经在应用层提供了一个类似Mirro的接口,程序员已经没必要再自己进行变化判断了.============================================ 引用页:http://hi.baidu.com/cxmlqkoyadbekqd/item/4f3a361bdf32d28d89a9561b 老陈-为什么黑洞远程控制的屏幕传输算法是

.Mo翻译文件结构解析

.Mo翻译文件结构解析 .mo文件是.po文件经msgfmt编译后生成的翻译文件, 便于程序阅读. 我这段时间折腾了下i18n, 根据Gettext的文档研究了一下Mo文件的结构, 在C#上实现了mo文件的解析和浏览, 现把.mo的结构和基于Winform实现的Mo查看器分享给大家. 结构 整型均为4字节(Int32), 下面不再标注 位置 标识 说明 0 Magic 用于识别Mo文件, 值为0x950412de或0xde120495 4 Major&Minor Revision 两个16位整型

[Win32]一个调试器的实现(十)显示变量

[Win32]一个调试器的实现(十)显示变量 作者:Zplutor 出处:http://www.cnblogs.com/zplutor/ 本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 上回介绍了微软的符号模型,有了这个基础知识,这回我们向MiniDebugger中添加两个新功能,分别是显示变量列表和以指定类型显示内存内容.显示变量列表用于列出当前函数内的局部变量或者全局变量:以指定类型显示内存内容用于读取指定

[Win32]一个调试器的实现(十一)显示函数调用栈

[Win32]一个调试器的实现(十一)显示函数调用栈 作者:Zplutor 出处:http://www.cnblogs.com/zplutor/ 本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 本文讲解如何在调试器中显示函数调用栈,如下图所示: 原理 首先我们来看一下显示调用栈所依据的原理.每个线程都有一个栈结构,用来记录函数的调用过程,这个栈是由高地址向低地址增长的,即栈底的地址比栈顶的地址大.ESP寄存器的

[Win32]一个调试器的实现(二)调试事件的处理

[Win32]一个调试器的实现(二)调试事件的处理 作者:Zplutor 出处:http://www.cnblogs.com/zplutor/ 本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 上一篇文章说到了调试循环的写法,这回讲一下调试器应该如何处理各种调试事件. RIP_EVENT 关于这种调试事件的文档资料非常少,即使提到也只是用“系统错误”或者“内部错误”一笔带过.既然如此,我们也不需要对其进行什么处理

[Win32]一个调试器的实现(四)读取寄存器和内存

[Win32]一个调试器的实现(四)读取寄存器和内存 作者:Zplutor 出处:http://www.cnblogs.com/zplutor/ 本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 在前几篇文章中,我实现的那个调试器只能被动接收调试事件并输出这些事件的信息.现在,我要将它修改成可以接收命令,并根据命令对被调试进程进行各种操作.首先从最基本的操作开始. 获取寄存器的值 每个线程都有一个上下文环境,它包