在 Linux 核心中使用 gcov

gcov 分析核心支援允許將 GCC 的覆蓋率測試工具 gcov 與 Linux 核心一起使用。 執行核心的覆蓋率資料透過“gcov” debugfs 目錄以 gcov 相容格式匯出。 要獲取特定檔案的覆蓋率資料,請更改為核心構建目錄並使用 gcov 和 -o 選項,如下所示(需要 root)

# cd /tmp/linux-out
# gcov -o /sys/kernel/debug/gcov/tmp/linux-out/kernel spinlock.c

這將在當前目錄中建立帶有執行計數的原始碼檔案。 此外,圖形 gcov 前端(例如 lcov)可用於自動收集整個核心的資料的過程,並以 HTML 格式提供覆蓋率概覽。

可能的用途

  • 除錯(是否已到達此行?)

  • 改進測試(如何更改我的測試以覆蓋這些行?)

  • 最小化核心配置(如果從未執行關聯的程式碼,我是否需要此選項?)

準備工作

使用以下命令配置核心

CONFIG_DEBUG_FS=y
CONFIG_GCOV_KERNEL=y

要獲取整個核心的覆蓋率資料

CONFIG_GCOV_PROFILE_ALL=y

請注意,使用分析標誌編譯的核心會明顯更大,執行速度也會更慢。 此外,並非所有架構都支援 CONFIG_GCOV_PROFILE_ALL。

只有在掛載 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 核心配置

CONFIG_GCOV_PROFILE_RDS

在 RDS 上啟用 GCOV 分析,以檢查哪些函式或行已執行。 此配置由 rds 自測試用於生成覆蓋率報告。 如果未設定,則省略報告。

檔案

gcov 核心支援在 debugfs 中建立以下檔案

/sys/kernel/debug/gcov

所有 gcov 相關檔案的父目錄。

/sys/kernel/debug/gcov/reset

全域性重置檔案:寫入時將所有覆蓋率資料重置為零。

/sys/kernel/debug/gcov/path/to/compile/dir/file.gcda

gcov 工具可以理解的實際 gcov 資料檔案。 寫入時將檔案覆蓋率資料重置為零。

/sys/kernel/debug/gcov/path/to/compile/dir/file.gcno

指向 gcov 工具所需的靜態資料檔案的符號連結。 此檔案由 gcc 在使用選項 -ftest-coverage 編譯時生成。

模組

核心模組可能包含僅在模組解除安裝期間執行的清理程式碼。 gcov 機制提供了一種透過保留與解除安裝模組關聯的資料的副本,來收集此類程式碼的覆蓋率資料的方法。 此資料仍然可以透過 debugfs 獲得。 再次載入模組後,關聯的覆蓋率計數器將使用其先前例項的資料進行初始化。

可以透過指定 gcov_persist 核心引數來停用此行為

gcov_persist=0

在執行時,使用者還可以透過寫入其資料檔案或全域性重置檔案來選擇丟棄解除安裝模組的資料。

分離的構建和測試機器

gcov 核心分析基礎設施旨在開箱即用地用於核心在同一臺機器上構建和執行的設定。 如果核心在單獨的機器上執行,則必須進行特殊準備,具體取決於 gcov 工具的使用位置

  1. gcov 在 TEST 機器上執行

    測試機器上的 gcov 工具版本必須與用於核心構建的 gcc 版本相容。 此外,還需要將以下檔案從構建機器複製到測試機器

    從原始碼樹中
    • 所有 C 原始檔 + 標頭

    從構建樹中
    • 所有 C 原始檔 + 標頭

    • 所有 .gcda 和 .gcno 檔案

    • 所有指向目錄的連結

    請務必注意,這些檔案需要放置在測試機器上與構建機器上完全相同的檔案系統位置中。 如果任何路徑元件是符號連結,則需要使用實際目錄(由於 make 的 CURDIR 處理)。

  1. gcov 在 BUILD 機器上執行

    在每個測試用例之後,都需要將以下檔案從測試機器複製到構建機器

    從 sysfs 中的 gcov 目錄中
    • 所有 .gcda 檔案

    • 所有指向 .gcno 檔案的連結

    這些檔案可以複製到構建機器上的任何位置。 然後必須使用 -o 選項呼叫 gcov,該選項指向該目錄。

    構建機器上的示例目錄設定

    /tmp/linux:    kernel source tree
    /tmp/out:      kernel build directory as specified by make O=
    /tmp/coverage: location of the files copied from the test machine
    
    [user@build] cd /tmp/out
    [user@build] gcov -o /tmp/coverage/tmp/out/init main.c
    

關於編譯器的說明

GCC 和 LLVM gcov 工具不一定相容。 使用 gcov 來處理 GCC 生成的 .gcno 和 .gcda 檔案,並使用 llvm-cov 來處理 Clang。

GCC 和 Clang gcov 之間的構建差異由 Kconfig 處理。 它會根據檢測到的工具鏈自動選擇合適的 gcov 格式。

問題排查

問題

編譯在連結器步驟中中止。

原因

為未連結到主核心或由自定義連結器過程連結的原始檔指定了分析標誌。

解決方案

透過在相應的 Makefile 中指定 GCOV_PROFILE := nGCOV_PROFILE_basename.o := n,將受影響的原始檔從分析中排除。

問題

從 sysfs 複製的檔案顯示為空或不完整。

原因

由於 seq_file 的工作方式,某些工具(例如 cp 或 tar)可能無法正確地從 sysfs 複製檔案。

解決方案

使用 cat 讀取 .gcda 檔案,並使用 cp -d 複製連結。 或者,使用附錄 B 中顯示的機制。

附錄 A:gather_on_build.sh

在構建機器上收集覆蓋率元檔案的示例指令碼(參見 分離的構建和測試機器 a.

#!/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:gather_on_test.sh

在測試機器上收集覆蓋率資料檔案的示例指令碼(參見 分離的構建和測試機器 b.

#!/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"