基于ebpf的性能工具应用

描述

在实际的软件开发过程中,内存问题常常是耗费大量时间进行分析的挑战之一。为了更有效地定位和解决与内存相关的难题,一系列辅助工具应运而生,其中备受赞誉的Valgrind工具便是其中之一。事实上,笔者本人曾利用Valgrind工具成功地发现并解决了一个隐藏在软件中的bug,这充分体现了工具在开发过程中的重要性。

然而,同样强大的bpftrace工具同样具备简洁而直观的特点,能够协助我们高效地追踪内存泄漏问题。在这方面,bpftrace提供了一种更加精细的、实时的分析方式,帮助开发人员准确地定位代码中可能存在的内存泄漏情况。

构建样例

我们编写一个程序--mem_check.c,代码中包含正确的申请内存和释放内存的逻辑,同时包含存在内存泄露的代码代码。。

#include
#include

int main(){
char *p1 = NULL;
char *p2 = NULL;

for(int i = 0; i < 5; i++)
{
p1 = malloc(16);
}

for(int i = 0; i < 5; i++)
{
p2 = malloc(32);
free(p2);
}
getchar();
return 0;
}

上面的代码非常简单,我们申请了5次16个字节的内存,没有释放,存在内存泄露。申请5次32个字节的内存,有释放,没存在内存泄露。那么我们如何通过bpftrace定位呢?

我们通过bpftrace对mem_check.c进行动态的统计内存的申请和释放,定位内存泄露的问题。我们需要对关键的两个接口进行probe--malloc和free,这两个接口的实现在libc中。

编译mem_check.c文件,生成可执行文件:

gcc mem_check.c -o mem_check

探测mem_ckeck可执行文件

bpftrace可以对内核态进行探测也可以对用户态进行探测,其中探针如下:

  • 内核态探针:kprobe/kretprobe
  • 用户态探针:uprobe/uretprobe

mem_check.c是一个应用程序,显然我们需要使用用户态探针:uprobe/uretprobe

通过uprobe探测mem_check.c中的malloc函数,我们单行指令验证,参数格式是 uprobe:可执行文件:函数名:

代码

理论是没有没有问题,但实际发生错误:No probes to attach。原因:可执行文件mem_check中找不到符号:malloc,我们可以通过nm命令确定一下:

代码

我们发现malloc是一个链接自GLIBC_2.2.5的符号,并不是mem_ckeck自身的符号,所以我们探测的符号修改libc库中malloc符号,系统中可能存在多个c库,我们需要找到mem_ckeck程序使用的C库,通过ldd命令查看:

代码

mem_check可执行文件使用的C库为:/lib/x86_64-linux-gnu/libc.so.6,我们将可以执行文件替换为/lib/x86_64-linux-gnu/libc.so.6。再次执行,会出现大量内容,显然是其他进程调用了malloc引起的,而我们的mem_ckeck还没有运行,显然还没有探测我们的可执行程序。

bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc {printf("malloc calln")}'

我们需要进行过滤,增加filter只保留我们关心的应用程序的调用探测。bpftrace提供了系统变量comm表示可执行文件名 (进程名),只需要在上述指令中增加 filter,只处理comm=="mem_check"的malloc调用事件。左边终端执行探测,右边终端执行可执行文件。每调用一次malloc函数,就能探测到一次:

代码

使用bpftrace脚本进一步探测

将上面的单行命令变为bpftrace脚本--bpf_test.bt

BEGIN {
printf("start proben");
}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc calln");
}

END {
printf("end proben");
}

探测mem_check中malloc的内存空间大小。

malloc的原型:

void *malloc(size_t size);

bpftrace的uprobe和kprobe可以通过内置变量arg0、arg1 ··· ··· 访问函数参数,对bpf_test.bt修改就可以打印malloc申请内存的大小:

BEGIN {
printf("start proben");
}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
}

END {
printf("end proben");
}

如下图可以看到mem_check中申请内存的情况,最后一个malloc size 1024是mem_check自动创建输出缓冲区申请的内存,不用理会。

代码

探测mem_check中malloc的返回值

malloc的返回值是地址,需借助uretprobe进行探测,函数返回值可通过内置变量retval访问。uretprobe的filter与malloc参数探测时类似,脚本修改为:

BEGIN {
printf("start proben");
}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
}

uretprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("addr = %pn", retval);
}

END {
printf("end proben");
}

运行结果:

代码

探测mem_check中free

我们已经探测到mem_check的malloc的内存大小,内存的地址,我们通过探测free,然后匹配malloc和free的情况就可以查找内存的泄漏点。脚本修改为:

BEGIN {
printf("start proben");
}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
}

uretprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("addr = %pn", retval);
}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:free /comm == "mem_check"/{
printf("free addr = %pn", arg0);
}

END {
printf("end proben");
}

运行结果:

代码

探测内存泄露

上面我们已经探测到了mem_check中的malloc,free情况。我们可以通过malloc和free的地址集合差,就可以得到内存泄露的地址位置。

bpftrace底层使用的是eBPF的map作为存储结构,可以简单的看作K-V存储,我们可以利用map来统计地址集合差,步骤如下:

  1. 定义一个map变量@mem:保存malloc返回的内存地址。
  2. 当探测到free调用时,将@mem对应地址删除。
  3. 最后@mem剩下的就是内存泄露的地址。

内存泄露检测脚本如下:

BEGIN {
printf("start proben");
}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("malloc size: %dn", arg0);
@size = arg0;
}

uretprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc /comm == "mem_check"/{
printf("addr = %pn", retval);
@mem[retval] = @size;

}

uprobe:/lib/x86_64-linux-gnu/libc.so.6:free /comm == "mem_check"/{
printf("free addr = %pn", arg0);
delete(@mem[arg0]);
}

END {
printf("end proben");
}

运行结果:

代码

如上图,红色框中就是没有释放的内存和内存大小。

总结

通过编写一些简单的bpftrace脚本,我们就可以监视应用程序的内存分配和释放事件,捕获内存泄漏的迹象。这种直接的实时监控方式,使得开发者能够在问题出现时即刻获得反馈,从而更加迅速地解决潜在的内存问题,提升软件的稳定性和性能。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分