【Visual Leak Detector】库的 22 个 API 使用说明

饭宝  金牌会员 | 2023-4-17 10:26:19 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 951|帖子 951|积分 2853

说明

使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记。本篇主要介绍 VLD 库提供的 22 个外部接口。同系列文章目录可见 《内存泄漏检测工具》目录

目录


1. 头文件简介

VLD 2.5.1 安装 完成后,安装目录的 include 文件夹下有两个头文件:vld.h 和 vld_def.h,其中 vld.h 文件会 #include "vld_def.h",因此在实际使用时,项目中要同时添加这两个头文件(或将这两个文件放在编译器的搜索路径中),但只需包含 vld.h 文件。
2. 文件 vld_def.h 简介

这个文件里主要以宏的形式定义了 15 个 VLD 配置项的掩码,这 15 个配置项与 vld.ini 配置文件中的 14 个配置项不是完全对应的,将这些配置项掩码与 vld.h 文件中的接口结合起来用,可以实现在运行过程中对 VLD 的配置进行动态修改。其中 9 个配置项可作为接口 VLDSetOptions 的输入,另外 4 个配置项可作为接口 VLDSetReportOptions 的输入,剩余的 2 个配置项分别是 VLD_OPT_SELF_TEST 与 VLD_OPT_VLDOFF,其中 VLD_OPT_SELF_TEST 只能通过修改 vld.ini 文件中的 SelfTest 进行设置(详见 配置项 SelfTest),VLD_OPT_VLDOFF 只能通过修改 vld.ini 文件中的 VLD 进行设置(详见 配置项 VLD),当 VLD_OPT_VLDOFF 被设置后,所有接口也都会变得不可用。
  1. #define VLD_OPT_AGGREGATE_DUPLICATES    0x0001 //   If set, aggregate duplicate leaks in the leak report.
  2. #define VLD_OPT_MODULE_LIST_INCLUDE     0x0002 //   If set, modules in the module list are included, all others are excluded.
  3. #define VLD_OPT_REPORT_TO_DEBUGGER      0x0004 //   If set, the memory leak report is sent to the debugger.
  4. #define VLD_OPT_REPORT_TO_FILE          0x0008 //   If set, the memory leak report is sent to a file.
  5. #define VLD_OPT_SAFE_STACK_WALK         0x0010 //   If set, the stack is walked using the "safe" method (StackWalk64).
  6. #define VLD_OPT_SELF_TEST               0x0020 //   If set, perform a self-test to verify memory leak self-checking.
  7. #define VLD_OPT_SLOW_DEBUGGER_DUMP      0x0040 //   If set, inserts a slight delay between sending output to the debugger.
  8. #define VLD_OPT_START_DISABLED          0x0080 //   If set, memory leak detection will initially disabled.
  9. #define VLD_OPT_TRACE_INTERNAL_FRAMES   0x0100 //   If set, include useless frames (e.g. internal to VLD) in call stacks.
  10. #define VLD_OPT_UNICODE_REPORT          0x0200 //   If set, the leak report will be encoded UTF-16 instead of ASCII.
  11. #define VLD_OPT_VLDOFF                  0x0400 //   If set, VLD will be completely deactivated. It will not attach to any modules.
  12. #define VLD_OPT_REPORT_TO_STDOUT        0x0800 //   If set, the memory leak report is sent to stdout.
  13. #define VLD_OPT_SKIP_HEAPFREE_LEAKS     0x1000 //   If set, VLD skip HeapFree memory leaks.
  14. #define VLD_OPT_VALIDATE_HEAPFREE       0x2000 //   If set, VLD verifies and reports heap consistency for HeapFree calls.
  15. #define VLD_OPT_SKIP_CRTSTARTUP_LEAKS   0x4000 //   If set, VLD skip crt srtartup memory leaks.
复制代码
3. 文件 vld.h 简介

这个文件里主要声明了 VLD 的 22 个外部接口。文件前面的编译条件 defined _DEBUG || defined VLD_FORCE_ENABLE 表明 VLD 通常只能在 DEBUG 模式下运行,若要在 RELEASE 模式下运行,可以在包含头文件 vld.h 前预先定义 VLD_FORCE_ENABLE 宏。一个有趣的现象是,这个 _DEBUG 宏有时候在 RELEASE 模式下也会被定义,参考文章 神秘的 _DEBUG 宏从何处来?。接口中的一些类型别名定义如下:
  1. typedef int            VLD_BOOL;
  2. typedef unsigned int   VLD_UINT;
  3. typedef size_t         VLD_SIZET;
  4. typedef void*          VLD_HMODULE;
复制代码
3.1 接口 VLDDisable

该函数用于禁用当前线程的内存泄漏检测功能。
  1. // VLDDisable - Disables Visual Leak Detector's memory leak detection at
  2. //   runtime. If memory leak detection is already disabled, then calling this
  3. //   function has no effect.
  4. //
  5. //  Note: In multithreaded programs, this function operates on a per-thread
  6. //    basis. In other words, if you call this function from one thread, then
  7. //    memory leak detection is only disabled for that thread. If memory leak
  8. //    detection is enabled for other threads, then it will remain enabled for
  9. //    those other threads. It was designed to work this way to insulate you,
  10. //    the programmer, from having to ensure thread synchronization when calling
  11. //    VLDEnable() and VLDDisable(). Without this, calling these two functions
  12. //    unsynchronized could result in unpredictable and unintended behavior.
  13. //    But this also means that if you want to disable memory leak detection
  14. //    process-wide, then you need to call this function from every thread in
  15. //    the process.
  16. //
  17. //  Return Value:
  18. //
  19. //    None.
  20. //
  21. __declspec(dllimport) void VLDDisable ();
复制代码
3.2 接口 VLDEnable

该函数用于启用当前线程的内存泄漏检测功能。
  1. // VLDEnable - Enables Visual Leak Detector's memory leak detection at runtime.
  2. //   If memory leak detection is already enabled, which it is by default, then
  3. //   calling this function has no effect.
  4. //
  5. //  Note: In multithreaded programs, this function operates on a per-thread
  6. //    basis. In other words, if you call this function from one thread, then
  7. //    memory leak detection is only enabled for that thread. If memory leak
  8. //    detection is disabled for other threads, then it will remain disabled for
  9. //    those other threads. It was designed to work this way to insulate you,
  10. //    the programmer, from having to ensure thread synchronization when calling
  11. //    VLDEnable() and VLDDisable(). Without this, calling these two functions
  12. //    unsynchronized could result in unpredictable and unintended behavior.
  13. //    But this also means that if you want to enable memory leak detection
  14. //    process-wide, then you need to call this function from every thread in
  15. //    the process.
  16. //
  17. //  Return Value:
  18. //
  19. //    None.
  20. //
  21. __declspec(dllimport) void VLDEnable ();
复制代码
3.3 接口 VLDRestore

该函数用于还原当前线程内存泄漏检测功能的开关状态。
  1. // VLDRestore - Restore Visual Leak Detector's previous state.
  2. //
  3. //  Return Value:
  4. //
  5. //    None.
  6. //
  7. __declspec(dllimport) void VLDRestore ();
复制代码
为了便于理解,下面贴出这一接口对应的源码(详见 vld.cpp 第 2548~2561 行):
  1. void VisualLeakDetector::RestoreLeakDetectionState ()
  2. {
  3.     tls_t *tls;
  4.     if (m_options & VLD_OPT_VLDOFF) {
  5.         // VLD has been turned off.
  6.         return;
  7.     }
  8.     // Restore state memory leak detection for the current thread.
  9.     tls = getTls();
  10.     tls->flags &= ~(VLD_TLS_DISABLED | VLD_TLS_ENABLED);
  11.     tls->flags |= tls->oldFlags & (VLD_TLS_DISABLED | VLD_TLS_ENABLED);
  12. }
复制代码
3.4 接口 VLDGlobalDisable

该函数用于全局禁用 VLD 功能。
  1. // VLDGlobalDisable - Disables Visual Leak Detector's memory leak detection at
  2. //   runtime in all threads. If memory leak detection is already disabled,
  3. //   then calling this function has no effect.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) void VLDGlobalDisable ();
复制代码
3.5 接口 VLDGlobalEnable

该函数用于全局启用 VLD 功能。
  1. // VLDGlobalEnable - Enables Visual Leak Detector's memory leak detection
  2. //   at runtime in all threads. If memory leak detection is already enabled,
  3. //   which it is by default, then calling this function has no effect.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) void VLDGlobalEnable ();
复制代码
3.6 接口 VLDReportLeaks

该函数用于打印整个程序当前执行此函数前的内存泄漏报告。由于在程序关闭后 VLD 会自动输出报告,因此若不需要在程序运行过程中输出报告,就不需使用此函数。
  1. // VLDReportLeaks - Report leaks up to the execution point.
  2. //
  3. //  Return Value:
  4. //
  5. //    None.
  6. //
  7. __declspec(dllimport) VLD_UINT VLDReportLeaks ();
复制代码
3.7 接口 VLDReportThreadLeaks

该函数用于打印指定线程在执行此函数前的内存泄漏报告。
  1. // VLDReportThreadLeaks - Report thread leaks up to the execution point.
  2. //
  3. // threadId: thread Id.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) VLD_UINT VLDReportThreadLeaks (VLD_UINT threadId);
复制代码
3.8 接口 VLDGetLeaksCount

该函数用于获取整个程序当前的内存泄漏数量。
  1. // VLDGetLeaksCount - Return memory leaks count to the execution point.
  2. //
  3. //  Return Value:
  4. //
  5. //    None.
  6. //
  7. __declspec(dllimport) VLD_UINT VLDGetLeaksCount ();
复制代码
3.9 接口 VLDGetThreadLeaksCount

该函数用于获取指定线程当前的内存泄漏数量。
  1. // VLDGetThreadLeaksCount - Return thread memory leaks count to the execution point.
  2. //
  3. // threadId: thread Id.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) VLD_UINT VLDGetThreadLeaksCount (VLD_UINT threadId);
复制代码
3.10 接口 VLDMarkAllLeaksAsReported

该函数用于标记当前的泄漏为已经报告过,后续不再报告。
  1. // VLDMarkAllLeaksAsReported - Mark all leaks as reported.
  2. //
  3. //  Return Value:
  4. //
  5. //    None.
  6. //
  7. __declspec(dllimport) void VLDMarkAllLeaksAsReported ();
复制代码
3.11 接口 VLDMarkThreadLeaksAsReported

该函数用于标记指定线程当前的泄漏为已经报告过,后续不再报告。
  1. // VLDMarkThreadLeaksAsReported - Mark thread leaks as reported.
  2. //
  3. // threadId: thread Id.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) void VLDMarkThreadLeaksAsReported (VLD_UINT threadId);
复制代码
3.12 接口 VLDRefreshModules

该函数用于刷新加载的模块列表,以便针对动态加载的模块进行内存泄漏检查。
  1. // VLDRefreshModules - Look for recently loaded DLLs and patch them if necessary.
  2. //
  3. //  Return Value:
  4. //
  5. //    None.
  6. //
  7. __declspec(dllimport) void VLDRefreshModules();
复制代码
3.13 接口 VLDEnableModule

该函数用于对指定模块启用内存泄漏检测。输入参数为指定模块(dll 或者 exe)的句柄,可以通过调用 Win32 的 API 来获得(常用的有 GetModuleHandleW 函数LoadLibraryA 函数)。
  1. // VLDEnableModule - Enable Memory leak checking on the specified module.
  2. //
  3. // module: module handle.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) void VLDEnableModule(VLD_HMODULE module);
复制代码
例如,若想检测一个已载入当前进程的动态链接库 test.dll 是否存在内存泄漏,可以按以下方式使用该接口(需包含头文件 Windows.h,若为动态加载的模块,还需额外使用 VLDRefreshModules() 刷新内部的模块列表):
  1. // 调用 Win32 API 获得模块句柄
  2. HMODULE h_dll = GetModuleHandleW(L"test.dll");
  3. // 对该模块启用 VLD 功能
  4. VLDEnableModule(h_dll);
  5. // 调用模块中的函数
  6. ...
复制代码
3.14 接口 VLDDisableModule

该函数用于对指定模块禁用内存泄漏检测。与 VLDEnableModule 相对应,输入参数为指定模块(dll 或者 exe)的句柄,可以通过调用 Win32 的API 来获得。
  1. // VLDDisableModule - Disable Memory leak checking on the specified module.
  2. //
  3. // module: module handle.
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) void VLDDisableModule(VLD_HMODULE module);
复制代码
3.15 接口 VLDGetOptions

该函数用于获取当前的配置掩码值(与 VLDSetOptions 相对应),将掩码值结合 VLDSetOptions 的 9 个配置掩码宏可以人工推断出 VLD 当前的配置。
  1. // VLDGetOptions - Return all current options.
  2. //
  3. //  Return Value:
  4. //
  5. //    Mask of current options.
  6. //
  7. __declspec(dllimport) VLD_UINT VLDGetOptions();
复制代码
例如,若先前使用 VLDSetOptions 接口对 VLD 做了以下配置:
  1. VLDSetOptions(VLD_OPT_AGGREGATE_DUPLICATES | VLD_OPT_SKIP_HEAPFREE_LEAKS, 256, 64);
复制代码
则 VLDGetOptions() 的返回值为 4097,计算方式如下:
  1. VLD_OPT_AGGREGATE_DUPLICATES | VLD_OPT_SKIP_HEAPFREE_LEAKS == 0x1001 == 4097
复制代码
若调用此函数前,未使用 VLDSetOptions 接口更改配置,且未修改 vld.ini 配置文件,则该函数的返回值为默认的 16386,即 0x4002。
3.16 接口 VLDGetReportFilename

该函数用于获取生成的泄漏报告文件路径。注意:用于存储文件路径的 wchar_t 数组 filename 需预分配足够大的内存,以防出现意外的情况,MAX_PATH 为 windows 系统下文件路径的最大长度(详见 MAX_PATH,值为 260)。
  1. // VLDGetReportFilename - Return current report filename.
  2. //
  3. // filename: current report filename (max characters - MAX_PATH).
  4. //
  5. //  Return Value:
  6. //
  7. //    None.
  8. //
  9. __declspec(dllimport) void VLDGetReportFilename(wchar_t *filename);
复制代码
3.17 接口 VLDSetOptions

该函数用于设置配置掩码值(由相应的配置掩码宏通过逻辑运算得到)、设置为每个泄漏块数据转储的最大字节数(参考 配置项 MaxDataDump)、设置对每个泄漏块进行堆栈跟踪的最大帧数(参考 配置项 MaxTraceFrames)。
  1. // VLDSetOptions - Update the report options via function call rather than INI file.
  2. //
  3. // option_mask: Only the following flags are checked
  4. // VLD_OPT_AGGREGATE_DUPLICATES
  5. // VLD_OPT_MODULE_LIST_INCLUDE
  6. // VLD_OPT_SAFE_STACK_WALK
  7. // VLD_OPT_SLOW_DEBUGGER_DUMP
  8. // VLD_OPT_TRACE_INTERNAL_FRAMES
  9. // VLD_OPT_START_DISABLED
  10. // VLD_OPT_SKIP_HEAPFREE_LEAKS
  11. // VLD_OPT_VALIDATE_HEAPFREE
  12. //
  13. // maxDataDump: maximum number of user-data bytes to dump for each leaked block.
  14. //
  15. // maxTraceFrames: maximum number of frames per stack trace for each leaked block.
  16. //
  17. //  Return Value:
  18. //
  19. //    None.
  20. //
  21. __declspec(dllimport) void VLDSetOptions(VLD_UINT option_mask, VLD_SIZET maxDataDump, VLD_UINT maxTraceFrames);
复制代码
查看该接口的 源码 可知,第一个参数的合法输入为以下 9 个配置掩码宏之一(上面的接口说明并不全面)、或者它们的逻辑或(|)组合:
  1. // option_mask 的合法输入:以下 9 个配置宏之一
  2. #define VLD_OPT_AGGREGATE_DUPLICATES    0x0001 //   If set, aggregate duplicate leaks in the leak report.
  3. #define VLD_OPT_MODULE_LIST_INCLUDE     0x0002 //   If set, modules in the module list are included, all others are excluded.
  4. #define VLD_OPT_SAFE_STACK_WALK         0x0010 //   If set, the stack is walked using the "safe" method (StackWalk64).
  5. #define VLD_OPT_SLOW_DEBUGGER_DUMP      0x0040 //   If set, inserts a slight delay between sending output to the debugger.
  6. #define VLD_OPT_START_DISABLED          0x0080 //   If set, memory leak detection will initially disabled.
  7. #define VLD_OPT_TRACE_INTERNAL_FRAMES   0x0100 //   If set, include useless frames (e.g. internal to VLD) in call stacks.
  8. #define VLD_OPT_SKIP_HEAPFREE_LEAKS     0x1000 //   If set, VLD skip HeapFree memory leaks.
  9. #define VLD_OPT_VALIDATE_HEAPFREE       0x2000 //   If set, VLD verifies and reports heap consistency for HeapFree calls.
  10. #define VLD_OPT_SKIP_CRTSTARTUP_LEAKS   0x4000 //   If set, VLD skip crt srtartup memory leaks.
复制代码
例如,我想同时开启 VLD_OPT_AGGREGATE_DUPLICATES 与 VLD_OPT_SKIP_HEAPFREE_LEAKS,并设置 maxDataDump=256,maxTraceFrames=64,可以用以下几种方式传参,它们的效果是一样的:
  1. VLDSetOptions(VLD_OPT_AGGREGATE_DUPLICATES | VLD_OPT_SKIP_HEAPFREE_LEAKS, 256, 64);
  2. VLDSetOptions(0x1001, 256, 64);
  3. VLDSetOptions(4097, 256, 64);
复制代码
需要注意的是,每次使用此函数时,内部都会先将这 9 个配置置零,然后使用新输入的配置,因此,若输入不合法,就会丢失对应 bit 处上一次的配置状态,具体细节可查看源码。
3.18 接口 VLDSetModulesList

该函数用于设置要包含或者排除泄漏检测的模块列表。阅读 源码 可知,第一个参数 modules 的最大有效长度为 512。
  1. // VLDSetModulesList - Set list of modules included/excluded in leak detection
  2. // depending on parameter "includeModules".
  3. //
  4. // modules: list of modules to be forcefully included/excluded in leak detection.
  5. //
  6. // includeModules: include or exclude that modules.
  7. //
  8. //  Return Value:
  9. //
  10. //    None.
  11. //
  12. __declspec(dllimport) void VLDSetModulesList(const wchar_t *modules, VLD_BOOL includeModules);
复制代码
源码(详见 vld.cpp 第 867~882 行)中判断某个模块 modulename 是否在所给列表 m_forcedModuleList 中的核心代码如下,由于使用的是 wcsstr 函数 进行字符串比较,因此模块名之间可用任意字符或字符串进行分隔,且不区分大小写,这与配置文件 vld.ini 中的 ForceIncludeModules 配置项一样(详见 配置项 ForceIncludeModules)。另外,从源码中也可以看出,当第二个参数为 false 时,将执行后面的判断语句(条件为 wcsstr(m_forcedModuleList, modulename) != NULL),意味着不在列表中的模块都会被开启内存检测,这可能会导致很多误判,被大量的伪泄漏刷屏,因此需慎传 false。
  1. // This module does not import VLD. This means that none of the module's
  2. // sources #included vld.h.
  3. if ((m_options & VLD_OPT_MODULE_LIST_INCLUDE) != 0)
  4. {
  5.     if (wcsstr(m_forcedModuleList, modulename) == NULL) {
  6.         // Exclude this module from leak detection.
  7.         moduleFlags |= VLD_MODULE_EXCLUDED;
  8.     }
  9. }
  10. else
  11. {
  12.     if (wcsstr(m_forcedModuleList, modulename) != NULL) {
  13.         // Exclude this module from leak detection.
  14.         moduleFlags |= VLD_MODULE_EXCLUDED;
  15.     }
  16. }
复制代码
QT 中实际使用时(测试环境:QT 5.9.2MSVC 2015 32bitDebug 模式,VLD 版本为 2.5.1),发现这个函数好像只能修改内部的 m_forcedModuleList 列表,并没有按照第二个参数 includeModules 立马更新指定模块的检测状态,即使接着使用 VLDRefreshModules() 函数进行刷新,也没有实现按新列表进行检测的效果。因此,要实现对某个 DLL 库的动态检测控制,最好还是使用 VLDEnableModule 、VLDDisableModule、VLDRefreshModules 这三个接口。若没有动态检测控制的需求,则可以修改配置文件 vld.ini 中的 ForceIncludeModules 配置项(详见 配置项 ForceIncludeModules),修改配置文件是没有这个问题的。
3.19 接口 VLDGetModulesList

该函数用于获取检测中的模块列表。获取的模块列表是由 VLDSetModulesList 设置的,也可能是由 vld.ini 文件中的 ForceIncludeModules 设置的,用于存储模块列表的 wchar_t 数组 modules 需预分配足够大的内存(通常为 512),以防出现意外的情况。第二个参数 size 为想要获取的字符串长度,一般设置为第一个参数 modules 的长度(通常为 512)。
  1. // VLDGetModulesList - Return current list of included/excluded modules
  2. // depending on flag VLD_OPT_TRACE_INTERNAL_FRAMES.
  3. //
  4. // modules: destination string for list of included/excluded modules (maximum length 512 characters).
  5. //
  6. // size: maximum string size.
  7. //
  8. //  Return Value:
  9. //
  10. //    VLD_BOOL: TRUE if include modules, otherwise FALSE.
  11. //
  12. __declspec(dllimport) VLD_BOOL VLDGetModulesList(wchar_t *modules, VLD_UINT size);
复制代码
3.20 接口 VLDSetReportOptions

该函数用于设置泄漏报告输出方式。
  1. // VLDSetReportOptions - Update the report options via function call rather than INI file.
  2. //
  3. // Only the following flags are checked
  4. // VLD_OPT_REPORT_TO_DEBUGGER
  5. // VLD_OPT_REPORT_TO_FILE
  6. // VLD_OPT_REPORT_TO_STDOUT
  7. // VLD_OPT_UNICODE_REPORT
  8. //
  9. // filename is optional and can be NULL.
  10. //
  11. //  Return Value:
  12. //
  13. //    None.
  14. //
  15. __declspec(dllimport) void VLDSetReportOptions(VLD_UINT option_mask, const wchar_t *filename);
复制代码
第一个参数的合法输入为以下 4 个配置掩码宏之一、或者它们的逻辑或(|)组合,第二个参数可以为 NULL 但不能省略。
  1. // option_mask 的合法输入:以下 4 个配置宏之一
  2. #define VLD_OPT_REPORT_TO_DEBUGGER      0x0004 //   If set, the memory leak report is sent to the debugger.
  3. #define VLD_OPT_REPORT_TO_FILE          0x0008 //   If set, the memory leak report is sent to a file.
  4. #define VLD_OPT_REPORT_TO_STDOUT        0x0800 //   If set, the memory leak report is sent to stdout.
  5. #define VLD_OPT_UNICODE_REPORT          0x0200 //   If set, the leak report will be encoded UTF-16 instead of ASCII.
复制代码
需要注意的是,如果设置了 VLD_OPT_UNICODE_REPORT,即使没设置 VLD_OPT_REPORT_TO_FILE,泄漏报告也会输出到文件,这与 vld.ini 文件中的 配置项 ReportEncoding 效果一样。阅读 源码 可知,若设置了 VLD_OPT_REPORT_TO_STDOUT,程序会调用 fputs(messagea, stdout) 输出泄漏报告至标准输出窗,若设置了 VLD_OPT_REPORT_TO_DEBUGGER,程序会调用 OutputDebugStringW(messagew) 输出泄露报告至调试窗(详见 OutputDebugStringW 函数)。
3.21 接口 VLDSetReportHook

该函数用于自定义内存泄漏报告回调函数,与 CrtSetReportHook 函数 很相似。
  1. // VLDSetReportHook - Installs or uninstalls a client-defined reporting function by hooking it
  2. //  into the C run-time debug reporting process (debug version only).
  3. //
  4. // mode: The action to take: VLD_RPTHOOK_INSTALL or VLD_RPTHOOK_REMOVE.
  5. //
  6. // pfnNewHook: Report hook to install or remove.
  7. //
  8. //  Return Value:
  9. //
  10. //    int: 0 if success.
  11. //
  12. __declspec(dllimport) int VLDSetReportHook(int mode,  VLD_REPORT_HOOK pfnNewHook);
复制代码
其中的 VLD_REPORT_HOOK 在 vld_def.h 中有给出别名声明,自定义的报告函数必须是以下类型的函数。
  1. typedef int (__cdecl * VLD_REPORT_HOOK)(int reportType, wchar_t *message, int *returnValue);
复制代码
其中的 VLD_RPTHOOK_INSTALL 与 VLD_RPTHOOK_REMOVE 也在 vld_def.h 中有给出宏定义,安装自定义报告函数时传 VLD_RPTHOOK_INSTALL,卸载时传 VLD_RPTHOOK_INSTALL,若设置成功,VLDSetReportHook 返回 0,否则返回 -1。
  1. #define VLD_RPTHOOK_INSTALL  0
  2. #define VLD_RPTHOOK_REMOVE   1
复制代码
查看 API 源码(详见 utility.cpp 第 674~774 行),可以得到以下几点信息:

  • 自定义报告函数的返回值为 true(非零)时,其后不会调用默认的报告输出函数,返回值为 false(零)时,其后仍会调用默认的报告输出函数。
  • VLD 传递给自定义报告函数的第一个参数 reportType 值恒为 0,可以不使用这个值;
  • 第二个参数是每次检测到泄漏时默认的输出信息,可以对此做一个字符串过滤,只输出想要的信息(可使用 OutputDebugStringW 或 fputs 进行打印,其他输出函数可能无法正常使用,因为 VLD 源码未包含对应头文件);
  • 第三个参数是自定义报告函数传递给 VLD 的值,当 *returnValue = 1 时,会触发 __debugbreak() 函数(详见 __debugbreak 函数),将在代码中引起断点,并在其中提示用户运行调试程序,当 *returnValue != 1 时,无任何影响。
例如,我定义了以下自定义报告函数:
  1. int MyReportHook(int, wchar_t* message, int* returnValue)
  2. {
  3.     // 不让VLD触发__debugbreak()函数
  4.     *returnValue = 0;
  5.     // 使用默认的 Block 信息
  6.     if (wcsstr(message, L"Block") != NULL) {
  7.         return false;
  8.     }
  9.     // 使用自定义的退出信息
  10.     if (wcsstr(message, L"Visual Leak Detector is now exiting") != NULL) {
  11.         wchar_t wcs[512]{};
  12.         wcscpy (wcs, L"This is a custom output: ");
  13.         wcscat (wcs, message);
  14.         OutputDebugStringW(wcs);
  15.         return true;
  16.     }
  17.     // 忽略其他信息
  18.     return true;
  19. }
复制代码
然后在 main 主函数的开头添加上:
  1. VLDSetReportHook(VLD_RPTHOOK_INSTALL, MyReportHook);
复制代码
DEBUG 模式下运行程序,程序结束后得到以下结果:
  1. Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
  2. Visual Leak Detector Version 2.5.1 installed.
  3. ---------- Block 2 at 0x015D2418: 40 bytes ----------
  4. ---------- Block 1 at 0x015D27E0: 40 bytes ----------
  5. This is a custom output: Visual Leak Detector is now exiting.
复制代码
未使用自定义报告函数时(或者紧接着使用 VLD_RPTHOOK_REMOVE 卸载 MyReportHook)的输出为:
  1. Visual Leak Detector read settings from: D:\Program Files (x86)\Visual Leak Detector\vld.ini
  2. Visual Leak Detector Version 2.5.1 installed.
  3. WARNING: Visual Leak Detector detected memory leaks!
  4. ---------- Block 2 at 0x00964198: 40 bytes ----------
  5.   Leak Hash: 0x01EF1389, Count: 1, Total 40 bytes
  6.   Call Stack (TID 10072):
  7.     ucrtbased.dll!malloc()
  8.     f:\dd\vctools\crt\vcstartup\src\heap\new_array.cpp (15): testVLD.exe!operator new[]() + 0x9 bytes
  9.     e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (60): testVLD.exe!main() + 0x7 bytes
  10.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (74): testVLD.exe!invoke_main() + 0x1B bytes
  11.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): testVLD.exe!__scrt_common_main_seh() + 0x5 bytes
  12.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): testVLD.exe!__scrt_common_main()
  13.     f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): testVLD.exe!mainCRTStartup()
  14.     KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
  15.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
  16.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
  17.   Data:
  18.     CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
  19.     CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
  20.     CD CD CD CD    CD CD CD CD                                   ........ ........
  21. ---------- Block 1 at 0x00964248: 40 bytes ----------
  22.   Leak Hash: 0x5D0E4327, Count: 1, Total 40 bytes
  23.   Call Stack (TID 10072):
  24.     ucrtbased.dll!malloc()
  25.     f:\dd\vctools\crt\vcstartup\src\heap\new_array.cpp (15): testVLD.exe!operator new[]() + 0x9 bytes
  26.     e:\cworkspace\qt 5.9\qtdemo\testvld\main.cpp (59): testVLD.exe!main() + 0x7 bytes
  27.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (74): testVLD.exe!invoke_main() + 0x1B bytes
  28.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (264): testVLD.exe!__scrt_common_main_seh() + 0x5 bytes
  29.     f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl (309): testVLD.exe!__scrt_common_main()
  30.     f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): testVLD.exe!mainCRTStartup()
  31.     KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
  32.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
  33.     ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
  34.   Data:
  35.     CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
  36.     CD CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
  37.     CD CD CD CD    CD CD CD CD                                   ........ ........
  38. Visual Leak Detector detected 2 memory leaks (152 bytes).
  39. Largest number used: 152 bytes.
  40. Total allocations: 152 bytes.
  41. Visual Leak Detector is now exiting.
复制代码
3.22 接口 VLDResolveCallstacks

该函数用于对所有已跟踪的调用堆栈进行符号解析,当调用堆栈中存在已卸载的模块函数时,可以调用此函数重新整理调用堆栈中的符号信息(函数名、行号、指令偏移量等信息)。
  1. // VLDResolveCallstacks - Performs symbol resolution for all saved extent CallStack's that have
  2. // been tracked by Visual Leak Detector. This function is necessary for applications that
  3. // dynamically load and unload modules, and through which memory leaks might be included.
  4. // If this is NOT called, stack traces may have stack frames with no symbol information. This
  5. // happens because the symbol API's cannot look up symbols for a binary / module that has been unloaded
  6. // from the process.
  7. //
  8. //  Return Value:
  9. //
  10. //    int: 0 if successfully resolved all callstacks.
  11. //
  12. __declspec(dllexport) int VLDResolveCallstacks();
复制代码
按编码规范来说,这个接口前面应该是 __declspec(dllimport),但它实际却是 __declspec(dllexport),这种用法不知是库作者笔误,还是别有用途,有兴趣的可以深究下去。
4. 接口使用思路


  • 使用 VLDDisable、VLDEnable、VLDRestore 可以做到只检测指定线程的内存泄漏,或者排除指定线程的内存泄漏检测。
  • 使用 VLDGlobalDisable、VLDGlobalEnable 可以做到只检测特定时间阶段的内存泄漏,比如只检测程序完成某项任务期间的内存泄漏,而不检测其他时间段的内存泄漏。
  • 使用 VLDEnableModule、VLDDisableModule、VLDSetModulesList、VLDGetModulesList、VLDRefreshModules、VLDResolveCallstacks 可以实现对指定模块进行内存检测的动态控制。
  • 使用 VLDReportLeaks、VLDReportThreadLeaks、VLDMarkAllLeaksAsReported、VLDMarkThreadLeaksAsReported、VLDSetReportOptions、VLDSetReportHook、VLDGetReportFilename 可以实现对泄漏报告的动态定制。
  • 使用 VLDGetLeaksCount、VLDGetThreadLeaksCount 可以实现对泄漏信息的实时获取。
  • 使用 VLDGetOptions、VLDSetOptions 可以实现运行过程中动态修改 VLD 的配置。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

饭宝

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表