From 17a6cfbd40ca81794bfa6d83b8c5594d75714044 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 21 Aug 2018 14:02:48 -0700 Subject: [PATCH] [infra] Use coverage utils from Chromium and other fixes (follow-up #1547). (#1741) * [infra] Use coverage utils from Chromium and other fixes (follow-up #1547). * Sort dependencies in an alpha order plus make more readable. * Re-order arguments passed to coverage_helper script. * Rename REPORT_DIR variable and put summary.json into platform specific dir. * Fix -src-root-dir value. --- infra/base-images/base-builder/compile | 2 +- infra/base-images/base-runner/Dockerfile | 36 ++++++-- infra/base-images/base-runner/coverage | 28 +++--- infra/base-images/base-runner/coverage_helper | 17 ++++ .../base-runner/coverage_helper.py | 88 ------------------- 5 files changed, 67 insertions(+), 104 deletions(-) create mode 100755 infra/base-images/base-runner/coverage_helper delete mode 100755 infra/base-images/base-runner/coverage_helper.py diff --git a/infra/base-images/base-builder/compile b/infra/base-images/base-builder/compile index 705b2ada5..08c54b0d5 100755 --- a/infra/base-images/base-builder/compile +++ b/infra/base-images/base-builder/compile @@ -68,7 +68,7 @@ BUILD_CMD="bash -eux $SRC/build.sh" # We need to preserve source code files for generating a code coverage report. # We need exact files that were compiled, so copy both $SRC and $WORK dirs. -COPY_SOURCES_CMD="cp -r $SRC $WORK $OUT" +COPY_SOURCES_CMD="cp -rL $SRC $WORK $OUT" if [ "${BUILD_UID-0}" -ne "0" ]; then adduser -u $BUILD_UID --disabled-password --gecos '' builder diff --git a/infra/base-images/base-runner/Dockerfile b/infra/base-images/base-runner/Dockerfile index 604ece3fd..66600a7f5 100644 --- a/infra/base-images/base-runner/Dockerfile +++ b/infra/base-images/base-runner/Dockerfile @@ -16,11 +16,37 @@ FROM gcr.io/oss-fuzz-base/base-image MAINTAINER mike.aizatsky@gmail.com -RUN apt-get install -y zip file libunwind8 binutils libblocksruntime0 \ - fonts-dejavu python3 libcap2 wget -COPY bad_build_check coverage coverage_helper.py download_corpus llvm-cov \ - llvm-profdata llvm-symbolizer reproduce run_fuzzer sancov test_all \ - minijail0 run_minijail targets_list /usr/local/bin/ +RUN apt-get install -y \ + binutils \ + file \ + fonts-dejavu \ + git \ + libblocksruntime0 \ + libcap2 \ + libunwind8 \ + python3 \ + python3-pip \ + wget \ + zip + +RUN git clone https://chromium.googlesource.com/chromium/src/tools/code_coverage /opt/code_coverage +RUN pip3 install -r /opt/code_coverage/requirements.txt + +COPY bad_build_check \ + coverage \ + coverage_helper \ + download_corpus \ + llvm-cov \ + llvm-profdata \ + llvm-symbolizer \ + minijail0 \ + reproduce \ + run_fuzzer \ + run_minijail \ + sancov \ + targets_list \ + test_all \ + /usr/local/bin/ # Default environment options for various sanitizers. # Note that these match the settings used in ClusterFuzz and diff --git a/infra/base-images/base-runner/coverage b/infra/base-images/base-runner/coverage index ee076f909..467cb8b38 100755 --- a/infra/base-images/base-runner/coverage +++ b/infra/base-images/base-runner/coverage @@ -25,20 +25,24 @@ fi DUMPS_DIR="$OUT/dumps" FUZZER_STATS_DIR="$OUT/fuzzer_stats" LOGS_DIR="$OUT/logs" -REPORT_DIR="$OUT/report" +REPORT_ROOT_DIR="$OUT/report" +REPORT_PLATFORM_DIR="$OUT/report/linux" -for directory in $DUMPS_DIR $FUZZER_STATS_DIR $LOGS_DIR $REPORT_DIR; do +for directory in $DUMPS_DIR $FUZZER_STATS_DIR $LOGS_DIR $REPORT_ROOT_DIR \ + $REPORT_PLATFORM_DIR; do rm -rf $directory mkdir -p $directory done - PROFILE_FILE="$DUMPS_DIR/merged.profdata" -SUMMARY_FILE="$REPORT_DIR/summary.json" +SUMMARY_FILE="$REPORT_PLATFORM_DIR/summary.json" + +# Use path mapping, as $SRC directory from the builder is copied into $OUT/$SRC. +PATH_EQUIVALENCE_ARGS="-path-equivalence=/,$OUT" # It's important to use $COVERAGE_EXTRA_ARGS as the last argument, because it # can contain paths to source files / directories which are positional args. -LLVM_COV_COMMON_ARGS="-path-equivalence=/,$OUT \ +LLVM_COV_COMMON_ARGS="$PATH_EQUIVALENCE_ARGS \ -ignore-filename-regex=.*src/libfuzzer/.* $COVERAGE_EXTRA_ARGS" # Timeout for running a single fuzz target. @@ -74,7 +78,7 @@ function run_fuzz_target { # Delete unnecessary and (potentially) large .profraw files. rm $profraw_file_mask - shared_libraries=$(coverage_helper.py shared_libs -object=$target) + shared_libraries=$(coverage_helper shared_libs -build-dir=$OUT -object=$target) llvm-cov export -summary-only -instr-profile=$profdata_file -object=$target \ $shared_libraries $LLVM_COV_COMMON_ARGS > $FUZZER_STATS_DIR/$target.json } @@ -113,7 +117,7 @@ llvm-profdata merge -sparse $DUMPS_DIR/*.profdata -o $PROFILE_FILE # TODO(mmoroz): add script from Chromium for rendering directory view reports. # The first path in $objects does not have -object= prefix (llvm-cov format). -shared_libraries=$(coverage_helper.py shared_libs -object=$objects) +shared_libraries=$(coverage_helper shared_libs -build-dir=$OUT -object=$objects) objects="$objects $shared_libraries" # It's important to use $LLVM_COV_COMMON_ARGS as the last argument due to @@ -121,15 +125,19 @@ objects="$objects $shared_libraries" LLVM_COV_ARGS="-instr-profile=$PROFILE_FILE $objects $LLVM_COV_COMMON_ARGS" # Generate HTML report. -llvm-cov show -format=html -output-dir=$REPORT_DIR \ +llvm-cov show -format=html -output-dir=$REPORT_ROOT_DIR \ -Xdemangler c++filt -Xdemangler -n $LLVM_COV_ARGS # Export coverage summary in JSON format. llvm-cov export -summary-only $LLVM_COV_ARGS > $SUMMARY_FILE +# Post process HTML report. +coverage_helper -v post_process -src-root-dir=$SRC -summary-file=$SUMMARY_FILE \ + -output-dir=$REPORT_ROOT_DIR $PATH_EQUIVALENCE_ARGS + if [[ -n $HTTP_PORT ]]; then # Serve the report locally. - echo "Serving the report on http://127.0.0.1:$HTTP_PORT/" - cd $REPORT_DIR + echo "Serving the report on http://127.0.0.1:$HTTP_PORT/linux/index.html" + cd $REPORT_ROOT_DIR python3 -m http.server $HTTP_PORT fi diff --git a/infra/base-images/base-runner/coverage_helper b/infra/base-images/base-runner/coverage_helper new file mode 100755 index 000000000..22c9cb5d6 --- /dev/null +++ b/infra/base-images/base-runner/coverage_helper @@ -0,0 +1,17 @@ +#!/bin/bash -u +# Copyright 2018 Google Inc. +# +# 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. +# +################################################################################ +python3 /opt/code_coverage/coverage_utils.py $@ diff --git a/infra/base-images/base-runner/coverage_helper.py b/infra/base-images/base-runner/coverage_helper.py deleted file mode 100755 index b2fa56a07..000000000 --- a/infra/base-images/base-runner/coverage_helper.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/python3 -# Copyright 2018 Google Inc. -# -# 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. -# -################################################################################ - -import argparse -import os -import re -import subprocess -import sys - - -def get_shared_libraries(binary_paths): - """Returns list of shared libraries used by specified binaries.""" - shared_libraries = [] - cmd = ['ldd'] - shared_library_path_re = re.compile( - r'.*\.so[.0-9]*\s=>\s(.*' + os.getenv('OUT') + r'.*\.so[.0-9]*)\s.*') - - cmd.extend(binary_paths) - output = subprocess.check_output(cmd).decode("utf-8", "ignore") - - for line in output.splitlines(): - match = shared_library_path_re.match(line) - if not match: - continue - - shared_library_path = match.group(1) - if shared_library_path in shared_libraries: - continue - - assert os.path.exists(shared_library_path), ('Shared library "%s" used by ' - 'the given target(s) does not ' - 'exist.' % shared_library_path) - with open(shared_library_path, 'rb') as f: - data = f.read() - - # Do not add non-instrumented libraries. Otherwise, llvm-cov errors outs. - if b'__llvm_cov' in data: - shared_libraries.append(shared_library_path) - - return shared_libraries - - -def print_shared_libraries(args): - if not args.object: - print("ERROR: No binaries are specified.", file=sys.stderr) - return 1 - - paths = get_shared_libraries(args.object) - if not paths: - return 0 - - # Print output in the format that can be passed to llvm-cov tool. - output = ' '.join(['-object=%s' % path for path in paths]) - print(output) - return 0 - - -def main(): - parser = argparse.ArgumentParser('coverage_helper', - description='coverage script helper') - subparsers = parser.add_subparsers(dest='command') - - shared_libs_parser = subparsers.add_parser('shared_libs', - help='Detect shared libraries.') - shared_libs_parser.add_argument('-object', action='append', - help='Path to the binary using shared libs.') - - args = parser.parse_args() - if args.command == 'shared_libs': - return print_shared_libraries(args) - - -if __name__ == '__main__': - sys.exit(main())