点击这里在哔哩哔哩bilibili在线观看配套的加餐视频(就是一些补充)。
除前面我们介绍过的GDB调试方法只适用于虚拟机中,我们平时看代码学习时可以用一下,如果是在工作中客户遇到的问题,GDB调试方法就用不上了,这时就需要用到其他调试方法了。
编译相关软件包:
sudo apt install build-essential -y
安装kernel-debuginfo
软件包(必须要是ubuntu server才能找到对应内核版本的软件包),参考Debug symbol packages,如果安装时下载很慢,也可以在仓库先下载好,放到/var/cache/apt/archives/partial/
目录再安装:
sudo apt install ubuntu-dbgsym-keyring -y
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
sudo apt-get update -y
sudo apt install linux-image-`uname -r`-dbgsym -y
或者先下载ddeb
文件,然后:
sudo dpkg -i xxx.ddeb
如果要编译外部模块,需要复制vmlinux
:
cp /usr/lib/debug/boot/vmlinux-`uname -r` /usr/lib/modules/`uname -r`/build/vmlinux
安装内核源码:
apt search linux-source # 查看版本信息
apt install linux-source -y
cd /usr/src/linux-source-5.15.0/
tar xvf linux-source-5.15.0.tar.bz2
编译相关软件包:
sudo dnf groupinstall "Development Tools" -y # 这里安装的 kernel-devel 对应的内核版本可能不一致
安装kernel-debuginfo
软件包:
sudo dnf --enablerepo=fedora-debuginfo install kernel-debuginfo
安装kernel-devel
软件包:
sudo dnf install kernel-devel-`uname -r` -y # kernel-headers-`uname -r` 可能会找不到
如果要编译外部模块,需要复制vmlinux
:
cp /usr/lib/debug/lib/modules/`uname -r`/vmlinux /usr/lib/modules/`uname -r`/build/
下载内核源码:
# 如果下载太慢,可以先在其他地方下载好
wget https://kojipkgs.fedoraproject.org/packages/kernel/6.8.5/301.fc40/src/kernel-6.8.5-301.fc40.src.rpm
mkdir sources
cd sources/
rpm2cpio ../kernel-6.8.5-301.fc40.src.rpm | cpio -idmv
tar xvf linux-6.8.5.tar.xz
内核编译时打开Documentation/9psetup中的配置。虚拟机中执行脚本 mod-cfg.sh
(直接运行bash mod-cfg.sh
可以查看使用帮助)挂载和链接模块目录
ftrace
名字来源于 function trace。
CONFIG_FTRACE=y
CONFIG_HAVE_FUNCTION_TRACER=y
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_IRQSOFF_TRACER=y
CONFIG_SCHED_TRACER=y
# CONFIG_ENABLE_DEFAULT_TRACERS # 这个好像必须要关闭
CONFIG_FTRACE_SYSCALLS=y
CONFIG_PREEMPT_TRACER=y
CONFIG_DYNAMIC_FTRACE=y
/sys/kernel/debug/tracing/
目录下的常见tracer和event如下:
available_tracers
: 支持的跟踪器。available_events
: 支持的事件。current_tracer
: 当前正在使用的跟踪器,默认为nop
。trace
: 用cat
命令查看跟踪信息。tracing_on
: 开启或暂停。options
: 选项。irqsoff
跟踪中断延迟。
cd /sys/kernel/debug/tracing/
echo 0 > options/function-trace # 为了减少延迟
echo irqsoff > current_tracer
echo 1 > tracing_on
... # 停一会儿,收集日志
echo 0 > tracing_on
cat trace_pipe | less
function
和function_graph
跟踪函数。
cd /sys/kernel/debug/tracing/
cat available_filter_functions # 查看可跟踪的函数
echo 0 > tracing_on
cat set_ftrace_pid
echo 1234 > set_ftrace_pid # 指定pid
echo ext2_readdir > set_graph_function # 跟踪某个函数
# echo function > current_tracer
echo function_graph > current_tracer # 更加直观
echo 1 > tracing_on
... # 收集日志
echo 0 > tracing_on
cat trace_pipe | less
还可以指定要跟踪和不跟踪的函数,需要打开配置CONFIG_DYNAMIC_FTRACE
:
echo func1 func2 > set_ftrace_filter # 要跟踪的函数
echo func3 func4 > set_ftrace_notrace # 不跟踪的函数
echo 'ext2_*' >> set_ftrace_filter # ext2_ 开头的函数
echo '*ext4*' >> set_ftrace_notrace # 包含ext4的函数
echo > set_ftrace_notrace # 清空
tracepoint
比如我们要打开ext2_dio_read_iter()
函数的ext2_dio_read_begin
的tracepoint:
cd /sys/kernel/debug/tracing/
echo nop > current_tracer
echo 1 > tracing_on
cat available_events | grep ext2
echo ext2:ext2_dio_read_begin > set_event
# find events/ -name "*ext2*" # 也可以查找函数所在位置,比较慢
# echo 1 > events/ext2/ext2_dio_read_begin/enable # 使能函数的tracepoint
# echo ext2:* > set_event # 所有的ext2跟踪点
fallocate -l 10M ~/image
mkfs.ext2 -F image
mount image /mnt
echo 1234567890 > /mnt/file-in
dd if=/mnt/file-in of=~/file-out iflag=direct bs=512 count=1 # bs不能随意指定
cat trace_pipe
到相应tracepoint
的目录下,设置跟踪条件:
cd /sys/kernel/debug/tracing/
cd events/ext2/ext2_dio_read_begin
ls # enable filter format hist id trigger
trace-cmd
和kernelshark
sudo apt install trace-cmd -y
sudo apt install kernelshark -y # 图形界面
使用按照reset -> record -> stop -> report
:
trace-cmd record -h # 查看帮助
trace-cmd record -e 'ext2_dio_read_begin' # 输出文件 trace.dat
trace-cmd report trace.dat # 字符界面解析数据
kernelshark trace.dat # 图形化查看数据
trace_marker
cd /sys/kernel/debug/tracing/
echo nop > current_tracer # 必须要是nop
echo 1 > tracing_on
echo "hello trace_marker" > trace_marker
echo 0 > tracing_on
cat trace_pipe | less
kprobe
kprobe trace
kprobe的使用如下:
cd /sys/kernel/debug/tracing/
# 可以用 kprobe 跟踪的函数
cat available_filter_functions
echo 1 > tracing_on
# x86_64函数参数用到的寄存器: RDI, RSI, RDX, RCX, R8, R9
# aarch64函数参数用到的寄存器: X0 ~ X7
# f_mode 在 file 结构体中的偏移为 20, x32代表32位(4字节),注意 rdi 寄存器要写成 di
echo 'p:p_ext2_readdir ext2_readdir f_mode=+20(%di):x32' >> kprobe_events
echo 1 > events/kprobes/p_ext2_readdir/enable
echo stacktrace > events/kprobes/p_ext2_readdir/trigger
echo '!stacktrace' > events/kprobes/p_ext2_readdir/trigger
echo 0 > events/kprobes/p_ext2_readdir/enable
echo '-:p_ext2_readdir' >> kprobe_events
# kretprobe,可以跟踪函数返回值
# 注意要用单引号
echo 'r:r_ext2_readdir ext2_readdir ret=$retval' >> kprobe_events
echo 1 > events/kprobes/r_ext2_readdir/enable
echo stacktrace > events/kprobes/r_ext2_readdir/trigger
echo '!stacktrace' > events/kprobes/r_ext2_readdir/trigger
echo 0 > events/kprobes/r_ext2_readdir/enable
echo '-:r_ext2_readdir' >> kprobe_events
echo 0 > trace # 清除trace信息
cat trace_pipe
kprobe
模块参考 kprobes 里的例子。
printk
8个打印等级:
#define KERN_EMERG KERN_SOH "0" /* 系统不可用 */
#define KERN_ALERT KERN_SOH "1" /* 需要立刻处理 */
#define KERN_CRIT KERN_SOH "2" /* 紧急 */
#define KERN_ERR KERN_SOH "3" /* 错误 */
#define KERN_WARNING KERN_SOH "4" /* 警告 */
#define KERN_NOTICE KERN_SOH "5" /* 重要提示 */
#define KERN_INFO KERN_SOH "6" /* 提示 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息 */
默认配置是等级高于CONFIG_CONSOLE_LOGLEVEL_DEFAULT
会打印,qemu启动时可以指定append="... loglevel=8
。
/proc/sys/kernel/printk
文件中的内容含义如下:
int console_printk[4] = {
/* 控制台输出等级 */
CONSOLE_LOGLEVEL_DEFAULT, /* 默认消息输出等级 */
MESSAGE_LOGLEVEL_DEFAULT, /* 最低输出等级 */
CONSOLE_LOGLEVEL_MIN, /* 默认控制台输出等级,启动时 */
CONSOLE_LOGLEVEL_DEFAULT, };
常用的输出函数有print_hex_dump()
和dump_stack()
。
include/asm-generic/bug.h
文件中的BUG_ON(condition)
当满足条件(condition == true
)时会panic。WARN_ON(condition)
当满足条件(condition == true
)时不会panic,只会打印信息。
打开配置CONFIG_DYNAMIC_DEBUG
。
cd /sys/kernel/debug/dynamic_debug/
cat control | less # 查看所有的动态打印
echo 'file fs/ext4/extents.c +p' > control # 打开文件中所有的动态打印
echo 'module ext4 -p' > control # 关闭ext4模块所有动态打印
echo 'func ext4_ext_binsearch +p' > control # 打开某个函数的打印
echo -n '*ext4* -p' > control # 关闭文件路径中包含ext4的打印
echo -n '+p' > control # 所有打印
系统启动相关的代码(如smpboot
),需要在启动时传递参数:
# p: 打开
# f: 函数名
# l: 行号
# m: 模块名
# t: 线程id
qemu-system-x86_64 -append "... smpboot.dyndbg=+plftm"
也可以修改子系统的Makefile
,添加以下内容:
ccflags-y += -DDEBUG
ccflags-y += -DVERBOSE_DEBUG
kdump
和crash
如果内核版本不是最新的(比如4.19或5.10等),那么发行版的包管理器安装的crash就可以用,但如果内核版本是最新的,可能就需要通过源码安装crash:
git clone https://github.com/crash-utility/crash.git
sudo apt-get install autoconf automake libtool texinfo -y
cd crash
make -j64 # 如果下载gdb很慢,可以先在其他地方先下载好(如 http://ftp.gnu.org/gnu/gdb/gdb-10.2.tar.gz),放到相应的位置
# make target=ARM64 -j64 # 交叉编译能解析arm64 vmcore的crash
以fedora40为例。
安装工具:
sudo dnf install kexec-tools -y
sudo dnf install crash -y
修改/etc/default/grub
文件,在GRUB_CMDLINE_LINUX=
一行的最后添加以下内容:
GRUB_CMDLINE_LINUX="... crashkernel=512M" # 根据内存大小来决定
然后重新生成grub配置:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
sudo reboot # 重启才会生效
开启kdump服务:
sudo systemctl enable kdump.service # 设置成开机启动
sudo systemctl start kdump.service # 启动
sudo systemctl status kdump.service # 查看状态
如果系统有问题发生崩溃,会在/var/crash
目录下生成vmcore
文件。
为了验证kdump功能是否可用,我们可以手动触发:
sudo su root
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger
启动crash:
crash /var/crash/${ip}-${date-time}/vmcore /usr/lib/debug/lib/modules/`uname -r`/vmlinux
以ubuntu24.04为例。
安装工具:
sudo apt-get update -y
sudo apt install linux-crashdump -y
sudo apt install crash -y
修改/etc/default/grub
文件,在GRUB_CMDLINE_LINUX=
一行的最后添加以下内容:
GRUB_CMDLINE_LINUX="... crashkernel=512M" # 根据内存大小来决定
然后重新生成grub配置:
sudo grub-mkconfig -o /boot/grub/grub.cfg
注意ubuntu server(如ubuntu22.04.4)的/boot/grub/grub.cfg
中的crashkernel
后的值是512M-:192M
,要删掉后面的-:192M
,否则无法生成vmcore
。
再重启系统:
sudo reboot # 重启才会生效
开启kdump服务:
sudo systemctl enable kdump-tools.service # 设置成开机启动
sudo systemctl start kdump-tools.service # 启动
sudo systemctl status kdump-tools.service # 查看状态
如果系统有问题发生崩溃,会在/var/crash
目录下生成vmcore
文件。
为了验证kdump功能是否可用,我们可以手动触发:
sudo su root
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger
启动crash:
crash /var/crash/${date-time}/dump.${date-time} /usr/lib/debug/boot/vmlinux-`uname -r`
qemu启动的命令行最好指定-append "nokaslr ..."
。
在qemu环境中运行,不需要安装kdump
工具。有些发行版默认发生oops时不会panic,需要修改配置(注意这样修改重启后会还原):
echo 1 > /proc/sys/kernel/panic_on_oops # 注意不能用 vim 编辑
cat /proc/sys/kernel/panic_on_oops # 确认是否生效
echo 3000 > /proc/sys/kernel/panic # panic之后多久重启
按ctrl + a c
打开QEMU控制台,使用以下命令导出vmcore:
(qemu) dump-guest-memory /your_path/vmcore
(qemu) dump-guest-memory -z /your_path/vmcore # 压缩
除了panic时导出vmcore,还可以手动触发导出vmcore,这在一些场景下收集信息非常有用:
# 这个命令启用了 Magic SysRq 键。Magic SysRq 键提供了一组能够直接与内核进行交互的调试和故障排除功能。
# 当启用 Magic SysRq 后,您可以使用 Magic SysRq 键与其他键组合来触发特定的操作
echo 1 > /proc/sys/kernel/sysrq
# 这个命令触发了 Magic SysRq 键中的 "c" 操作。在 Magic SysRq 中,"c" 表示让内核立即进行系统内核转储。
# 这对于在系统发生严重故障时收集调试信息非常有用。
echo c > /proc/sysrq-trigger # 相当于Alt + SysRq + c
如果要让内核在hungtask或softlockup等情况触发panic,可以执行以下操作:
sysctl -w kernel.softlockup_panic=1 # -w:表示“写”操作,用来修改内核参数
sysctl -w kernel.hung_task_panic=0
sysctl kernel.softlockup_panic # 查看
# 或者用以下命令
echo 1 > /proc/sys/kernel/softlockup_panic # 和sysctl命令效果一样
echo 0 > /proc/sys/kernel/hung_task_panic
cat /proc/sys/kernel/softlockup_panic # 查看
x86_64
启动crash
:
crash vmlinux vmcore
aarch64
启动crash
要特殊处理:
# 先启动gdb打印变量值
(gdb) target remote:5555
(gdb) p /x kimage_voffset # 在我的虚拟机中值为 0xffff80003fe00000
crash vmlinux vmcore -m vabits_actual=48 -m kimage_voffset=0xffff80003fe00000
加载ko模块:
crash> help mod # 帮助命令
crash> mod -s <module name> <ko path> # 加载
crash> mod -d <module name> # 删除
crash
常用命令启动crash
:
crash vmlinux vmcore
crash vmlinux # 在线调试,vmcore是/proc/kcore
help
命令:
crash> help # 查看支持的所有命令
crash> help bt # 查看具体命令(bt)的用法
sys
命令查看系统信息:
crash> sys
bt
命令:
crash> bt # 查看崩溃瞬间正在运行的进程的内核栈
crash> bt <pid> # 查看指定pid进程的栈
# -F[F]: 类似于 -f,不同之处在于当适用时以符号方式显示堆栈数据;如果堆栈数据引用了 slab cache 对象,将在方括号内显示 slab cache 的名称;在 ia64 架构上,将以符号方式替代参数寄存器的内容。如果输入 -F 两次,并且堆栈数据引用了 slab cache 对象,将同时显示地址和 slab cache 的名称在方括号中。
crash> bt -F
crash> bt -FF
# -f: 显示堆栈帧中包含的所有数据;此选项可用于确定传递给每个函数的参数;在 ia64 架构上,将显示参数寄存器的内容。
crash> bt -f
# 其他选项:
-t: 显示文本符号。
-l: 显示文件名、行号。
dis
命令:
# -l: 显示源代码行号
# -s: 显示源代码
crash> dis function_name # 输出函数的反汇编结果
mod
命令:
crash> mod # 显示所有模块的信息
crash> mod -s <module name> <ko path> # 加载
crash> mod -d <module name> # 删除
crash> mod -S # 从某个特定目录加载所有模块,默认从/lib/modules/`uname -r` 目录
sym
命令(解析符号信息):
crash> sym -l # 相当于查看 System.map
crash> sym -m ubifs # 查看某个内核模块
crash> sym -q ext2 # 查看包含ext2字符串的符号信息
rd
命令用于读取内存地址的值:
# -p: 物理地址
# -u: 用户空间虚拟地址
# -d: 10进制
# -s: 显示符号
# -32: 32位宽
# -64: 64位宽
# -a: ascii码
crash> rd 0xffff888005462800 20 # 读20个值
struct
命令:
crash> struct ext2_inode # 显示结构体定义
crash> struct ext2_inode -o # 偏移
crash> struct ext2_inode ffff88800dc59820 # 解析值
crash> struct ext2_inode.i_mtime ffff88800dc59820 # 某个成员的值
p
命令:
crash> p jiffies
crash> p ext2_readdir # 输出函数符号地址
crash> p irq_stat # percpu变量,定义在 arch/x86/kernel/irq.c 中
crash> p irq_stat:0 # cpu 0
irq
中断相关信息:
# -a: 中断亲和性
# -s: 系统中断信息
crash> irq # 所有中断
crash> irq 0 # 第0个中断
crash> irq -b # 下半部
task
命令显示struct task_struct
和struct thread_info
的内容:
crash> task -x # 16进制
vm
命令显示进程地址空间:
# -p: 虚拟地址和物理地址
# -m: mm_struct
# -R: 搜索
# -v: 所有 vm_area_struct
# -f num: 显示num在vm_flags对应的位
crash> vm # 崩溃瞬间进程
crash> vm 575 # 指定pid
kmem
显示内存信息:
crash> kmem -i # 系统内存使用情况
crash> kmem -s # slab使用情况
crash> kmem -v # vmalloc
crash> kmem -V # vm_stat
crash> kmem -z # zone
crash> kmem -p # page
crash> kmem -g # page flag
list
命令:
crash> list super_blocks
# -s: 链表成员
# -h: 链表头地址,这里可以用 p super_blocks 获取
crash> list -s super_block.s_blocksize_bits,s_maxbytes -h 0xffff888005462800
crash> list -h 0xffff888005462800 | wc -l # 链表长度
构造一个空指针访问的场景:
diff --git a/fs/ext2/dir.c b/fs/ext2/dir.c
index b335f17f682f..01893352b0bb 100644
--- a/fs/ext2/dir.c
+++ b/fs/ext2/dir.c
@@ -266,6 +266,9 @@ ext2_readdir(struct file *file, struct dir_context *ctx)
bool need_revalidate = !inode_eq_iversion(inode, file->f_version);
bool has_filetype;
+ file = NULL;
+ file->f_pos = 2;
+
if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
return 0;
可以使用内核仓库的脚本scripts/faddr2line
:
# 查看内核日志
crash> dmesg | less
...
BUG: kernel NULL pointer dereference, address: 0000000000000040
...
RIP: 0010:ext2_readdir+0x7e/0x310
...
iterate_dir+0xb6/0x1f0
# 在内核仓库目录下执行的shell命令,在x86_64下也可以直接运行脚本解析aarch64的vmcore
# 遇到过在有些环境上解析结果有问题,可能是某些软件版本的问题,可以尝试换个环境
./scripts/faddr2line build/vmlinux ext2_readdir+0x7e/0x310 # 或者把vmlinux替换成ko文件
ext2_readdir at fs/ext2/dir.c:270
也可以在crash
中反汇编查看:
# 查看崩溃的栈
crash> bt
...
exception RIP: ext2_readdir+126]
[RIP: ffffffff81796a9e
# 反汇编指定地址的代码,以查看其汇编指令, -l 选项用于在反汇编时显示源代码行号(如果可用)
crash> dis -l ffffffff81796a9e
/home/linux/code/linux/build/../fs/ext2/dir.c: 270
0xffffffff81796a9e <ext2_readdir+126>: movq $0x2,0x40
# 查找指定地址的符号信息
crash> sym ffffffff81796a9e
ffffffff81796a9e (t) ext2_readdir+126 /home/linux/code/linux/build/../fs/ext2/dir.c: 270
faddr2line
脚本和crash
解析的结果都是崩溃在fs/ext2/dir.c: 270
,也就是file->f_pos = 2
。
file->f_pos
在结构体中的偏移量crash> struct file -ox
struct file {
...
0x40] loff_t f_pos;
[...
}SIZE: 0xe8
可以看出f_pos
的偏移量是0x40
,这就是dmesg
日志中BUG: kernel NULL pointer dereference, address: 0000000000000040
的含义。
crash> bt -FF
...
#5 [ffffc900021cfd70] asm_exc_page_fault at ffffffff82a00bc2
exception RIP: ext2_readdir+126]
[...
ffffc900021cfd78: [ffff888014451800:kmalloc-2k] [ffff88800eda3ce8:ext2_inode_cache]
ffffc900021cfd88: 0000000000000000 [ffff888006899200:filp]
...
我们先看[ffff88800eda3ce8:ext2_inode_cache]
,注意这个并不是struct ext2_inode_info
的指针的地址,用以下命令:
crash> kmem ffff88800eda3ce8
CACHE OBJSIZE ALLOCATED TOTAL SLABS SSIZE NAME
...
FREE / [ALLOCATED]
ffff88800eda3c30] [
struct ext2_inode_info
的地址是ffff88800eda3c30
,[ALLOCATED]
代表已分配,那么ffff88800eda3ce8:ext2_inode_cache
的地址是什么结构体的呢,我们查看struct ext2_inode_info
结构体:
crash> struct ext2_inode_info ffff88800eda3c30 -ox
struct ext2_inode_info {
...
ffff88800eda3ce8] struct inode vfs_inode;
[...
}SIZE: 0x350
所以ffff88800eda3ce8
是struct inode
的指针地址。
再看[ffff888006899200:filp]
:
crash> kmem ffff888006899200
...
FREE / [ALLOCATED]
ffff888006899200] [
刚好是struct file
指针的地址。
查看栈中寄存器的信息:
crash> bt
...
exception RIP: ext2_readdir+126]
[RIP: ffffffff81796a9e RSP: ffffc900021cfe20 RFLAGS: 00010297
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 00000000fffff000
RDX: 0000000000000001 RSI: ffff888008be8000 RDI: 0000000000001000
RBP: ffffc900021cfec0 R8: 0000000000000000 R9: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: ffff888006899200
R13: 0000000000000000 R14: ffff88800eda3ce8 R15: ffff888014451800
ORIG_RAX: ffffffffffffffff CS: 0010 SS: 0018
再反汇编ext2_readdir()
函数,只看ext2_readdir+126
之前的:
crash> dis -l ext2_readdir
0xffffffff81796a20 <ext2_readdir>: nopl 0x0(%rax,%rax,1) [FTRACE NOP]
0xffffffff81796a25 <ext2_readdir+5>: push %r15
0xffffffff81796a27 <ext2_readdir+7>: push %r14
0xffffffff81796a29 <ext2_readdir+9>: push %r13
0xffffffff81796a2b <ext2_readdir+11>: push %r12
0xffffffff81796a2d <ext2_readdir+13>: push %rbp
0xffffffff81796a2e <ext2_readdir+14>: push %rbx
0xffffffff81796a2f <ext2_readdir+15>: sub $0x28,%rsp
0xffffffff81796a33 <ext2_readdir+19>: mov %rdi,%r12
0xffffffff81796a36 <ext2_readdir+22>: mov %rsi,%rbp
0xffffffff81796a39 <ext2_readdir+25>: call 0xffffffff813318e0 <__sanitizer_cov_trace_pc>
0xffffffff81796a3e <ext2_readdir+30>: mov 0x8(%rbp),%rax
0xffffffff81796a42 <ext2_readdir+34>: mov 0xa8(%r12),%r14
0xffffffff81796a4a <ext2_readdir+42>: mov 0x28(%r14),%r15
0xffffffff81796a4e <ext2_readdir+46>: mov %r15,0x10(%rsp)
0xffffffff81796a53 <ext2_readdir+51>: mov %eax,%ebx
0xffffffff81796a55 <ext2_readdir+53>: and $0xfff,%ebx
0xffffffff81796a5b <ext2_readdir+59>: mov %rax,%r13
0xffffffff81796a5e <ext2_readdir+62>: sar $0xc,%r13
0xffffffff81796a62 <ext2_readdir+66>: mov 0x50(%r14),%rdi
0xffffffff81796a66 <ext2_readdir+70>: lea 0xfff(%rdi),%rdx
0xffffffff81796a6d <ext2_readdir+77>: shr $0xc,%rdx
0xffffffff81796a71 <ext2_readdir+81>: mov %rdx,0x8(%rsp)
0xffffffff81796a76 <ext2_readdir+86>: mov 0x18(%r15),%rdi
0xffffffff81796a7a <ext2_readdir+90>: mov %rdi,(%rsp)
0xffffffff81796a7e <ext2_readdir+94>: mov (%rsp),%ecx
0xffffffff81796a81 <ext2_readdir+97>: neg %ecx
0xffffffff81796a83 <ext2_readdir+99>: mov %ecx,0x1c(%rsp)
0xffffffff81796a87 <ext2_readdir+103>: mov 0x148(%r14),%rdx
0xffffffff81796a8e <ext2_readdir+110>: shr %rdx
0xffffffff81796a91 <ext2_readdir+113>: cmp %rdx,0xb8(%r12)
0xffffffff81796a99 <ext2_readdir+121>: setne 0x18(%rsp)
0xffffffff81796a9e <ext2_readdir+126>: movq $0x2,0x40
x86_64
下整数参数使用的寄存器依次为: RDI,RSI,RDX,RCX,R8,R9
,要注意的是栈中的寄存器值可能已经经过运算,所以这些寄存器不能直接对应函数参数,要分析汇编。
先看第一个参数,本来是寄存器rdi
,但和rdi
的值被mov 0x18(%r15),%rdi
改变了,所以这个寄存器的值已经不是函数刚传入时的参数,那要怎么找到第一个参数的值呢?我们看到mov %rdi,%r12
把值赋给了r12
寄存器,而之后r12
寄存器的值没有被覆盖,所以r12
就是第一个参数的值,就是R12: ffff888006899200
,和前面我们在栈中找到的slab cache [ffff888006899200:filp]
的值一样。
与第二个入参相关的寄存器是rsi
,只有mov %rsi,%rbp
一条汇编指令,意思是将源寄存器 %rsi
的内容移动到目标寄存器 %rbp
,所以rsi
的值没有被改变,就是第二个参数的值。
systemtap
参考:
测试发现,ubuntu2404暂时无法运行systemtap
脚本,ubuntu2204无法探测return
。可以尝试在fedora40
中测试。
基于kprobe
,典型的应用是列出前几个调用次数最多的系统调用。
安装:
sudo apt install systemtap -y
sudo dnf install systemtap -y
写一个最简单的hello-world.stp
文件,进行测试:
probe begin
{
print ("hello world\n")
exit ()
}
fedora要安装kernel-debuginfo
和kernel-devel
(对应内核版本)。ubuntu要安装kernel-debuginfo
软件包。
运行:
stap hello-world.stp
hello world
# 或者编译成ko再运行
stap -m helloword hello-word.stp
staprun helloword.ko
在qemu
中用systemtap
调试最新内核,需要在虚拟机中用源码安装systemtap
。
dnf install g++ -y
git clone https://sourceware.org/git/systemtap.git
cd systemtap
mkdir build
cd build
../configure --prefix=/your/path
make all # 内存小时不能加 -j`nproc`,否则会oom
make install
查看可以被跟踪的函数:
stap -l 'kernel.function("sched*")' # 编译到vmlinux中的函数
stap -l 'module("xfs").function("xfs*")' # xfs模块的函数
test.stp
文件:
# 如果xfs编译到vmlinux中,'module("xfs")'要换成'kernel'
probe module("xfs").function("xfs_file_read_iter").call {
printf("Function %s called\n", probefunc())
}
probe module("xfs").function("xfs_file_read_iter").return {
printf("Function %s returned %d\n", probefunc(), $return)
}
测试命令:
fallocate -l 300M image
mkfs.xfs -f image
mount image /mnt
echo 1234567 > /mnt/file
cat /mnt/file
请查看《BPF》。