1.. include:: ../disclaimer-zh_TW.rst 2 3:Original: Documentation/dev-tools/gcov.rst 4:Translator: 趙軍奎 Bernard Zhao <bernard@vivo.com> 5 6在Linux內核裏使用gcov做代碼覆蓋率檢查 7===================================== 8 9gcov分析核心支持在Linux內核中啓用GCC的覆蓋率測試工具 gcov_ ,Linux內核 10運行時的代碼覆蓋率數據會以gcov兼容的格式導出到“gcov”debugfs目錄中,可 11以通過gcov的 ``-o`` 選項(如下示例)獲得指定文件的代碼運行覆蓋率統計數據 12(需要跳轉到內核編譯路徑下並且要有root權限):: 13 14 # cd /tmp/linux-out 15 # gcov -o /sys/kernel/debug/gcov/tmp/linux-out/kernel spinlock.c 16 17這將在當前目錄中創建帶有執行計數註釋的源代碼文件。 18在獲得這些統計文件後,可以使用圖形化的gcov前端工具(比如 lcov_ ),來實現 19自動化處理Linux內核的覆蓋率運行數據,同時生成易於閱讀的HTML格式文件。 20 21可能的用途: 22 23* 調試(用來判斷每一行的代碼是否已經運行過) 24* 測試改進(如何修改測試代碼,儘可能地覆蓋到沒有運行過的代碼) 25* 內核最小化配置(對於某一個選項配置,如果關聯的代碼從來沒有運行過, 26 是否還需要這個配置) 27 28.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html 29.. _lcov: http://ltp.sourceforge.net/coverage/lcov.php 30 31 32準備 33---- 34 35內核打開如下配置:: 36 37 CONFIG_DEBUG_FS=y 38 CONFIG_GCOV_KERNEL=y 39 40獲取整個內核的覆蓋率數據,還需要打開:: 41 42 CONFIG_GCOV_PROFILE_ALL=y 43 44需要注意的是,整個內核開啓覆蓋率統計會造成內核鏡像文件尺寸的增大, 45同時內核運行也會變慢一些。 46另外,並不是所有的架構都支持整個內核開啓覆蓋率統計。 47 48代碼運行覆蓋率數據只在debugfs掛載完成後纔可以訪問:: 49 50 mount -t debugfs none /sys/kernel/debug 51 52 53定製化 54------ 55 56如果要單獨針對某一個路徑或者文件進行代碼覆蓋率統計,可以在內核相應路 57徑的Makefile中增加如下的配置: 58 59- 單獨統計單個文件(例如main.o):: 60 61 GCOV_PROFILE_main.o := y 62 63- 單獨統計某一個路徑:: 64 65 GCOV_PROFILE := y 66 67如果要在整個內核的覆蓋率統計(開啓CONFIG_GCOV_PROFILE_ALL)中單獨排除 68某一個文件或者路徑,可以使用如下的方法:: 69 70 GCOV_PROFILE_main.o := n 71 72和:: 73 74 GCOV_PROFILE := n 75 76此機制僅支持鏈接到內核鏡像或編譯爲內核模塊的文件。 77 78 79相關文件 80-------- 81 82gcov功能需要在debugfs中創建如下文件: 83 84``/sys/kernel/debug/gcov`` 85 gcov相關功能的根路徑 86 87``/sys/kernel/debug/gcov/reset`` 88 全局復位文件:向該文件寫入數據後會將所有的gcov統計數據清0 89 90``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcda`` 91 gcov工具可以識別的覆蓋率統計數據文件,向該文件寫入數據後 92 會將本文件的gcov統計數據清0 93 94``/sys/kernel/debug/gcov/path/to/compile/dir/file.gcno`` 95 gcov工具需要的軟連接文件(指向編譯時生成的信息統計文件),這個文件是 96 在gcc編譯時如果配置了選項 ``-ftest-coverage`` 時生成的。 97 98 99針對模塊的統計 100-------------- 101 102內核中的模塊會動態的加載和卸載,模塊卸載時對應的數據會被清除掉。 103gcov提供了一種機制,通過保留相關數據的副本來收集這部分卸載模塊的覆蓋率數據。 104模塊卸載後這些備份數據在debugfs中會繼續存在。 105一旦這個模塊重新加載,模塊關聯的運行統計會被初始化成debugfs中備份的數據。 106 107可以通過對內核參數gcov_persist的修改來停用gcov對模塊的備份機制:: 108 109 gcov_persist = 0 110 111在運行時,用戶還可以通過寫入模塊的數據文件或者寫入gcov復位文件來丟棄已卸 112載模塊的數據。 113 114 115編譯機和測試機分離 116------------------ 117 118gcov的內核分析插樁支持內核的編譯和運行是在同一臺機器上,也可以編譯和運 119行是在不同的機器上。 120如果內核編譯和運行是不同的機器,那麼需要額外的準備工作,這取決於gcov工具 121是在哪裏使用的: 122 123.. _gcov-test_zh: 124 125a) 若gcov運行在測試機上 126 127 測試機上面gcov工具的版本必須要跟內核編譯機器使用的gcc版本相兼容, 128 同時下面的文件要從編譯機拷貝到測試機上: 129 130 從源代碼中: 131 - 所有的C文件和頭文件 132 133 從編譯目錄中: 134 - 所有的C文件和頭文件 135 - 所有的.gcda文件和.gcno文件 136 - 所有目錄的鏈接 137 138 特別需要注意,測試機器上面的目錄結構跟編譯機器上面的目錄機構必須 139 完全一致。 140 如果文件是軟鏈接,需要替換成真正的目錄文件(這是由make的當前工作 141 目錄變量CURDIR引起的)。 142 143.. _gcov-build_zh: 144 145b) 若gcov運行在編譯機上 146 147 測試用例運行結束後,如下的文件需要從測試機中拷貝到編譯機上: 148 149 從sysfs中的gcov目錄中: 150 - 所有的.gcda文件 151 - 所有的.gcno文件軟鏈接 152 153 這些文件可以拷貝到編譯機的任意目錄下,gcov使用-o選項指定拷貝的 154 目錄。 155 156 比如一個是示例的目錄結構如下:: 157 158 /tmp/linux: 內核源碼目錄 159 /tmp/out: 內核編譯文件路徑(make O=指定) 160 /tmp/coverage: 從測試機器上面拷貝的數據文件路徑 161 162 [user@build] cd /tmp/out 163 [user@build] gcov -o /tmp/coverage/tmp/out/init main.c 164 165 166關於編譯器的注意事項 167-------------------- 168 169GCC和LLVM gcov工具不一定兼容。 170如果編譯器是GCC,使用 gcov_ 來處理.gcno和.gcda文件,如果是Clang編譯器, 171則使用 llvm-cov_ 。 172 173.. _gcov: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html 174.. _llvm-cov: https://llvm.org/docs/CommandGuide/llvm-cov.html 175 176GCC和Clang gcov之間的版本差異由Kconfig處理的。 177kconfig會根據編譯工具鏈的檢查自動選擇合適的gcov格式。 178 179問題定位 180-------- 181 182可能出現的問題1 183 編譯到鏈接階段報錯終止 184 185問題原因 186 分析標誌指定在了源文件但是沒有鏈接到主內核,或者客製化了鏈接程序 187 188解決方法 189 通過在相應的Makefile中使用 ``GCOV_PROFILE := n`` 190 或者 ``GCOV_PROFILE_basename.o := n`` 來將鏈接報錯的文件排除掉 191 192可能出現的問題2 193 從sysfs複製的文件顯示爲空或不完整 194 195問題原因 196 由於seq_file的工作方式,某些工具(例如cp或tar)可能無法正確地從 197 sysfs複製文件。 198 199解決方法 200 使用 ``cat`` 讀取 ``.gcda`` 文件,使用 ``cp -d`` 複製鏈接,或者使用附錄B 201 中所示的機制。 202 203 204附錄A:collect_on_build.sh 205-------------------------- 206 207用於在編譯機上收集覆蓋率元文件的示例腳本 208(見 :ref:`編譯機和測試機分離 a. <gcov-test_zh>` ) 209 210.. code-block:: sh 211 212 #!/bin/bash 213 214 KSRC=$1 215 KOBJ=$2 216 DEST=$3 217 218 if [ -z "$KSRC" ] || [ -z "$KOBJ" ] || [ -z "$DEST" ]; then 219 echo "Usage: $0 <ksrc directory> <kobj directory> <output.tar.gz>" >&2 220 exit 1 221 fi 222 223 KSRC=$(cd $KSRC; printf "all:\n\t@echo \${CURDIR}\n" | make -f -) 224 KOBJ=$(cd $KOBJ; printf "all:\n\t@echo \${CURDIR}\n" | make -f -) 225 226 find $KSRC $KOBJ \( -name '*.gcno' -o -name '*.[ch]' -o -type l \) -a \ 227 -perm /u+r,g+r | tar cfz $DEST -P -T - 228 229 if [ $? -eq 0 ] ; then 230 echo "$DEST successfully created, copy to test system and unpack with:" 231 echo " tar xfz $DEST -P" 232 else 233 echo "Could not create file $DEST" 234 fi 235 236 237附錄B:collect_on_test.sh 238------------------------- 239 240用於在測試機上收集覆蓋率數據文件的示例腳本 241(見 :ref:`編譯機和測試機分離 b. <gcov-build_zh>` ) 242 243.. code-block:: sh 244 245 #!/bin/bash -e 246 247 DEST=$1 248 GCDA=/sys/kernel/debug/gcov 249 250 if [ -z "$DEST" ] ; then 251 echo "Usage: $0 <output.tar.gz>" >&2 252 exit 1 253 fi 254 255 TEMPDIR=$(mktemp -d) 256 echo Collecting data.. 257 find $GCDA -type d -exec mkdir -p $TEMPDIR/\{\} \; 258 find $GCDA -name '*.gcda' -exec sh -c 'cat < $0 > '$TEMPDIR'/$0' {} \; 259 find $GCDA -name '*.gcno' -exec sh -c 'cp -d $0 '$TEMPDIR'/$0' {} \; 260 tar czf $DEST -C $TEMPDIR sys 261 rm -rf $TEMPDIR 262 263 echo "$DEST successfully created, copy to build system and unpack with:" 264 echo " tar xfz $DEST" 265 266