内存泄露专题(5)动态内存追踪大杀器:bcc

简介: 内存泄露专题(5)动态内存追踪大杀器:bcc

eBPF这几年网上炒得很热,甚至于有许多开发者十分迷信该技术,虽然eBPF的确能解决不少问题,但这种技术是否能真正用于生产环境,其实是需要根据场景而异的。这里就不做过多的篇幅来讲述eBPF的概念了,感兴趣的可以上网自己查找相关资料。

bcc是一个eBPF的工具集,使用C/C++实现,外层使用python进行封装。由于eBPF需要Linux kernel 4.1以上才能够支持,因此,如果你的服务器是CentOS7或者比较低版本的Ubuntu,都是无法使用该工具的。笔者所使用的的服务器是CentOS7.9,内核版本为3.10.0,因此,常规手段无法支持,需要进行源码编译。

[root@ck08 sbin]# uname -a
Linux ck08 3.10.0-1160.53.1.el7.x86_64 #1 SMP Fri Jan 14 13:59:45 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

bcc环境部署

安装 devtoolset-7llvm-toolset-7

yum install -y  devtoolset-7 llvm-toolset-7

切换编译器版本:

source scl_source enable devtoolset-7 llvm-toolset-7

下载bcc源码:

git clone https://github.com/iovisor/bcc.git

编译bcc代码:

mkdir build
cd build
cmake ..

bcc-memleak使用

假设我们有一段代码如下:

//bcc_test.c
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<malloc.h>
void func1();
void func2();
int main() {
  int i = 0;
  while(1) func1();
  return 0;
}
void func1() {
   char *str = NULL;
   str = (char *)malloc(64*1024); //64k
   strcpy(str,"testing");
   usleep(100000);
}

编译:

gcc -o bcc_test bcc_test.c -g

同样需要保证O0编译。

以上程序会每隔100毫秒去申请64K内存,而且程序在系统OOM之前永远不会终止。

运行程序:

./bcc_test

关于memleak的参数说明如下:

-h 打印使用信息。
-p PID 仅跟踪此进程 ID(在内核中过滤)。
-t 打印所有分配和空闲请求和结果的跟踪。
-a 打印除调用堆栈外未释放的分配列表(及其大小)。
-o OLDER 仅打印早于 OLDER 毫秒的分配。有助于消除误报。默认值为 500 毫秒。
-c COMMAND 运行指定的命令并仅跟踪其分配。这会跟踪 libc 分配器。
--combined-only 使用在内核空间中预先计算的统计信息。从内核中提取的数据量显着减少,代价是失去基于时间的误报过滤 (-o) 的能力。
--wa-missing-free 弥补free的动作,以减轻free缺失时的误判。
-s SAMPLE_RATE 大致记录每个 SAMPLE_RATE-th 分配以减少开销。
-t TOP 仅打印顶部的 TOP 堆栈(按大小排序)。默认值为 10。 -z MIN_SIZE 仅捕获大于或等于 MIN_SIZE 字节的分配。
-Z MAX_SIZE 仅捕获小于或等于 MAX_SIZE 字节的分配。
-O OBJ 附加到指定对象中的分配函数,而不是解析 libc。在分析内核分配时忽略。
INTERVAL 每隔 INTERVAL 秒打印未完成分配及其调用堆栈的摘要。默认间隔为 5 秒。
COUNT 打印未完成的分配汇总 COUNT 次,然后退出。

我们在另外的窗口中打开bcc调试:

source scl_source enable devtoolset-7 llvm-toolset-7
python3 /data01/chenyc/bcc/tools/memleak.py -p `pidof bcc_test` 10

以上命令是使用memleak分析 bcc_test程序,且每隔10秒收集一次内存信息,我们得到结果如下:

[root@ck08 sbin]# python3 /data01/chenyc/bcc/tools/memleak.py -p `pidof bcc_test` 10
Attaching to pid 158449, Ctrl+C to quit.
[10:10:43] Top 10 stacks with outstanding allocations:
        6225920 bytes in 95 allocations from stack
                0x000000000040057c      func1+0x1a [bcc_test]
                0x0000000000400560      main+0x19 [bcc_test]
                0x00007efe98a9c555      __libc_start_main+0xf5 [libc-2.17.so]
[10:10:53] Top 10 stacks with outstanding allocations:
        12779520 bytes in 195 allocations from stack
                0x000000000040057c      func1+0x1a [bcc_test]
                0x0000000000400560      main+0x19 [bcc_test]
                0x00007efe98a9c555      __libc_start_main+0xf5 [libc-2.17.so]
[10:11:03] Top 10 stacks with outstanding allocations:
        19333120 bytes in 295 allocations from stack
                0x000000000040057c      func1+0x1a [bcc_test]
                0x0000000000400560      main+0x19 [bcc_test]
                0x00007efe98a9c555      __libc_start_main+0xf5 [libc-2.17.so]
[10:11:13] Top 10 stacks with outstanding allocations:
        25886720 bytes in 395 allocations from stack
                0x000000000040057c      func1+0x1a [bcc_test]
                0x0000000000400560      main+0x19 [bcc_test]
                0x00007efe98a9c555      __libc_start_main+0xf5 [libc-2.17.so]
[10:11:23] Top 10 stacks with outstanding allocations:
        32440320 bytes in 495 allocations from stack
                0x000000000040057c      func1+0x1a [bcc_test]
                0x0000000000400560      main+0x19 [bcc_test]
                0x00007efe98a9c555      __libc_start_main+0xf5 [libc-2.17.so]
[10:11:33] Top 10 stacks with outstanding allocations:
        38993920 bytes in 595 allocations from stack
                0x000000000040057c      func1+0x1a [bcc_test]
                0x0000000000400560      main+0x19 [bcc_test]
                0x00007efe98a9c555      __libc_start_main+0xf5 [libc-2.17.so]

从以上信息可以知道,bcc每隔10秒在屏幕上输出当前这段时间内内存的申请和释放情况,从上面的信息可以读出,10:10:53195次内存申请,总内存大小为12779520字节,即12480kb,主要发生在func1函数中。

10:11:03295次内存申请,总内存大小为19333120字节,即18880kb,主要也发生在func1函数中。也就是说,在10秒的时间内,func1函数申请的内存次数多了100次,内存多了18880-12480 = 6400kb。这与实际的代码是符合的。

当我们持续观察一段时间,发现allocations的次数越来越多,并没有明显的下降,且调用栈都是相同的地方,基本就可以判断是这个地方内存泄漏了。

优缺点分析

从上面的例子可以看出,bcc较之mtrace,使用起来更加麻烦一些,它配置繁琐,依赖众多,而且因为要指定pid,它要求必须是一个长期运行的程序,否则还没运行就已经结束了,没有办法指定到pid。它观察的是运行期的状态,判断是否出现内存泄漏,更加依赖人为的分析判断。所以它比较适合于那种已经确定程序有内存泄漏,但是不知道泄漏在什么地方的场景,可以使用该工具快速定位出问题所在。

但是从上面的例子中也可以看出,虽然memleak可以打印出调用栈的层级关系,但是没有更加准确的代码行号,因此当代码封装比较深时,可能并不能非常好的分析出问题所在。

而且,memleak分析内存,是使用eBPF技术直接侵入内核,因此,在运行了memleak之后,CPU的占用直接翻了一倍不止。因此,该种方案仅作为开发环境调试手段是不错的,但是能否应用在生产环境,仍然需要慎重考虑。

笔者之所以有使用memleak分析内存的需求,自然是基于memleak自身的一些优点。首先是memleak目前已支持了mmap函数(memleak added mmap and munmap tracing by yuzhichang · Pull Request #3866 · iovisor/bcc (github.com), 我司同事提交的PR),这对于像笔者开发的代码中实用到apr_pool内存池的场景有着得天独厚的支持。

其次,因为内存池的引入,因为在程序开始申请内存池,在程序结束销毁内存池,对于一个应用程序的生命周期来说,只要程序正常结束,是没有任何内存泄漏的(因为在销毁内存池时内存最终会被释放掉),但是在程序运行的过程中可能在不断地申请内存,这种情况一般内存泄漏检测工具并不能很好地检测出来,所以更加依赖运行时的分析。

因此,对于memleak的使用,大多时候因场景而异,大家酌情使用即可。


本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师

目录
相关文章
|
5月前
|
存储 网络虚拟化 索引
【OSTEP】分页(Paging) | 页表中究竟有什么 | 页表存在哪 | 内存追踪
【OSTEP】分页(Paging) | 页表中究竟有什么 | 页表存在哪 | 内存追踪
47 0
|
3月前
|
架构师 Unix Linux
内存泄露专题(4)mtrace内存追踪
内存泄露专题(4)mtrace内存追踪
36 0
|
监控 Java Android开发
记录一次内存泄漏追踪(二)
记录一次内存泄漏追踪(二)
|
存储 Java 容器
记录一次内存泄漏追踪(一)
记录一次内存泄漏追踪(一)
|
4天前
|
存储
数据在内存中的存储之整数存储
数据在内存中的存储之整数存储
12 0
|
1月前
|
存储 JSON 监控
Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
【2月更文挑战第30天】Higress Controller**不是将配置信息推送到Istio的内存存储里面的**。
15 1
|
1月前
|
存储 C语言
C语言--------数据在内存中的存储
C语言--------数据在内存中的存储
26 0
|
4天前
|
存储 算法
【三种方法】求一个整数存储在内存中二进制中的1的个数附两道课外练习题
【三种方法】求一个整数存储在内存中二进制中的1的个数附两道课外练习题
7 0
|
11天前
|
存储 NoSQL Oracle
Oracle 12c的内存列存储:数据的“闪电侠”
【4月更文挑战第19天】Oracle 12c的内存列存储以超高速度革新数据处理,结合列存储与内存技术,实现快速查询与压缩。它支持向量化查询和并行处理,提升效率,但需合理配置以平衡系统资源。作为数据管理员,应善用此功能,适应业务需求和技术发展。
|
19天前
|
存储 编译器 C语言
爱上C语言:整型和浮点型在内存中的存储(进制转换,原码,反码,补码以及大小端)
爱上C语言:整型和浮点型在内存中的存储(进制转换,原码,反码,补码以及大小端)