在Visual Studio C++和Windows中,调试内存填充模式是什么?

243

在Visual Studio中,当我们在C++调试器中查看变量的时候,经常会看到"baadf00d"、"CC"和"CD"。

据我所知,"CC"仅在DEBUG模式下使用,用于指示未初始化的new()或alloc()内存。而"CD"表示已被delete或free释放的内存。我只见过在RELEASE版本中出现"baadf00d"(但我可能是错的)。

偶尔我们会遇到跟踪内存泄漏、缓冲区溢出等问题,这种信息就非常有用了。

请问有人能够友善地指出内存何时以及在哪些模式下设置为可识别的字节模式以便进行调试?


操作系统在malloc/free/new/delete时为什么会将内存初始化为0xCD、0xDD等值?这是什么时候发生的? - phuclv
@Lưu Vĩnh Phúc:问题不在于操作系统,而在于调试器。0xCD和0xDD上的“D”代表调试(即malloc_dbg是通过malloc调用的,如https://msdn.microsoft.com/en-us/library/aa270812(v=vs.60).aspx所述)。我认为它还会在堆周围添加栅栏/帖子以跟踪缓冲区溢出。当您遇到双重删除或多次释放(甚至可能调用delete而不是delete[])和已处理的悬空指针的错误时,它非常有用,并且当您检查数据时,它是“0xDD”(或未初始化堆显示0xCD)。 - HidekiAI
我没有说那是操作系统的问题。是另一个提问者错误地写了标题。 - phuclv
3个回答

357

更多信息请点击此链接:

https://zh.wikipedia.org/wiki/魔数_(程序设计)#调试值

* 0xABABABAB:Microsoft的HeapAlloc()用于标记已分配堆内存后“无人之境”的守卫字节
* 0xABADCAFE:将初始值设置为此值以初始化所有空闲内存以捕获错误指针
* 0xBAADF00D:由Microsoft的LocalAlloc(LMEM_FIXED)用于标记未初始化的已分配堆内存
* 0xBADCAB1E:断开与调试器连接时返回给Microsoft eVC调试器的错误代码
* 0xBEEFCACE:由Microsoft .NET用作资源文件中的魔数 
* 0xCCCCCCCC:由Microsoft的C ++调试运行时库用于标记未初始化的堆栈内存
* 0xCDCDCDCD:由Microsoft的C ++调试运行时库用于标记未初始化的堆内存
* 0xDDDDDDDD:由Microsoft的C ++调试堆用于标记释放的堆内存
* 0xDEADDEAD:当用户手动触发崩溃时,Microsoft Windows STOP错误代码
* 0xFDFDFDFD:由Microsoft的C ++调试堆用于标记已分配堆内存前后的“无人之境”守卫字节
* 0xFEEEFEEE:由Microsoft的HeapFree()用于标记已释放的堆内存

25
我看到这里有 BAADF00D(坏食物),BEEFCACE(牛肉蛋糕),BAADCAB1E(坏电缆),BADCAFE(糟糕的咖啡馆)和 DEADDEAD(死亡死亡)。这是有意为之吗? - Anderson Green
47
当然是有意为之的。这被称为“六边形语言”。 - user142019
34
过去在做一些底层编程(操作系统内核)时,我们曾使用C0CAC01A…;) - Per Lundberg
5
0xDEADBEEF,0xC0EDBABE也是经典用语,即使它们没有被微软广泛采用。 - J. Paulding
4
作为保罗·麦卡特尼的粉丝,我喜欢 BEA71E5 - BlueRaja - Danny Pflughoeft
显示剩余8条评论

115

调试内存分配添加了许多有用的信息。这个表格更加完整:

http://www.nobugs.org/developer/win32/debug_crt_heap.html#table

地址      偏移量  在 HeapAlloc() 后 在 malloc() 后 在 free() 期间 在 HeapFree() 后 注释
0x00320FD8  -40    0x01090009     0x01090009     0x01090009    0x0109005A    Win32 堆信息
0x00320FDC  -36    0x01090009     0x00180700     0x01090009    0x00180400    Win32 堆信息
0x00320FE0  -32    0xBAADF00D     0x00320798     0xDDDDDDDD    0x00320448    指向先前分配的 CRT 堆块的指针
0x00320FE4  -28    0xBAADF00D     0x00000000     0xDDDDDDDD    0x00320448    指向后来分配的 CRT 堆块的指针
0x00320FE8  -24    0xBAADF00D     0x00000000     0xDDDDDDDD    0xFEEEFEEE    调用 malloc() 的文件名
0x00320FEC  -20    0xBAADF00D     0x00000000     0xDDDDDDDD    0xFEEEFEEE    调用 malloc() 的行号
0x00320FF0  -16    0xBAADF00D     0x00000008     0xDDDDDDDD    0xFEEEFEEE    malloc() 分配的字节数
0x00320FF4  -12    0xBAADF00D     0x00000001     0xDDDDDDDD    0xFEEEFEEE    类型(0=已释放,1=正常,2=CRT 使用等)0x00320FF8  -8     0xBAADF00D    0x00000031     0xDDDDDDDD    0xFEEEFEEE     请求编号,从0开始增加
0x00320FFC  -4     0xBAADF00D    0xFDFDFDFD     0xDDDDDDDD    0xFEEEFEEE     禁止使用的内存区域
0x00321000  +0     0xBAADF00D    0xCDCDCDCD     0xDDDDDDDD    0xFEEEFEEE     您想要的8个字节
0x00321004  +4     0xBAADF00D    0xCDCDCDCD     0xDDDDDDDD    0xFEEEFEEE     您想要的8个字节
0x00321008  +8     0xBAADF00D    0xFDFDFDFD     0xDDDDDDDD    0xFEEEFEEE     禁止使用的内存区域
0x0032100C  +12    0xBAADF00D    0xBAADF00D     0xDDDDDDDD    0xFEEEFEEE     Win32堆分配以16字节为单位向上取整
0x00321010  +16    0xABABABAB    0xABABABAB     0xABABABAB    0xFEEEFEEE     Win32堆管理
0x00321014  +20    0xABABABAB    0xABABABAB     0xABABABAB    0xFEEEFEEE     Win32堆管理
0x00321018  +24    0x00000010    0x00000010     0x00000010    0xFEEEFEEE     Win32堆管理
0x0032101C  +28    0x00000000    0x00000000     0x00000000    0xFEEEFEEE     Win32堆管理
0x00321020  +32    0x00090051    0x00090051     0x00090051    0xFEEEFEEE     Win32堆管理0x00321024 +36 0xFEEE0400 0xFEEE0400 0xFEEE0400 0xFEEEFEEE Win32堆管理
0x00321028 +40 0x00320400 0x00320400 0x00320400 0xFEEEFEEE Win32堆管理
0x0032102C +44 0x00320400 0x00320400 0x00320400 0xFEEEFEEE Win32堆管理

9

关于0xCC0xCD,它们是上世纪80年代来自英特尔8088/8086处理器指令集的遗留物。 0xCC软件中断操作码 INT 0xCD的特殊情况。特殊的单字节版本0xCC允许程序生成中断3

虽然软件中断号原则上是任意的,但传统上使用INT 3作为调试器断点断点函数,这个约定至今仍在使用。每当启动调试器时,它都会安装一个INT 3的中断处理程序,以便当该操作码被执行时,将触发调试器。通常情况下,它会暂停当前正在运行的程序并显示一个交互式提示符。
通常,x86的INT操作码是两个字节:0xCD后跟从0-255的所需中断号。尽管您可以发出0xCD 0x03来表示INT 3,但英特尔决定添加一个特殊版本——没有其他字节的0xCC——因为一个操作码必须只有一个字节才能作为可靠的“填充字节”用于未使用的内存。
重点在于允许优雅地恢复如果处理器错误地跳转到不包含任何预期指令的内存中。多字节指令不适合此目的,因为错误的跳转可能会落在任何可能的字节偏移量上,在那里它必须继续使用正确形成的指令流。
显然,对于这个问题,一字节操作码可以轻松解决,但也可能存在古怪的例外情况:例如,考虑填充序列0xCDCDCDCD(也在此页面上提到),我们可以看到它相当可靠,因为无论指令指针落在哪里(除了最后一个填充字节),CPU都可以继续执行有效的两字节x86指令CD CD,本例中用于生成软件中断205(0xCD)。
更奇怪的是,而CD CC CD CC 100% 可以解释--给出INT 3INT 204--序列CC CD CC CD不太可靠,只有75%如图所示,但通常在重复作为int大小的内存填充器时达到99.99%。

显示 INT 指令的同步 8088/8086 指令手册页面
宏汇编器参考, 1987


哇,我没有意识到0xCC是INT3。这很有道理(不是巧合)。以前我曾在有JMP的地方注入“NOP + INT3”来检查寄存器,然后跳转到少数情况下(很久以前)。感谢您提供的见解,谜团得以解决! - HidekiAI
“NOP”的作用是什么?使用“eb”(输入字节)命令输入单个“0xCC”字节不就足够了吗? - Glenn Slayden
以前的一个习惯,一些代码会读取两个字节并尝试将其用作跳转表,或者在列出汇编代码时,通过添加NOP,当反汇编时它不会显示为“???”或其他(更可读);总之,由于多种原因,只是在BRK之前或之后注入NOP成为了一种习惯;噢,在某些情况下,一些应用程序会尝试对地址块进行校验和,因此我会用INT3 + [some-hex] grin平衡JMP $XYZW。 - HidekiAI

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接