From bd940c0dd56754bae282e75a880f30020d3eb33f Mon Sep 17 00:00:00 2001 From: Yijie Ma Date: Fri, 14 Apr 2023 13:03:12 -0700 Subject: [PATCH] [Fuzzing] Add a build config option which enables Clang source-based code coverage (#32858) This maybe used to quickly verify the code coverage of a modified test locally (e.g. fuzzer). Example: ``` # Build and run target; the raw profile will be written to $LLVM_PROFILE_FILE when the program exits $ bazel build --config=dbg --config=fuzzer_asan --config=coverage //test/core/end2end/fuzzers:api_fuzzer $ LLVM_PROFILE_FILE="api_fuzzer.profraw" bazel-bin/test/core/end2end/fuzzers/api_fuzzer test/core/end2end/fuzzers/api_fuzzer_corpus/* # Create coverage report $ llvm-profdata-14 merge -sparse api_fuzzer.profraw -o api_fuzzer.profdata $ llvm-cov-14 report ./bazel-bin/test/core/end2end/fuzzers/api_fuzzer --instr-profile=api_fuzzer.profdata ``` Sample report: https://gist.githubusercontent.com/yijiem/b9d2a8662f9b7a82df4a9d33cd5d3b39/raw/f94e444f2558aeb9cd8e1997cb27dbcad1ae7f2c/gistfile1.txt One trick is that the binary needs to be statically linked, e.g. specify `linkstatic = 1` on the BUILD target. See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html for more info. --- tools/bazel.rc | 4 ++ tools/fuzzing/generate_coverage_report.sh | 80 +++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100755 tools/fuzzing/generate_coverage_report.sh diff --git a/tools/bazel.rc b/tools/bazel.rc index 7fad50618ba..da0d5892086 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -60,6 +60,10 @@ build:fuzzer_asan --linkopt=-fsanitize=fuzzer,address build:fuzzer_asan --action_env=ASAN_OPTIONS=detect_leaks=1:color=always build:fuzzer_asan --action_env=LSAN_OPTIONS=suppressions=test/core/util/lsan_suppressions.txt:report_objects=1 +build:coverage --copt=-fprofile-instr-generate +build:coverage --copt=-fcoverage-mapping +build:coverage --linkopt=-fprofile-instr-generate + build:fork_support --cxxopt=-DGRPC_ENABLE_FORK_SUPPORT=1 build:fork_support --cxxopt=-DGRPC_POSIX_FORK_ALLOW_PTHREAD_ATFORK=1 build:fork_support --action_env=GRPC_ENABLE_FORK_SUPPORT=1 diff --git a/tools/fuzzing/generate_coverage_report.sh b/tools/fuzzing/generate_coverage_report.sh new file mode 100755 index 00000000000..86a57b19b2c --- /dev/null +++ b/tools/fuzzing/generate_coverage_report.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# Copyright 2023 The gRPC Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This tool builds and runs a fuzzer target and generates coverage report under +# /tmp. +# +# Example: +# Run fuzzer with existing corpus (no fuzzing): +# $ ./tools/fuzzing/generate_coverage_report.sh //test/core/end2end/fuzzers:api_fuzzer test/core/end2end/fuzzers/api_fuzzer_corpus/* +# +# Run with fuzzing: +# $ ./tools/fuzzing/generate_coverage_report.sh //test/core/end2end/fuzzers:api_fuzzer -max_total_time=10 test/core/end2end/fuzzers/api_fuzzer_corpus +# +# Note that if a crash happened during fuzzing, the coverage data will not be dumped. +# See https://github.com/google/fuzzing/issues/41#issuecomment-1027653690 for workaround. + +err() { + echo "$*" >&2 +} + +if [[ -z "$1" ]]; then + err "target not specified" + exit 1 +fi + +RANDOM=$(date +%s) +FILENAME="${RANDOM}" + +export LLVM_PROFILE_FILE=/tmp/"${FILENAME}".profraw +OUTPUT_BASE=$(bazel info output_base) +MIDDLE="execroot/com_github_grpc_grpc/bazel-out/k8-dbg/bin" + +CLANG_MAJOR_VERSION=$(clang --version | grep version | sed -r 's/.*version ([^\.]+).*/\1/') +LLVM_PROFDATA="llvm-profdata-${CLANG_MAJOR_VERSION}" +LLVM_COV="llvm-cov-${CLANG_MAJOR_VERSION}" + +which "${LLVM_PROFDATA}" +if (( $? != 0 )); then + err "${LLVM_PROFDATA} not found" + exit 1 +fi + +TARGET=$(bazel query "$1") +TARGET_BINARY_PATH="${OUTPUT_BASE}/${MIDDLE}/$(echo ${TARGET:2} | sed 's/:/\//')" + +# Build: +bazel build --dynamic_mode=off --config=dbg --config=fuzzer_asan --config=coverage "${TARGET}" +# Run: +"${TARGET_BINARY_PATH}" ${@:2} + +if [[ ! -e "${LLVM_PROFILE_FILE}" ]]; then + err "Profile file ${LLVM_PROFILE_FILE} not created" + exit 1 +fi + +# Create coverage report: +"${LLVM_PROFDATA}" merge -sparse "${LLVM_PROFILE_FILE}" -o /tmp/"${FILENAME}".profdata +"${LLVM_COV}" report "${TARGET_BINARY_PATH}" --format=text --instr-profile=/tmp/"${FILENAME}".profdata > /tmp/"${FILENAME}".cov + +if (( $? == 0 )); then + echo "Coverage summary report created: /tmp/${FILENAME}.cov" + echo "Merged profile data file: /tmp/${FILENAME}.profdata" + echo "Raw profile data file: /tmp/${FILENAME}.profraw" + echo "There are other ways to explore the data, see https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#creating-coverage-reports" +else + err "Something went wrong" + exit 1 +fi