diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /Documentation/translations/zh_CN/dev-tools | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'Documentation/translations/zh_CN/dev-tools')
6 files changed, 1204 insertions, 0 deletions
diff --git a/Documentation/translations/zh_CN/dev-tools/gcov.rst b/Documentation/translations/zh_CN/dev-tools/gcov.rst new file mode 100644 index 0000000000..3158c9da13 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/gcov.rst @@ -0,0 +1,264 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/gcov.rst +:Translator: 赵军奎 Bernard Zhao <bernard@vivo.com> + +在Linux内核里使用gcov做代码覆盖率检查 +===================================== + +gcov分析核心支持在Linux内核中启用GCC的覆盖率测试工具 gcov_ ,Linux内核 +运行时的代码覆盖率数据会以gcov兼容的格式导出到“gcov”debugfs目录中,可 +以通过gcov的 ``-o`` 选项(如下示例)获得指定文件的代码运行覆盖率统计数据 +(需要跳转到内核编译路径下并且要有root权限):: + + # cd /tmp/linux-out + # gcov -o /sys/kernel/debug/gcov/tmp/linux-out/kernel spinlock.c + +这将在当前目录中创建带有执行计数注释的源代码文件。 +在获得这些统计文件后,可以使用图形化的gcov前端工具(比如 lcov_ ),来实现 +自动化处理Linux内核的覆盖率运行数据,同时生成易于阅读的HTML格式文件。 + +可能的用途: + +* 调试(用来判断每一行的代码是否已经运行过) +* 测试改进(如何修改测试代码,尽可能地覆盖到没有运行过的代码) +* 内核最小化配置(对于某一个选项配置,如果关联的代码从来没有运行过, + 是否还需要这个配置) + +.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html +.. _lcov: http://ltp.sourceforge.net/coverage/lcov.php + + +准备 +---- + +内核打开如下配置:: + + CONFIG_DEBUG_FS=y + CONFIG_GCOV_KERNEL=y + +获取整个内核的覆盖率数据,还需要打开:: + + CONFIG_GCOV_PROFILE_ALL=y + +需要注意的是,整个内核开启覆盖率统计会造成内核镜像文件尺寸的增大, +同时内核运行也会变慢一些。 +另外,并不是所有的架构都支持整个内核开启覆盖率统计。 + +代码运行覆盖率数据只在debugfs挂载完成后才可以访问:: + + mount -t debugfs none /sys/kernel/debug + + +定制化 +------ + +如果要单独针对某一个路径或者文件进行代码覆盖率统计,可以在内核相应路 +径的Makefile中增加如下的配置: + +- 单独统计单个文件(例如main.o):: + + GCOV_PROFILE_main.o := y + +- 单独统计某一个路径:: + + GCOV_PROFILE := y + +如果要在整个内核的覆盖率统计(开启CONFIG_GCOV_PROFILE_ALL)中单独排除 +某一个文件或者路径,可以使用如下的方法:: + + GCOV_PROFILE_main.o := n + +和:: + + GCOV_PROFILE := n + +此机制仅支持链接到内核镜像或编译为内核模块的文件。 + + +相关文件 +-------- + +gcov功能需要在debugfs中创建如下文件: + +``/sys/kernel/debug/gcov`` + gcov相关功能的根路径 + +``/sys/kernel/debug/gcov/reset`` + 全局复位文件:向该文件写入数据后会将所有的gcov统计数据清0 + +``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcda`` + gcov工具可以识别的覆盖率统计数据文件,向该文件写入数据后 + 会将本文件的gcov统计数据清0 + +``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcno`` + gcov工具需要的软连接文件(指向编译时生成的信息统计文件),这个文件是 + 在gcc编译时如果配置了选项 ``-ftest-coverage`` 时生成的。 + + +针对模块的统计 +-------------- + +内核中的模块会动态的加载和卸载,模块卸载时对应的数据会被清除掉。 +gcov提供了一种机制,通过保留相关数据的副本来收集这部分卸载模块的覆盖率数据。 +模块卸载后这些备份数据在debugfs中会继续存在。 +一旦这个模块重新加载,模块关联的运行统计会被初始化成debugfs中备份的数据。 + +可以通过对内核参数gcov_persist的修改来停用gcov对模块的备份机制:: + + gcov_persist = 0 + +在运行时,用户还可以通过写入模块的数据文件或者写入gcov复位文件来丢弃已卸 +载模块的数据。 + + +编译机和测试机分离 +------------------ + +gcov的内核分析插桩支持内核的编译和运行是在同一台机器上,也可以编译和运 +行是在不同的机器上。 +如果内核编译和运行是不同的机器,那么需要额外的准备工作,这取决于gcov工具 +是在哪里使用的: + +.. _gcov-test_zh: + +a) 若gcov运行在测试机上 + + 测试机上面gcov工具的版本必须要跟内核编译机器使用的gcc版本相兼容, + 同时下面的文件要从编译机拷贝到测试机上: + + 从源代码中: + - 所有的C文件和头文件 + + 从编译目录中: + - 所有的C文件和头文件 + - 所有的.gcda文件和.gcno文件 + - 所有目录的链接 + + 特别需要注意,测试机器上面的目录结构跟编译机器上面的目录机构必须 + 完全一致。 + 如果文件是软链接,需要替换成真正的目录文件(这是由make的当前工作 + 目录变量CURDIR引起的)。 + +.. _gcov-build_zh: + +b) 若gcov运行在编译机上 + + 测试用例运行结束后,如下的文件需要从测试机中拷贝到编译机上: + + 从sysfs中的gcov目录中: + - 所有的.gcda文件 + - 所有的.gcno文件软链接 + + 这些文件可以拷贝到编译机的任意目录下,gcov使用-o选项指定拷贝的 + 目录。 + + 比如一个是示例的目录结构如下:: + + /tmp/linux: 内核源码目录 + /tmp/out: 内核编译文件路径(make O=指定) + /tmp/coverage: 从测试机器上面拷贝的数据文件路径 + + [user@build] cd /tmp/out + [user@build] gcov -o /tmp/coverage/tmp/out/init main.c + + +关于编译器的注意事项 +-------------------- + +GCC和LLVM gcov工具不一定兼容。 +如果编译器是GCC,使用 gcov_ 来处理.gcno和.gcda文件,如果是Clang编译器, +则使用 llvm-cov_ 。 + +.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html +.. _llvm-cov: https://llvm.org/docs/CommandGuide/llvm-cov.html + +GCC和Clang gcov之间的版本差异由Kconfig处理的。 +kconfig会根据编译工具链的检查自动选择合适的gcov格式。 + +问题定位 +-------- + +可能出现的问题1 + 编译到链接阶段报错终止 + +问题原因 + 分析标志指定在了源文件但是没有链接到主内核,或者客制化了链接程序 + +解决方法 + 通过在相应的Makefile中使用 ``GCOV_PROFILE := n`` + 或者 ``GCOV_PROFILE_basename.o := n`` 来将链接报错的文件排除掉 + +可能出现的问题2 + 从sysfs复制的文件显示为空或不完整 + +问题原因 + 由于seq_file的工作方式,某些工具(例如cp或tar)可能无法正确地从 + sysfs复制文件。 + +解决方法 + 使用 ``cat`` 读取 ``.gcda`` 文件,使用 ``cp -d`` 复制链接,或者使用附录B + 中所示的机制。 + + +附录A:collect_on_build.sh +-------------------------- + +用于在编译机上收集覆盖率元文件的示例脚本 +(见 :ref:`编译机和测试机分离 a. <gcov-test_zh>` ) + +.. code-block:: sh + + #!/bin/bash + + KSRC=$1 + KOBJ=$2 + DEST=$3 + + if [ -z "$KSRC" ] || [ -z "$KOBJ" ] || [ -z "$DEST" ]; then + echo "Usage: $0 <ksrc directory> <kobj directory> <output.tar.gz>" >&2 + exit 1 + fi + + KSRC=$(cd $KSRC; printf "all:\n\t@echo \${CURDIR}\n" | make -f -) + KOBJ=$(cd $KOBJ; printf "all:\n\t@echo \${CURDIR}\n" | make -f -) + + find $KSRC $KOBJ \( -name '*.gcno' -o -name '*.[ch]' -o -type l \) -a \ + -perm /u+r,g+r | tar cfz $DEST -P -T - + + if [ $? -eq 0 ] ; then + echo "$DEST successfully created, copy to test system and unpack with:" + echo " tar xfz $DEST -P" + else + echo "Could not create file $DEST" + fi + + +附录B:collect_on_test.sh +------------------------- + +用于在测试机上收集覆盖率数据文件的示例脚本 +(见 :ref:`编译机和测试机分离 b. <gcov-build_zh>` ) + +.. code-block:: sh + + #!/bin/bash -e + + DEST=$1 + GCDA=/sys/kernel/debug/gcov + + if [ -z "$DEST" ] ; then + echo "Usage: $0 <output.tar.gz>" >&2 + exit 1 + fi + + TEMPDIR=$(mktemp -d) + echo Collecting data.. + find $GCDA -type d -exec mkdir -p $TEMPDIR/\{\} \; + find $GCDA -name '*.gcda' -exec sh -c 'cat < $0 > '$TEMPDIR'/$0' {} \; + find $GCDA -name '*.gcno' -exec sh -c 'cp -d $0 '$TEMPDIR'/$0' {} \; + tar czf $DEST -C $TEMPDIR sys + rm -rf $TEMPDIR + + echo "$DEST successfully created, copy to build system and unpack with:" + echo " tar xfz $DEST" diff --git a/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst b/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst new file mode 100644 index 0000000000..17b5ce85a9 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/gdb-kernel-debugging.rst @@ -0,0 +1,167 @@ +.. highlight:: none + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/gdb-kernel-debugging.rst +:Translator: 高超 gao chao <gaochao49@huawei.com> + +通过gdb调试内核和模块 +===================== + +Kgdb内核调试器、QEMU等虚拟机管理程序或基于JTAG的硬件接口,支持在运行时使用gdb +调试Linux内核及其模块。Gdb提供了一个强大的python脚本接口,内核也提供了一套 +辅助脚本以简化典型的内核调试步骤。本文档为如何启用和使用这些脚本提供了一个简要的教程。 +此教程基于QEMU/KVM虚拟机,但文中示例也适用于其他gdb stub。 + + +环境配置要求 +------------ + +- gdb 7.2+ (推荐版本: 7.4+) 且开启python支持 (通常发行版上都已支持) + +设置 +---- + +- 创建一个QEMU/KVM的linux虚拟机(详情请参考 www.linux-kvm.org 和 www.qemu.org )。 + 对于交叉开发,https://landley.net/aboriginal/bin 提供了一些镜像和工具链, + 可以帮助搭建交叉开发环境。 + +- 编译内核时开启CONFIG_GDB_SCRIPTS,关闭CONFIG_DEBUG_INFO_REDUCED。 + 如果架构支持CONFIG_FRAME_POINTER,请保持开启。 + +- 在guest环境上安装该内核。如有必要,通过在内核command line中添加“nokaslr”来关闭KASLR。 + 此外,QEMU允许通过-kernel、-append、-initrd这些命令行选项直接启动内核。 + 但这通常仅在不依赖内核模块时才有效。有关此模式的更多详细信息,请参阅QEMU文档。 + 在这种情况下,如果架构支持KASLR,应该在禁用CONFIG_RANDOMIZE_BASE的情况下构建内核。 + +- 启用QEMU/KVM的gdb stub,可以通过如下方式实现 + + - 在VM启动时,通过在QEMU命令行中添加“-s”参数 + + 或 + + - 在运行时通过从QEMU监视控制台发送“gdbserver” + +- 切换到/path/to/linux-build(内核源码编译)目录 + +- 启动gdb:gdb vmlinux + + 注意:某些发行版可能会将gdb脚本的自动加载限制在已知的安全目录中。 + 如果gdb报告拒绝加载vmlinux-gdb.py(相关命令找不到),请将:: + + add-auto-load-safe-path /path/to/linux-build + + 添加到~/.gdbinit。更多详细信息,请参阅gdb帮助信息。 + +- 连接到已启动的guest环境:: + + (gdb) target remote :1234 + + +使用Linux提供的gdb脚本的示例 +---------------------------- + +- 加载模块(以及主内核)符号:: + + (gdb) lx-symbols + loading vmlinux + scanning for modules in /home/user/linux/build + loading @0xffffffffa0020000: /home/user/linux/build/net/netfilter/xt_tcpudp.ko + loading @0xffffffffa0016000: /home/user/linux/build/net/netfilter/xt_pkttype.ko + loading @0xffffffffa0002000: /home/user/linux/build/net/netfilter/xt_limit.ko + loading @0xffffffffa00ca000: /home/user/linux/build/net/packet/af_packet.ko + loading @0xffffffffa003c000: /home/user/linux/build/fs/fuse/fuse.ko + ... + loading @0xffffffffa0000000: /home/user/linux/build/drivers/ata/ata_generic.ko + +- 对一些尚未加载的模块中的函数函数设置断点,例如:: + + (gdb) b btrfs_init_sysfs + Function "btrfs_init_sysfs" not defined. + Make breakpoint pending on future shared library load? (y or [n]) y + Breakpoint 1 (btrfs_init_sysfs) pending. + +- 继续执行:: + + (gdb) c + +- 加载模块并且能观察到正在加载的符号以及断点命中:: + + loading @0xffffffffa0034000: /home/user/linux/build/lib/libcrc32c.ko + loading @0xffffffffa0050000: /home/user/linux/build/lib/lzo/lzo_compress.ko + loading @0xffffffffa006e000: /home/user/linux/build/lib/zlib_deflate/zlib_deflate.ko + loading @0xffffffffa01b1000: /home/user/linux/build/fs/btrfs/btrfs.ko + + Breakpoint 1, btrfs_init_sysfs () at /home/user/linux/fs/btrfs/sysfs.c:36 + 36 btrfs_kset = kset_create_and_add("btrfs", NULL, fs_kobj); + +- 查看内核的日志缓冲区:: + + (gdb) lx-dmesg + [ 0.000000] Initializing cgroup subsys cpuset + [ 0.000000] Initializing cgroup subsys cpu + [ 0.000000] Linux version 3.8.0-rc4-dbg+ (... + [ 0.000000] Command line: root=/dev/sda2 resume=/dev/sda1 vga=0x314 + [ 0.000000] e820: BIOS-provided physical RAM map: + [ 0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable + [ 0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved + .... + +- 查看当前task struct结构体的字段(仅x86和arm64支持):: + + (gdb) p $lx_current().pid + $1 = 4998 + (gdb) p $lx_current().comm + $2 = "modprobe\000\000\000\000\000\000\000" + +- 对当前或指定的CPU使用per-cpu函数:: + + (gdb) p $lx_per_cpu("runqueues").nr_running + $3 = 1 + (gdb) p $lx_per_cpu("runqueues", 2).nr_running + $4 = 0 + +- 使用container_of查看更多hrtimers信息:: + + (gdb) set $next = $lx_per_cpu("hrtimer_bases").clock_base[0].active.next + (gdb) p *$container_of($next, "struct hrtimer", "node") + $5 = { + node = { + node = { + __rb_parent_color = 18446612133355256072, + rb_right = 0x0 <irq_stack_union>, + rb_left = 0x0 <irq_stack_union> + }, + expires = { + tv64 = 1835268000000 + } + }, + _softexpires = { + tv64 = 1835268000000 + }, + function = 0xffffffff81078232 <tick_sched_timer>, + base = 0xffff88003fd0d6f0, + state = 1, + start_pid = 0, + start_site = 0xffffffff81055c1f <hrtimer_start_range_ns+20>, + start_comm = "swapper/2\000\000\000\000\000\000" + } + + +命令和辅助调试功能列表 +---------------------- + +命令和辅助调试功能可能会随着时间的推移而改进,此文显示的是初始版本的部分示例:: + + (gdb) apropos lx + function lx_current -- Return current task + function lx_module -- Find module by name and return the module variable + function lx_per_cpu -- Return per-cpu variable + function lx_task_by_pid -- Find Linux task by PID and return the task_struct variable + function lx_thread_info -- Calculate Linux thread_info from task variable + lx-dmesg -- Print Linux kernel log buffer + lx-lsmod -- List currently loaded modules + lx-symbols -- (Re-)load symbols of Linux kernel and currently loaded modules + +可以通过“help <command-name>”或“help function <function-name>”命令 +获取指定命令或指定调试功能的更多详细信息。 diff --git a/Documentation/translations/zh_CN/dev-tools/index.rst b/Documentation/translations/zh_CN/dev-tools/index.rst new file mode 100644 index 0000000000..02577c3790 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/index.rst @@ -0,0 +1,40 @@ +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/index.rst +:Translator: 赵军奎 Bernard Zhao <bernard@vivo.com> + +============ +内核开发工具 +============ + +本文档是有关内核开发工具文档的合集。 +目前这些文档已经整理在一起,不需要再花费额外的精力。 +欢迎任何补丁。 + +有关测试专用工具的简要概述,参见 +Documentation/translations/zh_CN/dev-tools/testing-overview.rst + +.. class:: toc-title + + 目录 + +.. toctree:: + :maxdepth: 2 + + testing-overview + sparse + gcov + kasan + gdb-kernel-debugging + +Todolist: + + - coccinelle + - kcov + - ubsan + - kmemleak + - kcsan + - kfence + - kgdb + - kselftest + - kunit/index diff --git a/Documentation/translations/zh_CN/dev-tools/kasan.rst b/Documentation/translations/zh_CN/dev-tools/kasan.rst new file mode 100644 index 0000000000..8fdb20c966 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/kasan.rst @@ -0,0 +1,462 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/kasan.rst +:Translator: 万家兵 Wan Jiabing <wanjiabing@vivo.com> + +内核地址消毒剂(KASAN) +===================== + +概述 +---- + +Kernel Address SANitizer(KASAN)是一种动态内存安全错误检测工具,主要功能是 +检查内存越界访问和使用已释放内存的问题。 + +KASAN有三种模式: + +1. 通用KASAN +2. 基于软件标签的KASAN +3. 基于硬件标签的KASAN + +用CONFIG_KASAN_GENERIC启用的通用KASAN,是用于调试的模式,类似于用户空 +间的ASan。这种模式在许多CPU架构上都被支持,但它有明显的性能和内存开销。 + +基于软件标签的KASAN或SW_TAGS KASAN,通过CONFIG_KASAN_SW_TAGS启用, +可以用于调试和自我测试,类似于用户空间HWASan。这种模式只支持arm64,但其 +适度的内存开销允许在内存受限的设备上用真实的工作负载进行测试。 + +基于硬件标签的KASAN或HW_TAGS KASAN,用CONFIG_KASAN_HW_TAGS启用,被 +用作现场内存错误检测器或作为安全缓解的模式。这种模式只在支持MTE(内存标签 +扩展)的arm64 CPU上工作,但它的内存和性能开销很低,因此可以在生产中使用。 + +关于每种KASAN模式的内存和性能影响的细节,请参见相应的Kconfig选项的描述。 + +通用模式和基于软件标签的模式通常被称为软件模式。基于软件标签的模式和基于 +硬件标签的模式被称为基于标签的模式。 + +支持 +---- + +体系架构 +~~~~~~~~ + +在x86_64、arm、arm64、powerpc、riscv、s390、xtensa和loongarch上支持通用KASAN, +而基于标签的KASAN模式只在arm64上支持。 + +编译器 +~~~~~~ + +软件KASAN模式使用编译时工具在每个内存访问之前插入有效性检查,因此需要一个 +提供支持的编译器版本。基于硬件标签的模式依靠硬件来执行这些检查,但仍然需要 +一个支持内存标签指令的编译器版本。 + +通用KASAN需要GCC 8.3.0版本或更高版本,或者内核支持的任何Clang版本。 + +基于软件标签的KASAN需要GCC 11+或者内核支持的任何Clang版本。 + +基于硬件标签的KASAN需要GCC 10+或Clang 12+。 + +内存类型 +~~~~~~~~ + +通用KASAN支持在所有的slab、page_alloc、vmap、vmalloc、堆栈和全局内存 +中查找错误。 + +基于软件标签的KASAN支持slab、page_alloc、vmalloc和堆栈内存。 + +基于硬件标签的KASAN支持slab、page_alloc和不可执行的vmalloc内存。 + +对于slab,两种软件KASAN模式都支持SLUB和SLAB分配器,而基于硬件标签的 +KASAN只支持SLUB。 + +用法 +---- + +要启用KASAN,请使用以下命令配置内核:: + + CONFIG_KASAN=y + +同时在 ``CONFIG_KASAN_GENERIC`` (启用通用KASAN模式), ``CONFIG_KASAN_SW_TAGS`` +(启用基于硬件标签的KASAN模式),和 ``CONFIG_KASAN_HW_TAGS`` (启用基于硬件标签 +的KASAN模式)之间进行选择。 + +对于软件模式,还可以在 ``CONFIG_KASAN_OUTLINE`` 和 ``CONFIG_KASAN_INLINE`` +之间进行选择。outline和inline是编译器插桩类型。前者产生较小的二进制文件, +而后者快2倍。 + +要将受影响的slab对象的alloc和free堆栈跟踪包含到报告中,请启用 +``CONFIG_STACKTRACE`` 。要包括受影响物理页面的分配和释放堆栈跟踪的话, +请启用 ``CONFIG_PAGE_OWNER`` 并使用 ``page_owner=on`` 进行引导。 + +启动参数 +~~~~~~~~ + +KASAN受到通用 ``panic_on_warn`` 命令行参数的影响。当它被启用时,KASAN +在打印出错误报告后会使内核恐慌。 + +默认情况下,KASAN只对第一个无效的内存访问打印错误报告。使用 +``kasan_multi_shot``,KASAN对每一个无效的访问都打印一份报告。这会禁用 +了KASAN报告的 ``panic_on_warn``。 + +另外,独立于 ``panic_on_warn`` 、 ``kasan.fault=`` boot参数可以用 +来控制恐慌和报告行为。 + +- ``kasan.fault=report`` 或 ``=panic`` 控制是否只打印KASAN report或 + 同时使内核恐慌(默认: ``report`` )。即使 ``kasan_multi_shot`` 被 + 启用,恐慌也会发生。 + +基于软件和硬件标签的KASAN模式(见下面关于各种模式的部分)支持改变堆栈跟 +踪收集行为: + +- ``kasan.stacktrace=off`` 或 ``=on`` 禁用或启用分配和释放堆栈痕 + 迹的收集(默认: ``on`` )。 + +- ``kasan.stack_ring_size=<number of entries>`` 指定堆栈环的条 + 目数(默认: ``32768`` )。 + +基于硬件标签的KASAN模式是为了在生产中作为一种安全缓解措施使用。因此,它 +支持额外的启动参数,允许完全禁用KASAN或控制其功能。 + +- ``kasan=off`` 或 ``=on`` 控制KASAN是否被启用(默认: ``on`` )。 + +- ``kasan.mode=sync``, ``=async`` or ``=asymm`` 控制KASAN是否 + 被配置为同步、异步或非对称的执行模式(默认: ``同步`` )。 + 同步模式:当标签检查异常发生时,会立即检测到不良访问。 + 异步模式:不良访问的检测是延迟的。当标签检查异常发生时,信息被存储在硬 + 件中(对于arm64来说是在TFSR_EL1寄存器中)。内核周期性地检查硬件,并\ + 且只在这些检查中报告标签异常。 + 非对称模式:读取时同步检测不良访问,写入时异步检测。 + +- ``kasan.vmalloc=off`` or ``=on`` 禁用或启用vmalloc分配的标记(默认: ``on`` )。 + +错误报告 +~~~~~~~~ + +典型的KASAN报告如下所示:: + + ================================================================== + BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0xa8/0xbc [test_kasan] + Write of size 1 at addr ffff8801f44ec37b by task insmod/2760 + + CPU: 1 PID: 2760 Comm: insmod Not tainted 4.19.0-rc3+ #698 + Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014 + Call Trace: + dump_stack+0x94/0xd8 + print_address_description+0x73/0x280 + kasan_report+0x144/0x187 + __asan_report_store1_noabort+0x17/0x20 + kmalloc_oob_right+0xa8/0xbc [test_kasan] + kmalloc_tests_init+0x16/0x700 [test_kasan] + do_one_initcall+0xa5/0x3ae + do_init_module+0x1b6/0x547 + load_module+0x75df/0x8070 + __do_sys_init_module+0x1c6/0x200 + __x64_sys_init_module+0x6e/0xb0 + do_syscall_64+0x9f/0x2c0 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + RIP: 0033:0x7f96443109da + RSP: 002b:00007ffcf0b51b08 EFLAGS: 00000202 ORIG_RAX: 00000000000000af + RAX: ffffffffffffffda RBX: 000055dc3ee521a0 RCX: 00007f96443109da + RDX: 00007f96445cff88 RSI: 0000000000057a50 RDI: 00007f9644992000 + RBP: 000055dc3ee510b0 R08: 0000000000000003 R09: 0000000000000000 + R10: 00007f964430cd0a R11: 0000000000000202 R12: 00007f96445cff88 + R13: 000055dc3ee51090 R14: 0000000000000000 R15: 0000000000000000 + + Allocated by task 2760: + save_stack+0x43/0xd0 + kasan_kmalloc+0xa7/0xd0 + kmem_cache_alloc_trace+0xe1/0x1b0 + kmalloc_oob_right+0x56/0xbc [test_kasan] + kmalloc_tests_init+0x16/0x700 [test_kasan] + do_one_initcall+0xa5/0x3ae + do_init_module+0x1b6/0x547 + load_module+0x75df/0x8070 + __do_sys_init_module+0x1c6/0x200 + __x64_sys_init_module+0x6e/0xb0 + do_syscall_64+0x9f/0x2c0 + entry_SYSCALL_64_after_hwframe+0x44/0xa9 + + Freed by task 815: + save_stack+0x43/0xd0 + __kasan_slab_free+0x135/0x190 + kasan_slab_free+0xe/0x10 + kfree+0x93/0x1a0 + umh_complete+0x6a/0xa0 + call_usermodehelper_exec_async+0x4c3/0x640 + ret_from_fork+0x35/0x40 + + The buggy address belongs to the object at ffff8801f44ec300 + which belongs to the cache kmalloc-128 of size 128 + The buggy address is located 123 bytes inside of + 128-byte region [ffff8801f44ec300, ffff8801f44ec380) + The buggy address belongs to the page: + page:ffffea0007d13b00 count:1 mapcount:0 mapping:ffff8801f7001640 index:0x0 + flags: 0x200000000000100(slab) + raw: 0200000000000100 ffffea0007d11dc0 0000001a0000001a ffff8801f7001640 + raw: 0000000000000000 0000000080150015 00000001ffffffff 0000000000000000 + page dumped because: kasan: bad access detected + + Memory state around the buggy address: + ffff8801f44ec200: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb + ffff8801f44ec280: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc + >ffff8801f44ec300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 + ^ + ffff8801f44ec380: fc fc fc fc fc fc fc fc fb fb fb fb fb fb fb fb + ffff8801f44ec400: fb fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc + ================================================================== + +报告标题总结了发生的错误类型以及导致该错误的访问类型。紧随其后的是错误访问的 +堆栈跟踪、所访问内存分配位置的堆栈跟踪(对于访问了slab对象的情况)以及对象 +被释放的位置的堆栈跟踪(对于访问已释放内存的问题报告)。接下来是对访问的 +slab对象的描述以及关于访问的内存页的信息。 + +最后,报告展示了访问地址周围的内存状态。在内部,KASAN单独跟踪每个内存颗粒的 +内存状态,根据KASAN模式分为8或16个对齐字节。报告的内存状态部分中的每个数字 +都显示了围绕访问地址的其中一个内存颗粒的状态。 + +对于通用KASAN,每个内存颗粒的大小为8个字节。每个颗粒的状态被编码在一个影子字节 +中。这8个字节可以是可访问的,部分访问的,已释放的或成为Redzone的一部分。KASAN +对每个影子字节使用以下编码:00表示对应内存区域的所有8个字节都可以访问;数字N +(1 <= N <= 7)表示前N个字节可访问,其他(8 - N)个字节不可访问;任何负值都表示 +无法访问整个8字节。KASAN使用不同的负值来区分不同类型的不可访问内存,如redzones +或已释放的内存(参见 mm/kasan/kasan.h)。 + +在上面的报告中,箭头指向影子字节 ``03`` ,表示访问的地址是部分可访问的。 + +对于基于标签的KASAN模式,报告最后的部分显示了访问地址周围的内存标签 +(参考 `实施细则`_ 章节)。 + +请注意,KASAN错误标题(如 ``slab-out-of-bounds`` 或 ``use-after-free`` ) +是尽量接近的:KASAN根据其拥有的有限信息打印出最可能的错误类型。错误的实际类型 +可能会有所不同。 + +通用KASAN还报告两个辅助调用堆栈跟踪。这些堆栈跟踪指向代码中与对象交互但不直接 +出现在错误访问堆栈跟踪中的位置。目前,这包括 call_rcu() 和排队的工作队列。 + +实施细则 +-------- + +通用KASAN +~~~~~~~~~ + +软件KASAN模式使用影子内存来记录每个内存字节是否可以安全访问,并使用编译时工具 +在每次内存访问之前插入影子内存检查。 + +通用KASAN将1/8的内核内存专用于其影子内存(16TB以覆盖x86_64上的128TB),并使用 +具有比例和偏移量的直接映射将内存地址转换为其相应的影子地址。 + +这是将地址转换为其相应影子地址的函数:: + + static inline void *kasan_mem_to_shadow(const void *addr) + { + return (void *)((unsigned long)addr >> KASAN_SHADOW_SCALE_SHIFT) + + KASAN_SHADOW_OFFSET; + } + +在这里 ``KASAN_SHADOW_SCALE_SHIFT = 3`` 。 + +编译时工具用于插入内存访问检查。编译器在每次访问大小为1、2、4、8或16的内存之前 +插入函数调用( ``__asan_load*(addr)`` , ``__asan_store*(addr)``)。这些函数通过 +检查相应的影子内存来检查内存访问是否有效。 + +使用inline插桩,编译器不进行函数调用,而是直接插入代码来检查影子内存。此选项 +显著地增大了内核体积,但与outline插桩内核相比,它提供了x1.1-x2的性能提升。 + +通用KASAN是唯一一种通过隔离延迟重新使用已释放对象的模式 +(参见 mm/kasan/quarantine.c 以了解实现)。 + +基于软件标签的KASAN模式 +~~~~~~~~~~~~~~~~~~~~~~~ + +基于软件标签的KASAN使用软件内存标签方法来检查访问有效性。目前仅针对arm64架构实现。 + +基于软件标签的KASAN使用arm64 CPU的顶部字节忽略(TBI)特性在内核指针的顶部字节中 +存储一个指针标签。它使用影子内存来存储与每个16字节内存单元相关的内存标签(因此, +它将内核内存的1/16专用于影子内存)。 + +在每次内存分配时,基于软件标签的KASAN都会生成一个随机标签,用这个标签标记分配 +的内存,并将相同的标签嵌入到返回的指针中。 + +基于软件标签的KASAN使用编译时工具在每次内存访问之前插入检查。这些检查确保正在 +访问的内存的标签等于用于访问该内存的指针的标签。如果标签不匹配,基于软件标签 +的KASAN会打印错误报告。 + +基于软件标签的KASAN也有两种插桩模式(outline,发出回调来检查内存访问;inline, +执行内联的影子内存检查)。使用outline插桩模式,会从执行访问检查的函数打印错误 +报告。使用inline插桩,编译器会发出 ``brk`` 指令,并使用专用的 ``brk`` 处理程序 +来打印错误报告。 + +基于软件标签的KASAN使用0xFF作为匹配所有指针标签(不检查通过带有0xFF指针标签 +的指针进行的访问)。值0xFE当前保留用于标记已释放的内存区域。 + + +基于硬件标签的KASAN模式 +~~~~~~~~~~~~~~~~~~~~~~~ + +基于硬件标签的KASAN在概念上类似于软件模式,但它是使用硬件内存标签作为支持而 +不是编译器插桩和影子内存。 + +基于硬件标签的KASAN目前仅针对arm64架构实现,并且基于ARMv8.5指令集架构中引入 +的arm64内存标记扩展(MTE)和最高字节忽略(TBI)。 + +特殊的arm64指令用于为每次内存分配指定内存标签。相同的标签被指定给指向这些分配 +的指针。在每次内存访问时,硬件确保正在访问的内存的标签等于用于访问该内存的指针 +的标签。如果标签不匹配,则会生成故障并打印报告。 + +基于硬件标签的KASAN使用0xFF作为匹配所有指针标签(不检查通过带有0xFF指针标签的 +指针进行的访问)。值0xFE当前保留用于标记已释放的内存区域。 + +如果硬件不支持MTE(ARMv8.5之前),则不会启用基于硬件标签的KASAN。在这种情况下, +所有KASAN引导参数都将被忽略。 + +请注意,启用CONFIG_KASAN_HW_TAGS始终会导致启用内核中的TBI。即使提供了 +``kasan.mode=off`` 或硬件不支持MTE(但支持TBI)。 + +基于硬件标签的KASAN只报告第一个发现的错误。之后,MTE标签检查将被禁用。 + +影子内存 +-------- + +本节的内容只适用于软件KASAN模式。 + +内核将内存映射到地址空间的几个不同部分。内核虚拟地址的范围很大:没有足够的真实 +内存来支持内核可以访问的每个地址的真实影子区域。因此,KASAN只为地址空间的某些 +部分映射真实的影子。 + +默认行为 +~~~~~~~~ + +默认情况下,体系结构仅将实际内存映射到用于线性映射的阴影区域(以及可能的其他 +小区域)。对于所有其他区域 —— 例如vmalloc和vmemmap空间 —— 一个只读页面被映射 +到阴影区域上。这个只读的影子页面声明所有内存访问都是允许的。 + +这给模块带来了一个问题:它们不存在于线性映射中,而是存在于专用的模块空间中。 +通过连接模块分配器,KASAN临时映射真实的影子内存以覆盖它们。例如,这允许检测 +对模块全局变量的无效访问。 + +这也造成了与 ``VMAP_STACK`` 的不兼容:如果堆栈位于vmalloc空间中,它将被分配 +只读页面的影子内存,并且内核在尝试为堆栈变量设置影子数据时会出错。 + +CONFIG_KASAN_VMALLOC +~~~~~~~~~~~~~~~~~~~~ + +使用 ``CONFIG_KASAN_VMALLOC`` ,KASAN可以以更大的内存使用为代价覆盖vmalloc +空间。目前,这在arm64、x86、riscv、s390和powerpc上受支持。 + +这通过连接到vmalloc和vmap并动态分配真实的影子内存来支持映射。 + +vmalloc空间中的大多数映射都很小,需要不到一整页的阴影空间。因此,为每个映射 +分配一个完整的影子页面将是一种浪费。此外,为了确保不同的映射使用不同的影子 +页面,映射必须与 ``KASAN_GRANULE_SIZE * PAGE_SIZE`` 对齐。 + +相反,KASAN跨多个映射共享后备空间。当vmalloc空间中的映射使用影子区域的特定 +页面时,它会分配一个后备页面。此页面稍后可以由其他vmalloc映射共享。 + +KASAN连接到vmap基础架构以懒清理未使用的影子内存。 + +为了避免交换映射的困难,KASAN预测覆盖vmalloc空间的阴影区域部分将不会被早期 +的阴影页面覆盖,但是将不会被映射。这将需要更改特定于arch的代码。 + +这允许在x86上支持 ``VMAP_STACK`` ,并且可以简化对没有固定模块区域的架构的支持。 + +对于开发者 +---------- + +忽略访问 +~~~~~~~~ + +软件KASAN模式使用编译器插桩来插入有效性检查。此类检测可能与内核的某些部分 +不兼容,因此需要禁用。 + +内核的其他部分可能会访问已分配对象的元数据。通常,KASAN会检测并报告此类访问, +但在某些情况下(例如,在内存分配器中),这些访问是有效的。 + +对于软件KASAN模式,要禁用特定文件或目录的检测,请将 ``KASAN_SANITIZE`` 添加 +到相应的内核Makefile中: + +- 对于单个文件(例如,main.o):: + + KASAN_SANITIZE_main.o := n + +- 对于一个目录下的所有文件:: + + KASAN_SANITIZE := n + +对于软件KASAN模式,要在每个函数的基础上禁用检测,请使用KASAN特定的 +``__no_sanitize_address`` 函数属性或通用的 ``noinstr`` 。 + +请注意,禁用编译器插桩(基于每个文件或每个函数)会使KASAN忽略在软件KASAN模式 +的代码中直接发生的访问。当访问是间接发生的(通过调用检测函数)或使用没有编译器 +插桩的基于硬件标签的模式时,它没有帮助。 + +对于软件KASAN模式,要在当前任务的一部分内核代码中禁用KASAN报告,请使用 +``kasan_disable_current()``/``kasan_enable_current()`` 部分注释这部分代码。 +这也会禁用通过函数调用发生的间接访问的报告。 + +对于基于标签的KASAN模式,要禁用访问检查,请使用 ``kasan_reset_tag()`` 或 +``page_kasan_tag_reset()`` 。请注意,通过 ``page_kasan_tag_reset()`` +临时禁用访问检查需要通过 ``page_kasan_tag`` / ``page_kasan_tag_set`` 保 +存和恢复每页KASAN标签。 + +测试 +~~~~ + +有一些KASAN测试可以验证KASAN是否正常工作并可以检测某些类型的内存损坏。 +测试由两部分组成: + +1. 与KUnit测试框架集成的测试。使用 ``CONFIG_KASAN_KUNIT_TEST`` 启用。 +这些测试可以通过几种不同的方式自动运行和部分验证;请参阅下面的说明。 + +2. 与KUnit不兼容的测试。使用 ``CONFIG_KASAN_MODULE_TEST`` 启用并且只能作为模块 +运行。这些测试只能通过加载内核模块并检查内核日志以获取KASAN报告来手动验证。 + +如果检测到错误,每个KUnit兼容的KASAN测试都会打印多个KASAN报告之一,然后测试打印 +其编号和状态。 + +当测试通过:: + + ok 28 - kmalloc_double_kzfree + +当由于 ``kmalloc`` 失败而导致测试失败时:: + + # kmalloc_large_oob_right: ASSERTION FAILED at lib/test_kasan.c:163 + Expected ptr is not null, but is + not ok 4 - kmalloc_large_oob_right + +当由于缺少KASAN报告而导致测试失败时:: + + # kmalloc_double_kzfree: EXPECTATION FAILED at lib/test_kasan.c:974 + KASAN failure expected in "kfree_sensitive(ptr)", but none occurred + not ok 44 - kmalloc_double_kzfree + + +最后打印所有KASAN测试的累积状态。成功:: + + ok 1 - kasan + +或者,如果其中一项测试失败:: + + not ok 1 - kasan + +有几种方法可以运行与KUnit兼容的KASAN测试。 + +1. 可加载模块 + + 启用 ``CONFIG_KUNIT`` 后,KASAN-KUnit测试可以构建为可加载模块,并通过使用 + ``insmod`` 或 ``modprobe`` 加载 ``test_kasan.ko`` 来运行。 + +2. 内置 + + 通过内置 ``CONFIG_KUNIT`` ,也可以内置KASAN-KUnit测试。在这种情况下, + 测试将在启动时作为后期初始化调用运行。 + +3. 使用kunit_tool + + 通过内置 ``CONFIG_KUNIT`` 和 ``CONFIG_KASAN_KUNIT_TEST`` ,还可以使用 + ``kunit_tool`` 以更易读的方式查看KUnit测试结果。这不会打印通过测试 + 的KASAN报告。有关 ``kunit_tool`` 更多最新信息,请参阅 + `KUnit文档 <https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html>`_ 。 + +.. _KUnit: https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html diff --git a/Documentation/translations/zh_CN/dev-tools/sparse.rst b/Documentation/translations/zh_CN/dev-tools/sparse.rst new file mode 100644 index 0000000000..0664c634bc --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/sparse.rst @@ -0,0 +1,110 @@ +Copyright 2004 Linus Torvalds +Copyright 2004 Pavel Machek <pavel@ucw.cz> +Copyright 2006 Bob Copeland <me@bobcopeland.com> + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/sparse.rst + +:翻译: + + Li Yang <leoyang.li@nxp.com> + +:校译: + + 司延腾 Yanteng Si <siyanteng@loongson.cn> + +.. _cn_sparse: + +Sparse +====== + +Sparse是一个C程序的语义检查器;它可以用来发现内核代码的一些潜在问题。 关 +于sparse的概述,请参见https://lwn.net/Articles/689907/;本文档包含 +一些针对内核的sparse信息。 +关于sparse的更多信息,主要是关于它的内部结构,可以在它的官方网页上找到: +https://sparse.docs.kernel.org。 + +使用 sparse 工具做类型检查 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +"__bitwise" 是一种类型属性,所以你应该这样使用它:: + + typedef int __bitwise pm_request_t; + + enum pm_request { + PM_SUSPEND = (__force pm_request_t) 1, + PM_RESUME = (__force pm_request_t) 2 + }; + +这样会使 PM_SUSPEND 和 PM_RESUME 成为位方式(bitwise)整数(使用"__force" +是因为 sparse 会抱怨改变位方式的类型转换,但是这里我们确实需要强制进行转 +换)。而且因为所有枚举值都使用了相同的类型,这里的"enum pm_request"也将 +会使用那个类型做为底层实现。 + +而且使用 gcc 编译的时候,所有的 __bitwise/__force 都会消失,最后在 gcc +看来它们只不过是普通的整数。 + +坦白来说,你并不需要使用枚举类型。上面那些实际都可以浓缩成一个特殊的"int +__bitwise"类型。 + +所以更简单的办法只要这样做:: + + typedef int __bitwise pm_request_t; + + #define PM_SUSPEND ((__force pm_request_t) 1) + #define PM_RESUME ((__force pm_request_t) 2) + +现在你就有了严格的类型检查所需要的所有基础架构。 + +一个小提醒:常数整数"0"是特殊的。你可以直接把常数零当作位方式整数使用而 +不用担心 sparse 会抱怨。这是因为"bitwise"(恰如其名)是用来确保不同位方 +式类型不会被弄混(小尾模式,大尾模式,cpu尾模式,或者其他),对他们来说 +常数"0"确实 **是** 特殊的。 + +使用sparse进行锁检查 +-------------------- + +下面的宏对于 gcc 来说是未定义的,在 sparse 运行时定义,以使用sparse的“上下文” +跟踪功能,应用于锁定。 这些注释告诉 sparse 什么时候有锁,以及注释的函数的进入和 +退出。 + +__must_hold - 指定的锁在函数进入和退出时被持有。 + +__acquires - 指定的锁在函数退出时被持有,但在进入时不被持有。 + +__releases - 指定的锁在函数进入时被持有,但在退出时不被持有。 + +如果函数在不持有锁的情况下进入和退出,在函数内部以平衡的方式获取和释放锁,则不 +需要注释。 +上面的三个注释是针对sparse否则会报告上下文不平衡的情况。 + +获取 sparse 工具 +~~~~~~~~~~~~~~~~ + +你可以从 Sparse 的主页获取最新的发布版本: + + https://www.kernel.org/pub/software/devel/sparse/dist/ + +或者,你也可以使用 git 克隆最新的 sparse 开发版本: + + git://git.kernel.org/pub/scm/devel/sparse/sparse.git + +一旦你下载了源码,只要以普通用户身份运行: + + make + make install + +如果是标准的用户,它将会被自动安装到你的~/bin目录下。 + +使用 sparse 工具 +~~~~~~~~~~~~~~~~ + +用"make C=1"命令来编译内核,会对所有重新编译的 C 文件使用 sparse 工具。 +或者使用"make C=2"命令,无论文件是否被重新编译都会对其使用 sparse 工具。 +如果你已经编译了内核,用后一种方式可以很快地检查整个源码树。 + +make 的可选变量 CHECKFLAGS 可以用来向 sparse 工具传递参数。编译系统会自 +动向 sparse 工具传递 -Wbitwise 参数。 + +注意sparse定义了__CHECKER__预处理器符号。
\ No newline at end of file diff --git a/Documentation/translations/zh_CN/dev-tools/testing-overview.rst b/Documentation/translations/zh_CN/dev-tools/testing-overview.rst new file mode 100644 index 0000000000..69e7e4cb20 --- /dev/null +++ b/Documentation/translations/zh_CN/dev-tools/testing-overview.rst @@ -0,0 +1,161 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/dev-tools/testing-overview.rst +:Translator: 胡皓文 Hu Haowen <src.res.211@gmail.com> + +============ +内核测试指南 +============ + +有许多不同的工具可以用于测试Linux内核,因此了解什么时候使用它们可能 +很困难。本文档粗略概述了它们之间的区别,并阐释了它们是怎样糅合在一起 +的。 + +编写和运行测试 +============== + +大多数内核测试都是用kselftest或KUnit框架之一编写的。它们都让运行测试 +更加简化,并为编写新测试提供帮助。 + +如果你想验证内核的行为——尤其是内核的特定部分——那你就要使用kUnit或 +kselftest。 + +KUnit和kselftest的区别 +---------------------- + +.. note:: + 由于本文段中部分术语尚无较好的对应中文释义,可能导致与原文含义 + 存在些许差异,因此建议读者结合原文 + (Documentation/dev-tools/testing-overview.rst)辅助阅读。 + 如对部分翻译有异议或有更好的翻译意见,欢迎联系译者进行修订。 + +KUnit(Documentation/dev-tools/kunit/index.rst)是用于“白箱”测 +试的一个完整的内核内部系统:因为测试代码是内核的一部分,所以它能够访 +问用户空间不能访问到的内部结构和功能。 + +因此,KUnit测试最好针对内核中较小的、自包含的部分,以便能够独立地测 +试。“单元”测试的概念亦是如此。 + +比如,一个KUnit测试可能测试一个单独的内核功能(甚至通过一个函数测试 +一个单一的代码路径,例如一个错误处理案例),而不是整个地测试一个特性。 + +这也使得KUnit测试构建和运行非常地快,从而能够作为开发流程的一部分被 +频繁地运行。 + +有关更详细的介绍,请参阅KUnit测试代码风格指南 +Documentation/dev-tools/kunit/style.rst + +kselftest(Documentation/dev-tools/kselftest.rst),相对来说,大量用 +于用户空间,并且通常测试用户空间的脚本或程序。 + +这使得编写复杂的测试,或者需要操作更多全局系统状态的测试更加容易(诸 +如生成进程之类)。然而,从kselftest直接调用内核函数是不行的。这也就 +意味着只有通过某种方式(如系统调用、驱动设备、文件系统等)导出到了用 +户空间的内核功能才能使用kselftest来测试。为此,有些测试包含了一个伴 +生的内核模块用于导出更多的信息和功能。不过,对于基本上或者完全在内核 +中运行的测试,KUnit可能是更佳工具。 + +kselftest也因此非常适合于全部功能的测试,因为这些功能会将接口暴露到 +用户空间,从而能够被测试,而不是展现实现细节。“system”测试和 +“end-to-end”测试亦是如此。 + +比如,一个新的系统调用应该伴随有新的kselftest测试。 + +代码覆盖率工具 +============== + +支持两种不同代码之间的覆盖率测量工具。它们可以用来验证一项测试执行的 +确切函数或代码行。这有助于决定内核被测试了多少,或用来查找合适的测试 +中没有覆盖到的极端情况。 + +Documentation/translations/zh_CN/dev-tools/gcov.rst 是GCC的覆盖率测试 +工具,能用于获取内核的全局或每个模块的覆盖率。与KCOV不同的是,这个工具 +不记录每个任务的覆盖率。覆盖率数据可以通过debugfs读取,并通过常规的 +gcov工具进行解释。 + +Documentation/dev-tools/kcov.rst 是能够构建在内核之中,用于在每个任务 +的层面捕捉覆盖率的一个功能。因此,它对于模糊测试和关于代码执行期间信 +息的其它情况非常有用,比如在一个单一系统调用里使用它就很有用。 + +动态分析工具 +============ + +内核也支持许多动态分析工具,用以检测正在运行的内核中出现的多种类型的 +问题。这些工具通常每个去寻找一类不同的缺陷,比如非法内存访问,数据竞 +争等并发问题,或整型溢出等其他未定义行为。 + +如下所示: + +* kmemleak检测可能的内存泄漏。参阅 + Documentation/dev-tools/kmemleak.rst +* KASAN检测非法内存访问,如数组越界和释放后重用(UAF)。参阅 + Documentation/dev-tools/kasan.rst +* UBSAN检测C标准中未定义的行为,如整型溢出。参阅 + Documentation/dev-tools/ubsan.rst +* KCSAN检测数据竞争。参阅 Documentation/dev-tools/kcsan.rst +* KFENCE是一个低开销的内存问题检测器,比KASAN更快且能被用于批量构建。 + 参阅 Documentation/dev-tools/kfence.rst +* lockdep是一个锁定正确性检测器。参阅 + Documentation/locking/lockdep-design.rst +* 除此以外,在内核中还有一些其它的调试工具,大多数能在 + lib/Kconfig.debug 中找到。 + +这些工具倾向于对内核进行整体测试,并且不像kselftest和KUnit一样“传递”。 +它们可以通过在启用这些工具时运行内核测试以与kselftest或KUnit结合起来: +之后你就能确保这些错误在测试过程中都不会发生了。 + +一些工具与KUnit和kselftest集成,并且在检测到问题时会自动打断测试。 + +静态分析工具 +============ + +除了测试运行中的内核,我们还可以使用**静态分析**工具直接分析内核的源代 +码(**在编译时**)。内核中常用的工具允许人们检查整个源代码树或其中的特 +定文件。它们使得在开发过程中更容易发现和修复问题。 + + Sparse可以通过执行类型检查、锁检查、值范围检查来帮助测试内核,此外还 + 可以在检查代码时报告各种错误和警告。关于如何使用它的细节,请参阅 + Documentation/translations/zh_CN/dev-tools/sparse.rst。 + + Smatch扩展了Sparse,并提供了对编程逻辑错误的额外检查,如开关语句中 + 缺少断点,错误检查中未使用的返回值,忘记在错误路径的返回中设置错误代 + 码等。Smatch也有针对更严重问题的测试,如整数溢出、空指针解除引用和内 + 存泄漏。见项目页面http://smatch.sourceforge.net/。 + + Coccinelle是我们可以使用的另一个静态分析器。Coccinelle经常被用来 + 帮助源代码的重构和并行演化,但它也可以帮助避免常见代码模式中出现的某 + 些错误。可用的测试类型包括API测试、内核迭代器的正确使用测试、自由操 + 作的合理性检查、锁定行为的分析,以及已知的有助于保持内核使用一致性的 + 进一步测试。详情请见Documentation/dev-tools/coccinelle.rst。 + + 不过要注意的是,静态分析工具存在**假阳性**的问题。在试图修复错误和警 + 告之前,需要仔细评估它们。 + +何时使用Sparse和Smatch +---------------------- + +Sparse做类型检查,例如验证注释的变量不会导致无符号的错误,检测 +``__user`` 指针使用不当的地方,以及分析符号初始化器的兼容性。 + +Smatch进行流程分析,如果允许建立函数数据库,它还会进行跨函数分析。 +Smatch试图回答一些问题,比如这个缓冲区是在哪里分配的?它有多大?这 +个索引可以由用户控制吗?这个变量比那个变量大吗? + +一般来说,在Smatch中写检查比在Sparse中写检查要容易。尽管如此, +Sparse和Smatch的检查还是有一些重叠的地方。 + +Smatch和Coccinelle的强项 +------------------------ + +Coccinelle可能是最容易写检查的。它在预处理器之前工作,所以用Coccinelle +检查宏中的错误更容易。Coccinelle还能为你创建补丁,这是其他工具无法做到的。 + +例如,用Coccinelle你可以从 ``kmalloc_array(x, size, GFP_KERNEL)`` +到 ``kmalloc_array(x, size, GFP_KERNEL)`` 进行大规模转换,这真的很 +有用。如果你只是创建一个Smatch警告,并试图把转换的工作推给维护者,他们会很 +恼火。你将不得不为每个警告争论是否真的可以溢出。 + +Coccinelle不对变量值进行分析,而这正是Smatch的强项。另一方面,Coccinelle +允许你用简单的方法做简单的事情。 |