文章目录
- openssl3.2 - crypto-mdebug被弃用后, 内存泄漏检查的替代方法
- 概述
- 笔记
- 查看特性列表
- openssl3.2编译脚本 - 加入enable-crypto-mdebug
- 看看有没有替代内存诊断的方法?
- main.cpp
- my_openSSL_lib.h
- my_openSSL_lib.c
- 备注
- 备注
- 这招不行啊
- 显势调用默认上下文也不行
- END
openssl3.2 - crypto-mdebug被弃用后, 内存泄漏检查的替代方法
概述
调用openssl接口后, 如果用到了openssl对象, 需要释放, 否则会发生内存泄漏.
即使不是新手, 也不能保证释放函数都调用了. 想想我们自己写程序, new后, 没有delete的情况就知道, 可以理解.
谁能保证自己手搓的应用实现100%没内存泄漏呢?
看资料时, 发现openssl本身有这个检查库本身发生内存泄漏的特性, 大概就是申请内存时, openssl自己记录了一下, free内存时, 将对应记录删掉.
这样, 在程序退出前, 再调用一下内存分配记录列表接口, 就知道哪里的内存没释放.
那试试, 加入crypto-mdebug特性, 模拟一下内存泄漏(调用openssl_new(), 不调用openssl_free()), 看看啥效果.
笔记
查看特性列表
perl configdata.pm --dump > my_log.txt
查看my_log.txt, 就有openssl 特性列表.
有启用的特性列表, 也有被禁掉的特性列表.
如果要加入特性, 就看禁止列表中的特性.
怎么打开crypto-mdebug特性呢?
看Configure可知, 只要带上参数 enable-crypto-mdebug即可.
结合我最后实验可用的编译脚本, 加入 enable-crypto-mdebug
openssl3.2编译脚本 - 加入enable-crypto-mdebug
解开官方源码包
打开vs2019x64本地命令行, 选择管理员身份运行
cd /d D:\3rd_prj\crypt\openssl-3.2.0
set path=c:\nasm;%path%
perl Configure VC-WIN64A --debug enable-crypto-mdebug zlib-dynamic --with-zlib-include=D:\my_dev\lib\zlib_1d3 --with-zlib-lib=.\my_zlib_1d3.dll --prefix=c:\openssl_3d2 --openssldir=c:\openssl_3d2\common
nmake
手工拷贝, 将 my_zlib_1d3.dll 拷贝到以下4个目录
.\
.\apps
.\fuzz
.\test
nmake test
nmake install
手工拷贝
D:\my_dev\lib\zlib_1d3\my_zlib_1d3.dll => C:\openssl_3d2\bin\my_zlib_1d3.dll
归档
C:\openssl_3d2 剪切到自己的库目录 => D:\my_dev\lib\openssl_3d2
写个测试程序, 调用一下内存泄漏检查的相关接口, 看看能否编译过, 然后试试接口怎么用.
/*!
* \file main.cpp
*/
#include "my_openSSL_lib.h"
#include <openssl/crypto.h> // for mem leak API
int main(int argc, char** argv)
{
CRYPTO_mem_leaks(NULL);
return 0;
}
/*!
编译错误
已启动重新生成…
1>------ 已启动全部重新生成: 项目: prj_template, 配置: Debug x64 ------
1>main.cpp
1>D:\my_dev\my_local_git_prj\study\openSSL\exp\call_mem_leak_API\main.cpp(12,2): error C4996: 'CRYPTO_mem_leaks': Since OpenSSL 3.0
1>已完成生成项目“prj_template.vcxproj”的操作 - 失败。
========== 全部重新生成: 成功 0 个,失败 1 个,跳过 0 个 ==========
*/
直接编译不过…
看官方说明 file:///D:/3rd_prj/crypt/openssl-3.2.0/doc/html/man3/OPENSSL_malloc.html
The following functions have been deprecated since OpenSSL 3.0, and can be hidden entirely by defining OPENSSL_API_COMPAT with a suitable version value, see openssl_user_macros(7):
int CRYPTO_mem_leaks(BIO *b);
int CRYPTO_mem_leaks_fp(FILE *fp);
int CRYPTO_mem_leaks_cb(int (*cb)(const char *str, size_t len, void *u),
void *u);
int CRYPTO_set_mem_debug(int onoff);
int CRYPTO_mem_ctrl(int mode);
int OPENSSL_mem_debug_push(const char *info);
int OPENSSL_mem_debug_pop(void);
int CRYPTO_mem_debug_push(const char *info, const char *file, int line);
int CRYPTO_mem_debug_pop(void);
DESCRIPTION
看到官方说, 这些内存诊断函数已经弃用了, 用clang的检查代替(忘了是哪个文档了, 反正是官方文档中说的).
去看内存诊断函数的实现, 都是空的, 官方确实已经弃用了.
尝试将vs2019的工具链改为clang, 看不到效果, 且不能单步进入库函数内部.
用clang工具链编译, 可以编译过, 也可以运行, 不过无法进入调试版的pdb实现中.
已启动重新生成…
1>------ 已启动全部重新生成: 项目: prj_template, 配置: Debug x64 ------
1>main.cpp(12,2): warning : 'CRYPTO_mem_leaks' is deprecated: Since OpenSSL 3.0 [-Wdeprecated-declarations]
1>D:\my_dev\lib\openssl_3d2\include\openssl/crypto.h(411,1): message : 'CRYPTO_mem_leaks' has been explicitly marked deprecated here
1>D:\my_dev\lib\openssl_3d2\include\openssl/macros.h(194,49): message : expanded from macro 'OSSL_DEPRECATEDIN_3_0'
1>D:\my_dev\lib\openssl_3d2\include\openssl/macros.h(44,22): message : expanded from macro 'OSSL_DEPRECATED'
1>prj_template.vcxproj -> D:\my_dev\my_local_git_prj\study\openSSL\exp\call_mem_leak_API\x64\Debug\prj_template.exe
1>'pwsh.exe' 不是内部或外部命令,也不是可运行的程序
1>或批处理文件。
1>已完成生成项目“prj_template.vcxproj”的操作。
========== 全部重新生成: 成功 1 个,失败 0 个,跳过 0 个 ==========
那就不用clang来编译.
看看有没有替代内存诊断的方法?
找到一个opensslAPI CRYPTO_get_alloc_counts(), 可以取当前内存分配次数.
将这个API封装一下, 卡在openssl应用函数外边, 就可以间接的知道是否有内存泄漏, 如果有opensslAPI调用引起的内存泄漏, 可以迅速的缩小排查范围.
main.cpp
/*!
* \file main.cpp
*/
#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
bool is_OSSL_mem_leak();
void test_mem_leak(bool b_have_mem_leak);
int main(int argc, char** argv)
{
openssl_mdebug_begin();
test_mem_leak(true);
openssl_mdebug_end(true, false); // 如果需要断言, 参数2为true
openssl_mdebug_begin();
test_mem_leak(false);
openssl_mdebug_end(true, true);
/*
run result
test_mem_leak(test have mem leak)
>> malloc_count = 0, realloc_count = 0, free_count = 0
<< malloc_count = 1, realloc_count = 0, free_count = 0
err : !!! mem leak happen
test_mem_leak(test no mem leak)
*/
return 0;
}
void test_mem_leak(bool b_have_mem_leak)
{
void* pBuf = NULL;
printf("test_mem_leak(%s)\n", (b_have_mem_leak ? "test have mem leak" : "test no mem leak"));
pBuf = OPENSSL_malloc(2);
// do some task ...
if (!b_have_mem_leak)
{
OPENSSL_free(pBuf);
pBuf = NULL;
}
}
my_openSSL_lib.h
/*!
\file my_openSSL_lib.h
*/
#ifndef __MY_OPENSSL_LIB_H__
#define __MY_OPENSSL_LIB_H__
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") // for select()
#include <windows.h>
#include <stdbool.h>
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
#endif /* #ifdef _WIN32 */
// --------------------------------------------------------------------------------
// 开关宏 - begin
// --------------------------------------------------------------------------------
#define MY_USE_APPLINK
// --------------------------------------------------------------------------------
// 开关宏 - END
// --------------------------------------------------------------------------------
typedef struct _tag_openssl_mem_counter{
int malloc_count_begin;
int realloc_count_begin;
int free_count_begin;
int malloc_count_end;
int realloc_count_end;
int free_count_end;
} TAG_OPENSSL_MEM_COUNTER;
void openssl_mdebug_begin();
bool openssl_mdebug_end(bool show_tip, bool b_assert);
#ifdef __cplusplus
}
#endif
#endif /* #ifndef __MY_OPENSSL_LIB_H__ */
my_openSSL_lib.c
/*!
* \file D:\my_dev\my_local_git_prj\study\openSSL\nmake_test\test_c\prj_005_afalgtest.c\my_openSSL_lib.c
*/
#include "my_openSSL_lib.h"
#include "openssl/crypto.h" // for CRYPTO_get_alloc_counts
#include <assert.h>
#ifdef MY_USE_APPLINK
#include <openssl/applink.c> /*! for OPENSSL_Uplink(00007FF8B7EF0FE8,08): no OPENSSL_Applink */
#endif // #ifdef MY_USE_APPLINK
static TAG_OPENSSL_MEM_COUNTER gs_openssl_mdebug;
void openssl_mdebug_begin()
{
CRYPTO_get_alloc_counts(&gs_openssl_mdebug.malloc_count_begin, &gs_openssl_mdebug.realloc_count_begin, &gs_openssl_mdebug.free_count_begin);
}
bool openssl_mdebug_end(bool show_tip, bool b_assert)
{
bool b_rc = false;
long lCntBegin = 0;
long lCntEnd = 0;
CRYPTO_get_alloc_counts(&gs_openssl_mdebug.malloc_count_end, &gs_openssl_mdebug.realloc_count_end, &gs_openssl_mdebug.free_count_end);
lCntBegin = gs_openssl_mdebug.malloc_count_begin + gs_openssl_mdebug.realloc_count_begin - gs_openssl_mdebug.free_count_begin;
lCntEnd = gs_openssl_mdebug.malloc_count_end + gs_openssl_mdebug.realloc_count_end - gs_openssl_mdebug.free_count_end;
b_rc = (lCntBegin == lCntEnd);
if (!b_rc && show_tip)
{
printf(">> malloc_count = %d, realloc_count = %d, free_count = %d\n", gs_openssl_mdebug.malloc_count_begin, gs_openssl_mdebug.realloc_count_begin, gs_openssl_mdebug.free_count_begin);
printf("<< malloc_count = %d, realloc_count = %d, free_count = %d\n", gs_openssl_mdebug.malloc_count_end, gs_openssl_mdebug.realloc_count_end, gs_openssl_mdebug.free_count_end);
printf("err : !!! mem leak happen\n");
}
if (b_assert)
{
assert(true == b_rc);
}
return b_rc;
}
备注
以后有机缘再查查怎么用clang来查openssl应用是否有内存泄漏.
查资料时, 很多情况下都不是想查就能查到的.
很多时候, 是心里带着问题, 查其他资料时, 突然给了启发, 才将以前的问题搞掉.
这种调用CRYPTO_get_alloc_counts()来间接的排查是否调用opensslAPI时, 是否没有成对的分配,释放内存.
没有那么直接和方便, 不过也算是一种方法了. 有总比没有强.
希望以后能找到其他更好的方法来定位opensslAPI调用时的内存泄漏点.
备注
openssl的编译检查, 测试用例还是很严格的.
对外提供的API, 都有测试程序.
CRYPTO_get_alloc_counts()这个API的调用, 也能找到至少一处.
用SI将openssl源码编译的目录中的能识别的东东都包进来搜索, 必然能看到测试用例或者API调用的痕迹.
如果某个API虽然定义, 但是官方源码编译目录的实现中都没有用到, 那么咱么就不能用这个API.
这招不行啊
今天正好写个测试程序, 用到了这种内存泄漏检测方法. 有断言, 不好使.
int main(int argc, char** argv)
{
BIO* bio = NULL;
openssl_mdebug_begin();
test_bio_mem_leak();
// test_bio_new_mem_buf();
openssl_mdebug_end(false, true);
return 0;
}
void test_bio_mem_leak(void)
{
BIO* bio = BIO_new_mem_buf("Hello World\n", 12);
BIO_free(bio);
}
只能先关注这事, 以后再想办法了.
跟了一下openssl代码, 是产生默认库上下文中有很多openssl_malloc()引起的内存分配.
后续再看看, 自己显势调用产生销毁默认上下文的API, 是否可以继续用这种方法.
显势调用默认上下文也不行
int main(int argc, char** argv)
{
OSSL_LIB_CTX* _ossl_lib_ctx = NULL;
BIO* bio = NULL;
do {
openssl_mdebug_begin();
_ossl_lib_ctx = OSSL_LIB_CTX_get0_global_default();
if (NULL == _ossl_lib_ctx)
{
assert(false);
break;
}
test_bio_mem_leak();
// test_bio_new_mem_buf();
OSSL_LIB_CTX_free(_ossl_lib_ctx);
openssl_mdebug_end(true, false); // 到这里, 还是报错
} while (false);
return 0;
}
void test_bio_mem_leak(void)
{
BIO* bio = BIO_new_mem_buf("Hello World\n", 12);
BIO_free(bio);
}
报错原因, openssl函数调用中, 会有其他默认的上下文建立会调用内存分配.
具体是啥函数, 要去单步.
这种用内存分配计数的方法, 不能真实的间接观察到openssl内部的内存泄漏, 做了一个没用的实验…
还是要老老实实的看官方例子, 手工调用对应API的释放函数.