SystemTap调试器常用于Linux内核的动态调试,不过该工具集也可用于应用的跟踪调试。随着Linux内核及其应用程序的复杂度不断加深,使用一些在功能上区别于传统的GDB调试工具就变得越来越重要了。这类调试工具具有低延时(Low Latency),高性能,动态调试的特点。嵌入式Linux设备的系统软件通常不需从头开发,这些调试工具可以帮助开发者快速理解Linux内核、系统层软件,同时定位、解决一些软件上的缺陷。
SystemTap的工作机制比较特殊,它会将开发者编写的SystemTap Script转换为C语言代码,该C语言代码会编译成一个内核模块;之后该内核模块会被加载到Linux内核中运行,以实现内核和应用的一些跟踪、调试等功能。这些操作由命令行工具stap自动完成,不需开发者手动操作;当该命令行工具退出时,其会将插入Linux内核的模块卸载。与BPF跟踪调试功能相似,二者都需要Linux内核的支持(这样的跟踪调试过程才可能是高效的),不过后者是通过bpf系统调用向内核加载了eBPF字节码,由内核中的eBPF Jit功能模块执行。此外,SystemTap也可以使用bpf作为后端,使用bpf子系统来跟踪、监测、调试Linux内核和应用的运行。
64位ARM/Linux嵌入式设备上的SystemTap
上面提到,命令行工具stap会将SystemTap Script脚本转换为C代码并编译为Linux内核模块,这就意味着在嵌入式设备上搭建完整的Linux内核开发环境;对于一些可以运行ubuntu或Fedora的嵌入式ARM设备(例如NVIDIA的Jetson开发板),这一点很容易做到,但对于通常的嵌入式设备的系统开发而言,这几乎是不可能的。需要找到一种可行的交叉编译开发的方式,把针对嵌入式设备的SystemTap调试中的内核模块开发过程放在x86_64平台的主机上完成。庆幸的是,SystemTap支持这一开发、调试模试,本文主要记录了笔者实现这一调试过程的操作。
在进行调试之前,笔者已经分别为x86_64的主机和ARM64的嵌入式Linux设备编译了SystemTap-4.5,以及其依赖的开源库zlib和elfutils,该过程不再繁述。以下是笔者尝试在嵌入式设备上运行stap命令的结果:
# uname -a
Linux 5.10.63-v8+ #1 SMP PREEMPT Thu Oct 30 22:25:48 CST 2021 aarch64 GNU/Linux
# stap strace-open.stp
Checking "/lib/modules/5.10.63-v8+/build/.config" failed with error: No such file or directory
可见stap命令会在嵌入式设备上找Linux内核源码及其配置文件,但却不能找到。
在x86_64主机上交叉编译生成SystemTap的调试模块
首先,需要在主机上交叉编译arm64/Linux内核,stap编译生成内核模块时需要通过参数-r指出内核源码的路程。接下来,笔者选取了SystemTap官方教程文档中的一个简单调试脚本,strace-open.stp,其内容如下:
# cat strace-open.stp
probe syscall.openat
{
printf ("%s(%d) open (%s)n", execname(), pid(), argstr)
}
probe timer.ms(4000) # after 4 seconds
{
exit ()
}
在x86_64主机上使用stap命令行工具为arm64/Linux设备生成内核调试模块的操作为:
$ sudo -s
# export PATH=/opt/addon/bin:$PATH
# stap -v -k -a arm64 -r /home/yejq/program/linux-5.10
-B CROSS_COMPILE=aarch64-linux-gnu- -m straceopen.ko strace-open.stp
Truncating module name to 'straceopen'
WARNING: kernel release/architecture mismatch with host forces last-pass 4.
Pass 1: parsed user script and 462 library scripts using 99396virt/94488res/5756shr/88784data kb, in 420usr/60sys/796real ms.
Pass 2: analyzed script: 4 probes, 22 functions, 96 embeds, 2 globals using 160684virt/157136res/6948shr/150072data kb, in 3970usr/910sys/9580real ms.
Pass 3: translated to C into "/tmp/stapwfLwVy/straceopen_src.c" using 160684virt/157136res/6948shr/150072data kb, in 0usr/0sys/7real ms.
straceopen.ko
Pass 4: compiled C into "straceopen.ko" in 44840usr/5020sys/24288real ms.
Keeping temporary directory "/tmp/stapwfLwVy"
# ls
straceopen.ko strace-open.stp
# file straceopen.ko
straceopen.ko: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=1060f66c4fd57b5a9b5246e4e347bc1b8fdbaa2e, not stripped
以上的操作是以root用户来完成的。注意到上面的命令行参数-k,它指示保留临时的工作目录tmp/stapwfLwVy,在该目录下可以找到用于编译内核调试模块straceopen.ko的源代码文件straceopen_src.c,想了解SystemTap的实现细节的可以参考该代码。将straceopen.ko复制到嵌入式设备上,使用staprun命令来运行并加载该内核模块,可以看到调试的结果了:
# staprun ./straceopen.ko
cat(4277) open (AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
cat(4277) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
cat(4277) open (AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
cat(4277) open (AT_FDCWD, "/dev/null", O_RDONLY)
cat(4278) open (AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
cat(4278) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
cat(4278) open (AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
cat(4278) open (AT_FDCWD, "/dev/null", O_RDONLY)
systemd-udevd(179) open (AT_FDCWD, "/sys/fs/cgroup/unified/system.slice/systemd-udevd.service/cgroup.procs", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)
这样我们就能够在嵌入式设备上使用SystemTap调试工具集了。说它是一个工具集,是因为其包含了多种调试工具和机制,不仅限于Linux内核的调试。
使用eBPF作为SystemTap后端进行调试
SystemTap调试器出现得比eBPF早得多,不过SystemTap也支持使用BPF作为后端进行调试;不过该功能并不完善。需要将上面的命令增加一个--bpf参数,并修改strace-open.stp(修改后的文件为bpf-open.stp),就可以生成使用BPF子系统的BTF文件了:
# cat bpf-open.stp
probe kernel.function("__arm64_sys_openat")
{
printf("%s(%d) open unknown file (pt_regs: %d)n", execname(), pid(), $regs)
}
probe timer.ms(4000) # after 4 seconds
{
exit()
}
# stap -v -k -a arm64 -r /home/yejq/program/linux-5.10 -B CROSS_COMPILE=aarch64-linux-gnu- -m bpfopen --bpf bpf-open.stp
WARNING: kernel release/architecture mismatch with host forces last-pass 4.
Pass 1: parsed user script and 55 library scripts using 20696virt/15732res/5908shr/10084data kb, in 20usr/0sys/20real ms.
Pass 2: analyzed script: 3 probes, 7 functions, 0 embeds, 1 global using 81872virt/78292res/7096shr/71260data kb, in 3820usr/300sys/4116real ms.
Pass 3: pass skipped for stapbpf runtime in 0usr/0sys/0real ms.
bpfopen.bo
Pass 4: compiled BPF into "bpfopen.bo" in 0usr/0sys/46real ms.
Keeping temporary directory "/tmp/stapbiRBv9"
# ls
bpfopen.bo bpf-open.stp straceopen.ko strace-open.stp
root@ubuntu:/home/yejq/program/systemstap# file bpfopen.bo
bpfopen.bo: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped
如上,添加--bpf参数,生成的bpfopen.bo不是一个内核模块,而是一个BTF格式的文件。在嵌入式设备上则需使用stapbpf命令行工具加载之:
# stapbpf ./bpfopen.bo
cat(5474) open unknown file (pt_regs: 0)
cat(5474) open unknown file (pt_regs: 0)
cat(5474) open unknown file (pt_regs: 0)
cat(5474) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
产生以上结果需要要设备上打开另一个终端(例如打开Telnet登录到设备),并输入cat和ls等命令。经笔者验证,当前的SystemTap(版本4.5)对BPF后端的支持有限,不能正确获取__arm64_sys_openat系统调用函数的参数等信息(如上结果中pt_regs指针为空),这与man stapbpf中的说明一致:
LIMITATIONS
This runtime is in an early stage of development and it currently lacks support for a number of features available in the default
runtime.
SystemTap调试器常用于Linux内核的动态调试,不过该工具集也可用于应用的跟踪调试。随着Linux内核及其应用程序的复杂度不断加深,使用一些在功能上区别于传统的GDB调试工具就变得越来越重要了。这类调试工具具有低延时(Low Latency),高性能,动态调试的特点。嵌入式Linux设备的系统软件通常不需从头开发,这些调试工具可以帮助开发者快速理解Linux内核、系统层软件,同时定位、解决一些软件上的缺陷。
SystemTap的工作机制比较特殊,它会将开发者编写的SystemTap Script转换为C语言代码,该C语言代码会编译成一个内核模块;之后该内核模块会被加载到Linux内核中运行,以实现内核和应用的一些跟踪、调试等功能。这些操作由命令行工具stap自动完成,不需开发者手动操作;当该命令行工具退出时,其会将插入Linux内核的模块卸载。与BPF跟踪调试功能相似,二者都需要Linux内核的支持(这样的跟踪调试过程才可能是高效的),不过后者是通过bpf系统调用向内核加载了eBPF字节码,由内核中的eBPF Jit功能模块执行。此外,SystemTap也可以使用bpf作为后端,使用bpf子系统来跟踪、监测、调试Linux内核和应用的运行。
64位ARM/Linux嵌入式设备上的SystemTap
上面提到,命令行工具stap会将SystemTap Script脚本转换为C代码并编译为Linux内核模块,这就意味着在嵌入式设备上搭建完整的Linux内核开发环境;对于一些可以运行ubuntu或Fedora的嵌入式ARM设备(例如NVIDIA的Jetson开发板),这一点很容易做到,但对于通常的嵌入式设备的系统开发而言,这几乎是不可能的。需要找到一种可行的交叉编译开发的方式,把针对嵌入式设备的SystemTap调试中的内核模块开发过程放在x86_64平台的主机上完成。庆幸的是,SystemTap支持这一开发、调试模试,本文主要记录了笔者实现这一调试过程的操作。
在进行调试之前,笔者已经分别为x86_64的主机和ARM64的嵌入式Linux设备编译了SystemTap-4.5,以及其依赖的开源库zlib和elfutils,该过程不再繁述。以下是笔者尝试在嵌入式设备上运行stap命令的结果:
# uname -a
Linux 5.10.63-v8+ #1 SMP PREEMPT Thu Oct 30 22:25:48 CST 2021 aarch64 GNU/Linux
# stap strace-open.stp
Checking "/lib/modules/5.10.63-v8+/build/.config" failed with error: No such file or directory
可见stap命令会在嵌入式设备上找Linux内核源码及其配置文件,但却不能找到。
在x86_64主机上交叉编译生成SystemTap的调试模块
首先,需要在主机上交叉编译arm64/Linux内核,stap编译生成内核模块时需要通过参数-r指出内核源码的路程。接下来,笔者选取了SystemTap官方教程文档中的一个简单调试脚本,strace-open.stp,其内容如下:
# cat strace-open.stp
probe syscall.openat
{
printf ("%s(%d) open (%s)n", execname(), pid(), argstr)
}
probe timer.ms(4000) # after 4 seconds
{
exit ()
}
在x86_64主机上使用stap命令行工具为arm64/Linux设备生成内核调试模块的操作为:
$ sudo -s
# export PATH=/opt/addon/bin:$PATH
# stap -v -k -a arm64 -r /home/yejq/program/linux-5.10
-B CROSS_COMPILE=aarch64-linux-gnu- -m straceopen.ko strace-open.stp
Truncating module name to 'straceopen'
WARNING: kernel release/architecture mismatch with host forces last-pass 4.
Pass 1: parsed user script and 462 library scripts using 99396virt/94488res/5756shr/88784data kb, in 420usr/60sys/796real ms.
Pass 2: analyzed script: 4 probes, 22 functions, 96 embeds, 2 globals using 160684virt/157136res/6948shr/150072data kb, in 3970usr/910sys/9580real ms.
Pass 3: translated to C into "/tmp/stapwfLwVy/straceopen_src.c" using 160684virt/157136res/6948shr/150072data kb, in 0usr/0sys/7real ms.
straceopen.ko
Pass 4: compiled C into "straceopen.ko" in 44840usr/5020sys/24288real ms.
Keeping temporary directory "/tmp/stapwfLwVy"
# ls
straceopen.ko strace-open.stp
# file straceopen.ko
straceopen.ko: ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), BuildID[sha1]=1060f66c4fd57b5a9b5246e4e347bc1b8fdbaa2e, not stripped
以上的操作是以root用户来完成的。注意到上面的命令行参数-k,它指示保留临时的工作目录tmp/stapwfLwVy,在该目录下可以找到用于编译内核调试模块straceopen.ko的源代码文件straceopen_src.c,想了解SystemTap的实现细节的可以参考该代码。将straceopen.ko复制到嵌入式设备上,使用staprun命令来运行并加载该内核模块,可以看到调试的结果了:
# staprun ./straceopen.ko
cat(4277) open (AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
cat(4277) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
cat(4277) open (AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
cat(4277) open (AT_FDCWD, "/dev/null", O_RDONLY)
cat(4278) open (AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
cat(4278) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
cat(4278) open (AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
cat(4278) open (AT_FDCWD, "/dev/null", O_RDONLY)
systemd-udevd(179) open (AT_FDCWD, "/sys/fs/cgroup/unified/system.slice/systemd-udevd.service/cgroup.procs", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libpcre.so.3", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/lib/aarch64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC)
ls(4279) open (AT_FDCWD, ".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)
这样我们就能够在嵌入式设备上使用SystemTap调试工具集了。说它是一个工具集,是因为其包含了多种调试工具和机制,不仅限于Linux内核的调试。
使用eBPF作为SystemTap后端进行调试
SystemTap调试器出现得比eBPF早得多,不过SystemTap也支持使用BPF作为后端进行调试;不过该功能并不完善。需要将上面的命令增加一个--bpf参数,并修改strace-open.stp(修改后的文件为bpf-open.stp),就可以生成使用BPF子系统的BTF文件了:
# cat bpf-open.stp
probe kernel.function("__arm64_sys_openat")
{
printf("%s(%d) open unknown file (pt_regs: %d)n", execname(), pid(), $regs)
}
probe timer.ms(4000) # after 4 seconds
{
exit()
}
# stap -v -k -a arm64 -r /home/yejq/program/linux-5.10 -B CROSS_COMPILE=aarch64-linux-gnu- -m bpfopen --bpf bpf-open.stp
WARNING: kernel release/architecture mismatch with host forces last-pass 4.
Pass 1: parsed user script and 55 library scripts using 20696virt/15732res/5908shr/10084data kb, in 20usr/0sys/20real ms.
Pass 2: analyzed script: 3 probes, 7 functions, 0 embeds, 1 global using 81872virt/78292res/7096shr/71260data kb, in 3820usr/300sys/4116real ms.
Pass 3: pass skipped for stapbpf runtime in 0usr/0sys/0real ms.
bpfopen.bo
Pass 4: compiled BPF into "bpfopen.bo" in 0usr/0sys/46real ms.
Keeping temporary directory "/tmp/stapbiRBv9"
# ls
bpfopen.bo bpf-open.stp straceopen.ko strace-open.stp
root@ubuntu:/home/yejq/program/systemstap# file bpfopen.bo
bpfopen.bo: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped
如上,添加--bpf参数,生成的bpfopen.bo不是一个内核模块,而是一个BTF格式的文件。在嵌入式设备上则需使用stapbpf命令行工具加载之:
# stapbpf ./bpfopen.bo
cat(5474) open unknown file (pt_regs: 0)
cat(5474) open unknown file (pt_regs: 0)
cat(5474) open unknown file (pt_regs: 0)
cat(5474) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
ls(5475) open unknown file (pt_regs: 0)
产生以上结果需要要设备上打开另一个终端(例如打开Telnet登录到设备),并输入cat和ls等命令。经笔者验证,当前的SystemTap(版本4.5)对BPF后端的支持有限,不能正确获取__arm64_sys_openat系统调用函数的参数等信息(如上结果中pt_regs指针为空),这与man stapbpf中的说明一致:
LIMITATIONS
This runtime is in an early stage of development and it currently lacks support for a number of features available in the default
runtime.
举报