OpenSSL库之内存分配

大耗子 2020年06月20日 138次浏览

文章链接:https://codemouse.online/archives/2020-06-20223142

内存分配

用户在使用内存时,容易犯的错误就是内存泄露。当用户调用内存分配和释放函数时,查找内存泄露比较麻烦。

OpenSSL提供了内置的内存分配/释放函数。如果用户完全调用OpenSSL的内存分配和释放函数,可以方便的找到内存泄露点。OpenSSL分配内存时,在其内部维护一个内存分配哈希表,用于存放已经分配但未释放的内存信息。当用户申请内存分配时,在哈希表中添加此项信息,内存释放时删除该信息。当用户通过OpenSSL函数查找内存泄露点时,只需查询该哈希表即可。用户通过OpenSSL回调函数还能处理那些泄露的内存。

OpenSSL供用户调用的内存分配等函数主要在crypto/mem.c中实现,其内置的分配函数在crypto/mem_dbg.c中实现。默认情况下mem.c中的函数调用mem_dbg.c中的实现。如果用户实现了自己的内存分配函数以及查找内存泄露的函数,可以通过调用CRYPTO_set_mem_functions函数和CRYPTO_set_mem_debug_functions函数来设置。

下面主要介绍了OpenSSL内置的内存分配和释放函数。

内存数据结构

OpenSSL内存分配数据结构是一个内部数据结构,定义在crypto/mem_dbg.c中。如下所示:

typedef struct app_mem_info_st 
{ 
    unsigned long thread; 
    const char *file; 
    int line; 
    const char *info; 
    struct app_mem_info_st *next; /* tail of thread's stack */ 
    int references; 
} APP_INFO; 
typedef struct mem_st 
{ 
    void *addr; 
    int num; 
    const char *file; 
    int line; 
    unsigned long thread; 
    unsigned long order; 
    time_t time; 
    APP_INFO *app_info; 
} MEM; 

各项意义:
addr:分配内存的地址。
num:分配内存的大小。
file:分配内存的文件。
line:分配内存的行号。
thread:分配内存的线程ID。
order:第几次内存分配。
time:内存分配时间。
app_info:用于存放用户应用信息,为一个链表,里面存放了文件、行号以及线程ID等信息。
references:被引用次数。

主要函数

  1. int CRYPTO_mem_ctrl(int mode);
    源文件:mem_dbg.c
    功能:本函数主要用于控制内存分配时,是否记录内存信息。如果不记录内存信息,将不能查找内存泄露。
    说明:开启内存记录调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON),关闭内存记录调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF)。一旦CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON)被调用,直到用户调用CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF)前,用户 所有的opessl内存分配都会被记录。

  2. int CRYPTO_is_mem_check_on(void);
    功能:查询内存记录标记是否开启。

  3. void CRYPTO_dbg_malloc(void *addr, int num, const char *file, int line,int before_p);
    功能:本函数用于分配内存空间,如果内存记录标记开启,则记录用户申请的内存。
    说明:当需要记录内存信息时,该函数本身也需要申请内存插入哈希表,为了防止递归申请错误,它申请内存记录信息前必须暂时关闭内存记录标记,申请完毕再放开。

  4. void CRYPTO_dbg_free(void *addr, int before_p);
    功能:释放内存。
    说明:如果内存记录标记开启,还需要删除哈希表中对应的记录。

  5. void CRYPTO_mem_leaks(BIO *b);
    功能:将内存泄露输出到BIO中。

  6. void CRYPTO_mem_leaks_fp(FILE *fp);
    功能:将内存泄露输出到FILE中(文件或者标准输出)。
    说明:该函数调用了CRYPTO_mem_leaks。

  7. void CRYPTO_mem_leaks_cb(CRYPTO_MEM_LEAK_CB *cb);
    功能:处理内存泄露。
    说明:输入参数为用户自己实现的处理内存泄露的函数地址。该函数只需要处理一个内存泄露,OpenSSL通过lh_doall_arg调用用户函数来处理所有记录(泄露的内存)。

  8. OPENSSL_malloc
    功能:申请内存。

  9. OPENSSL_strdup
    功能:申请内存,并拷贝传入的字符串。

  10. OPENSSL_realloc
    功能:申请内存,并拷贝指定长度。

  11. OPENSSL_realloc_clean
    功能:申请内存,拷贝原内存到新内存,并释放原内存。。
    说明:新内存长度不能短与旧内存长度,否则返回NULL。

  12. OPENSSL_remalloc
    功能:释放内存,并重新申请。

  13. OPENSSL_free
    功能:释放内存。

  14. OPENSSL_malloc_locked
    功能:带锁的内存申请。

  15. OPENSSL_free_locked
    功能:带锁的内存释放。

编程示例

crypto库的内存使用

示例使用了基本的OpenSSL内存分配和释放函数。
OPENSSL_malloc: 分配内存空间。
OPENSSL_remalloc: 重新分配内存空间。
OPENSSL_realloc_clean: 重新分配内存空间,将老的数据进行拷贝,置乱老的数据空间并释放。
OPENSSL_malloc_locked 与锁有关。
OPENSSL_free: 释放空间。

#include <string.h> 
#include <OpenSSL/crypto.h> 
int main() { 
    char *p; 
    int i; 
    p=OPENSSL_malloc(4); 
    p=OPENSSL_remalloc(p,40); 
    p=OPENSSL_realloc(p,32); 
    for(i=0;i<32;i++) 
    memset(&p[i],i,1);
    /* realloc时将以前的内存区清除(置乱) */ 
    p=OPENSSL_realloc_clean(p,32,77); 
    p=OPENSSL_remalloc(p,40); 
    OPENSSL_malloc_locked(3); 
    OPENSSL_free(p); 
    return 0; 
}

BIO的内存使用

#include <OpenSSL/crypto.h> 
#include <OpenSSL/bio.h> 
int main() { 
    char *p; 
    BIO *b; 
    CRYPTO_malloc_debug_init(); 
    CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL); 
    CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); 
    p=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; 
}