内核测试工具

点击跳转到内核课程所有目录

点击这里查看配套的教学视频

下面介绍一些Linux内核的测试工具。

1 syzkaller

参考:

1.1 软件环境

打开All releases - The Go Programming Language,下载最新版本,如go1.22.5.linux-amd64.tar.gz:

wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
tar xvf go1.22.5.linux-amd64.tar.gz
export GOROOT=`pwd`/go
export PATH=$GOROOT/bin:$PATH

编译syzkaller源码:

git clone https://github.com/google/syzkaller
cd syzkaller
make # 编译结果在 bin/

安装软件:

sudo apt update
sudo apt install make gcc flex bison libncurses-dev libelf-dev libssl-dev -y

内核x86_64-config文件还要打开以下配置:

# Debug info for symbolization.
CONFIG_DEBUG_INFO_DWARF4=y

# Memory bug detector,这玩意儿会导致运行很慢,所以如果不是测试的不要打开
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y

1.2 生成qcow2镜像

sudo apt install debootstrap -y
mkdir syzkaller-image
cd syzkaller-image
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
chmod +x create-image.sh

为了加快下载速度,然后将create-image.sh中的DEBOOTSTRAP_PARAMS="--keyring /usr/share/keyrings/debian-archive-removed-keys.gpg $DEBOOTSTRAP_PARAMS后面的链接修改成https://repo.huaweicloud.com/debian/。但有些网络下也不会加快太多,可以想办法访问国外网络进行下载。

再运行脚本生成bullseye.img:

./create-image.sh

生成bullseye.img后,用以下脚本启动测试一下:

qemu-system-x86_64 \
    -m 2G \
    -smp 16 \
    -kernel /home/sonvhi/chenxiaosong/code/x86_64-linux/build/arch/x86/boot/bzImage \
    -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" \
    -drive file=bullseye.img,format=raw \
    -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \
    -net nic,model=e1000 \
    -enable-kvm \
    -nographic \

确保能远程登录:

ssh -i bullseye.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost

测试完后,要把虚拟机关机,因为syzkaller会自己启动虚拟机。如果你已经用上面的脚本启动了虚拟机,再启动syzkaller就会启动失败,而且qcow2镜像也会损坏。

1.3 运行

到syzkaller源码目录下,创建my.cfg文件如下:

{
    "target": "linux/amd64",
    "http": "0.0.0.0:56741",
    "workdir": "workdir",
    # 这个应该是vmlinux的路径
    "kernel_obj": "/home/sonvhi/chenxiaosong/code/x86_64-linux/build/",
    "image": "/home/sonvhi/chenxiaosong/syzkaller-image/bullseye.img",
    "sshkey": "/home/sonvhi/chenxiaosong/syzkaller-image/bullseye.id_rsa",
    "syzkaller": ".",
    # 只测 chmod 系统调用
    # "enable_syscalls": ["chmod"],
    "procs": 8,
    "type": "qemu",
    "vm": {
        "count": 4,
        "kernel": "/home/sonvhi/chenxiaosong/code/x86_64-linux/build/arch/x86/boot/bzImage",
        "cpu": 2,
        "mem": 2048
    }
}

运行:

mkdir workdir
./bin/syz-manager -config=my.cfg

这时就能通过网页查看测试结果。如果你是在docker中运行syzkaller,想在加一台电脑上访问网页,可以在宿主机中安装nginx,并在nginx配置文件/etc/nginx/sites-enabled/default中添加以下内容,172.17.0.3是docker的ip:

server {
        listen 56741;

        location / {
                proxy_pass http://172.17.0.3:56741/;
        }
}

这时就可以在其他电脑上访问http://192.168.3.224:56741/192.168.3.224是宿主机的ip)。

1.4 构造一个简单的bug

连续两次chmod调用的mode入参为0时,产生空指针解引用的bug,这个函数的执行路径是chmod() -> do_fchmodat() -> chmod_common()

diff --git a/fs/open.c b/fs/open.c
index 50e45bc7c4d8..ee7962ca777d 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -637,6 +637,12 @@ int chmod_common(const struct path *path, umode_t mode)
        struct iattr newattrs;
        int error;

+        static umode_t old_mode = 0xffff;
+        if (old_mode == 0 && mode == 0) {
+                path = NULL;
+        }
+        old_mode = mode;
+
        error = mnt_want_write(path->mnt);
        if (error)
                return error;

到syzkaller源码目录下,my.cfg文件修改成如下,增加enable_syscalls,只测试chmod:

{
    "target": "linux/amd64",
    "http": "0.0.0.0:56741",
    "workdir": "workdir",
    # 这个应该是vmlinux的路径
    "kernel_obj": "/home/sonvhi/chenxiaosong/code/x86_64-linux/build/",
    "image": "/home/sonvhi/chenxiaosong/syzkaller-image/bullseye.img",
    "sshkey": "/home/sonvhi/chenxiaosong/syzkaller-image/bullseye.id_rsa",
    "syzkaller": ".",
    # 只测 chmod 系统调用
    "enable_syscalls": ["chmod"],
    "procs": 8,
    "type": "qemu",
    "vm": {
        "count": 4,
        "kernel": "/home/sonvhi/chenxiaosong/code/x86_64-linux/build/arch/x86/boot/bzImage",
        "cpu": 2,
        "mem": 2048
    }
}

运行:

mkdir workdir
./bin/syz-manager -config=my.cfg

1.5 复现

可以在syzbot中找发现的bug,有crash的日志和复现程序(syz和C),把bin/linux_amd64/复制到要测试的虚拟机中,按以下步骤复现。

echo 1 > /proc/sys/kernel/panic_on_oops # 注意不能用 vim 编辑
cat /proc/sys/kernel/panic_on_oops # 确认是否生效
./linux_amd64/syz-execprog -executor=./linux_amd64/syz-executor -repeat=0 -procs=16 -cover=0 crash-log
./linux_amd64/syz-execprog -executor=./linux_amd64/syz-executor -repeat=0 -procs=16 -cover=0 file-with-a-single-program

2 xfstests

2.1 安装

sudo yum install acl attr automake bc dbench dump e2fsprogs fio gawk gcc \
        gdbm-devel git indent kernel-devel libacl-devel libaio-devel \
        libcap-devel libtool liburing-devel libuuid-devel lvm2 make psmisc \
        python3 quota sed sqlite udftools  xfsprogs -y
# 安装用于正在测试的文件系统的软件包,或者其他软件包
sudo yum install btrfs-progs exfatprogs f2fs-tools ocfs2-tools xfsdump \
        xfsprogs-devel

源码编译安装:

git clone git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
cd xfstests-dev
make -j`nproc`
sudo make install

2.2 测试前准备

qemu的启动脚本添加两个设备:

-drive file=1,if=none,format=raw,cache=writeback,file.locking=off,id=dd_1 \
-device scsi-hd,drive=dd_1,id=disk_1,logical_block_size=512,physical_block_size=512 \
-drive file=2,if=none,format=raw,cache=writeback,file.locking=off,id=dd_2 \
-device scsi-hd,drive=dd_2,id=disk_2,logical_block_size=512,physical_block_size=512 \

创建两个10G的文件:

fallocate -l 10G 1
fallocate -l 10G 2

然后启动虚拟机,进入虚拟机。

(可选) 创建 fsgqa 测试用户和组:

sudo useradd -m fsgqa
sudo useradd 123456-fsgqa
sudo useradd fsgqa2
sudo groupadd fsgqa

2.3 测试

2.3.1 ./check --help

翻译如下:

用法: ./check [选项] [测试列表]

check 选项
    -nfs       测试 NFS
    -afs       测试 AFS
    -glusterfs 测试 GlusterFS
    -cifs      测试 CIFS
    -9p        测试 9p
    -fuse      测试 fuse
    -virtiofs  测试 virtiofs
    -overlay   测试 overlay
    -pvfs2     测试 PVFS2
    -tmpfs     测试 TMPFS
    -ubifs     测试 ubifs
    -l         行模式 diff
    -udiff     显示统一 diff(默认)
    -n         只显示,不运行测试
    -T         输出时间戳
    -r         随机化测试顺序
    --exact-order  按指定的准确顺序运行测试
    -i <n>     重复测试列表 <n> 次
    -I <n>     重复测试列表 <n> 次,但在任何测试失败时停止继续迭代
    -d         将测试输出转储到标准输出
    -b         简要测试总结
    -R fmt[,fmt]  以指定的格式生成报告。支持的格式:xunit, xunit-quiet
    --large-fs   优化大文件系统的临时设备
    -s section   仅运行配置文件中指定的部分
    -S section   排除配置文件中指定的部分
    -L <n>       测试失败后循环测试 <n> 次,测量通过/失败的总体指标

测试列表选项
    -g group[,group...]   包含这些组中的测试
    -x group[,group...]   排除这些组中的测试
    -X exclude_file       排除单个测试
    -e testlist           排除特定的测试列表
    -E external_file      排除单个测试
    [testlist]            包含匹配名称的测试

testlist 参数是以 <test dir>/<test name> 形式的测试列表。

<test dir> 是 tests 下的一个目录,包含一个组文件,该文件列出了该目录下测试的名称。

<test name> 可以是一个特定的测试文件名(例如 xfs/001)或一个测试文件名匹配模式(例如 xfs/*)。

group 参数是测试组的名称,可以从所有测试目录中收集(例如 quick)或从特定测试目录中的组收集,形式为 <test dir>/<group name>(例如 xfs/quick)。
如果要运行测试套件中的所有测试,可以使用 "-g all" 来指定所有组。

exclude_file 参数是每个测试目录下的一个文件名。在该文件所在的每个测试目录中,列出的测试名称将从该目录的测试列表中排除。

external_file 参数是一个路径,指向一个包含要排除的测试列表的单个文件,格式为 <test dir>/<test name>。

示例:
 check xfs/001
 check -g quick
 check -g xfs/quick
 check -x stress xfs/*
 check -X .exclude -g auto
 check -E ~/.xfstests.exclude

2.3.2 ext4

创建local.config配置文件:

TEST_DEV=/dev/sda
TEST_DIR=/tmp/test
SCRATCH_DEV=/dev/sdb
SCRATCH_MNT=/tmp/scratch
FSTYP=ext4
MKFS_OPTIONS="-b 4096"
MOUNT_OPTIONS="-o acl,user_xattr"

测试命令:

./check generic/001
./check ext4/001
./check -g generic/dir # 组查看tests/generic/group.list

2.3.3 nfs

先执行bash nfs-svr-setup.sh启动nfs server。

创建local.config配置文件:

TEST_DEV=localhost:/s_test
TEST_DIR=/tmp/test
SCRATCH_DEV=localhost:/s_scratch
SCRATCH_MNT=/tmp/scratch
FSTYP=nfs
MOUNT_OPTIONS="-o vers=4.2"

测试命令:

./check generic/001
./check nfs/001
./check -g generic/dir # 组查看tests/generic/group.list

3 ltp

3.1 环境

github源码: linux-test-project/ltp,参考INSTALL安装需要的依赖软件。其中linux-headers相关的在debian上可以使用以下命令安装:

apt search linux-headers | less # 搜索对应的软件包名
apt install -y linux-headers-amd64 linux-headers-5.10.0-28-common

编译安装参考README中文翻译,默认安装在/opt/ltp中。

3.2 LTP Network Tests

LTP Network Tests README中文翻译

打开配置CONFIG_VETH=mCONFIG_NFS_FS=m

在debian发行版下,启动nfs server服务后,执行命令cd /opt/ltp/testscripts; ./network.sh -n后报错,换成手动执行第一个用例cd /opt/ltp/testcases/bin; PATH=$PATH:$PWD ./nfs01.sh -v 3 -t udp,报错nfs01 1 TCONF: rpc.statd not running,修改/opt/ltp/testcases/bin/nfs_lib.sh:

diff --git a/testcases/network/nfs/nfs_stress/nfs_lib.sh b/testcases/network/nfs/nfs_stress/nfs_lib.sh
index d3de3b7f1..4be0bcc6f 100644
--- a/testcases/network/nfs/nfs_stress/nfs_lib.sh
+++ b/testcases/network/nfs/nfs_stress/nfs_lib.sh
@@ -174,7 +174,7 @@ nfs_setup()
        fi

        if tst_cmd_available pgrep; then
-               for i in rpc.mountd rpc.statd; do
+               for i in rpc.mountd; do
                        pgrep $i > /dev/null || tst_brk TCONF "$i not running"
                done
        fi