Install bcc and use memleak on Ubuntu22.04
BCC (BPF Compiler Collection) 是一个强大的工具集,用于开发和运行基于 eBPF 的系统性能分析和网络监控工具。最近需要在为新的运行于 Ubuntu22.04 之上的服务排查内存泄漏的问题,而在 Ubuntu 系统中,虽然可以通过 apt 安装 BCC,但存在很多诡异的兼容性问题,通过源码编译安装的方式能获取最新功能并解决版本兼容问题。这里记录 bcc 在 Ubuntu22.04 下进行编译安装,并进行内存泄漏检测的全部过程。
环境准备
sudo su
apt update && apt upgrade -y
# 安装必要依赖
apt install -y bison build-essential cmake flex git libedit-dev \
libllvm14 llvm-14-dev libclang-14-dev python3.11 zlib1g-dev libelf-dev \
libfl-dev python3-distutils pip zip arping netperf iperf3
确认 python 环境,安装必备包:
pip3 install setuptools
- 安装内核头文件
bcc 需要依赖 Linux 内核的头文件,在 orb 虚拟机下,通过 uname -r 获取到的内核版本有特殊的后缀,无法直接安装。
如我的 orb 虚拟机:
root@ubuntu22:~/tools# uname -r
6.13.7-orbstack-00283-g9d1400e7e9c6
# 通过 apt install lin ux-headers-$(uname -r) 是查找不到对应的安装包的
root@ubuntu22:~/tools# apt install -y linux-header-$(uname -r)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
E: Unable to locate package linux-header-6.13.7-orbstack-00283-g9d1400e7e9c6
E: Couldn't find any package by glob 'linux-header-6.13.7-orbstack-00283-g9d1400e7e9c6'
如果通过 apt install linux-headers-$(uname -r) 安装报错,则可以通过安装通用内核头文件版本进行替代:
apt install -y linux-headers-generic
源码编译步骤
获取源码
git clone https://github.com/iovisor/bcc.git
cd bcc
git checkout v0.34.0
编译
依次执行如下命令:
mkdir build; cd build
cmake ..
make
make install
cmake -DPYTHON_CMD=python3 ..
pushd src/python/
make
make install
popd
验证安装
# 依然在 bcc/build 目录下,执行 ./examples/cpp/HelloWorld 输出如下内容即表示编译安装 c++版本可以正常运行
root@:~/workspace/tools/bcc/build# ./examples/cpp/HelloWorld
Starting HelloWorld with BCC 0.34.0+1f63ae6e
<...>-357844 [002] ....1 1312511.496364: bpf_trace_printk: Hello, World! Here I did a sys_clone call!
# 验证 python 工具是否可用,输出如下内容标识正常可用
(base) root@iZ2ze8gd919c5qtgeqbovnZ:~/workspace/tools/bcc/build# python3 ../examples/hello_world.py
b' <...>-356665 [000] ....1 1312599.534997: bpf_trace_printk: Hello, World!'
b''
b' <...>-3542460 [008] ....1 1312599.535595: bpf_trace_printk: Hello, World!'
b''
b' flannel-3542460 [008] ....1 1312599.535643: bpf_trace_printk: Hello, World!'
b''
b' flannel-3542460 [008] ....1 1312599.535675: bpf_trace_printk: Hello, World!'
b''
b' <...>-3542462 [011] ....1 1312599.535707: bpf_trace_printk: Hello, World!'
b''
b' cri-dockerd-356665 [000] ....1 1312599.536306: bpf_trace_printk: Hello, World!'
b''
b' <...>-3542465 [008] ....1 1312599.536762: bpf_trace_printk: Hello, World!'
b''
b' portmap-3542465 [008] ....1 1312599.536807: bpf_trace_printk: Hello, World!'
b''
b' portmap-3542465 [008] ....1 1312599.536850: bpf_trace_printk: Hello, World!'
常见问题
cmake 时报 Kernel headers: KERNELHEADERS_DIR-NOTFOUND
cmake 找不到 linux-headers 目录,如果明确已经安装了,确依然报此问题,则在 cmake .. 时指定 linux-headers-generic 的安装目录即可,如:
cmake .. -DKERNELHEADERS_DIR="/usr/include/x86_64-linux-gnu"
make 时报 python3 相关错误:ModuleNotFoundError
如下:
Traceback (most recent call last):
File "/home/chengaoyuan/opensource/bcc/build/src/python/bcc-python3/setup.py", line 3, in <module>
from setuptools import setup
ModuleNotFoundError: No module named 'setuptools'
make[2]: *** [src/python/CMakeFiles/bcc_py_python3.dir/build.make:84: src/python/bcc-python3/dist/bcc-0.34.0+d7667cf9.tar.gz] Error 1
make[1]: *** [CMakeFiles/Makefile2:1047: src/python/CMakeFiles/bcc_py_python3.dir/all] Error 2
make: *** [Makefile:146: all] Error 2
原因为系统的 Python 环境可能被自己折腾乱了,直接手动补充安装即可:
apt install -y pip
pip3 install setuptools
找不到lbbcc.so.o 中某个符号
执行 hello_world.py 脚本输出报错类似如下:
AttributeError: /lib/x86_64-linux-gnu/libbcc.so.0: undefined symbol: bpf_module_create_b
原因为 python3 中加载以来的 libbcc.so 版本与编译安装的不匹配导致,将 bcc/build 中生成的对 python3 进行覆盖即可:
# 进入 bcc/build 目录
cp -rf ./src/python/bcc-python3/bcc/* /usr/lib/python3/dist-packages/bcc/
找不到 asm/types.h 头文件
root@ubuntu22:~/tools# /usr/share/bcc/examples/hello_world.py)
In file included from <built-in>:2:
In file included from /virtual/include/bcc/bpf.h:12:
In file included from include/linux/types.h:6:
include/uapi/linux/types.h:5:10: fatal error: 'asm/types.h' file not found
#include <asm/types.h>
^~~~~~~~~~~~~
1 error generated.
Traceback (most recent call last):
File "/usr/share/bcc/examples/hello_world.py", line 12, in <module>
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/bcc/__init__.py", line 505, in __init__
raise Exception("Failed to compile BPF module %s" % (src_file or "<text>"))
Exception: Failed to compile BPF module <text>
在 orb 下的 ubuntu 虚拟机中遇到的,在 orbstack 的 ubuntu 下暂时未解决,网上找的解决方案基本是两种,可以尝试下,不过在我的 orbstack ubuntu 下不生效。 1、主动link /usr/include/x86_64-linux-gnu/asm 到 /usr/include/asm,也就是软链接过去。 2、另一种方案是通过安装 gcc-multilib 进行解决:apt-get install -y gcc-multilib。
内存泄漏检测
示例代码
命名为 test.cpp。
#include <unistd.h>
#include <iostream>
#include <vector>
static std::vector<char*> g_vec;
void memory_test() {
char* ptr = new char[1024];
char* ptr2 = (char *)malloc(111);
delete[] ptr;
g_vec.push_back(ptr2);
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
void my_func (int num) {
(void)num;
memory_test();
sleep(1);
}
int main() {
// 模拟内存泄漏
for(int i = 0; i < 1000; ++i) {
my_func(5);
}
sleep(30);
for (auto itor : g_vec) {
free(itor);
}
return 0;
}
静态编译链接方式检测
使用静态方式进行编译:
g++ -g -static test.cpp -o test.exe
生成的 test.exe 二进制文件无任何依赖,所以系统调用的一些接口符号均在 test.exe 文件中。
通过 memleak 进行检测(需要 root 权限):
# -O 为指定符号文件; -p 为指定进程的 ID
memleak -O test.exe -p ${pid}
输出内容:
(base) root@iZ2ze8gd919c5qtgeqbovnZ:~/workspace/tools/bcc/build# ps -aef | grep test
root 3553018 3552090 0 21:56 pts/0 00:00:00 ./test.exe
root 3553054 3357603 0 21:56 pts/10 00:00:00 grep --color=auto test
(base) root@iZ2ze8gd919c5qtgeqbovnZ:~/workspace/tools/bcc/build# memleak-bpfcc -O /root/workspace/debug/test.exe -p 3553018
Attaching to pid 3553018, Ctrl+C to quit.
[21:56:42] Top 10 stacks with outstanding allocations:
555 bytes in 5 allocations from stack
memory_test()+0x33 [test.exe]
my_func(int)+0x14 [test.exe]
main+0x2e [test.exe]
__libc_start_call_main+0x6a [test.exe]
[21:56:47] Top 10 stacks with outstanding allocations:
256 bytes in 1 allocations from stack
operator new(unsigned long)+0x1c [test.exe]
std::allocator_traits<std::allocator<char*> >::allocate(std::allocator<char*>&, unsigned long)+0x2c [test.exe]
std::_Vector_base<char*, std::allocator<char*> >::_M_allocate(unsigned long)+0x2e [test.exe]
void std::vector<char*, std::allocator<char*> >::_M_realloc_insert<char* const&>(__gnu_cxx::__normal_iterator<char**, std::vector<char*, std::allocator<char*> > >, char* const&)+0x95 [test.exe]
std::vector<char*, std::allocator<char*> >::push_back(char* const&)+0x7c [test.exe]
memory_test()+0x60 [test.exe]
my_func(int)+0x14 [test.exe]
main+0x2e [test.exe]
__libc_start_call_main+0x6a [test.exe]
1110 bytes in 10 allocations from stack
memory_test()+0x33 [test.exe]
my_func(int)+0x14 [test.exe]
main+0x2e [test.exe]
__libc_start_call_main+0x6a [test.exe]
[21:56:52] Top 10 stacks with outstanding allocations:
256 bytes in 1 allocations from stack
operator new(unsigned long)+0x1c [test.exe]
std::allocator_traits<std::allocator<char*> >::allocate(std::allocator<char*>&, unsigned long)+0x2c [test.exe]
std::_Vector_base<char*, std::allocator<char*> >::_M_allocate(unsigned long)+0x2e [test.exe]
void std::vector<char*, std::allocator<char*> >::_M_realloc_insert<char* const&>(__gnu_cxx::__normal_iterator<char**, std::vector<char*, std::allocator<char*> > >, char* const&)+0x95 [test.exe]
std::vector<char*, std::allocator<char*> >::push_back(char* const&)+0x7c [test.exe]
memory_test()+0x60 [test.exe]
my_func(int)+0x14 [test.exe]
main+0x2e [test.exe]
__libc_start_call_main+0x6a [test.exe]
1665 bytes in 15 allocations from stack
memory_test()+0x33 [test.exe]
my_func(int)+0x14 [test.exe]
main+0x2e [test.exe]
__libc_start_call_main+0x6a [test.exe]
每 10 秒钟输出一次,Top 10 stacks with outstanding allocations: 下面为内存申请且未释放的前 10 个堆栈信息。
非静态编译链接方式检测
使用非静态方式进行编译:
g++ -g test.cpp -o test.exe
生成的 test.exe 二进制文件有系统标准库依赖,如下:
(base) root@:~/workspace/debug# ldd test.exe
linux-vdso.so.1 (0x00007fffab6d7000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f3ca7fb6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3ca7d8d000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f3ca7ca6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3ca81f3000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3ca7c86000)
我们要动态追踪的系统调用 malloc、free 等在 libc.so 中,所以通过 -O 指定的符号文件在 /lib/x86_64-linux-gnu/libc.so.6 中。
通过 memleak 进行检测(需要 root 权限):
# -O 为指定符号文件; -p 为指定进程的 ID
memleak -O /lib/x86_64-linux-gnu/libc.so.6 -p ${pid}
输出内容:
同静态编译链接方式内容。
精确定位行号
有时候仅仅输出函数名称还不足够明确定位代码具体位置(如:函数体较长,存在多个申请内存的操作),那么就需要通过 objdump 和 addr2line 工具对函数地址进行解析。
这两个工具需要安装 binutils,安装: apt-get install -y binutils`。
例如需要排查上述一处 memory_test 中的一处内存泄漏:
555 bytes in 5 allocations from stack
memory_test()+0x33 [test.exe]
- 首先通过 objdump 找到二进制中函数地址
$ objdump -d test.exe | grep memory_test
0000000000404605 <_Z11memory_testv>:
404641: 74 0c je 40464f <_Z11memory_testv+0x4a>
40469e: 74 05 je 4046a5 <_Z11memory_testv+0xa0>
4046b6: e8 4a ff ff ff call 404605 <_Z11memory_testv>
000000000040481f <_GLOBAL__sub_I__Z11memory_testv>:
_Z11memory_testv 为 c++ 编译器为 memory_test 生成的修饰名,_Z11 表示这是一个函数,v 表示函数参数为空。
找到 memory_test 的函数地址为404605 ,内存泄漏位置的函数偏移为 0x33,则内存地址为 0x404605 + 0x33 = 0x404638,所以具体的泄漏代码位置为:
$ addr2line -e test.exe 0x404638
/mnt/data/debug/test.cpp:9
通过 nm 也可以获取指定函数的地址,如: nm test.exe grep memory_test
与 ASAN(Address Sanitizer) 的区别
通过 bcc 的 memleak 进行内存泄漏的检测,即可以对明显的 new/malloc 与 delete/free 的不对称进行检测,bcc 的 memleak 的主要优势在于可以在不重启进程,同时在不对进程造成任何性能影响的情况下,对内存进行检测。 还有就是对类似示例代码中对 vector 不断的增加数据导致内存不断的增长,但实际数据并为泄漏,在程序退出时还会删除的这种场景是不同于 ASAN 的检测的(ASAN 不检测这种场景),这类问题反而是线上最难解决的内存泄漏的问题。
但是 bcc 的 memleak 没有 ASAN 的内存越界、践踏的检测,所以在开发测试阶段应该用 ASAN 进行验证,及时找出内存的错误操作,消除 crash 的风险。
TODO
1、 解决 orbstack 下 ubuntu 源码安装 bcc。
2、 将 ubuntu 下源码编译安装的 bcc tools进行 dep 打包,实现一键安装。
参考资料
BCC 官方文档:https://github.com/iovisor/bcc/blob/master/INSTALL.md
版权声明: 如无特别声明,本文版权归 Mr Chen 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:《 Install bcc and use memleak on Ubuntu22.04 》
本文链接:https://gbcpp.github.io/notes/install-bcc.html
本文最后一次更新为 天前,文章中的某些内容可能已过时!