systemtap 探秘(一)- 基本介绍

Linux 内核(如下简称内核)提供了 kprobe 和 uprobe 的机制,容许用户经过编写本身的内核模块,挂载特定的事件来执行本身的函数。好比咱们能够在 accept 系统调用结束时记录下新建立的 fd;或者在 VFS 读写操做先后记录时间戳,统计它们的耗时。数据库

直接裸写内核模块费时费力,其中有部分工做仍是能够套模板的(好比从挂载 accept 系统调用改为挂载 write 调用)。并且更大的问题是它不够安全。要是内核态的程序崩溃了,那就是 kernel panic 的事了,准备去重启服务器吧。因而乎像是 systemtap 这样的项目就应运而生了。这一类项目提供了本身的 DSL,而后编译成内核模块。用户不用直接跟 C 代码和内核接口打交道,而是改用能力受限但安全得多的 DSL 编写本身的小工具。打个比方,就像写 SQL 查询数据库和直接编写数据库 C 插件的区别。后端

须要强调的是,systemtap DSL(如下简称 stp)并不是绝对地安全。首先,stp 容许咱们嵌入 C 代码,而这部分天然是不安全的。其次,stp 容许咱们经过宏来调整编译出来的内核模块的行为,而有些宏是有反作用的。举个例子,stp 里面的数组大小是预先分配好的,大小取决于 MAXMAPENTRIES这个宏(默认 2048)。有些时候,因为要插入不少的数据,你须要调大这个宏。若是简单粗暴地随便在后面加若干个 0,可能会致使内核分配内存失败,进而产生一系列问题(包括 kernel panic)。数组

对于 4.x 高版本的内核,咱们可使用 ebpf 机制而不是编写或者间接编写内核模块。ebpf 是个在内核态运行的,解释执行字节码的虚拟机。它从底子上提供了更多的安全限制,要比内核模块安全得多。并且编译成 ebpf 字节码要比编译内核模块快不少,这也是基于 ebpf 的内核 profile 的一大优点。安全

systemtap 除了能够生成内核模块,它还有生成 ebpf 字节码和生成 ptrace 代码的后端。2018 年,我曾尝试过 systmtap 的 ebpf 后端,发现该后端因为正在开发中,支持的 stp 语法特性较少,只有一部分 stp 文件能成功跑起来。相比较而言,BCC 支持的语言特性却是足够多,然而其对 debuginfo 的支持几乎等于没有,以至于用户态 profile 只能基于 USDT (刻薄一点,就是几乎不能用)。不知道它们俩如今发展得怎样了。服务器

回到一开始的话题上来。一次完整的 stap test.stp ... 会经历下面的阶段:函数

  1. parse stp 代码(词法分析和语法分析)
  2. 解读 stp 代码(语义分析)
  3. 生成 C 代码(中间代码生成)
  4. 编译内核模块
  5. 运行

其中最后一个阶段由 stap 建立的 staprun 子进程执行。这一阶段能够单独拆开来,即前四个阶段在开发机器上生成编译好的内核模块,而后第五个阶段在目标机器上执行。具体怎么作参考 systemtap 的文档。工具

若是你对其中的细节感兴趣,或者须要搞明白 systemtap 那些奇奇怪怪的错误信息,能够在运行时加上 -vvv 来显示更多上下文。优化

另外在命令中加入 -p 阶段序号 可让 systemtap 在执行完特定阶段后停下来。好比 stap -p3 会在生成完 C 代码后停下来。插件

本系列会按这五个阶段,一步步介绍整个流程。重点会放在第三阶段生成 C 代码的部分。咱们会讲讲那些 stp 语言的元素,好比全局数组、聚合函数,是怎样编译成 C 代码的。debug

不过在此以前,先从第一阶段开始吧。

词法分析和语法分析

这一阶段没什么好说的,就是解析 stp 代码,生成一棵 AST 树。

若是解析过程当中出错,会报告 parse error

parse error: expected literal string or number
        saw: operator ')' at test.stp:3:15
     source: probe begin() {
                           ^

1 parse error.
Pass 1: parse failed.  [man error::pass1]

上面是 systemtap 奇奇怪怪的报错信息的一个例子。正确的写法是 probe begin {,begin 后面不带括号。

systemtap 虽然发现了语法上的问题,可是它的错误信息里面没有考虑到当前正在解析 probe 的上下文。它觉得是在解析某些带参数的 probe,如 probe process("path").function("xxx"),因此才声称 expected literal string or number

不过好在 stp 的语法比较简单,即便看不懂错误提示也能改对。

语义分析

上一阶段结束后,stp 代码已经被解析成一棵 AST 树了。这一阶段主要是对这棵树作修剪,为下一阶段生成 C 代码作准备。

这一阶段由一系列分阶段组成。每一个分阶段会执行一个或多个 visitor,遍历整棵树,处理每一个节点。这些分阶段包括 stp 语言宏的展开、查找 stp 函数定义、匹配 stp 变量类型、debuginfo 相关的一些操做、若干种优化器等等。做为承上启下的一个阶段,该阶段涉及了不少细节。因为这些细节跟 systemtap 内部实现紧密相关,要想理解它们,最好须要阅读其实现代码。

这里咱们就只提下优化器部分。在生成 C 代码以前,systemtap 会执行一系列优化器,如常量折叠、冗余分支消除等,对 AST 树进行剪枝。对于大型的 stp 脚本,花在这一阶段的时间可能多达 10 秒,快遇上第四阶段编译内核模块的耗时了。若是你对 systemtap 脚本的预备耗时比较敏感,能够在运行时加上 -u 选项,跳过这些优化阶段。

预告

接下来,我会花上几篇文章的篇幅,讲讲第三阶段中 C 代码的生成,重点是 stp 语言的一些特性。

相关文章
相关标签/搜索