mcpField
C
coverage-analysis

coverage-analysis

github.com/trailofbitsテスト・品質7.9k5.5k 回インストール今日更新
リポジトリ

coverage-analysis について

coverage-analysisは、コーパスが実行するコードパスを測りたい開発者向けのファジングスキルです。C/C++とRustの計測ビルドやレポート作成に加え、実行されない経路の調査やキャンペーン間の比較方法を扱います。

主な機能

  1. 01LLVMとGCCの計測設定
  2. 02ファジングコーパスでのカバレッジ測定
  3. 03テキストまたはHTMLレポートの生成
  4. 04未実行経路やマジック値チェックの特定
  5. 05キャンペーン間のカバレッジ比較
  6. 06レポートからハーネスコードを除外

活用シーン

  1. 01ファジング開始時の基準値の作成
  2. 02停滞したファザーの調査
  3. 03ハーネス変更による到達範囲の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trailofbits/skills --skill coverage-analysis

インストール後、Claude Code で/coverage-analysisを入力して読み込まれたか確認します。

同梱ファイルagents/openai.yamlassets/trail-of-bits-mark.svg

詳細

カバレッジ分析

カバレッジ分析は、ファジング中にコードのどの部分が実行されているかを理解するうえで欠かせません。マジック値チェックのようなファジングの妨げを特定し、ハーネスの改善が時間の経過とともにどれほど効果を発揮しているかを追跡できます。

概要

ファジング中のコードカバレッジには、次の2つの重要な目的があります。

  1. ハーネスの有効性を評価する:ファジングハーネスによって、アプリケーションのどの部分が実際に実行されているかを把握します
  2. ファジングの進捗を追跡する:ハーネス、ファザー、またはテスト対象システム(SUT)を更新した際に、カバレッジがどのように変化するかを監視します

カバレッジは、ファザーの能力と性能を測るための代理指標です。絶対的な意味でファザーの性能を測定するのにカバレッジが最適ではない一方、特定のセットアップでハーネスが効果的に機能しているかどうかを確実に示します。

主な概念
概念説明
カバレッジ計測実行されたコードパスを追跡するコンパイラフラグ
コーパスカバレッジファジングコーパス内のすべてのテストケースを実行して得られるカバレッジ
マジック値チェックファザーの進行を妨げる、発見が困難な条件チェック
カバレッジガイド付きファジング新しいコードパスを発見する入力を優先するファジング戦略
カバレッジレポート実行済みコードと未実行コードを視覚的またはテキストで表したもの

適用するタイミング

次の場合にこの手法を適用します:

  • 新しいファジングキャンペーンを開始し、ベースラインを設定するとき
  • 新しいパスが見つからず、ファザーが停滞しているように見えるとき
  • ハーネスを変更した後、改善を確認するとき
  • 別のファザーに移行するとき
  • ディクショナリー項目やシード入力が必要な領域を特定するとき
  • 特定のコードパスに到達しない理由をデバッグするとき

次の場合はこの手法を省略します:

  • ファジングキャンペーンでクラッシュが活発に見つかっているとき
  • カバレッジのインフラストラクチャがまだセットアップされていないとき
  • コードベースが非常に大きく、全体のカバレッジレポートを作成するのが現実的でないとき
  • ファザー内部のカバレッジ指標でニーズを満たせるとき

クイックリファレンス

タスクコマンド/パターン
LLVM のカバレッジ計測(C/C++)-fprofile-instr-generate -fcoverage-mapping
GCC のカバレッジ計測-ftest-coverage -fprofile-arcs
cargo-fuzz のカバレッジ(Rust)cargo +nightly fuzz coverage <target>
LLVM プロファイルデータの生成llvm-profdata merge -sparse file.profraw -o file.profdata
LLVM カバレッジレポートllvm-cov report ./binary -instr-profile=file.profdata
LLVM HTML レポートllvm-cov show ./binary -instr-profile=file.profdata -format=html -output-dir html/
gcovr HTML レポートgcovr --html-details -o coverage.html

理想的なカバレッジワークフロー

次のワークフローは、ファジングキャンペーンにカバレッジ分析を組み込む際のベストプラクティスを示しています。

[Fuzzing Campaign]
       |
       v
[Generate Corpus]
       |
       v
[Coverage Analysis]
       |
       +---> Coverage Increased? --> Continue fuzzing with larger corpus
       |
       +---> Coverage Decreased? --> Fix harness or investigate SUT changes
       |
       +---> Coverage Plateaued? --> Add dictionary entries or seed inputs

重要な原則:リアルタイムのファザー統計ではなく、各ファジングキャンペーンの後に生成されたコーパスを使ってカバレッジを計算します。この方法により、異なるファジングツール間で再現性と比較可能性のある測定結果が得られます。

手順

手順 1:カバレッジ計測を有効にしてビルドする

ツールチェーンに応じて計測方法を選択します。

LLVM/Clang(C/C++):

clang++ -fprofile-instr-generate -fcoverage-mapping \
  -O2 -DNO_MAIN \
  main.cc harness.cc execute-rt.cc -o fuzz_exec

GCC(C/C++):

g++ -ftest-coverage -fprofile-arcs \
  -O2 -DNO_MAIN \
  main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov

Rust:

rustup toolchain install nightly --component llvm-tools-preview
cargo +nightly fuzz coverage fuzz_target_1
手順 2:実行ランタイムを作成する(C/C++のみ)

C/C++プロジェクトでは、コーパスを実行するランタイムを作成します。

// execute-rt.cc
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <stdint.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

void load_file_and_test(const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        printf("Failed to open file: %s\n", filename);
        return;
    }

    fseek(file, 0, SEEK_END);
    long filesize = ftell(file);
    rewind(file);

    uint8_t *buffer = (uint8_t*) malloc(filesize);
    if (buffer == NULL) {
        printf("Failed to allocate memory for file: %s\n", filename);
        fclose(file);
        return;
    }

    long read_size = (long) fread(buffer, 1, filesize, file);
    if (read_size != filesize) {
        printf("Failed to read file: %s\n", filename);
        free(buffer);
        fclose(file);
        return;
    }

    LLVMFuzzerTestOneInput(buffer, filesize);

    free(buffer);
    fclose(file);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Usage: %s <directory>\n", argv[0]);
        return 1;
    }

    DIR *dir = opendir(argv[1]);
    if (dir == NULL) {
        printf("Failed to open directory: %s\n", argv[1]);
        return 1;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_REG) {
            char filepath[1024];
            snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
            load_file_and_test(filepath);
        }
    }

    closedir(dir);
    return 0;
}
手順 3:コーパスを実行する

LLVM(C/C++):

LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/

GCC(C/C++):

./fuzz_exec_gcov corpus/

Rust: cargo fuzz coverage の実行時に、カバレッジデータが自動的に生成されます。

手順 4:カバレッジデータを処理する

LLVM:

# Merge raw profile data
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata

# Generate text report
llvm-cov report ./fuzz_exec \
  -instr-profile=fuzz.profdata \
  -ignore-filename-regex='harness.cc|execute-rt.cc'

# Generate HTML report
llvm-cov show ./fuzz_exec \
  -instr-profile=fuzz.profdata \
  -ignore-filename-regex='harness.cc|execute-rt.cc' \
  -format=html -output-dir fuzz_html/

gcovr を使った GCC:

# Install gcovr (uv manages the isolated environment)
uv tool install gcovr
export PATH="$HOME/.local/bin:$PATH"   # uv's tool bin, if not already present

# Generate report
gcovr --gcov-executable "llvm-cov gcov" \
  --exclude harness.cc --exclude execute-rt.cc \
  --root . --html-details -o coverage.html

Rust:

# Install required tools
cargo install cargo-binutils rustfilt

# Create HTML generation script
cat <<'EOF' > ./generate_html
#!/bin/sh
if [ $# -lt 1 ]; then
    echo "Error: Name of fuzz target is required."
    echo "Usage: $0 fuzz_target [sources...]"
    exit 1
fi
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
  "target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
  -instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
  -show-line-counts-or-regions -show-instantiations \
  -format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html

# Generate HTML report
./generate_html fuzz_target_1 src/lib.rs
ステップ 5:結果を分析する

カバレッジレポートを確認し、以下を特定します。

  • カバーされていないコードブロック:シード入力や辞書エントリーを改善する必要がある箇所
  • マジック値のチェック:進行を妨げる、値がハードコードされた条件文
  • デッドコード:ハーネスから到達できない可能性がある関数
  • カバレッジの変化:ベースラインと比較し、改善や後退を追跡する

よくあるパターン

パターン:マジック値を特定する

問題:ファザーがマジック値のチェックで保護されたパスを見つけられません。

カバレッジから判明すること:

// Coverage shows this block is never executed
if (buf == 0x7F454C46) {  // ELF magic number
    // start parsing buf
}

解決策:マジック値を辞書ファイルに追加します。

# magic.dict
"\x7F\x45\x4C\x46"
パターン:クラッシュする入力を処理する

問題:コーパスにクラッシュする入力が含まれていると、カバレッジの生成に失敗します。

変更前:

./fuzz_exec corpus/  # Crashes on bad input, no coverage generated

変更後:

// Fork before executing to isolate crashes
int main(int argc, char **argv) {
    // ... directory opening code ...

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_REG) {
            pid_t pid = fork();
            if (pid == 0) {
                // Child process - crash won't affect parent
                char filepath[1024];
                snprintf(filepath, sizeof(filepath), "%s/%s", argv[1], entry->d_name);
                load_file_and_test(filepath);
                exit(0);
            } else {
                // Parent waits for child
                waitpid(pid, NULL, 0);
            }
        }
    }
}
パターン:CMake との統合

ユースケース:CMake プロジェクトにカバレッジビルドを追加します。

project(FuzzingProject)
cmake_minimum_required(VERSION 3.0)

# Main binary
add_executable(program main.cc)

# Fuzzing binary
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2 -fsanitize=fuzzer)
target_link_libraries(fuzz -fsanitize=fuzzer)

# Coverage execution binary
add_executable(fuzz_exec main.cc harness.cc execute-rt.cc)
target_compile_definitions(fuzz_exec PRIVATE NO_MAIN)
target_compile_options(fuzz_exec PRIVATE -O2 -fprofile-instr-generate -fcoverage-mapping)
target_link_libraries(fuzz_exec -fprofile-instr-generate)

ビルドします。

cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target fuzz_exec

高度な使い方

ヒントとコツ
ヒント役立つ理由
LLVM 18 以降で -show-directory-coverage を使用する大規模なレポートをフラットなファイル一覧ではなく、ディレクトリ構造ごとに整理できます
HTML の見やすさを向上させるため、lcov 形式にエクスポートするllvm-cov export -format=lcov と genhtml を使うと、ファイルごとのレポートが見やすくなります
キャンペーン間でカバレッジを比較する.profdata ファイルをタイムスタンプ付きで保存し、経時的な進捗を追跡します
レポートからハーネスのコードを除外する-ignore-filename-regex を使い、SUT のカバレッジだけに絞ります
CI/CD でカバレッジを自動化するスケジュールされたファジングの実行後に、カバレッジレポートを自動生成します
Clang 14 以降では gcovr 5.1 以降を使用する古いバージョンの gcovr は、最近の LLVM との互換性に問題があります
カバレッジの増分更新

GCC の gcov 計測では、複数回の実行にわたって .gcda ファイルが増分更新されます。テストケースを追加しながらカバレッジを追跡する場合に便利です。

# First run
./fuzz_exec_gcov corpus_batch_1/
gcovr --html coverage_v1.html

# Second run (adds to existing coverage)
./fuzz_exec_gcov corpus_batch_2/
gcovr --html coverage_v2.html

# Start fresh
gcovr --delete  # Remove .gcda files
./fuzz_exec_gcov corpus/
大規模なコードベースの処理

数百のソースファイルがあるプロジェクトの場合:

  1. プレフィックスでフィルターする:関連するディレクトリだけを対象にレポートを生成します

    llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata /path/to/src/
    
  2. ディレクトリカバレッジを使用する:ディレクトリごとにグループ化し、表示を整理します(LLVM 18 以降)

    llvm-cov show -show-directory-coverage -format=html -output-dir html/
    
  3. プログラムによる分析用に JSON を生成する:

    llvm-cov export -format=lcov > coverage.json
    
差分カバレッジ

2 つのファジングキャンペーン間でカバレッジを比較します。

# Campaign 1
LLVM_PROFILE_FILE=campaign1.profraw ./fuzz_exec corpus1/
llvm-profdata merge -sparse campaign1.profraw -o campaign1.profdata

# Campaign 2
LLVM_PROFILE_FILE=campaign2.profraw ./fuzz_exec corpus2/
llvm-profdata merge -sparse campaign2.profraw -o campaign2.profdata

# Compare
llvm-cov show ./fuzz_exec \
  -instr-profile=campaign2.profdata \
  -instr-profile=campaign1.profdata \
  -show-line-counts-or-regions

避けるべきパターン

避けるべきパターン問題適切な方法
比較にファザーが報告するカバレッジを使用するファザーごとにカバレッジの計算方法が異なるため、ツール間の比較に意味がなくなります専用のカバレッジツール(llvm-cov、gcovr)を使い、再現性のある測定を行います
最適化を有効にしてカバレッジを生成する-O3 最適化によってコードが削除され、カバレッジが誤解を招く可能性がありますカバレッジビルドには -O2 または -O0 を使用します
ハーネスのコードをフィルターしないハーネスのカバレッジによって数値が水増しされ、SUT のカバレッジが見えにくくなります-ignore-filename-regex または --exclude を使ってハーネスファイルを除外します
LLVM と GCC の計測を混在させる互換性のない形式によって解析に失敗しますカバレッジビルドでは、1 つのツールチェーンに統一します
クラッシュする入力を無視するクラッシュによってカバレッジの生成が妨げられ、実際のカバレッジデータが隠れてしまいますまずクラッシュを修正するか、プロセスをフォークして分離します
カバレッジを経時的に追跡しない一度だけのカバレッジ確認では、後退や改善を見落としますカバレッジデータをタイムスタンプ付きで保存し、傾向を追跡します

ツール別のガイダンス

libFuzzer

libFuzzer はファジングの誘導に LLVM の SanitizerCoverage をデフォルトで使用しますが、レポートを生成するには別途計測が必要です。

カバレッジ用にビルドする:

clang++ -fprofile-instr-generate -fcoverage-mapping \
  -O2 -DNO_MAIN \
  main.cc harness.cc execute-rt.cc -o fuzz_exec

コーパスを実行してレポートを生成する:

LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec corpus/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov show ./fuzz_exec -instr-profile=fuzz.profdata -format=html -output-dir html/

統合のヒント:

  • カバレッジビルドでは -fsanitize=fuzzer を使用しないでください(プロファイル計測と競合します)
  • 同じハーネス関数(LLVMFuzzerTestOneInput)を別の main 関数とともに再利用します
  • -ignore-filename-regex フラグを使い、カバレッジレポートからハーネスのコードを除外します
  • テンプレートを多用する C++ コードでは、llvm-cov の -show-instantiation フラグの使用を検討してください
AFL++

AFL++ は独自のカバレッジフィードバック機構を備えていますが、詳細なレポートには標準の LLVM/GCC ツールを使用してください。

LLVM でカバレッジ用にビルドする:

clang++ -fprofile-instr-generate -fcoverage-mapping \
  -O2 main.cc harness.cc execute-rt.cc -o fuzz_exec

GCC でカバレッジ用にビルドする:

AFL_USE_ASAN=0 afl-gcc -ftest-coverage -fprofile-arcs \
  main.cc harness.cc execute-rt.cc -o fuzz_exec_gcov

実行してレポートを生成する:

# LLVM approach
LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec afl_output/queue/
llvm-profdata merge -sparse fuzz.profraw -o fuzz.profdata
llvm-cov report ./fuzz_exec -instr-profile=fuzz.profdata

# GCC approach
./fuzz_exec_gcov afl_output/queue/
gcovr --html-details -o coverage.html

統合のヒント:

  • カバレッジ用ビルドでは、AFL++ のインストルメンテーション(afl-clang-fast)を使用しないでください
  • 代わりに、カバレッジフラグを指定して標準のコンパイラーを使用してください
  • AFL++ の queue/ ディレクトリにコーパスが含まれています
  • AFL++ に組み込まれたカバレッジ統計は、リアルタイムでの監視には便利ですが、詳細な分析には向きません
cargo-fuzz(Rust)

cargo-fuzz は LLVM ツールを使用して、カバレッジを組み込みで生成します。

前提条件をインストールする:

rustup toolchain install nightly --component llvm-tools-preview
cargo install cargo-binutils rustfilt

カバレッジデータを生成する:

cargo +nightly fuzz coverage fuzz_target_1

HTML レポートのスクリプトを作成する:

cat <<'EOF' > ./generate_html
#!/bin/sh
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
  "target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
  -instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
  -show-line-counts-or-regions -show-instantiations \
  -format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html

レポートを生成する:

./generate_html fuzz_target_1 src/lib.rs

統合のヒント:

  • カバレッジには必ずナイトリーツールチェーンを使用してください
  • -Xdemangler=rustfilt フラグを指定すると、関数名が読みやすくなります
  • クレートのコードに焦点を当てるには、ソースファイル(例:src/lib.rs)でフィルターしてください
  • Rust 固有の出力を改善するには、-show-line-counts-or-regions と -show-instantiations を使用してください
  • コーパスは fuzz/corpus/<target>/ にあります
honggfuzz

honggfuzz は標準の LLVM/GCC カバレッジインストルメンテーションに対応しています。

カバレッジ用にビルドする:

# Use standard compiler, not honggfuzz compiler
clang -fprofile-instr-generate -fcoverage-mapping \
  -O2 harness.c execute-rt.c -o fuzz_exec

コーパスを実行する:

LLVM_PROFILE_FILE=fuzz.profraw ./fuzz_exec honggfuzz_workspace/

統合のヒント:

  • カバレッジ用ビルドでは hfuzz-clang を使用しないでください
  • honggfuzz のコーパスは通常、ワークスペースディレクトリにあります
  • libFuzzer と同じ LLVM ワークフローを使用してください

トラブルシューティング

問題原因解決策
error: no profile data availableプロファイルが生成されていないか、パスが間違っていますLLVM_PROFILE_FILE が設定されており、.profraw ファイルが存在することを確認してください
Failed to load coverageバイナリとプロファイルデータが一致していません実行時に使用したものと同じフラグでバイナリを再ビルドしてください
カバレッジレポートが 0% と表示されるレポート生成に間違ったバイナリを使用していますファジング用バイナリではなく、インストルメント済みバイナリを使用してください
no_working_dir_found エラー(gcovr).gcda ファイルが想定外の場所にあります--gcov-ignore-errors=no_working_dir_found フラグを追加してください
クラッシュによってカバレッジを生成できないコーパスにクラッシュを引き起こす入力が含まれていますクラッシュをフィルターするか、フォーク方式を使って障害を切り分けてください
ハーネス変更後にカバレッジが低下するハーネスが一部のコードパスをスキップするようになりましたハーネスのロジックを確認してください。より多くの入力形式に対応する必要がある場合があります
HTML レポートがフラットなファイル一覧になる古い LLVM バージョンを使用していますLLVM 18 以降にアップグレードし、-show-directory-coverage を使用してください
incompatible instrumentationLLVM と GCC のカバレッジを混在させています同じツールチェーンですべてを再ビルドしてください

関連スキル

この手法を使用するツール
スキル適用方法
libfuzzerフィードバックに SanitizerCoverage を使用します。カバレッジ分析によってハーネスの有効性を評価できます
aflppフィードバックにエッジカバレッジを使用します。詳細な分析には別途インストルメンテーションが必要です
cargo-fuzzRust プロジェクト向けに cargo fuzz coverage コマンドを組み込みで提供します
honggfuzzエッジカバレッジを使用します。標準の LLVM/GCC ツールで分析できます
関連する手法
スキル関連性
fuzz-harness-writingカバレッジから、ハーネスが到達するコードパスがわかり、ハーネスの改善に役立ちます
fuzzing-dictionariesカバレッジから、辞書エントリーが必要なマジック値のチェックを特定できます
corpus-managementカバレッジ分析により、冗長なテストケースを特定してコーパスを整理できます
sanitizersカバレッジによって、サニタイザーをインストルメントしたコードが実際に実行されているかを確認できます

リソース

主要な外部リソース

LLVM のソースベースのコードカバレッジ 分岐カバレッジ、リージョンカバレッジ、既存のビルドシステムとの統合などの高度な機能を含む、LLVM のプロファイル計測に関する包括的なガイドです。コンパイラフラグ、ランタイムの動作、プロファイルデータ形式について説明しています。

llvm-cov コマンドガイド show、report、export などの llvm-cov コマンドに関する詳細な CLI リファレンスです。すべてのフィルタリングオプション、出力形式、llvm-profdata との統合について記載しています。

gcovr ドキュメント gcov データからカバレッジレポートを生成する gcovr ツールの完全なガイドです。HTML テーマ、フィルタリングオプション、複数ディレクトリのプロジェクト、CI/CD との統合パターンについて説明しています。

SanitizerCoverage ドキュメント LLVM の SanitizerCoverage 計測に関する低レベルのドキュメントです。インライン 8 ビットカウンター、PC テーブル、ファジングツールがカバレッジフィードバックをガイダンスに利用する方法について説明しています。

ファザーの性能評価について ファジングの性能指標としてのカバレッジの限界を検証する研究論文です。単純なコードカバレッジ率にとどまらない、よりきめ細かな評価方法を提唱しています。

動画リソース

該当なし。カバレッジ分析は主にツールとワークフローに関するトピックであり、ドキュメントを読み、実践を通じて学ぶのが最適です。

コメント