为什么我得到_CrtIsValidHeapPointer(block)和/或is_block_type_valid(header-> _block_use)断言? [英] Why do I get _CrtIsValidHeapPointer(block) and/or is_block_type_valid(header->_block_use) assertions?

查看:90
本文介绍了为什么我得到_CrtIsValidHeapPointer(block)和/或is_block_type_valid(header-> _block_use)断言?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我在调试模式下使用VisualStudio编译的程序运行时,有时会得到

调试断言失败!表达式: _CrtIsValidHeapPointer(block)

调试断言失败!表达式: is_block_type_valid(header-> _block_use)

(或两个之后)断言.

是什么意思?我如何找到并解决此类问题的根源?

解决方案

这些断言表明,应该释放的指针无效(或不再有效)( _CrtIsValidHeapPointer -assertion)或在程序运行期间某个时刻堆已损坏( is_block_type_valid(header-> _block_use) -assertion又名 _Block_Type_Is_Valid(pHead-> nBlockUse) -assertion在早期版本中.)

从堆中获取内存时,函数 malloc / free 不直接与操作系统通信,而是与内存管理器(通常由相应的内存管理器)通信C运行时.VisualStudio/Windows SDK为调试构建提供了一个特殊的堆内存管理器,该管理器在运行时执行其他完整性检查.

_CrtIsValidHeapPointer 只是一种启发式方法,但是在无效指针的情况下,此函数可能会报告问题.

1.何时启动 _CrtIsValidHeapPointer 断言?

有一些最常见的情况:

A.指针从堆开始并不会指向内存:

  char * mem =不在堆上!";免费(内存); 

这里的文字没有存储在堆上,因此不能/不应该被释放.

B.指针的值不是 malloc / calloc :

返回的原始地址

  unsigned char * mem =(unsigned char *)malloc(100);mem ++;免费(内存);//mem地址错误! 

由于递增后 mem 的值不再对齐64字节,因此健全性检查可以很容易地看出它不能成为堆指针!

稍微复杂一点,但并非异常的C ++示例(不匹配 new [] delete ):

 结构A {int a = 0;〜A(){//析构函数并不简单!std :: cout<<<<"\ n";}};* mem =新的A [10];删除内存; 

当调用 new A [n] 时,实际上 sizeof(size_t)+ n * sizeof(A)个字节的内存是通过 malloc (当类 A 的析构函数不是平凡的时),数组中的元素数量保存在分配的内存的开头,而返回的指针 mem 则不到 malloc 返回的原始地址,但是到地址+偏移量( sizeof(size_t)).但是, delete 对该偏移量一无所知,并尝试删除地址错误的指针( delete [] 会做正确的事情).

C.双重免费:

  unsigned char * mem =(unsigned char *)malloc(10);免费(内存);免费(内存);#指针已被释放 

D.来自另一个运行时/内存管理器的指针

Windows程序具有一次使用多个运行时的能力:每个使用的dll都可能具有其自己的运行时/内存管理器/堆,因为它是静态链接的,或者因为它们具有不同的版本.因此,在一个dll中分配的内存在另一个使用不同堆的dll中释放时可能会失败(请参见例如 SO-question或此 SO-问题).

2.何时断言 is_block_type_valid(header-> _block_use)断言?

在上述情况A.和B.中,还会触发 is_block_type_valid(header-> _block_use).断言 _CrtIsValidHeapPointer 后, free 函数(更精确的 free_dbg_nolock )将在块标题(供用户使用的特殊数据结构)中查找信息.调试堆,稍后会提供有关它的更多信息),并检查块类型是否有效.但是,由于指针完全是伪造的,因此在内存中预期 nBlockUse 的位置是一些随机值.

但是,在某些情况下,如果在没有先前 _CrtIsValidHeapPointer 断言的情况下触发 is_block_type_valid(header-> _block_use),就会发生这种情况.

A. _CrtIsValidHeapPointer 未检测到无效的指针

这里是一个例子:

  unsigned char * mem =(unsigned char *)malloc(100);mem + = 64;免费(内存); 

由于debug-heap用 0xCD 填充了分配的内存,因此我们可以确保访问 nBlockUse 会产生错误的类型,从而导致上述断言.

>

B.堆的破坏

在大多数情况下,当 is_block_type_valid(header-> _block_use)在没有 _CrtIsValidHeapPointer 的情况下触发时,这意味着堆由于某些超出范围的原因而被破坏写.

因此,如果我们精致"的话,(并且不要覆盖无人之地"-稍后再介绍):

  unsigned char * mem =(unsigned char *)malloc(100);*(mem-17)= 64;//击败_block_use.免费(内存); 

仅导致 is_block_type_valid(header-> _block_use).


在上述所有情况下,都可以通过跟踪内存分配来找到潜在的问题,但是了解更多有关debug-heap的结构会很有帮助.

例如,可以找到有关调试堆的概述.在文档中,也可以在相应的Windows工具包中找到该实现的所有详细信息(例如 C:\ Program Files(x86)\ Windows Kits \ 10 \ Source \ 10.0.16299.0 \ ucrt \ heap \ debug_heap.cpp ).

简而言之:当在调试堆上分配内存时,会分配比所需更多的内存,因此会使用其他结构,例如无人区".附加信息,例如 _block_use ,可以存储在真实"广告旁边.记忆.实际的内存布局为:

  ------------------------------------------------------------------------|街区的标头+没有人的土地|真实"记忆|没有人的土地|----------------------------------------------------------------------|32个字节+ 4个字节|?字节|4个字节|------------------------------------------------------------------------ 

无人区"中的每个字节在结尾处和开头处都将其设置为特殊值( 0xFD ),因此一旦覆盖,我们就可以注册越界写访问权限(只要它们最多相距4个字节)).

例如,在 new [] - delete -mismatch的情况下,我们可以在指针之前分析内存,以查看这是否不是人的土地(此处作为代码,但通常在调试器中完成):

 <代码>* mem =新的A [10];...//代替//删除内存;//调查内存:unsigned char * ch = reinterpret_cast< unsigned char *>(mem);对于(int i = 0; i< 16; i ++){std :: cout<<(int)(*(ch-i))<<"" ;;} 

我们得到:

  0 0 0 0 0 0 0 0 0 10 253 253 253 253 253 0 0 52 

即前8个字节用于元素的数量(10),而不是我们看到的无人区".( 0xFD = 253 ),然后是其他信息.很容易看出来,出了什么问题-如果指针正确,则前4个值在 253 .

当Debug-heap释放内存时,它将用特殊的字节值覆盖它: 0xDD ,即 221 .还可以通过设置标志 _CRTDBG_DELAY_FREE_MEM_DF 来限制曾经使用和已释放的内存的重用,因此,不仅在 free 调用之后,而且在整个运行过程中,内存都保持标记状态该程序.因此,当我们第二次尝试释放相同的指针时,debug-heap可以看到,内存已经被释放一次并触发断言.

因此,通过分析指针周围的值,也很容易看出问题是双重释放的:

  unsigned char * mem =(unsigned char *)malloc(10);免费(内存);对于(int i = 0; i< 16; i ++){printf(%d",(int)(*(mem-i)));}免费(内存);//第二次免费 

打印

  221 221 221 221 221 221 221 221 221 221 221 221 221 221 221 221 221 

内存,即内存已被释放一次.

关于堆损坏的检测:

无人区的目的是检测超出范围的写入,但这仅适用于在任一方向上偏移4个字节的情况,例如:

  unsigned char * mem =(unsigned char *)malloc(100);*(mem-1)= 64;//破坏无人区免费(内存); 

导致

  HEAP CORRUPTION DETECTED:在正常块(#13266)之前的0x0000025C6CC21050.CRT检测到应用程序在启动堆缓冲区之前已写入内存. 

查找堆损坏的一种好方法是使用 _CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF) ASSERT(_CrtCheckMemory()); (请参见SO-post ).但是,这在某种程度上是间接的-如 SO-post ( gflags 需要大约30倍以上的内存,而慢大约10倍,这是很正常的.


顺便说一句,随着时间的推移, _CrtMemBlockHeader 的定义发生了变化,并且不再显示在 struct _CrtMemBlockHeader{_CrtMemBlockHeader * _block_header_next;_CrtMemBlockHeader * _block_header_prev;char const * _file_name;int _line_number;int _block_use;size_t _data_size;长_request_number;无符号字符_gap [no_mans_land_size];//其次是://未签名的char _data [_data_size];//无符号字符_another_gap [no_mans_land_size];};

When I run my with VisualStudio compiled programs in debug-mode, sometimes I get

Debug assertion failed! Expression: _CrtIsValidHeapPointer(block)

or

Debug assertion failed! Expression: is_block_type_valid(header->_block_use)

(or both after each other) assertions.

What does it mean? How can I find and fix the origin of such problems?

解决方案

These assertions show that either the pointer, which should be freed is not (or no longer) valid (_CrtIsValidHeapPointer-assertion) or that the heap was corrupted at some point during the run of the program (is_block_type_valid(header->_block_use)-assertion aka _Block_Type_Is_Valid (pHead->nBlockUse)-assertion in earlier versions).

When acquiring memory from the heap, functions malloc/free don't communicate directly with the OS, but with a memory manager, which is usually provided by the corresponding C-runtime. VisualStudio/Windows SDK provide a special heap-memory manager for debug-builds, which performs additional sanity checks during the run time.

_CrtIsValidHeapPointer is just a heuristic, but there are enough cases of invalid pointers, for which this function can report a problem.

1. When does _CrtIsValidHeapPointer-assertion fire?

There are some of the most usual scenarios:

A. Pointer doesn't point to a memory from the heap to begin with:

char *mem = "not on the heap!";
free(mem); 

here the literal isn't stored on the heap and thus can/should not be freed.

B. The value of the pointer isn't the original address returned by malloc/calloc:

unsigned char *mem = (unsigned char*)malloc(100);
mem++;
free(mem); // mem has wrong address!

As value of mem is no longer 64byte aligned after the increment, the sanity check can easily see that it cannot be a heap-pointer!

A slightly more complex, but not unusual C++-example (mismatch new[] and delete):

struct A {
    int a = 0;
    ~A() {// destructor is not trivial!
         std::cout << a << "\n";
    }
};
A *mem = new A[10];
delete mem;

When new A[n] is called, actually sizeof(size_t)+n*sizeof(A)bytes memory are allocated via malloc (when the destructor of the class A is not trivial), the number of elements in array is saved at the beginning of the allocated memory and the returned pointer mem points not to the original address returned by malloc, but to address+offset (sizeof(size_t)). However, delete knows nothing about this offset and tries to delete the pointer with wrong address (delete [] would do the right thing).

C. double-free:

unsigned char *mem = (unsigned char*)malloc(10);
free(mem);
free(mem);  # the pointer is already freed

D. pointer from another runtime/memory manager

Windows programs have the ability to use multiple runtimes at once: every used dll could potentially have its own runtime/memory manager/heap, because it was linked statically or because they have different versions. Thus, a memory allocated in one dll, could fail when freed in another dll, which uses a different heap (see for example this SO-question or this SO-question).

2. When does is_block_type_valid(header->_block_use)-assertion fire?

In the above cases A. and B., in addition also is_block_type_valid(header->_block_use) will fire. After _CrtIsValidHeapPointer-assertion, the free-function (more precise free_dbg_nolock) looks for info in the block-header (a special data structure used by debug-heap, more information about it later on) and checks that the block type is valid. However, because the pointer is completely bogus, the place in the memory, where nBlockUse is expected to be, is some random value.

However, there are some scenarios, when is_block_type_valid(header->_block_use) fires without previous _CrtIsValidHeapPointer-assertion.

A. _CrtIsValidHeapPointer doesn't detect invalid pointer

Here is an example:

unsigned char *mem = (unsigned char*)malloc(100);
mem+=64;
free(mem);

Because debug-heap fills the allocated memory with 0xCD, we can be sure that accessing nBlockUse will yield a wrong type, thus leading to the above assertion.

B. Corruption of the heap

Most of the time, when is_block_type_valid(header->_block_use) fires without _CrtIsValidHeapPointer it means, that the heap was corrupted due to some out-of-range writes.

So if we "delicate" (and don't overwrite "no man's land"-more on that later):

unsigned char *mem = (unsigned char*)malloc(100);
*(mem-17)=64; // thrashes _block_use.
free(mem);

leads only to is_block_type_valid(header->_block_use).


In all above cases, it is possible to find the underlying issue by following memory allocations, but knowing more about the structure of debug-heap helps a lot.

An overview about debug-heap can be found e.g. in documentation, alternatively all details of the implementation can be found in the corresponding Windows Kit,(e.g. C:\Program Files (x86)\Windows Kits\10\Source\10.0.16299.0\ucrt\heap\debug_heap.cpp).

In a nutshell: When a memory is allocated on a debug heap, more memory than needed is allocated, so additional structures such as "no man's land" and additional info, such as _block_use, can be stored next to the "real" memory. The actual memory layout is:

------------------------------------------------------------------------
| header of the block + no man's land |  "real" memory | no man's land |
----------------------------------------------------------------------
|    32 bytes         +      4bytes   |     ? bytes    |     4 bytes   |
------------------------------------------------------------------------

Every byte in "no man's land" at the end and at the beginning are set to a special value (0xFD), so once it is overwritten we can register out-of-bounds write access (as long as they are at most 4 bytes off).

For example in the case of new[]-delete-mismatch we can analyze memory before the pointer, to see whether this is no man's land or not (here as code, but normally done in debugger):


A *mem = new A[10];
...
// instead of
//delete mem;
// investigate memory:
unsigned char* ch = reinterpret_cast<unsigned char*>(mem);
for (int i = 0; i < 16; i++) {
    std::cout << (int)(*(ch - i)) << " ";
}

we get:

0 0 0 0 0 0 0 0 10 253 253 253 253 0 0 52

i.e. the first 8 bytes are used for the number of elements (10), than we see "no man's land" (0xFD=253) and then other information. It is easy to see, what is going wrong - if the pointer where correct, the first 4 values where 253.

When Debug-heap frees memory it overwrites it with a special byte value: 0xDD, i.e. 221. One also can restrict the reuse of once used and freed memory by setting flag _CRTDBG_DELAY_FREE_MEM_DF, thus the memory stays marked not only directly after the free-call, but during the whole run of the program. So when we try to free the same pointer a second time, debug-heap can see, taht the memory was already freed once and fire the assertion.

Thus, it is also easy to see, that the problem is a double-free, by analyzing the values around pointer:

unsigned char *mem = (unsigned char*)malloc(10);
free(mem);
for (int i = 0; i < 16; i++) {
    printf("%d ", (int)(*(mem - i)));
}
free(mem); //second free

prints

221 221 221 221 221 221 221 221 221 221 221 221 221 221 221 221

the memory, i.e. the memory was already freed once.

On the detection of heap-corruption:

The purpose of no-man's land is to detect out-of-range writes, this however works only for being off for 4 bytes in either direction, e.g.:

unsigned char *mem = (unsigned char*)malloc(100);
*(mem-1)=64; // thrashes no-man's land
free(mem);

leads to

HEAP CORRUPTION DETECTED: before Normal block (#13266) at 0x0000025C6CC21050.
CRT detected that the application wrote to memory before start of heap buffer.

A good way to find heap corruption is to use _CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF) or ASSERT(_CrtCheckMemory());(see this SO-post). However, this is somewhat indirect - a more direct way it to use gflags as explained in this SO-post (it is not unusual that gflags needs about 30 times more memory and is about 10 times slower).


Btw, the definition of _CrtMemBlockHeader changed over the time and no longer the one shown in online-help, but:

struct _CrtMemBlockHeader
{
    _CrtMemBlockHeader* _block_header_next;
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;
    
    int                 _block_use;
    size_t              _data_size;
    
    long                _request_number;
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
};

这篇关于为什么我得到_CrtIsValidHeapPointer(block)和/或is_block_type_valid(header-&gt; _block_use)断言?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆