Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,265 @@
.. include:: ../disclaimer-zh_TW.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_TW:
a) 若gcov運行在測試機上
測試機上面gcov工具的版本必須要跟內核編譯機器使用的gcc版本相兼容,
同時下面的文件要從編譯機拷貝到測試機上:
從源代碼中:
- 所有的C文件和頭文件
從編譯目錄中:
- 所有的C文件和頭文件
- 所有的.gcda文件和.gcno文件
- 所有目錄的鏈接
特別需要注意,測試機器上面的目錄結構跟編譯機器上面的目錄機構必須
完全一致。
如果文件是軟鏈接,需要替換成真正的目錄文件(這是由make的當前工作
目錄變量CURDIR引起的)。
.. _gcov-build_zh_TW:
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_TW>` )
.. 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_TW>` )
.. 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"
@@ -0,0 +1,164 @@
.. highlight:: none
.. include:: ../disclaimer-zh_TW.rst
:Original: Documentation/process/debugging/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 $leftmost = $lx_per_cpu(hrtimer_bases).clock_base[0].active.rb_root.rb_leftmost
(gdb) p *$container_of($leftmost, "struct hrtimer", "node")
$5 = {
node = {
node = {
__rb_parent_color = 18446612686384860673,
rb_right = 0xffff888231da8b00,
rb_left = 0x0
},
expires = 1228461000000
},
_softexpires = 1228461000000,
function = 0xffffffff8137ab20 <tick_nohz_handler>,
base = 0xffff888231d9b4c0,
state = 1 '\001',
is_rel = 0 '\000',
is_soft = 0 '\000',
is_hard = 1 '\001'
}
命令和輔助調試功能列表
----------------------
命令和輔助調試功能可能會隨着時間的推移而改進,此文顯示的是初始版本的部分示例::
(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>”命令
獲取指定命令或指定調試功能的更多詳細信息。
@@ -0,0 +1,43 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_TW.rst
:Original: Documentation/dev-tools/index.rst
:Translator: 趙軍奎 Bernard Zhao <bernard@vivo.com>
============
內核開發工具
============
本文檔是有關內核開發工具文檔的合集。
目前這些文檔已經整理在一起,不需要再花費額外的精力。
歡迎任何補丁。
有關測試專用工具的簡要概述,參見
Documentation/translations/zh_TW/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
@@ -0,0 +1,460 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_TW.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 [kasan_test]
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 [kasan_test]
kmalloc_tests_init+0x16/0x700 [kasan_test]
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 [kasan_test]
kmalloc_tests_init+0x16/0x700 [kasan_test]
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是否正常工作並可以檢測某些類型的內存損壞。
所有 KASAN 測試均與 KUnit 測試框架集成,並且可以啟用
透過 ``CONFIG_KASAN_KUNIT_TEST``。可以運行測試並進行部分驗證
以幾種不同的方式自動進行;請參閱下面的說明。
如果偵測到錯誤,每個 KASAN 測試都會列印多個 KASAN 報告之一。
然後測試列印其編號和狀態。
當測試通過::
ok 28 - kmalloc_double_kzfree
當由於 ``kmalloc`` 失敗而導致測試失敗時::
# kmalloc_large_oob_right: ASSERTION FAILED at mm/kasan/kasan_test.c:245
Expected ptr is not null, but is
not ok 5 - kmalloc_large_oob_right
當由於缺少KASAN報告而導致測試失敗時::
# kmalloc_double_kzfree: EXPECTATION FAILED at mm/kasan/kasan_test.c:709
KASAN failure expected in "kfree_sensitive(ptr)", but none occurred
not ok 28 - kmalloc_double_kzfree
最後打印所有KASAN測試的累積狀態。成功::
ok 1 - kasan
或者,如果其中一項測試失敗::
not ok 1 - kasan
有幾種方法可以執行 KASAN 測試。
1. 可加載模塊
啟用 ``CONFIG_KUNIT`` 後,測試可以建置為可載入模組
並且透過使用 ``insmod`` 或 ``modprobe`` 來載入 ``kasan_test.ko`` 來運作。
2. 內置
透過內建 ``CONFIG_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
@@ -0,0 +1,91 @@
Chinese translated version of Documentation/dev-tools/sparse.rst
If you have any comment or update to the content, please contact the
original document maintainer directly. However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help. Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.
Traditional Chinese maintainer: Hu Haowen <2023002089@link.tyut.edu.cn>
-------------------------------------------------------------------------
Documentation/dev-tools/sparse.rst 的繁體中文翻譯
如果想評論或更新本文的內容,請直接聯繫原文檔的維護者。如果你使用英文
交流有困難的話,也可以向繁體中文版維護者求助。如果本翻譯更新不及時或
者翻譯存在問題,請聯繫繁體中文版維護者。
繁體中文版維護者: 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn>
繁體中文版翻譯者: 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn>
以下爲正文
-------------------------------------------------------------------------
Copyright 2004 Linus Torvalds
Copyright 2004 Pavel Machek <pavel@ucw.cz>
Copyright 2006 Bob Copeland <me@bobcopeland.com>
使用 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 工具
~~~~~~~~~~~~~~~~
你可以從 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 參數。
@@ -0,0 +1,162 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_TW.rst
:Original: Documentation/dev-tools/testing-overview.rst
:Translator: 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn>
============
內核測試指南
============
有許多不同的工具可以用於測試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
允許你用簡單的方法做簡單的事情。