一般情况下,在高优化等级时,编译器会把栈帧破坏掉,导致生成的火焰图调用关系是错误的。因此我们需要用编译选项-fno-omit-frame-pointer
编译自己的代码,从而使得自己的代码不破坏栈帧。
由于系统库通常是采用高优化等级编译的,因此调用系统库函数时仍然会破坏栈帧。这时需要用-fno-omit-frame-pointer
重新编译系统库。
¶ glibc
¶ 安装依赖
可以用nix安装,不需要root权限:
nix-env -iA nixpkgs.bison |
Nix包管理器安装和使用教程:使用国内源安装和使用Nix包管理器
¶ 获取源码
glibc每六个月发布类似于2.31这样的minor
version,然后在这里fork出这个minor
version的branch,比如release/2.31/master
,以后的bug
fix都commit到这个branch上,不再单独发布bug fix
version。因此我们可以先clone glibc的repo,然后checkout到目标minor
version的branch即可。
debian
12上glibc版本是2.36-9+deb12u7
,所以应该获取2.36
的源码。
debian
11上glibc版本是2.31-13+deb11u7
,应该获取2.31
的源码。
git clone https://mirrors.tuna.tsinghua.edu.cn/git/glibc.git |
¶ 编译
mkdir -p build |
这里面包含了这些库:
ld-linux-x86-64.so.2 |
¶ gcc
如果要编译C++程序的话,还需要编译gcc
,因为libstdc++
是gcc
提供的。
¶ 获取源码
gcc的功能更新会更新major version,后面的minor version都是bug fix version,官方文档:https://gcc.gnu.org/develop.html
debian 12
上的gcc版本是12.2.0-14
,这里应该编译安装它的最新bug
fix版本12.4.0
。
debian 11 上的gcc版本是10.2.1
,这里应该编译安装它的最新bug
fix版本10.5.0
。
version=12.4.0 |
或者也可以用git获取最新的bug fix版本:
git clone https://mirrors.tuna.tsinghua.edu.cn/git/gcc.git |
¶ 安装依赖
sudo apt install libgmp-dev libmpfr-dev libmpc-dev |
或者自动下载安装依赖:
./contrib/download_prerequisites |
¶ 用系统自带的glibc编译
理论上这里使用刚刚编译的glibc编译gcc最好,但是这需要使用交叉编译的方法,太复杂了,我试了很久都没有成功。由于我们之前编译安装的glibc跟系统里自带的glibc是兼容的(只是修了一些BUG),所以这里可以直接用系统自带的glibc编译。
mkdir -p build |
参考:How to edit and re-build the GCC libstdc++ C++ standard library source?
¶ 编译选项
以后我们用这套编译选项来编译其他软件:
INSTALL_ROOT=$HOME/no-omit-frame-pointer |
-Wl,-rpath
: https://stackoverflow.com/a/6562437/13688160
-Wl,--dynamic-linker
: Specifying the
dynamic linker / loader to be used when launching an executable on
Linux
¶ 常见库的编译
先export上面说的编译选项,然后就可以用正常的方法编译了。
¶
libgflags
git clone https://github.com/gflags/gflags.git |
¶ liburcu
Debian
12上版本是0.13.2-1
,所以这里应该下载安装0.13.3
Debian 11上版本是0.12.2-1
,所以这里应该下载安装0.12.5
version=0.13.3 |
¶ boost
Debian 11和Debian
12的版本都是1.74.0.3
,所以这里应该安装1.74.0
。
编译安装教程:https://www.boost.org/doc/libs/1_74_0/more/getting_started/unix-variants.html
wget https://archives.boost.io/release/1.74.0/source/boost_1_74_0.tar.bz2 |
编译安装所有库:
./b2 --prefix=$INSTALL_ROOT install link=shared cflags="$CFLAGS" cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" |
也可以选择性编译安装某些库。查看有哪些库:
./b2 --show-libraries |
编译安装某个库,比如program_options
,加上--with-program_options
即可:
./b2 --with-program_options --prefix=$INSTALL_ROOT install link=shared cflags="$CFLAGS" cxxflags="$CXXFLAGS" linkflags="$LDFLAGS" |
文档写的--with-libraries=library-name-list
应该是bootstrap.sh
的参数,没试过。
¶ xxhash
wget https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.2.tar.gz |
¶ liburing
Debian
12上的版本是2.3-3
,所以应该安装2.3
。
wget https://git.kernel.dk/cgit/liburing/snapshot/liburing-2.3.tar.bz2 |
¶ tbb
wget https://github.com/oneapi-src/oneTBB/archive/refs/tags/v2021.13.0.tar.gz |
¶ libfmt
Debian12上的版本是9.1.0
wget https://github.com/fmtlib/fmt/archive/refs/tags/9.1.0.tar.gz |
¶
(可选)打包no-omit-frame-pointer
编译环境
可以在一台机器上把no-omit-frame-pointer
的编译器和常见库都编译好,然后打包到别的机器上使用。但是要注意在目标机器解压后需要修改一下rpath:
cd no-omit-frame-pointer |
¶ 编译目标应用
¶ CMake项目
export上面说的编译选项后就可以照常编译了。
¶ 直接使用gcc
hello.c
:
|
CC hello.c -o hello $CFLAGS $LDFLAGS |
linux-vdso.so.1 (0x00007ffc37f52000) |
可以看到这里用的是我们自己编译的glibc
参考:
Where is linux-vdso.so.1 present on the file system
¶ 直接使用g++
hello.cpp
:
|
CXX hello.cpp -o hello $CXXFLAGS $LDFLAGS |
linux-vdso.so.1 (0x00007ffff89fe000) |
¶ 采样
perf record --call-graph=fp 命令 参数... |
然后生成火焰图:perf.data生成火焰图
¶
不推荐:--call-graph=dwarf
如果不想重新编译系统库的话,可以使用linux
perf的--call-graph=dwarf
,这样perf会把一部分栈内存保存下来,然后通过后处理来unwind,从而得到调用栈。但是这种方法仍然存在调用关系错乱的问题,而且采样的时间开销和空间开销很大,采样频率需要稍微调低一点。
参考文献:
https://users.rust-lang.org/t/opt-level-2-removes-debug-symbols-needed-in-perf-profiling/16835/7
¶ 相关
Consider not omitting frame pointers by default on targets with many registers