【 PERF 】systemtap 环境搭建与错误排查

• 9 分钟阅读 •

Overview

SystemTap provides free software (GPL) infrastructure to simplify the gathering of information about the running Linux system. This assists diagnosis of a performance or functional problem. SystemTap eliminates the need for the developer to go through the tedious and disruptive instrument, recompile, install, and reboot sequence that may be otherwise required to collect data.
SystemTap provides a simple command line interface and scripting language for writing instrumentation for a live running kernel plus user-space applications. We are publishing samples, as well as enlarging the internal “tapset” script library to aid reuse and abstraction.

工具安装


标准发行版

看自己的选择,发行版的的话本身用的是标准内核,推荐使用命令自动安装,系统会自动匹配适合自己系统的版本。
1、安装systemtap基础包与实时编译所需要的内核配置及头文件(与正在使用的内核一致)

sudo apt-get install systemtap
sudo apt-get install linux-headers-$(uname -r)

测试一下这部分的安装的正确性:

sudo stap -e 'probe begin { printf("Hello, World!\n"); exit() }'

如果能够成功的看到Hello, World!,是否有一分小激动。
但这,仅仅是开始使用这把利器迈出的一小步,如果要高级的调试内核,那还需要debuginfo的援助。
官方这么说:

SystemTap needs information about the kernel in order to place instrumentation in it (in other words, probe it). This information also allows SystemTap to generate the code for the instrumentation.

这部分安装可以这样弄:

wget http://ddebs.ubuntu.com/pool/main/l/linux/linux-image-xxx.ddeb
sudo dpkg -i linux-image-xxx.ddeb

自己去下载对应的调试辅助文件,版本信息需根据自己的系统确定。

本部分参考:
1、Using SystemTap, Installation and Setup
2、Install SystemTap in Ubuntu

自行编译

为什么要自行编译,如果跟随着内核前沿的脚本开发,也就是latest kernel的话,一般发行版的软件支持是跟不上进度的,所以既然内核使用的是最新,那么工具也要是最新才行。
首先要保证自己的内核支持以下配置。

内核配置

CONFIG_DEBUG_INFO
CONFIG_KPROBES
CONFIG_DEBUG_FS
CONFIG_RELAY

克隆systemtap源码

git clone https://sourceware.org/git/systemtap.git

注意:编译源码前请确认所需依赖已安装,因为一旦编译出错了,可能需要重新configuremake

sudo apt-get install libdw-dev
sudo apt-get install gettext

开始编译,编译命令及步骤如下,

./configure
make all -jN
sudo make install

编译这部分可参考:
Build your own systemtap

报错处理

如果读者自行编译的工具发现无法正常运行,相信本小节能帮助您解决,需要注意的是,自行编译的工具也像发行版使用那样,该有的依赖还是要有,也就是linux-headersdebuginfo
这一块出问题比较多,笔者在解决这块问题上也是费了一定的时间。
这两部分怎么弄,我们依次看来,

linux-headers

直接把内核使用的对应源码拷到了设备中,然后建立软链接,让系统能找到它。

ln -s /path/to/your/linux /lib/modules/`uname -r`/build

debuginfo

把带有调试信息的vmlinux拷到/lib/modules/uname -r/build下。

编译正常但加载出错

1)ERROR: module version mismatch (#12 SMP PREEMPT Tue Jan 11 10:49:55 CST 2022 vs #10 SMP PREEMPT Mon Jan 10 14:03:23 CST 2022), release 5.15.0-rc7

由于运行内核与编译内核编译时间不一致造成,可手动强制修改编译时依赖的源代码与运行内核相同,在下面的文件中定义,

/lib/modules/5.15.0-rc7/build/include/generated/compile.h

然后修改下stp源文件重新stap即可,触发重新编译。

2)版本差异

修改版本定义文件,在以下文件中定义,

/lib/modules/5.15.0-rc7/build/include/generated/utsrelease.h

然后参考上条重新编译即可。

补充

今天再次搭建systemtap环境时,运行hello world就一直报错,开始时以为是版本出现了差异(内核版本高于最新的stap能够支持的版本),所以降低内核版本重新编译试了下,将host编译完的代码全部导入到虚拟环境中发现有以下报错,挣扎了几次错误依旧,挑了一下明显的报错如下,

百思不得解,遂跟踪了下stap的源码,发现它的逻辑是,如果vmalloc_node在内核已经定义,那么stap中就不再重新定义,所以这里stap又定义了一遍就惹人怀疑,
相继找出,

output_exportconf(s, o2, "vmalloc_node", "STAPCONF_VMALLOC_NODE");

进而找出这个方法的定义,

void output_exportconf(systemtap_session& s, ofstream& o, const char *symbol,
                     const char *deftrue)
{
  if (s.kernel_exports.find(symbol) != s.kernel_exports.end())
    o << "#define " << deftrue << " 1" << endl;
}

可以看出就是它将解析出宏定义,但为什么没有正确的解析出vmalloc_node呢?未解,继续跟进,
最终定位到,

int
systemtap_session::parse_kernel_exports ()
{
  string kernel_exports_file = kernel_build_tree + "/Module.symvers";
  struct stat st;
  int rc = stat(kernel_exports_file.c_str(), &st);
  if (rc != 0)
    {
        clog << _F("Checking \"%s\" failed with error: %s\nEnsure kernel development headers & makefiles are installed",
                   kernel_exports_file.c_str(), strerror(errno)) << endl;
	return rc;
    }

  ifstream kef (kernel_exports_file.c_str());
  string line;
  while (getline (kef, line))
    {
      vector<string> tokens;
      tokenize (line, tokens, "\t");
      if (tokens.size() == 4 &&
          tokens[2] == "vmlinux" &&
          tokens[3].substr(0,13) == string("EXPORT_SYMBOL"))
        kernel_exports.insert (tokens[1]);
      // RHEL4 Module.symvers file only has 3 tokens.  No
      // 'EXPORT_SYMBOL' token at the end of the line.
      else if (tokens.size() == 3 && tokens[2] == "vmlinux")
        kernel_exports.insert (tokens[1]);
    }
  if (verbose > 2)
    clog << _NF("Parsed kernel \"%s\", containing one vmlinux export",
                "Parsed kernel \"%s\", containing %zu vmlinux exports",
                kernel_exports.size(), kernel_exports_file.c_str(),
                kernel_exports.size()) << endl;

  kef.close();
  return 0;
}

所以答案就清晰明了了,解析的依据就是Module.symvers
当我去查看这个最后种种铁证均指向的嫌疑文件Module.symvers时,它自爆了,文件内容竟然是空白!!

来解释下发生这一幕的原由,因为我设备中的内核环境均在在宿主机中拷贝过来的,但当stap运行时,会编译ko,过程中会涉及到源码scripts下一些工具的使用,而现在这些工具是在宿主机中编译的,平台不一致,所以运行会失败,我自作聪明的make了下,待工具重新编译后停掉,并没有完成完全编译,所以Module.symvers被意外清空掉了。

最后,建议在构建设备中内核源码环境时,最好是克隆一份同版本的源码,并在设备中完全编译,这样在使用起来就不会出现平台不符的问题。

文章标签:

上一篇 : 【 PERF 】PLY 环境搭建
下一篇 : 【 MM 】聊聊 Compaction 与 Page migrate
阅读进度 0%