本文涉及环境为Ubuntu 20.04 x86_64、Ubuntu 20.04 ARM64、Linux Kernel源码Tag v5.9.11。
Perf工具对于Linux性能分析非常有用,在PC的Linux发行版上,只需要使用相应系统的包管理命令安装即可,例如,在Ubuntu系统上,直接使用sudo apt-get install -y linux-tools-common linux-tools-generic
命令安装,其中linux-tools-common
提供了各种linux tools
的包装器,如acpidbg、perf
等,它们会在运行时引用到适合当前内核版本的工具,而linux-tools-generic
是个虚拟包,指向当前发行版最新的linux tools
版本,这里面包含了真正要执行的命令。
常规交叉编译方法及其问题
对于嵌入式系统,通常需要自己编译perf,perf的源码已经集成到Linux Kernel中,以ARM64位体系架构为例,常规的交叉编译方法是执行如下命令:
1 2 3 4 5 $ sudo apt-get install gcc-aarch64-linux-gnu flex libssl-dev $ cd KERNEL_SRC/tools/perf $ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- V=1 VF=1 |& tee build.log $ file perf perf: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=eebe3fc457239da83b56a90b27b251df10160b3b, for GNU/Linux 3.7.0, with debug_info, not stripped
从上面的file命令输出可以看到这个perf程序是动态链接的,如此就要求交叉编译时引用的动态库与目标板上运行时的动态库是兼容的,省事的做法是,编译perf时的环境和构建目标系统的环境是一致的,这包括宿主机操作系统版本和工具链版本等。否则在目标板上运行时可能出现类似下面的错误:
1 2 $ perf perf: /lib/libc.so.6: version `GLIBC_2.28' not found (required by perf)
然而,make的输出可能还给出了其它信息,查看make命令最开始的输出,可以看到类似以下的内容:
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 Auto-detecting system features: ... dwarf: [ OFF ] ... dwarf_getlocations: [ OFF ] ... glibc: [ on ] ... libbfd: [ OFF ] ... libcap: [ OFF ] ... libelf: [ OFF ] ... libnuma: [ OFF ] ... numa_num_possible_cpus: [ OFF ] ... libperl: [ OFF ] ... libpython: [ OFF ] ... libcrypto: [ OFF ] ... libunwind: [ OFF ] ... libdw-dwarf-unwind: [ OFF ] ... zlib: [ OFF ] ... lzma: [ OFF ] ... get_cpuid: [ OFF ] ... bpf: [ on ] ... libaio: [ on ] ... libzstd: [ OFF ] ... disassembler-four-args: [ OFF ] ... backtrace: [ on ] ... eventfd: [ on ] ... fortify-source: [ on ] ... sync-compare-and-swap: [ on ] ... get_current_dir_name: [ on ] ... gettid: [ on ] ... libelf-getphdrnum: [ OFF ] ... libelf-gelf_getnote: [ OFF ] ... libelf-getshdrstrndx: [ OFF ] ... libelf-mmap: [ OFF ] ... libpython-version: [ OFF ] ... libslang: [ OFF ] ... libslang-include-subdir: [ OFF ] ... pthread-attr-setaffinity-np: [ on ] ... pthread-barrier: [ on ] ... reallocarray: [ on ] ... stackprotector-all: [ on ] ... timerfd: [ on ] ... sched_getcpu: [ on ] ... sdt: [ OFF ] ... setns: [ on ] ... file-handle: [ on ] Makefile.config:389: No libelf found. Disables 'probe' tool, jvmti and BPF support in 'perf record' . Please install libelf-dev, libelf-devel or elfutils-libelf-devel Makefile.config:556: No sys/sdt.h found, no SDT events are defined, please install systemtap-sdt-devel or systemtap-sdt-dev Makefile.config:631: Disabling post unwind, no support found. Makefile.config:697: No libcrypto.h found, disables jitted code injection, please install openssl-devel or libssl-dev Makefile.config:713: slang not found, disables TUI support. Please install slang-devel, libslang-dev or libslang2-dev Makefile.config:759: Missing perl devel files. Disabling perl scripting support, please install perl-ExtUtils-Embed/libperl-dev Makefile.config:791: No 'python-config' tool was found: disables Python support - please install python-devel/python-dev Makefile.config:875: No liblzma found, disables xz kernel module decompression, please install xz-devel/liblzma-dev Makefile.config:888: No libzstd found, disables trace compression, please install libzstd-dev[el] and/or set LIBZSTD_DIR Makefile.config:899: No libcap found, disables capability support, please install libcap-devel/libcap-dev Makefile.config:912: No numa.h found, disables 'perf bench numa mem' benchmark, please install numactl-devel/libnuma-devel/libnuma-dev Makefile.config:967: No libbabeltrace found, disables 'perf data' CTF format support, please install libbabeltrace-dev[el]/libbabeltrace-ctf-dev
以上表明,有多个库找不到,以致相应的特性得到支持,这可能导致最后使用时出现问题,例如:所捕获数据用来生成火焰图时有些函数名是unknown(这个问题详见后文参考资料)。
要生成一个能在目标系统上运行的,支持所有特性的perf版本,基本上有这些要求:
编译时与运行时内核版本一致,这个一般不难满足,直接使用构建目标系统时用到的内核源码或者使用一个相同版本号的标准内核源码即可
编译成静态链接的版本,这样就无需处理目标系统上已有的支持库是否缺失或者与编译时链接库版本不一致的问题
编译时尽可能提供所有特性需要依赖的库
这里面最难的就是提供所有特性需要依赖的静态库,手工编译源码安装一些工具的开发者可能都会有印象,支持库的依赖解决是很麻烦的,各种库的版本要匹配,对编译器的版本可能还存在依赖。正是因为这样,各大Linux发行版通过各自的方式解决了包依赖,使得安装软件非常方便。那么,在嵌入式开发中,如何处理这一情况?Buildroot及Yocto项目都提供了构建一个完整的、定制化的嵌入式Linux系统,包括了根文件系统、工具链等。然而,在perf这个实例里,Buildroot目前还缺少上述日志中列出的多个包,而Yocto至少已知构建速度很慢、学习曲线很陡峭。其它的类似项目还包括Cross Linux From Scratch ,可能也会有类似的问题。
利用Ubuntu ARM64 Docker的解决方案
Ubuntu是开发人员中常用的系统,提供了非常丰富的软件包,这里面包括许多的支持库,这些库不仅有交叉编译的版本,还有静态链接的版本(包名通常是PackageName-dev-ARCH-cross),如果能够利用Ubuntu丰富的库来生成静态链接的perf,可以大大减少我们自己去生成这些依赖库的时间。
平时,我们在x86或x86_64体系架构上运行Ubuntu,虽然可以安装各种以-cross结尾的库来交叉编译,但逐一安装就很繁琐了,事实上Ubuntu如果已经支持某个包,想要自己编译安装它,是很简单的,以i2c-tools为例,大概执行以下几步就能搞定
1 2 3 4 5 6 apt-get source i2c-tools sudo apt-get build-dep i2c-tools apt-get source --compile i2c-tools
当然我们平时不需要这么做,因为直接sudo apt-get install i2c-tools
就能下载预编译好的包安装了。这里对我们有用的是apt-get build-dep
命令可以轻而易举地帮我们解决大部分依赖问题,通过一定的配置(sudo dpkg --add-architecture arm64
),还可以使用sudo apt-get build-dep -a arm64 PackageName
来安装其它体系架构的依赖包。然而,Ubuntu同样提供了其它架构的版本,查看Ubuntu 21.04 Release 页面,已经支持ARM、ARM64、PowerPC、System Z等多种体系架构。因此,如果我们在ARM64位版本的Ubuntu里安装相应的依赖库,并执行编译,就不存在交叉编译的问题,而是直接生成ARM64位版本。所以,现在的首要问题是能够运行一个ARM64位版本的Ubuntu系统,从头安装这样的一个系统当然很麻烦,好在我们可以使用docker技术,在x86_64位Ubuntu中,执行ARM64位版本的Ubuntu系统。在Docker Hub 的arm64v8/ubuntu 仓库中,已经提供了Ubuntu的多个ARM64位版本,这些容器不能直接在x86_64的Linux版本上运行,而是需要使用qemu模拟器来执行它,简单地来说,就是如下的操作:
1 2 3 4 5 6 7 sudo apt-get install -y qemu-user-static docker run -it -v /usr/bin/qemu-aarch64-static:/usr/bin/qemu-aarch64-static \ -v /home:/home \ -v /etc/passwd:/etc/passwd \ -v /etc/group:/etc/group \ -v /etc/shadow:/etc/shadow \ arm64v8/ubuntu:20.04 bash
现在我们已经获得一个Ubuntu 20.04的ARM64位容器,可以通过在容器中运行下列的命令来确认当前体系架构:
接下来,可以在这个容器里安装好编译linux-tools-generic的必要依赖并执行编译:
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 sed -i "s/# deb-src/deb-src/g" /etc/apt/sources.list apt-get update -y apt-get install -y apt-utils bc bison dialog flex python3 python2-minimal default-jdk apt-get build-dep -y linux-tools-generic apt-get install -y libelf-dev apt-get install -y libdw-dev apt-get install -y systemtap-sdt-dev apt-get install -y libssl-dev apt-get install -y libslang2-dev apt-get install -y libperl-dev apt-get install -y python-dev apt-get install -y liblzma-dev apt-get install -y libzstd-dev apt-get install -y libcap-dev apt-get install -y libnuma-dev apt-get install -y libiberty-dev apt-get install -y libbabeltrace-dev apt-get install -y libbabeltrace-ctf-dev apt-get install -y libunwind-dev apt-get install -y libbz2-dev apt-get install -y binutils-dev apt-get install -y libglib2.0-dev apt-get install -y libopencsd-dev apt-get install -y libclang-9-dev clang apt-get install -y llvm-9-dev llvm llvm-9 apt-get install -y libpfm4-dev make V=1 VF=1 FEATURE_TESTS=all NO_LIBBABELTRACE=1 NO_JVMTI=1 |& tee shared.log rm perf make V=1 VF=1 FEATURE_TESTS=all NO_LIBBABELTRACE=1 NO_JVMTI=1 LDFLAGS=-static |& tee static.log
有些在ARM64架构上确实还不支持的,只能保留OFF了,为OFF的feature可能有:
libnuma:较低版本的Ubuntu(如16.04)没有libnuma-dev库,20.04是有的
numa_num_possible_cpus:同上
get_cpuid:尽管在2019年12月13日的一个内核patch中(提交ID:df5a5f3cf24608457bb5e57297dd9f0d528be58f
)已经增加了ARM64版本的get_cpuid支持,但是,Ubuntu 20.04的内核头文件及libgcc-X-dev包尚未提供支持
disassembler-four-args:较早期的binutils-dev(至少 2.26.1前)提供的disassembler函数接口不同于新版本的,导致编译出错
gettid:早期版本的libc6-dev(如2.23)可能并没有提供此函数
libslang-include-subdir:测试libslang的头文件目录布局,并不太影响功能,libslang是用于TUI的,可以不支持
reallocarray:早期版本的libc6-dev(如2.23)可能并没有提供此函数
bionic:这是Android的C库,可以不支持,如果是用于Android,可考虑安装NDK来支持
compile-[x]32:这两个都以生成32位版的perf有关,并不需要
hello:简单的测试程序,这个为OFF,通常只是因为没有对所有feature进行测试而已,如果需要测试所有的,可以给make指定FEATURE_TESTS=all
,也就是make V=1 VF=1 LDFLAGS=-static FEATURE_TESTS=all
gtk2/gtk2-infobar:GTK+ GUI支持,嵌入式系统一般不需要
libbabeltrace:静态链接时,链接参数指定的库不够导致编译错误,可以使用NO_LIBBABELTRACE=1禁止这一支持,以免引起编译错误,或使用下面的补丁
libopencsd:Ubuntu 20.04上的OpenCSD版本较低,为0.12.2,不满足v5.9.11版Kernel对OpenCSD 1.0.0以上的要求,如果需要可以手工下载源码编译安装
libunwind-x86:这里是编译ARM64架构的perf,自然是不支持的
libunwind-x86_64:这里是编译ARM64架构的perf,自然是不支持的
libunwind-arm:这里是编译ARM64架构的perf,自然是不支持的
libunwind-debug-frame-arm:这里是编译ARM64架构的perf,自然是不支持的
llvm:首先,使用llvm-10时编译会出错,原因是函数定义不匹配,需要将/usr/bin/llvm-config
指向llvm9(rm /usr/bin/llvm-config && ln -s ../lib/llvm-9/bin/llvm-config /usr/bin/llvm-config
)。其次,改为llvm9后静态编译,Ubuntu 20.04没有LLVM-9.a这个静态库,若是动态链接,无此问题
clang:同上
libbpf:Ubuntu 20.04尚未提供此库,但在编译过程中,会使用内核源码中生成的tools/lib/bpf/libbpf.a
libdebuginfod:Ubuntu 20.04的libelf-dev尚未提供debuginfod的支持
clang-bpf-co-re:与clang版本有关,已知clang-10是没有问题的,clang-9是有问题的
其它的feature检测,则可能是由于tools/build/feature/Makefile本身定义的规则有问题导致,通常是链接库缺少,在使用Ubuntu 20.04 ARM64版本编译v5.9.11 tag对应的版本时,至少要对内核源码做如下做如下修改,才能使相应的feature检测正确:
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 diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile index 8da2556cdbfa..af8a4caff3ef 100644 --- a/tools/build/feature/Makefile +++ b/tools/build/feature/Makefile @@ -137,7 +137,7 @@ $(OUTPUT)test -libopencsd.bin: DWARFLIBS := -ldw ifeq ($(findstring -static,${LDFLAGS} ),-static) -DWARFLIBS += -lelf -lebl -lz -llzma -lbz2 +DWARFLIBS += -lelf -lebl -ldl -lz -llzma -lbz2 endif $(OUTPUT)test -dwarf.bin: diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 2d6690b30856..123df1d500ff 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -962,6 +962,9 @@ ifndef NO_LIBBABELTRACE CFLAGS += -DHAVE_LIBBABELTRACE_SUPPORT $(LIBBABELTRACE_CFLAGS) LDFLAGS += $(LIBBABELTRACE_LDFLAGS) EXTLIBS += -lbabeltrace-ctf + ifeq ($(findstring -static,${LDFLAGS} ),-static) + EXTLIBS += -lbabeltrace -lglib-2.0 -luuid + endif $(call detected,CONFIG_LIBBABELTRACE) else msg := $(warning No libbabeltrace found, disables 'perf data' CTF format support, please install libbabeltrace-dev[el]/libbabeltrace-ctf-dev);
在做完上述修改后,重新执行
1 2 3 4 5 $ make V=1 VF=1 FEATURE_TESTS=all NO_JVMTI=1 |& tee shared.log $ rm perf $ make V=1 VF=1 FEATURE_TESTS=all NO_JVMTI=1 LDFLAGS=-static |& tee static.log $ ls -lh perf -rwxr-xr-x 1 root root 38M May 26 18:49 perf
看见的将是大片的[ on ]而不再是大片的[ OFF ],如果编译顺利完成的话,得到的就是一个较完整的静态链接版perf了,而其文件大小将远远大于未启用这些特性的版本。尽管本例中并不完全支持所有特性,但剩余的通过编译源码的方式来处理,已经不再具备很大的工作量了。同时需要注意的:这些只是在用户空间的perf程序中集成了相应的特性,具体到某些功能能不能支持,还依赖于内核版本。这也是为什么Ubuntu系统对一些Linux tools做了包装脚本的原因,包装脚本会在运行时检测当前内核版本来确定运行哪一个目录下的程序。
可能遇到的问题
编译过程中提示如下错误:
1 2 3 /usr/bin/make FIXDEP=1 -f Makefile.perf /usr/bin/sh: line 0: command : -c: invalid option command : usage: command [-pVv] command [arg ...]
当没有安装python2时,tools/perf/Makefile.config
的$(if $(call get-executable,$(PYTHON)-config),$(PYTHON)-config,python-config)
将执行出错,产生这条提示,使用apt-get install -y python2-minimal
安装好python2后即可解决。
编译过程中提示如下错误:
1 2 dangerous relocation: unsupported relocation /usr/bin/ld: BFD (GNU Binutils for Ubuntu) 2.24 internal error, aborting at ../../bfd/elfnn-aarch64.c line 4331 in elf64_aarch64_final_link_relocate
这个是binutils包的问题,2.31版本后已经解决,这就是为什么使用Ubuntu 20.04的原因,这一版本里自带的binutils是2.34版本。
编译过程中提示如下错误:
1 2 gcc -g -Wall -fPIC -I. -I.. -I /home/oak/work/perf/tools/include -D_GNU_SOURCE -shared -static -Wl,-z,noexecstack -lunwind-aarch64 -lunwind -lunwind-aarch64 -Wl,--allow-multiple-definition -Wl,-E -fstack-protector-strong -L/usr/local /lib -L/usr/lib/aarch64-linux-gnu/perl/5.30/CORE -L/usr/lib/python2.7/config-aarch64-linux-gnu -L/usr/lib -Xlinker -export -dynamic -Wl,-O1 -Wl,-Bsymbolic-functions -nostartfiles -o plugin_kmem.so plugin_kmem-in.o /usr/bin/ld: /usr/lib/gcc/aarch64-linux-gnu/9/../../../aarch64-linux-gnu/libc.a(malloc.o): relocation R_AARCH64_ADR_PREL_PG_HI21 against symbol `__free_hook' which may bind externally can not be used when making a shared object; recompile with -fPIC
这里的原因在于生成各个插件时,Makefile只定义了编译成共享库的方式,不单是plugin_kmem.so,其它的插件也一样,包括libperf-jvmti.so等,此时指定了-static选项,而它原本又具有-shared选项,冲突的选项导致了生成这些库失败,解决办法是先编译一次动态链接版本的,把这些共享库都给生成了,而jvmti总是会重新编译,直接使用NO_JVMTI=1禁止,然后再指定-static选项编译perf:
1 2 3 make V=1 VF=1 FEATURE_TESTS=all NO_JVMTI=1 |& tee shared.log rm perf make V=1 VF=1 FEATURE_TESTS=all NO_JVMTI=1 LDFLAGS=-static |& tee static.log
执行过程中提示如下错误:
1 2 3 4 5 6 $ perf record -F 99 -p 1913 -g -- sleep 5 (process:9417): GLib-CRITICAL **: 05:32:39.141: g_hash_table_insert_internal: assertion 'hash_table != NULL' failed (process:9417): GLib-CRITICAL **: 05:32:39.141: g_hash_table_lookup: assertion 'hash_table != NULL' failed (process:9417): GLib-CRITICAL **: 05:32:39.141: g_hash_table_insert_internal: assertion 'hash_table != NULL' failed (process:9417): GLib-CRITICAL **: 05:32:39.141: g_hash_table_lookup: assertion 'hash_table != NULL' failed (process:9417): GLib-CRITICAL **: 05:32:39.141: g_hash_table_insert_internal: assertion 'hash_table != NULL' failed
如果出现这个错误,则说明系统安装的libglib2.0-dev有问题,libglib2.0-dev是GTK的库,简单的办法是不使用这个库,但是,如此一来,Ubuntu 20.04 ARM64版本上编译v5.9.11时需要禁止libbabeltrace,因为系统安装的libbabeltrace使用了libglib2.0-dev中定义的函数,可用的编译命令如下:
1 2 3 make V=1 VF=1 FEATURE_TESTS=all NO_LIBBABELTRACE=1 NO_JVMTI=1 |& tee shared.log rm perf make V=1 VF=1 FEATURE_TESTS=all NO_LIBBABELTRACE=1 NO_JVMTI=1 LDFLAGS=-static |& tee static.log
参考资料