工作的某个项目通过对openssl改造,在测试过程中发现内存泄漏,定位困难,由于是开源程序,内存分配的接口又没有使用项目提供的,分析原因不是很方便,通过搜索openssl的文章,原来自身就有其工具可以参考,这里记录下如何使用以及其运作的机制,在其他项目是个调试的手段。
程序用例
利用openssl的库进行程序编写,只使用其内存分配、读写文件接口。
#include <openssl/crypto.h>
#include <openssl/bio.h>
int main()
{
char *p = NULL;
BIO *b = NULL;
CRYPTO_malloc_debug_init();
CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL);
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
p = (char*)OPENSSL_malloc(4);
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF);
b = BIO_new_file("leak.log","w");
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
CRYPTO_mem_leaks(b);
OPENSSL_free(p);
BIO_free(b);
return 0;
}
编译与测试
gcc openssl_checkleak.c -lcrypto -o test_leak
./test_leak
cat leak.log
[16:54:32] 0 file=openssl_checkleak.c, line=13, thread=140667925382848, number=4, address=0160B010
4 bytes leaked in 1 chunks
程序接口说明
对于内存的跟踪并不需要重新编译openssl的库,程序中显示调用CRYPTO_malloc_debug_init
,会重新配置内存相关的接口,内存跟踪方法会介入。
如果不加入 CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL);
显示如下,没有时间与线程的debug
cat leak.log
0 file=openssl_checkleak.c, line=14, number=4, address=0208B010
4 bytes leaked in 1 chunks
开启跟踪内存 CRYPTO_MEM_CHECK_ON
关闭跟踪内存 CRYPTO_MEM_CHECK_OFF
用例中打开文件也使用了openssl的内存分配,这块内存是常驻使用的,所以在使用前将内存跟踪关闭,不对其跟踪,以免在leak文件中产生干扰。
通过CRYPTO_mem_leaks
接口将系统正在使用的内存输出到文件。
更详细的使用方法可以通过man来获取。
内部实现
正常使用openssl内存管理的接口为CRYPTO_malloc_init()
,内部初始化如下
默认使用malloc、realloc、free的标准接口。如果没有调用此接口进行初始化,系统也会提供默认的malloc。
# define CRYPTO_malloc_init() CRYPTO_set_mem_functions(\
malloc, realloc, free)
int CRYPTO_set_mem_functions(void *(*m) (size_t), void *(*r) (void *, size_t), void (*f) (void *))
{
...
malloc_func = m; //即 malloc 接口
malloc_ex_func = default_malloc_ex;
...
}
调试的接口初始化为NULL
static void (*malloc_debug_func) (void *, int, const char *, int, int) = NULL;
static void (*realloc_debug_func) (void *, void *, int, const char *, int,
int)
= NULL;
static void (*free_debug_func) (void *, int) = NULL;
static void (*set_debug_options_func) (long) = NULL;
static long (*get_debug_options_func) (void) = NULL;
如果在编译时启用了CRYPTO_MDEBUG
宏,则默认开启检测机制,不需要配置调试接口。
static void (*malloc_debug_func) (void *, int, const char *, int, int)
= CRYPTO_dbg_malloc;
static void (*realloc_debug_func) (void *, void *, int, const char *, int, int)
= CRYPTO_dbg_realloc;
static void (*free_debug_func) (void *, int) = CRYPTO_dbg_free;
static void (*set_debug_options_func) (long) = CRYPTO_dbg_set_options;
static long (*get_debug_options_func) (void) = CRYPTO_dbg_get_options;
通过CRYPTO_malloc_debug_init
接口在程序中调用调试接口
# define CRYPTO_malloc_debug_init() do {\
CRYPTO_set_mem_debug_functions(\
CRYPTO_dbg_malloc,\
CRYPTO_dbg_realloc,\
CRYPTO_dbg_free,\
CRYPTO_dbg_set_options,\
CRYPTO_dbg_get_options);\
} while(0)
内存分配OPENSSL_malloc
记录分配信息的处理机制
# define OPENSSL_malloc(num) CRYPTO_malloc((int)num,__FILE__,__LINE__)
利用宏来实现文件与行的定位。(可以再增加被哪个函数调用的信息)
CRYPTO_malloc
malloc_debug_func(NULL, num, file, line, 0);
ret = malloc_ex_func(num, file, line);
malloc_debug_func(ret, num, file, line, 1);
malloc_ex_func
是真正分配内存的接口
static void *default_malloc_ex(size_t num, const char *file, int line)
{
return malloc_func(num);
}
malloc_func
被赋值为malloc
malloc_debug_func
接口在分配内存前后各调用了两次,对于malloc
来说,第一次其实没有作用,直接返回,realloc
会涉及到其中的作用。
CRYPTO_dbg_malloc 内部处理
- 在
is_MemCheck_on
状态处理,即开启记录内存信息。 MemCheck_off()
代码关闭记录分配,因为内部要分配内存,否则陷入无限循环- 分配一个 MEM 结构
- 存储 地址addr、文件file、行号line、大小num
- 记录序号 order
lh_MEM_insert
将MEM结构记录到hash中MemCheck_on
重新开启
# define lh_MEM_insert(lh,inst) LHM_lh_insert(MEM,lh,inst)
void *lh_insert(_LHASH *lh, void *data) 插入到hash表中。
OPENSSL_free
操作
# define OPENSSL_free(addr) CRYPTO_free(addr)
free_debug_func(str, 0);
free_func(str);
free_debug_func(NULL, 1);
释放后的free_debug_func
,参数为1,直接返回,不会处理。
- 在 is_MemCheck_on 状态处理
- MemCheck_off() 后面要释放内存,否则陷入无限循环
- m.addr = addr; mp = lh_MEM_delete(mh, &m); 通过addr作为key value,找到hash node,然后删除,释放存储分配信息的内存
- MemCheck_on 重新开启