什么时候以及为什么编译器会在malloc / free / new / delete上将内存初始化为0xCD,0xDD等?


129

我知道编译器有时会使用诸如0xCD和的某些模式来初始化内存0xDD。我想知道的是何时以及为什么发生这种情况。

什么时候

这特定于所使用的编译器吗?

malloc/newfree/delete工作以同样的方式对本?

它是平台特定的吗?

它会在其他操作系统(例如Linux或)上发生VxWorks吗?

为什么

我的理解是,这仅在Win32调试配置中发生,它用于检测内存溢出并帮助编译器捕获异常。

您能提供一些有关初始化有用的实际例子吗?

我记得读过一些东西(也许在代码完成2中),说在分配内存时将内存初始化为已知模式是很好的,某些模式会触发中断,Win32从而导致调试器中显示异常。

这有多便携?

Answers:


191

Microsoft的编译器在调试模式下进行编译时,其各种未使用/未初始化的内存的简要说明(支持可能会因编译器版本而异):

Value     Name           Description 
------   --------        -------------------------
0xCD     Clean Memory    Allocated memory via malloc or new but never 
                         written by the application. 

0xDD     Dead Memory     Memory that has been released with delete or free. 
                         It is used to detect writing through dangling pointers. 

0xED or  Aligned Fence   'No man's land' for aligned allocations. Using a 
0xBD                     different value here than 0xFD allows the runtime
                         to detect not only writing outside the allocation,
                         but to also identify mixing alignment-specific
                         allocation/deallocation routines with the regular
                         ones.

0xFD     Fence Memory    Also known as "no mans land." This is used to wrap 
                         the allocated memory (surrounding it with a fence) 
                         and is used to detect indexing arrays out of 
                         bounds or other accesses (especially writes) past
                         the end (or start) of an allocated block.

0xFD or  Buffer slack    Used to fill slack space in some memory buffers 
0xFE                     (unused parts of `std::string` or the user buffer 
                         passed to `fread()`). 0xFD is used in VS 2005 (maybe 
                         some prior versions, too), 0xFE is used in VS 2008 
                         and later.

0xCC                     When the code is compiled with the /GZ option,
                         uninitialized variables are automatically assigned 
                         to this value (at byte level). 


// the following magic values are done by the OS, not the C runtime:

0xAB  (Allocated Block?) Memory allocated by LocalAlloc(). 

0xBAADF00D Bad Food      Memory allocated by LocalAlloc() with LMEM_FIXED,but 
                         not yet written to. 

0xFEEEFEEE               OS fill heap memory, which was marked for usage, 
                         but wasn't allocated by HeapAlloc() or LocalAlloc(). 
                         Or that memory just has been freed by HeapFree(). 

免责声明:这张桌子是从我身边的一些笔记中摘录的-它们可能不是100%正确(或连贯)。

其中许多值是在vc / crt / src / dbgheap.c中定义的:

/*
 * The following values are non-zero, constant, odd, large, and atypical
 *      Non-zero values help find bugs assuming zero filled data.
 *      Constant values are good, so that memory filling is deterministic
 *          (to help make bugs reproducible).  Of course, it is bad if
 *          the constant filling of weird values masks a bug.
 *      Mathematically odd numbers are good for finding bugs assuming a cleared
 *          lower bit.
 *      Large numbers (byte values at least) are less typical and are good
 *          at finding bad addresses.
 *      Atypical values (i.e. not too often) are good since they typically
 *          cause early detection in code.
 *      For the case of no man's land and free blocks, if you store to any
 *          of these locations, the memory integrity checker will detect it.
 *
 *      _bAlignLandFill has been changed from 0xBD to 0xED, to ensure that
 *      4 bytes of that (0xEDEDEDED) would give an inaccessible address under 3gb.
 */

static unsigned char _bNoMansLandFill = 0xFD;   /* fill no-man's land with this */
static unsigned char _bAlignLandFill  = 0xED;   /* fill no-man's land for aligned routines */
static unsigned char _bDeadLandFill   = 0xDD;   /* fill free objects with this */
static unsigned char _bCleanLandFill  = 0xCD;   /* fill new objects with this */

有时调试运行时还会用已知值填充缓冲区(或缓冲区的一部分),例如,std::string分配中的“松弛”空间或传递给的缓冲区fread()。在这些情况下,将使用给定名称_SECURECRT_FILL_BUFFER_PATTERN(在中定义crtdefs.h)的值。我不确定它什么时候被引入,但是至少在VS 2005(VC ++ 8)中它是在调试运行时中。

最初,用于填充这些缓冲区0xFD的值是-与无人区相同的值。但是,在VS 2008(VC ++ 9)中,该值已更改为0xFE。我以为是因为在某些情况下,填充操作可能会超出缓冲区的末尾,例如,如果调用方传入的缓冲区大小太大了,则该操作会结束fread()。在那种情况下,该值0xFD可能不会触发检测到该溢出,因为如果缓冲区大小仅增加一个,则填充值将与用于初始化金丝雀的no man's land值相同。没有人的土地没有变化,就不会引起人们的注意。

因此,在VS 2008中更改了填充值,因此这种情况将更改无人区的金丝雀,从而在运行时发现问题。

正如其他人指出的那样,这些值的关键属性之一是,如果取消引用具有这些值之一的指针变量,则将导致访问冲突,因为在标准的32位Windows配置中,用户模式地址不会高于0x7fffffff。


1
我不知道它是否在MSDN上-我从那里到那里将其拼凑在一起,或者我可能是从其他网站上获得的。
Michael Burr

2
哦,是的-其中一些来自DbgHeap.c中的CRT来源。
Michael Burr

其中一些位于MSDN(msdn.microsoft.com/en-us/library/bebs9zyz.aspx)上,但不是全部。好清单。
肖恩e

3
@seane-仅供参考,您的链接似乎已消失。可在此处找到新的文本(文本已得到增强):msdn.microsoft.com/en-us/library/974tc9t1.aspx
Simon Mourier 2014年

这些块的名称是什么?它是记忆屏障,门栏,记忆栅栏还是栅栏指令(en.wikipedia.org/wiki/Memory_barrier)?
kr85

36

关于填充值0xCCCCCCCC的一个不错的属性是在x86汇编中,操作码0xCC是int3操作码,它是软件断点中断。因此,如果您试图在已填充该填充值的未初始​​化内存中执行代码,则会立即遇到断点,并且操作系统将允许您附加调试器(或终止进程)。


6
并且0xCD是int指令,因此执行0xCD 0xCD将生成一个int CD,这也会陷阱。
塔德·马歇尔

2
在当今世界,Data Execution Prevention甚至不允许CPU从堆中获取指令。自XP SP2起,此答案已过时。
MSalters

2
@MSalters:是的,的确是这样,默认情况下,新分配的内存是不可执行的,但是有人可以轻松使用该内存VirtualProtect()mprotect()使其成为可执行文件。
亚当·罗森菲尔德

您无法从数据块执行代码。永远 继续猜。
丹丹

9

Visual Studio是特定于编译器和OS的,将不同类型的内存设置为不同的值,以便在调试器中可以轻松查看是否已将内存过度分配到已分配的内存,固定数组或未初始化的对象中。当我搜寻这些细节时,会有人张贴这些细节...

http://msdn.microsoft.com/zh-CN/library/974tc9t1.aspx


我的猜测是,它还用于检查您是否也忘记正确终止字符串(因为已打印0xCD或0xDD)。
斯特拉格

0xCC =未初始化的本地(堆栈)变量0xCD =未初始化的类(堆?)变量0xDD =删除的变量
FryGuy

@FryGuy正如我在这里解释的那样,有一个实际的原因决定(其中的)一些价值观。
Glenn Slayden '18年

4

不是操作系统,而是编译器。您也可以修改行为-参见本文底部。

Microsoft Visual Studio生成(在调试模式下)二进制文件,该二进制文件用0xCC预填充堆栈内存。它还在每个堆栈帧之间插入一个空格,以检测缓冲区溢出。这是一个非常简单的示例(在实践中,Visual Studio会发现此问题并发出警告):

...
   bool error; // uninitialised value
   if(something)
   {
      error = true;
   }
   return error;

如果Visual Studio没有将变量预先初始化为已知值,则可能很难发现该错误。使用预初始化的变量(或更确切地说,预初始化的堆栈内存),该问题在每次运行时均可重现。

但是,有一个小问题。Visual Studio使用的值是TRUE-除0以外的任何值都是。实际上,当您在发布模式下运行代码时,可能会将统一变量分配给恰好包含0的堆栈内存,这意味着您可能会遇到一个统一变量错误,该错误仅在发布模式下才会表现出来。

这让我很烦,所以我编写了一个脚本来通过直接编辑二进制文件来修改预填充值,从而使我能够找到未初始化的变量问题,这些问题仅在堆栈包含零时才会出现。该脚本仅修改堆栈预填充;尽管应该可以,但我从未尝试过堆预填充。可能不涉及编辑运行时DLL。


1
在使用诸如GCC之类的值进行初始化之前,VS是否不会发出警告?
斯特拉格

3
是的,但并非总是如此,因为它取决于静态分析。因此,很容易将其与指针算法混淆。
Airsource Ltd

3
“不是操作系统,而是编译器。” 实际上,它不是编译器,而是运行时库。
Adrian McCarthy

调试时,Visual Studio调试器将显示bool的值(如果不是0或1则显示true,如true(204))。因此,如果您跟踪代码,则很容易看到这种错误。
Phil1970年

4

这特定于所使用的编译器吗?

实际上,它几乎始终是运行时库的功能(例如C运行时库)。运行时通常与编译器密切相关,但是可以交换一些组合。

我相信在Windows上,调试堆(HeapAlloc等)也使用特殊的填充模式,这些填充模式不同于调试C运行时库中的malloc和free实现。因此它也可能是OS的功能,但是在大多数情况下,它只是语言运行时库。

就此而言,malloc / new和free / delete是否以相同的方式工作?

new和delete的内存管理部分通常是通过malloc和free实现的,因此,new和delete分配的内存通常具有相同的功能。

它是平台特定的吗?

详细信息是特定于运行时的。通常选择所使用的实际值,以使其在查看十六进制转储时不仅看起来异常明显,而且还具有某些可利用处理器功能的特性。例如,经常使用奇数,因为它们可能会导致对齐错误。使用大值(而不是0),因为如果循环到未初始化的计数器,它们会导致意外的延迟。在x86上,0xCC是一条int 3指令,因此,如果您执行未初始化的内存,则会被捕获。

它会在其他操作系统(例如Linux或VxWorks)上发生吗?

它主要取决于您使用的运行时库。

您能提供一些有关初始化有用的实际例子吗?

我在上面列出了一些。通常,选择这些值可以增加如果使用无效的内存部分执行某些操作时发生异常事件的可能性:长时间延迟,陷阱,对齐错误等。堆管理器有时还使用特殊的填充值来分配分配之间的间隙。如果这些模式发生了变化,它就知道某个地方有一个错误的写操作(例如缓冲区溢出)。

我记得读过一些东西(也许在代码完成2中),在分配内存时将内存初始化为已知模式是很好的,某些模式会触发Win32中的中断,这将导致调试器中显示异常。

这有多便携?

编写Solid Code(可能还有Code Complete)讨论了选择填充模式时要考虑的事项。我在这里提到了其中一些,Wikipedia上有关Magic Number(编程)的文章也对此进行了总结。有些技巧取决于您所使用的处理器的具体情况(例如,是否需要对齐的读写操作以及将哪些值映射到将捕获的指令)。其他技巧,例如使用较大的值和在内存转储中脱颖而出的异常值,则更容易移植。



2

“为什么”的明显原因是假设您有一个类似这样的类:

class Foo
{
public:
    void SomeFunction()
    {
        cout << _obj->value << endl;
    }

private:
    SomeObject *_obj;
}

然后实例化一个a Foo并调用SomeFunction,它将导致尝试读取访问冲突0xCDCDCDCD。这意味着您忘记了初始化。那就是“为什么”。如果不是,则指针可能已经与其他一些内存对齐,并且调试起来会更加困难。这只是让您知道访问冲突的原因。请注意,这种情况非常简单,但是在较大的类中,很容易犯该错误。

AFAIK,这仅在调试模式下(而不是发行版)在Visual Studio编译器上有效


您的解释没有遵循,因为您还会遇到尝试读取的访问冲突0x00000000,这将同样有用(或更多,作为错误的地址)。正如我在此页面上的另一条评论中所指出的那样,0xCD(和0xCC)的真正原因是它们是可解释的x86操作码,它们触发了软件中断,并且允许在一种特定且罕见的错误类型中正常恢复到调试器中。即CPU错误地尝试在非代码区域中执行字节时。如您所述,除了此功能用途之外,填充值还只是建议性提示。
Glenn Slayden

2

可以很容易地看到,内存已从其初始起始值更改了,通常是在调试过程中更改的,但有时对于发行代码也有所更改,因为您可以在运行过程中将调试器附加到进程中。

这也不只是内存,许多调试器会在进程启动时将寄存器内容设置为哨兵值(某些版本的AIX会将某些寄存器设置得有些0xdeadbeef幽默)。


1

IBM XLC编译器具有“ initauto”选项,该选项将为自动变量分配您指定的值。我在调试版本中使用了以下内容:

-Wc,'initauto(deadbeef,word)'

如果查看未初始化变量的存储,它将设置为0xdeadbeef

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.