Skip to content

local usage of FI, the branch-blockers.json always empty for C/C++ target despite llvm-cov branch coverage #2258

@fa1c4

Description

@fa1c4

Hello, developers.
I’m trying to use Fuzz Introspector’s RuntimeCoverageAnalysis on an lcms target built with the FI-provided LLVM toolchain and llvm-cov show -show-branches=count. The HTML report is generated correctly and other analyses work (OptimalTargets, AnnotatedCFG, etc.), but branch blockers are never found:

  • The log shows: overlay_calltree_with_coverage: [+] found 0 branch blockers.
  • The generated branch-blockers.json contains an empty list:
{"cms_transform_fuzzer": []}

This happens even though the cms_transform_fuzzer.covreport file clearly contains many Branch (line:col): [True: X, False: Y] entries from llvm-cov.

Environment

  • Fuzz Introspector: built from main
  • FI checkout path: /home/fa1c4/Desktop/fuzz-introspector
  • FI layout:
pwd && ls
/home/fa1c4/Desktop/fuzz-introspector
api_test.sh  build_all.sh  ci_checks.sh    CODE_OF_CONDUCT.md  Dockerfile  LICENSE               README.md         scripts      src    tools
build        CHARTER.pdf   code_checks.sh  doc                 frontends   oss_fuzz_integration  requirements.txt  SECURITY.md  tests
  • LLVM/clang: using the toolchain built by FI (from build/llvm-build/bin):
FI_ROOT=/home/fa1c4/Desktop/fuzz-introspector
CLANG_BASE="$FI_ROOT/build/llvm-build/bin"

export PATH="$CLANG_BASE:$PATH"
export AR="$CLANG_BASE/llvm-ar"
export RANLIB="$CLANG_BASE/llvm-ranlib"
export CC="$CLANG_BASE/clang"
export CXX="$CLANG_BASE/clang++"$FI_ROOT/build/llvm-build/bin/clang --version
clang version 21.1.0-rc3 (https://github.com/llvm/llvm-project/ 6096d35ea93c75f648a253a00775b4d74915c819)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/fa1c4/Desktop/fuzz-introspector/build/llvm-build/bin
❯ $FI_ROOT/build/llvm-build/bin/llvm-cov --version
LLVM (http://llvm.org/):
  LLVM version 21.1.0-rc3
  Optimized build.

Target: lcms (Little CMS) + FI

Build script

This is the script I use to build lcms with FI instrumentation and coverage:

#!/usr/bin/env bash
# FI_build_lcms.sh: build lcms target with Fuzz Introspector + coverage
set -euo pipefail
set -x

ROOT=$(pwd)

# Path to Fuzz Introspector checkout (adjust if different)
FI_ROOT=/home/fa1c4/Desktop/fuzz-introspector
CLANG_BASE="$FI_ROOT/build/llvm-build/bin"

export PATH="$CLANG_BASE:$PATH"

export AR="$CLANG_BASE/llvm-ar"
export RANLIB="$CLANG_BASE/llvm-ranlib"
export CC="$CLANG_BASE/clang"
export CXX="$CLANG_BASE/clang++"

# Common compile flags: hardening + coverage + FI instrumentation
common_cflags="-O2 -Wall -Wextra -D_FORTIFY_SOURCE=2 \
  -fstack-protector-strong -fstack-clash-protection \
  -fPIE -pie -fPIC -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code \
  -fprofile-instr-generate -fcoverage-mapping \
  -fsanitize=fuzzer-no-link -g -flto"

export CFLAGS="$common_cflags"
export CXXFLAGS="$common_cflags -std=c++11"

# Link with libFuzzer (FI uses this)
export LIB_FUZZING_ENGINE="-fsanitize=fuzzer"

# Enable Fuzz Introspector instrumentation in clang
export FUZZ_INTROSPECTOR=1

# Clean previous build
[ -d ./lcms ] && rm -rf lcms
[ -d ./out ] && rm -rf out
mkdir ./out

# Get lcms + fuzzer harness
git clone https://github.com/mm2/Little-CMS.git lcms
cp /home/fa1c4/Desktop/lcms_cov/ext1_cms_transform_fuzzer.cc ./lcms/cms_transform_fuzzer.cc

export SRC="$ROOT/lcms"
export OUT="$ROOT/out"

cd "$SRC"
./autogen.sh
./configure

# Optional: use gold for faster LTO, like jsoncpp FI example
export LDFLAGS="-fuse-ld=gold"

# Keep bear so you still get compile_commands.json if you need it
bear -- make -j"$(nproc)" clean
bear -- make -j"$(nproc)" all

# Build FI-instrumented libFuzzer target
$CXX $CXXFLAGS cms_transform_fuzzer.cc -I include/ src/.libs/liblcms2.a \
    $LIB_FUZZING_ENGINE -o "$OUT/cms_transform_fuzzer"

echo "[+] Done Building (FI): $OUT/cms_transform_fuzzer"

Steps to reproduce

1. Build lcms target with FI

cd /home/fa1c4/Desktop/fuzz-introspector
# (Build FI + LLVM toolchain via repo scripts, then:)

cd /home/fa1c4/Desktop
./FI_build_lcms.sh

Relevant build log (tail):

[Log level 1] : 09:37:14 : Fuzz introspector is running
[Log level 2] : 09:37:14 : Using default configuration
[Log level 1] : 09:37:14 : Running introspector on ld-temp.o
[Log level 1] : 09:37:14 : This is a fuzzer, performing analysis
[Log level 1] : 09:37:14 : Logging next yaml tile to fuzzerLogFile-0-1rgEopXyfV.data.yaml
[Log level 1] : 09:37:14 : Wrapping all functions
[Log level 1] : 09:37:15 : Ended wrapping all functions
[Log level 1] : 09:37:15 : Finished introspector module
[+] Done Building (FI): /home/fa1c4/Desktop/lcms_cov/out/cms_transform_fuzzer

2. Run the fuzzer once to collect coverage

cd /home/fa1c4/Desktop/lcms_cov/out

LLVM_PROFILE_FILE=cms_transform_fuzzer.profraw \
  ./cms_transform_fuzzer ../corpus -runs=0

Example output:

INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1129346469
INFO: Loaded 1 modules   (7552 inline 8-bit counters): 7552 [0x..., 0x...), 
INFO: Loaded 1 PC tables (7552 PCs): 7552 [0x...,0x...), 
INFO:     1058 files found in ../corpus
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 34798 bytes
INFO: seed corpus: files: 1058 min: 2b max: 34798b total: 1768529b rss: 29Mb
#512  pulse  cov: 1284 ft: 2874 corp: 344/252Kb exec/s: 170 rss: 211Mb
#1024 pulse  cov: 1433 ft: 3359 corp: 593/639Kb exec/s: 170 rss: 211Mb
#1059 INITED cov: 1436 ft: 3377 corp: 607/811Kb exec/s: 176 rss: 211Mb
#1059 DONE   cov: 1436 ft: 3377 corp: 607/811Kb lim: 32548 exec/s: 176 rss: 211Mb
Done 1059 runs in 6 second(s)

3. Generate coverage report with llvm-cov

LLVM_BIN=/home/fa1c4/Desktop/fuzz-introspector/build/llvm-build/bin

$LLVM_BIN/llvm-profdata merge -sparse cms_transform_fuzzer.profraw \
  -o cms_transform_fuzzer.profdata

$LLVM_BIN/llvm-cov show \
  -instr-profile=cms_transform_fuzzer.profdata \
  -object=./cms_transform_fuzzer \
  -show-branches=count \
  -line-coverage-gt=0 \
  > cms_transform_fuzzer.covreport

At this point cms_transform_fuzzer.covreport contains many Branch (line:col): [True: X, False: Y] lines (and of course line coverage entries).

4. Copy FI raw data

cp ../lcms/fuzzerLogFile-0-*.data* .
ls

cms_transform_fuzzer            cms_transform_fuzzer.profraw                         fuzzerLogFile-0-1rgEopXyfV.data.debug_all_globals  fuzzerLogFile-0-1rgEopXyfV.data.yaml
cms_transform_fuzzer.covreport  fuzzerLogFile-0-1rgEopXyfV.data                      fuzzerLogFile-0-1rgEopXyfV.data.debug_all_types
cms_transform_fuzzer.profdata   fuzzerLogFile-0-1rgEopXyfV.data.debug_all_functions  fuzzerLogFile-0-1rgEopXyfV.data.debug_info

5. Correlate binaries to FI logs

python /home/fa1c4/Desktop/fuzz-introspector/src/main.py \
  correlate --binaries-dir=.

2025-12-09 09:41:09.143 INFO cli - main: Running fuzz introspector post-processing
2025-12-09 09:41:09.144 INFO utils - scan_executables_for_fuzz_introspector_logs: File: ./cms_transform_fuzzer is executable
2025-12-09 09:41:09.304 INFO utils - scan_executables_for_fuzz_introspector_logs: Found match fuzzerLogFile-0-1rgEopXyfV
2025-12-09 09:41:09.306 INFO commands - correlate_binaries_to_logs: Pairings: [{'executable_path': './cms_transform_fuzzer', 'fuzzer_log_file': 'fuzzerLogFile-0-1rgEopXyfV'}]
2025-12-09 09:41:09.308 INFO cli - main: Ending fuzz introspector post-processing



SRC="/home/fa1c4/Desktop/lcms_cov/lcms" QT_QPA_PLATFORM=offscreen \
python /home/fa1c4/Desktop/fuzz-introspector/src/main.py report \
  --target-dir=. \
  --correlation-file=./exe_to_fuzz_introspector_logs.yaml \
  --analyses OptimalTargets RuntimeCoverageAnalysis \
             FuzzEngineInputAnalysis AnnotatedCFG \
  --output-json OptimalTargets RuntimeCoverageAnalysis \
               FuzzEngineInputAnalysis AnnotatedCFG

...
2025-12-09 09:41:17.689 INFO fuzzer_profile - accummulate_profile: cms_transform_fuzzer: loading coverage
2025-12-09 09:41:17.690 INFO fuzzer_profile - _load_coverage: Loading coverage of type c-cpp
2025-12-09 09:41:17.690 INFO code_coverage - load_llvm_coverage: Loading LLVM coverage for target cms_transform_fuzzer
2025-12-09 09:41:17.691 INFO code_coverage - load_llvm_coverage: Found 1 coverage reports
2025-12-09 09:41:17.691 INFO code_coverage - load_llvm_coverage: Using the following coverages ['./cms_transform_fuzzer.covreport']
2025-12-09 09:41:17.691 INFO code_coverage - load_llvm_coverage: Reading coverage report: ./cms_transform_fuzzer.covreport
...
2025-12-09 09:41:18.057 INFO utils - get_target_coverage_url: Extracting coverage for /covreport/linux -- cms_transform_fuzzer
2025-12-09 09:41:18.057 INFO analysis - overlay_calltree_with_coverage: Using coverage url: /covreport/linux
...
2025-12-09 09:41:18.111 INFO analysis - overlay_calltree_with_coverage: Updating branch complexities
2025-12-09 09:41:18.115 INFO analysis - overlay_calltree_with_coverage: [+] found 0 branch blockers.

7. branch-blockers.json content

cat branch-blockers.json

{"cms_transform_fuzzer": []}

Observed behavior

  • FI clearly loads the .covreport and uses it (the HTML report shows line coverage; OptimalTargets etc. work).
  • However, RuntimeCoverageAnalysis / branch blocker discovery always finds zero branch blockers:
2025-12-09 09:41:18.115 INFO analysis - overlay_calltree_with_coverage: [+] found 0 branch blockers.
  • This is surprising because the cms_transform_fuzzer.covreport file from llvm-cov show -show-branches=count does include many Branch (line:col): [True: X, False: Y] entries for the lcms code (e.g., Branch (7:7): [True: 2, False: 1.05k], etc.).

Expected behavior

Given that:

  • The coverage report includes branch coverage (Branch (line:col)),
  • FI successfully sees the coverage file and uses it,
  • And RuntimeCoverageAnalysis runs without errors,

…I expected:

  • overlay_calltree_with_coverage to detect some branch blockers in the lcms call tree, and
  • branch-blockers.json to contain a non-empty list of blocker locations for cms_transform_fuzzer (or at least to reflect branches present in the coverage report).

Questions / help requested

  • Is there a known incompatibility between the current llvm-cov show output (with -show-branches=count) and the branch coverage parsing in code_coverage.load_llvm_coverage?
  • Is my llvm-cov show invocation missing flags that FI expects for branch blocker analysis?
  • Could branch_cov_map be staying empty due to assumptions about the text format that have changed in newer LLVM versions?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions