Surftrace
surftrace is a tool that allows you to surf the linux kernel
Install / Use
/learn @aliyun/SurftraceREADME

1、简介
surftrace是一个ftrace的自动封装器和开发编译平台,既能让用户基于libbpf快速构建工程进行开发,也能作为ftrace的封装器进行trace命令编写。项目包含surftrace工具集和pylcc、glcc(python or generic C language for libbpf Compiler Collection),提供远程和本地eBPF的编译能力。

1.1、ftrace原理与不足
ftrace是一个内核中的追踪器,用于帮助系统开发者或设计者查看内核运行情况,它可以被用来调试或者分析延迟/性能等常见问题。早期 ftrace 是一个 function tracer,仅能够记录内核的函数调用流程。如今ftrace已经成为一个开发框架,从2.6内核开始引入,是一套公认安全、可靠、高效的内核数据获取方式。
ftrace对使用者的要求比较高,以对内核符号 wake_up_new_task 进行trace,同时要获取入参(struct task_struct *)->comm 成员信息为例,启动配置需要经历三个步骤:
echo 'p:f0 wake_up_new_task comm=+0x678(%di):string' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
要想停止需要继续配置如下:
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo -:f0 >> /sys/kernel/debug/tracing/kprobe_events
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
一共需要六个步骤。其中,最困难的是第一个参数解析步骤。通常情况下,需要使用gdb 加载对应内核vmlinux, 对 struct task_struct 结构体中 comm成员进行偏移计算。上述方法如果不经常使用,重新手工操作的时间成本非常高,导致真正直接采用ftrace对内核信息进行采集的案例非常少,相关资料文献也匮乏。
1.2、surftrace目标
surftrace的主要目标是为了降低内核trace难度,达到快速高效获取内核信息目标。综合来说要达到以下效果:
-
- 一键trace内核符号,并获取指定内核数据;
-
- 除了C和linux 操作系统内核,用户无需新增学习掌握其它知识点(需要获取数据进行二次处理除外);
-
- 覆盖大部分主流发行版内核;
-
- 类似bcc开发模式,达到libbpf最佳资源消耗;
2、surftrace 命令使用
使用surftrace,需要满足以下条件:
-
- 公开发行版linux内核,支持目录清单参考:http://mirrors.openanolis.cn/coolbpf/db/ (持续更新)
-
- 内核支持ftrace,已配置了debugfs,root权限;
-
- Python2 >= 2.7; Python3 >= 3.5,已安装pip;
surftrace支持 remote(默认),local和gdb三种表达式解析器,要求分别如下:
-
- remote mode:可以访问pylcc.openanolis.cn
-
- local mode:从http://pylcc.openanolis.cn/db/ 下载对应arch和内核的下载到本地
-
- gdb mode:gdb version > 8.0,存放有对应内核的vmlinux;对于gdb模式而言,不受公开发行版内核限制(性能太弱,已经不再推荐)
2.1、安装
我们以龙蜥 4.19.91-24.8.an8.x86_64内核为例,需要root用户,执行以下命令进行安装:
pip3 install surftrace
Collecting surftrace
Downloading http://mirrors.cloud.aliyuncs.com/pypi/packages/b9/a2/f7e04bb8ebb12e6517162a70886e3ffe8d466437b15624590c9301fdcc52/surftrace-0.2.tar.gz
Building wheels for collected packages: surftrace
Running setup.py bdist_wheel for surftrace ... done
Stored in directory: /root/.cache/pip/wheels/cf/28/93/187f359be189bf0bf4a70197c53519c6ca54ffb957bcbebf5a
Successfully built surftrace
Installing collected packages: surftrace
Successfully installed surftrace-0.2
0.6以上(含)的版本采用https流的方式与服务器传输数据,低于0.6版本采用tcp 流传输。后者服务将从2023年12月31号起后下线。
检查安装是否成功
surftrace --help
usage: surftrace [-h] [-v VMLINUX] [-m MODE] [-d DB] [-r RIP] [-f FILE]
[-g GDB] [-F FUNC] [-o OUTPUT] [-l LINE] [-a ARCH] [-s] [-S]
[traces [traces ...]]
Trace ftrace kprobe events.
positional arguments:
traces set trace args.
optional arguments:
-h, --help show this help message and exit
-v VMLINUX, --vmlinux VMLINUX
set vmlinux path.
-m MODE, --mode MODE set arg parser, fro
-d DB, --db DB set local db path.
-r RIP, --rip RIP set remote server ip, remote mode only.
-f FILE, --file FILE set input args path.
-g GDB, --gdb GDB set gdb exe file path.
-F FUNC, --func FUNC disasassemble function.
-o OUTPUT, --output OUTPUT
set output bash file
-l LINE, --line LINE get file disasemble info
-a ARCH, --arch ARCH set architecture.
-s, --stack show call stacks.
-S, --show only show expressions.
examples:
2.2、常规函数入口trace
接下来我们以 以下两个常用内核符号为例,它的原型定义如下:
void wake_up_new_task(struct task_struct *p);
struct file *do_filp_open(int dfd, struct filename *pathname, const struct open_flags *op);
2.2.1、追踪符号入口和返回点
- 命令:surftrace 'p wake_up_new_task' 'r wake_up_new_task'
surftrace 'p wake_up_new_task' 'r wake_up_new_task'
echo 'p:f0 wake_up_new_task' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 'r:f1 wake_up_new_task' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f1/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
surftrace-2336 [001] .... 1447.877666: f0: (wake_up_new_task+0x0/0x280)
surftrace-2336 [001] d... 1447.877670: f1: (_do_fork+0x153/0x3d0 <- wake_up_new_task)
示例中入参有两个表达式,所有表达式要用单引号括起来。
- 'p wake_up_new_task':p表示表示probe函数入口;
- 'r wake_up_new_task':r表示probe函数返回位置;
后面的wake_up_new_task是要trace的函数符号,这个符号必须要在tracing/available_filter_functions 中可以找到的。
2.2.2、获取函数入参
要获取 do_filp_open 函数 第一个入参dfd,它的数据类型是:int。
- 命令:surftrace 'p do_filp_open dfd=%0'
surftrace 'p do_filp_open dfd=%0'
echo 'p:f0 do_filp_open dfd=%di:u32' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
surftrace-2435 [001] .... 2717.606277: f0: (do_filp_open+0x0/0x100) dfd=4294967196
AliYunDun-1812 [000] .... 2717.655955: f0: (do_filp_open+0x0/0x100) dfd=4294967196
AliYunDun-1812 [000] .... 2717.856227: f0: (do_filp_open+0x0/0x100) dfd=4294967196
- dfd是自定义变量,可以自行定义,名字不冲突即可
- %0表示第一个入参,%1表示第二个……
前面打印中,dfd是按照十进制显示的,可能没有十六进制那么直观,指定十六进制的方法:
命令:surftrace 'p do_filp_open dfd=X%0'
surftrace 'p do_filp_open dfd=X%0'
echo 'p:f0 do_filp_open dfd=%di:x32' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
surftrace-2459 [000] .... 3137.167885: f0: (do_filp_open+0x0/0x100) dfd=0xffffff9c
AliYunDun-1812 [001] .... 3137.171997: f0: (do_filp_open+0x0/0x100) dfd=0xffffff9c
AliYunDun-1826 [001] .... 3137.201401: f0: (do_filp_open+0x0/0x100) dfd=0xffffff9c
传参编号%前面使用了X进制类型标识符,共有SUX三种类型,分别对应有符号十进制、无符号十进制和十六进制,不指定默认为U类型。
2.2.3、解析入参结构体
wake_up_new_task入参类型为struct task_struct *,如果要获取入参中comm成员,即任务名,
- 命令:surftrace 'p wake_up_new_task comm=%0->comm'
surftrace 'p wake_up_new_task comm=%0->comm'
echo 'p:f0 wake_up_new_task comm=+0xae0(%di):string' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
surftrace-2421 [000] .... 2368.261019: f0: (wake_up_new_task+0x0/0x280) comm="surftrace"
bash-2392 [001] .... 2375.809655: f0: (wake_up_new_task+0x0/0x280) comm="bash"
bash-2392 [001] .... 2379.038534: f0: (wake_up_new_task+0x0/0x280) comm="bash"
bash-2392 [000] .... 2381.237443: f0: (wake_up_new_task+0x0/0x280) comm="bash"
方法和C语言获取结构体成员方法一样。
结构体类型可以级联访问:
surftrace 'p wake_up_new_task uesrs=S%0->mm->mm_users'
echo 'p:f0 wake_up_new_task uesrs=+0x58(+0x850(%di)):s32' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
surftrace-2471 [001] .... 3965.234680: f0: (wake_up_new_task+0x0/0x280) uesrs=2
bash-2392 [000] .... 3970.094475: f0: (wake_up_new_task+0x0/0x280) uesrs=1
bash-2392 [000] .... 3971.954463: f0: (wake_up_new_task+0x0/0x280) uesrs=1
surftrace 'p wake_up_new_task node=%0->se.run_node.rb_left'
echo 'p:f0 wake_up_new_task node=+0xa8(%di):u64' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
surftrace-2543 [001] .... 5926.605145: f0: (wake_up_new_task+0x0/0x280) node=0
bash-2392 [001] .... 5940.292293: f0: (wake_up_new_task+0x0/0x280) node=0
bash-2392 [001] .... 5945.207106: f0: (wake_up_new_task+0x0/0x280) node=0
systemd-journal-553 [000] .... 5953.211998: f0: (wake_up_new_task+0x0/0x280) node=0
2.2.4、设置过过滤器
过滤器需要放在表达式最后,以f:开头,可以使用括号和&& ||逻辑表达式进行组合,具体写法可以参考ftrace文档说明
命令行 surftrace 'p wake_up_new_task comm=%0->comm f:comm=="python3"'
surftrace 'p wake_up_new_task comm=%0->comm f:comm=="python3"'
echo 'p:f0 wake_up_new_task comm=+0xb28(%di):string' >> /sys/kernel/debug/tracing/kprobe_events
echo 'comm=="python3"' > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/filter
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/events/kprobes/f0/enable
echo 0 > /sys/kernel/debug/tracing/instances/surftrace/options/stacktrace
echo 1 > /sys/kernel/debug/tracing/instances/surftrace/tracing_on
<...>-2640781 [002] .... 6305734.444913: f0: (wake_up_new_task+0x0/0x250) comm="python3"
<...>-2640781 [002] .... 6305734.447806: f0: (wake_up_new_task+0x0/0x250) comm="python3"
<...>-2640781 [002] .... 6305734.450897: f0: (wake_up_new_task+0x0/0x250) comm="python3"
系统会默认提供 'common_pid', 'common_preempt_count', 'common_flags', 'common_type' 这5个变量作为过滤器,该变量由系统提供,无需额外定义。
2.2.5、函数内部追踪
函数内部追踪需要结合函数内部汇编代码进行推导,该方法并不通用,该内容操作进供参考。反汇编do_filp_open函数
3699 in fs/namei.c
0xffffffff812adb65 <+85>: mov %r13d,%edx
0xffffffff812adb70 <+96>: or $0x40,%edx
0xffffffff812adb73 <+99>: mov %r12,%rsi
0xffffffff812adb76 <+102>: mov %rsp,%rdi
0xffffffff812adb89 <+121>: callq 0xffffffff812ac760 <path_openat>
0xffffffff812adb92 <+130>:
Related Skills
node-connect
342.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
85.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
342.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
342.5kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
