最近做rtmp推流的时候由于使用了很多librtmp和其他代码,逻辑混乱导致功能虽然都实现了,但是出现了内存泄露的现象。其实不改也不会影响使用,因为吃的一点也不厉害,反复大量的启动关闭才会出现泄露,实际使用没有那么变态的操作。但谁让咱严谨呢。
首先上网查,无非那几个办法。但我们是嵌入式交叉编译,valgrind就别想了,因为连mtrace都没有!mtrace还是很好用的,就按其他博客写的那样去操作就行。设置环境变量,调用mtrace,linux系统自动去给你统计内存开销情况。到时候反汇编指针就OK了。
下面说说我的办法吧,其实大家也都是采用的这个办法,简单点说就是重定向内存开销接口。把malloc、calloc、realloc、strdup和free重定向成你自己写的接口。然后用一个写着内存大小接口等数据的数据结构链表来记录所有的申请还没有被销毁的内存。开一块内存加一个节点,free一块就从链表中删除。然后你想什么时候看看这个链表就把它写到文件里,自己去cat文件即可。
结果是美好的,一下子就找到了内存重开没释放的逻辑,然后分析修改代码,问题就解决了。为什么会出现这次的内存泄露呢,就是因为代码不熟悉,不全是我写的。虽然改完了,功能也都正常,但心里还是不放心,因为我暂时还无法完全掌控。下面我把代码贴出来,修改调试了很久,里面做了很多注释,独立性相当强,不和任何模块相关。可以说拿过去直接用就行。后面我会说使用方法。
//leak.h
#ifndef PROJECT_LEAK_H
#define PROJECT_LEAK_H
#include
#include
#include
#include "rtmp_client_protocol.h"
#ifdef LEAK_CHECK
void leak_free(void *_ptr,const char *_file,const char *_func, unsigned int _line);
void * leak_malloc(size_t _size,const char *_file,const char *_func, unsigned int _line);
void * leak_calloc(size_t nmemb, size_t size, const char *_file,const char *_func, unsigned int _line);
void * leak_realloc(void *ptr, size_t size, const char *_file,const char *_func, unsigned int _line);
char * leak_strdup(const char *_s, const char *_file,const char *_func, unsigned int _line);
# ifdef free
# undef free
# endif
# ifdef malloc
# undef malloc
# endif
# ifdef calloc
# undef calloc
# endif
# ifdef realloc
# undef realloc
# endif
# ifdef strdup
# undef strdup
# endif
#define free(p) leak_free(p, __FILE__, __FUNCTION__, __LINE__)
#define malloc(size) leak_malloc(size, __FILE__, __FUNCTION__, __LINE__)
#define calloc(n, size) leak_calloc(n, size, __FILE__, __FUNCTION__, __LINE__)
#define realloc(p, size) leak_realloc(p, size, __FILE__, __FUNCTION__, __LINE__)
#define strdup(s) leak_strdup(s, __FILE__, __FUNCTION__, __LINE__)
int leak_dbg_msg(char *_buf, int _size);
void leak_output(void);
#endif
#endif //PROJECT_LEAK_H
//leak.c
#include
#include
#include
#include
#include
#define MAX_FUNC_INFO 32 /*the functions of per process memory leak number, 32 is enough*/
#define LEAK_VERIFY 0x20190121 /*just a random value to check memory point*/
#define MAX_MSG_LEN 4*1024 /*the file length, you can expand it as you want*/
#define LEAK_PATH "./leak_check" /*the path of the file which records the memory you forget*/
typedef struct {
char *file;
char *func;
int line;
int pid;
size_t size;
void *pre;
void *nxt;
int verify; /*0x20190121*/
void *base[0];
}__attribute__((packed)) leak_check ;
leak_check *leak_recent = NULL; /*the list of the memory chunk, */
pthread_mutex_t g_leak_mtx = PTHREAD_MUTEX_INITIALIZER;
#define leak_lock pthread_mutex_lock(&g_leak_mtx)
#define leak_unlock pthread_mutex_unlock(&g_leak_mtx)
#define leak_warn(fmt, arg...) printf("[leak warning]" fmt "\n", ##arg)
#define leak_err(fmt, arg...) fprintf(stdout, "[leak error]" fmt "\n", ##arg)
static int _leak_list_add(leak_check *new)
{
if (NULL == new) return -1;
new->verify = LEAK_VERIFY;
if (leak_recent) {
leak_check *pre = leak_recent;
leak_check *nxt = leak_recent->nxt;
if (nxt)
leak_err("internal error nxt = %p\n", nxt);
new->nxt = NULL;
new->pre = pre;
if (pre)
pre->nxt = new;
} else {
new->nxt = NULL;
new->pre = NULL;
}
leak_recent = new;
return 0;
}
static int _leak_list_del(leak_check *entry)
{
if (NULL == entry) return -1;
leak_check *pre = entry->pre;
leak_check *nxt = entry->nxt;
if (pre && nxt) {
nxt->pre = pre;
pre->nxt = nxt;
} else if (pre) {
pre->nxt = NULL;
if (leak_recent == entry) {
leak_recent = pre;
} else {
leak_err("[leak internal error] leak_recent = %p entry = %p\n", leak_recent, entry);
}
} else if (nxt) {
nxt->pre = NULL;
} else {
if (leak_recent == entry) {
leak_recent = NULL;
}
}
entry->verify = 0;
return 0;
}
static inline int _leak_check_magic(leak_check *_in)
{
/*
* CLion has AI function, it remind me to change some codes.
* These codes are two if before, if(!_in) and if(LEAK_VERIFY
* ==_in->verify)else... Clion told me just one line is ok.
*/
return !_in ? -1 : LEAK_VERIFY == _in->verify ? 0 : -1;
}
static leak_check *leak_get_addr_verified(void *_s)
{
leak_check *presult = NULL;
leak_check *nxt = NULL;
leak_check *pre = NULL;
if(_s != NULL)/*amazing CLion, kernel style*/
{
presult = (leak_check *)(_s - sizeof(leak_check));
if(0 == _leak_check_magic(presult))
{
nxt = presult->nxt;
pre = presult->pre;
if(!((!nxt || 0 == _leak_check_magic(nxt)) && (!pre || 0 == _leak_check_magic(pre))))
{
presult = NULL;
}
}else
{
presult = NULL;
}
LABEL_EXIT:
return presult;
}else
{
goto LABEL_EXIT;
}
}
void * leak_malloc(size_t _size, char *_file, char *_func,unsigned int _line)
{
void *pmalloc = NULL;
leak_check *pleak = NULL;
if ((int)_size < 0) {
leak_warn("[%d %s] malloc size{%u} may be negtive", _line, _func, _size);
}
pmalloc = malloc(_size + sizeof(leak_check));
if (!pmalloc) {
leak_warn("[%d %s] malloc {%u} returned null", _line, _func, _size);
return NULL;
}
pleak = (leak_check*)pmalloc;
pleak->file = _file;
pleak->func = _func;
pleak->line = _line;
pleak->size = _size;
pleak->pid = syscall(SYS_getpid);
leak_lock;
_leak_list_add(pleak);
leak_unlock;
return pleak->base;
}
char *leak_strdup(char *_s, char *_file, char *_func,unsigned int _line)
{
void *pmalloc = NULL;
leak_check *pleak = NULL;
if (NULL == _s) {
leak_err("[%d %s] strdup null", _line, _func);
return NULL;
}
pmalloc = malloc(strlen(_s) + 1 + sizeof(leak_check));
if (!pmalloc) {
leak_warn("[%d %s] malloc {%u} returned null", _line, _func, strlen(_s));
return NULL;
}
pleak = (leak_check*)pmalloc;
pleak->file = _file;
pleak->func = _func;
pleak->line = _line;
pleak->size = strlen(_s) + 1;
pleak->pid = syscall(SYS_getpid);
strcpy((char*)pleak->base, _s);
leak_lock;
_leak_list_add(pleak);
leak_unlock;
return (char*)pleak->base;
}
void leak_free(void *_ptr, char *_file, char *_func,unsigned int _line)
{
leak_check *result = NULL;
if (!_ptr) {
(void)leak_err("[%d %s] free null", _line, _func);
}
leak_lock;
result = leak_get_addr_verified(_ptr);
if (!result) {
(void)leak_err("[%d %s] free unkown addr {%p}", _line, _func, _ptr);
free(_ptr);
goto UNLOCK;
}
_leak_list_del(result);
free(result);
UNLOCK:
leak_unlock;
return;
}
void * leak_calloc(size_t _nmemb, size_t _size, char *_file, char *_func,unsigned int _line)
{
void *pmalloc = NULL;
leak_check *pleak = NULL;
if ((int)_size < 0 || (int)_nmemb < 0) {
leak_warn("[%d %s] calloc size{%u} _nmemb {%u} may be negtive", _line, _func, _size, _size);
}
pmalloc = malloc(_size * _nmemb + sizeof(leak_check));
if (!pmalloc) {
leak_warn("[%d %s] malloc {%u} returned null", _line, _func, _size);
return NULL;
}
(void)memset(pmalloc, 0, _size * _nmemb + sizeof(leak_check));
pleak = (leak_check*)pmalloc;
pleak->file = _file;
pleak->func = _func;
pleak->line = _line;
pleak->size = _size*_nmemb;
pleak->pid = syscall(SYS_getpid);
leak_lock;
_leak_list_add(pleak);
leak_unlock;
return pleak->base;
}
void *leak_realloc(void *_ptr, size_t _size, char *_file, char *_func,unsigned int _line)
{
void *result = NULL;
leak_check *pleak = NULL;
leak_lock;
result = leak_get_addr_verified(_ptr);
if (!result) {
(void)leak_err("[%d %s] realloc unkown addr {%p}", _line, _func, _ptr);
leak_unlock;
return realloc(_ptr, _size);
}
_leak_list_del(result);
leak_unlock;
pleak = realloc(result, _size + sizeof(leak_check));
if (!pleak) {
leak_warn("[%d %s] realloc {%u} returned null", _line, _func, _size);
return NULL;
}
pleak->size = _size;
leak_lock;
_leak_list_add(pleak);
leak_unlock;
return pleak->base;
}
typedef struct {
int size;
int line;
char *file;
char *func;
}function_info;
typedef struct {
int pid;
int size;
function_info fun[MAX_FUNC_INFO];
}heap_info;
static void _add_heap_info(leak_check *_search,heap_info *_info, int _size)
{
int i, j;
heap_info *result = NULL;
if (NULL == _search) goto LABEL_EXIT;
for (i = 0; i < _size; i ++)
{
if (_info[i].pid == _search->pid ||
_info[i].pid == 0) {
result = &_info[i];
result->size += _search->size;
result->pid = _search->pid;
} else {
continue;
}
for (j = 0; j < MAX_FUNC_INFO; j ++) {
if (NULL == result->fun[j].func ||
(0==strcmp(result->fun[j].func, _search->func) && result->fun[j].line == _search->line))
{
result->fun[j].size += _search->size;
result->fun[j].func = _search->func;
result->fun[j].line = _search->line;
result->fun[j].file = _search->file;
} else {
continue;
}
break;
}
break;
}
LABEL_EXIT:
return;
}
int leak_dbg_msg(char *_buf, int _size)
{
int i, j;
int alloc_size = 0;
if (NULL == _buf) {
goto LABEL_EXIT;
}
leak_check *search = NULL;
heap_info info[64] = {{0,0}};
_buf[0] = '\0';
memset(&info[0], 0, sizeof (info));
leak_lock;
search = leak_recent;
printf("sizeof(info)=%d sizeof(info[0])=%d sizeof(heap_info)=%d\n",sizeof(info),sizeof(info[0]), sizeof(heap_info));
while (search) {
_add_heap_info(search, info, sizeof(info)/sizeof(info[0]));
alloc_size += search->size;
search = (leak_check*)search->pre;
}
leak_unlock;
for (i = 0; i < sizeof(info)/sizeof(info[0]); i ++) {
if (0 == info[i].pid) break;
snprintf(&_buf[strlen(_buf)], _size-strlen(_buf), "├─T─%d\n├─────────┬────[%d]\n", info[i].pid, info[i].size);
for (j = 0; j < MAX_FUNC_INFO; j ++) {
if (NULL == info[i].fun[j].func) break;
snprintf(&_buf[strlen(_buf)], _size-strlen(_buf), "│ ├────[%d] %s():%d",
info[i].fun[j].size,info[i].fun[j].func, info[i].fun[j].line);
snprintf(&_buf[strlen(_buf)], _size-strlen(_buf), "%s", "\n");
}
}
snprintf(&_buf[strlen(_buf)], _size-strlen(_buf),"└──total──┴────[%d] \n", alloc_size);
LABEL_EXIT:
return 0;
}
void leak_output(void)
{
FILE *file = NULL;
file = fopen(LEAK_PATH, "w+");
char buffer[MAX_MSG_LEN];
buffer[0]=0;
leak_dbg_msg(buffer, MAX_MSG_LEN);
// truncate(PATH, 0);/*There is fopen(w+), so comment out truncate*/
// fseek(file, 0, 0);/*There is fopen(w+), so comment out fseek*/
fprintf(file, buffer);
fflush(file);
}
//test.c
void *test_process(void *arg)
{
char *leak_point=malloc(666);
char *leak_point1=malloc(777);
dbgprintf("leak_point=%p leak_point1=%p",leak_point,leak_point1);
leak_output();
return NULL;
}
一个.c和一个.h,外加一个test_process()接口,应该知道怎么用。使用的时候只需要做一件事,调用leak_output(),查看leak_check文件!最终的效果如下图所示。有第一点需要说明,这个同样适用于多进程,只不过我这里只有一个进程,所以就重复显示了1443这个值。当然更复杂的情况我也没有测试,说不好在特殊情况下会有段错误等异常出现。
内存是linux开发必须关注的一个事情,嵌入式上更是。嵌入式linux种种特殊条件导致引入开源代码的时候必须自己修改,裁剪。内存泄露是很常见的情况。工作时间长了自然会遇到内存泄露的问题,因为你肯定会遇到修改离职员工维护的代码的时候。不熟悉的复杂逻辑,修改的时候非常容易忘记free,还有那些野指针。跟踪起来其实并不复杂,但大部分情况不允许你从新去做这个事情,因为跟踪内存泄露的模块早已经被前人写完实际使用了很久很久。但这又是一个很重要的技能,(其实没那么重要,但其他的还有什么能衡量吗?)所以说没做过的还是自己做一下的好。
刚刚开会的时候同事说我们这个linux系统自带有这个功能,不用自己做,我一会去看看是否真的有宏。用今天上午的一个截图结尾吧。