首页
社区
课程
招聘
[原创]Windows 之 CRT的检测内存泄露
发表于: 2023-7-15 15:42 16639

[原创]Windows 之 CRT的检测内存泄露

2023-7-15 15:42
16639

使用方法非常简单,首先定义宏_CRTDBG_MAP_ALLOC,然后包含头文件crtdbg.h,最后在main函数结尾调用_CrtDumpMemoryLeaks统计内存申请和释放的情况。相关例子如下,编译的时候需要在Debug模式:

运行结果如下:

在安装Visual Studio之后,Windows CRT的源码已经被存放在C:\Program Files (x86)\Windows Kits\10\Source\,这个目录下面有多个sdk的版本,我选择的是19041

在C++编程语言中,内存申请对应的关键字是newmalloc,其实new最后调用的也是malloc函数,对应源代码文件是debug_heap.cpp。在包含相关头文件之后,malloc函数的调用栈为:malloc -> _malloc_dbg -> heap_alloc_dbg -> heap_alloc_dbg_internal。heap_alloc_dbg_internal函数分析如下:

_pfnAllocHook有一个默认的回调函数,也允许程序员自己定义,回调函数原型如下:

设置回调函数的接口为_CrtSetAllocHook.
3. 调用Windows API分配内存,不过需要多分配一些冗余内存,记录一些信息,用于管理malloc分配的内存。
管理的数据结构如下:

结构中成员_gap填充了no_mans_land_size(4)个0xFD,在释放内存时检测写内存时是否出现溢出(上溢)。该结构后续的内容是malloc返回的内存,内存中被填充了0xCD。最后内存_another_gap也是填充了no_mans_land_size(4)个0xFD,在释放内存时检测写内存时是否出现溢出(下溢)。

在C++编程语言中,内存扩容的关键字为realloc,对应的源文件是realloc.cpp,realloc函数的调用栈为:realloc -> _realloc_dbg -> realloc_dbg_nolock。该函数的函数原型如下:

在C++编程语言中,内存释放对应的关键字是deletefree,delete操作符最后调用到free函数,对应的源文件是debug_heap.cpp
free函数的调用栈为:free -> _free_dbg -> free_dbg_nolock,_free_dbg函数会获取临界区然后调用free_dbg_nolock。free_dbg_nolock函数分析过程如下:

调用_CrtDumpMemoryLeaks进行内存统计,主要是两个函数:_CrtMemCheckpoint(统计)和_CrtMemDumpAllObjectsSince(显示)

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
 
int main()
{
    std::cout << "Hello World!\n";
    int* x = (int*)malloc(sizeof(int));
    *x = 7;
    printf("%d\n", *x);
    x = (int*)calloc(3, sizeof(int));
    x[0] = 7;
    x[1] = 77;
    x[2] = 777;
 
    printf("%d %d %d\n", x[0], x[1], x[2]);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
    _CrtDumpMemoryLeaks();
}
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
 
int main()
{
    std::cout << "Hello World!\n";
    int* x = (int*)malloc(sizeof(int));
    *x = 7;
    printf("%d\n", *x);
    x = (int*)calloc(3, sizeof(int));
    x[0] = 7;
    x[1] = 77;
    x[2] = 777;
 
    printf("%d %d %d\n", x[0], x[1], x[2]);
    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
    _CrtDumpMemoryLeaks();
}
Detected memory leaks!
Dumping objects ->
main.cpp(16) : {163} normal block at 0x000002882AE17740, 12 bytes long.
 Data: <    M       > 07 00 00 00 4D 00 00 00 09 03 00 00
main.cpp(10) : {162} normal block at 0x000002882AE148C0, 4 bytes long.
 Data: <    > 07 00 00 00
Object dump complete.
// main.cpp(x) 表示在main.cpp的x行申请的内存没有被释放
Detected memory leaks!
Dumping objects ->
main.cpp(16) : {163} normal block at 0x000002882AE17740, 12 bytes long.
 Data: <    M       > 07 00 00 00 4D 00 00 00 09 03 00 00
main.cpp(10) : {162} normal block at 0x000002882AE148C0, 4 bytes long.
 Data: <    > 07 00 00 00
Object dump complete.
// main.cpp(x) 表示在main.cpp的x行申请的内存没有被释放
__acrt_lock(__acrt_heap_lock);
 
extern "C" void __cdecl __acrt_lock(_In_ __acrt_lock_id _Lock)
{
    EnterCriticalSection(&__acrt_lock_table[_Lock]);
}
__acrt_lock(__acrt_heap_lock);
 
extern "C" void __cdecl __acrt_lock(_In_ __acrt_lock_id _Lock)
{
    EnterCriticalSection(&__acrt_lock_table[_Lock]);
}
if (_crtBreakAlloc != -1 && request_number == _crtBreakAlloc)
{
    _CrtDbgBreak();
}
 
if (_pfnAllocHook && !_pfnAllocHook(
    _HOOK_ALLOC,
    nullptr,
    size,
    block_use,
    request_number,
    reinterpret_cast<unsigned char const*>(file_name),
    line_number))
{
    if (file_name)
        _RPTN(_CRT_WARN, "Client hook allocation failure at file %hs line %d.\n", file_name, line_number);
    else
        _RPT0(_CRT_WARN, "Client hook allocation failure.\n");
 
    __leave;
}
if (_crtBreakAlloc != -1 && request_number == _crtBreakAlloc)
{
    _CrtDbgBreak();
}
 
if (_pfnAllocHook && !_pfnAllocHook(
    _HOOK_ALLOC,
    nullptr,
    size,
    block_use,
    request_number,
    reinterpret_cast<unsigned char const*>(file_name),
    line_number))
{
    if (file_name)
        _RPTN(_CRT_WARN, "Client hook allocation failure at file %hs line %d.\n", file_name, line_number);
    else
        _RPT0(_CRT_WARN, "Client hook allocation failure.\n");
 
    __leave;
}
typedef int (__cdecl * _CRT_ALLOC_HOOK)(
    int  const allocation_type,
    void*                const data,
    size_t               const size,
    int                  const block_use,
    long                 const request,
    unsigned char const* const file_name,
    int                  const line_number
);
typedef int (__cdecl * _CRT_ALLOC_HOOK)(
    int  const allocation_type,
    void*                const data,
    size_t               const size,
    int                  const block_use,
    long                 const request,
    unsigned char const* const file_name,
    int                  const line_number
);
struct _CrtMemBlockHeader
{
    _CrtMemBlockHeader* _block_header_next;     //
    _CrtMemBlockHeader* _block_header_prev;     // 双向链表,访问该双向链表的全局变量为__acrt_first_block
    char const*         _file_name;             // 调用malloc的文件名
    int                 _line_number;           // 调用malloc的行数
 
    int                 _block_use;             // 内存类型,所有内存类型如下
    /*
     #define _FREE_BLOCK      0     // 内存释放
     #define _NORMAL_BLOCK    1     // 内存申请
     #define _CRT_BLOCK       2     // 标注CRT库申请的内存
     #define _IGNORE_BLOCK    3     // 此类内存不进行管理
     #define _CLIENT_BLOCK    4     // 暂时没找到用法
     #define _MAX_BLOCKS      5     // 暂时没找到用法
     */
    size_t              _data_size;             // malloc分配的大小
    long                _request_number;        // 记录分配内存的序号,每次分配内存自增1
    unsigned char       _gap[no_mans_land_size]; // 标记
 
    // Followed by:
    // unsigned char    _data[_data_size];          // malloc返回的内存
    // unsigned char    _another_gap[no_mans_land_size];    // 标记
};
struct _CrtMemBlockHeader
{
    _CrtMemBlockHeader* _block_header_next;     //
    _CrtMemBlockHeader* _block_header_prev;     // 双向链表,访问该双向链表的全局变量为__acrt_first_block
    char const*         _file_name;             // 调用malloc的文件名
    int                 _line_number;           // 调用malloc的行数
 
    int                 _block_use;             // 内存类型,所有内存类型如下
    /*
     #define _FREE_BLOCK      0     // 内存释放
     #define _NORMAL_BLOCK    1     // 内存申请
     #define _CRT_BLOCK       2     // 标注CRT库申请的内存
     #define _IGNORE_BLOCK    3     // 此类内存不进行管理
     #define _CLIENT_BLOCK    4     // 暂时没找到用法
     #define _MAX_BLOCKS      5     // 暂时没找到用法
     */
    size_t              _data_size;             // malloc分配的大小
    long                _request_number;        // 记录分配内存的序号,每次分配内存自增1
    unsigned char       _gap[no_mans_land_size]; // 标记
 
    // Followed by:
    // unsigned char    _data[_data_size];          // malloc返回的内存
    // unsigned char    _another_gap[no_mans_land_size];    // 标记
};
static void * __cdecl realloc_dbg_nolock(
    void*       const block,
    size_t*     const new_size,
    int         const block_use,
    char const* const file_name,
    int         const line_number,
    bool        const reallocation_is_allowed
    ) throw()
static void * __cdecl realloc_dbg_nolock(
    void*       const block,
    size_t*     const new_size,
    int         const block_use,
    char const* const file_name,
    int         const line_number,
    bool        const reallocation_is_allowed
    ) throw()
if (!block)         // block为nullptr,蜕变为malloc(size)
{
    return _malloc_dbg(*new_size, block_use, file_name, line_number);
}
if (reallocation_is_allowed && *new_size == 0)  // *new_size为0,则蜕变为free(block)
{
    _free_dbg(block, block_use);
    return nullptr;
}
if (!block)         // block为nullptr,蜕变为malloc(size)
{
    return _malloc_dbg(*new_size, block_use, file_name, line_number);
}
if (reallocation_is_allowed && *new_size == 0)  // *new_size为0,则蜕变为free(block)
{
    _free_dbg(block, block_use);
    return nullptr;
}
if (_pfnAllocHook && !_pfnAllocHook(
    _HOOK_REALLOC,
    block,
    *new_size,
    block_use,
    request_number,
    reinterpret_cast<unsigned char const*>(file_name),
    line_number))
{
    if (file_name)
        _RPTN(_CRT_WARN, "Client hook re-allocation failure at file %hs line %d.\n", file_name, line_number);
    else
        _RPT0(_CRT_WARN, "Client hook re-allocation failure.\n");
 
    return nullptr;
}
if (_pfnAllocHook && !_pfnAllocHook(
    _HOOK_REALLOC,
    block,
    *new_size,
    block_use,
    request_number,
    reinterpret_cast<unsigned char const*>(file_name),
    line_number))
{
    if (file_name)
        _RPTN(_CRT_WARN, "Client hook re-allocation failure at file %hs line %d.\n", file_name, line_number);
    else
        _RPT0(_CRT_WARN, "Client hook re-allocation failure.\n");
 
    return nullptr;
}
is_block_an_aligned_allocation(block)       // 检查block是否被_aligned_malloc分配的,若是,则返回nullptr.
_ASSERTE(_CrtIsValidHeapPointer(block));    // 保证block内存属于进程堆
检查 -> 堆的类型是否是_IGNORE_BLOCK
检查 -> block的header的_data_size是否被破坏
检查 -> *new_size 是否过大
// Ensure the new requested size is not too large:
if (*new_size > static_cast<size_t>(_HEAP_MAXREQ - no_mans_land_size - sizeof(_CrtMemBlockHeader)))
{
    errno = ENOMEM;
    return nullptr;
}
is_block_an_aligned_allocation(block)       // 检查block是否被_aligned_malloc分配的,若是,则返回nullptr.

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 4
支持
分享
最新回复 (1)
雪    币: 3535
活跃值: (31016)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-7-15 23:04
1
游客
登录 | 注册 方可回帖
返回
//