mock the malloc

有时候为了排查malloc内存分配相关的问题,需要对malloc的分配逻辑进行打印埋点,用于分析分配出来的内存是不是被正常释放掉,以及特定的大块内存分配的堆栈来源。基于这个问题,有了本文的一些尝试。 先看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//malloc_mock.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <stddef.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <execinfo.h>
#include <string.h>

static void *(*real_malloc)(size_t) = NULL;

static void (*real_free)(void *) = NULL;

#define MAX_LOG_SIZE 1024 * 1024
#define BACKTRACE_SIZE 10

static int isdigitstr(char *str)
{
return (strspn(str, "0123456789") == strlen(str));
}

int p_size = 0;

// void __attribute__((constructor)) init()
void init()
{
char *max_log_size = getenv("MAX_LOG_SIZE");
if (max_log_size == NULL || isdigitstr(max_log_size) == 0)
{
p_size = MAX_LOG_SIZE;
}
else
{
p_size = atoi(max_log_size);
}
fprintf(stderr, "the MAX_LOG_SIZE is %d \n", p_size);
real_malloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "malloc");
real_free = (void (*)(void *))dlsym(RTLD_NEXT, "free");
printf("has init the malloc/free func , the real_malloc addr is %p , the real_free addr is %p \n ", real_malloc, real_free);
}

void *malloc(size_t size)
{
if (real_malloc == NULL)
{
init();
}

void *addr = (*real_malloc)(size);
if (size > p_size)
{
pid_t pid = getpid();
pid_t tid = gettid();
fprintf(stderr, "malloc(%ld) = %p , pid : %d, tid : %d \n", size, addr, pid, tid);
void *array[BACKTRACE_SIZE];
int i, s = backtrace(array, BACKTRACE_SIZE);
char **stacks = backtrace_symbols(array, s);
for (i = 0; i < s; i++)
{
printf("- %s \n", stacks[i]);
}
}
return addr;
}

void free(void *ptr)
{
return (*real_free)(ptr);
}

int main(int argc, char *argv[])
{
if (real_malloc == NULL)
init();

void *ptr = malloc(10);
free(ptr);
return 0;
}


上述的代码中,有几个细节点,做一下记录

  • __attribute__((constructor))__attribute__((destructor)) 在编译动态库的时候,gcc会自动编译成加载动态库会执行的函数,以及卸载动态库会执行的函数。 函数本身不会要求一定是static的。
  • dlsym 函数可以从动态库中获取指定函数的引用,同时使用RTLD_NEXT,可以从下一个加载的动态库中查找,而不会直接加载当前的动态库造成冲突
  • backtrace 配合 backtrace_symbols 可以获取当前线程的堆栈信息。 具体的详细内容可以参考 man backtrace
  • gdb 调试的时候,可以通过set environment LD_PRELOAD=./malloc_mock 进行调试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//malloc.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{

size_t size = 10 * 1024 * 1024; // 10 MB

int pagesize = getpagesize();

char *ptr = malloc(size);
int i = 0;

// touch the memory page
for (i = 0; i < size; i += pagesize)
{
ptr[i] = 0;
}

printf("malloc the size %ld mem , the ptr is %p , just wait ! \r\n", size, ptr);
int c = getchar();

free(ptr);

printf("free the ptr %p \r\n ", ptr);

sleep(10);

return 0;
}


  • malloc 返回的是一个void** 指针类型,其可以正常转换为其他类型,但在做内存touch的时候,需要留意指针类型的长度
1
2
3
4

LD_PRELOAD=./malloc_mock.so java


参考: