diff --git a/.github/workflows/valgrind.yml b/.github/workflows/valgrind.yml new file mode 100755 index 00000000..7e786b8c --- /dev/null +++ b/.github/workflows/valgrind.yml @@ -0,0 +1,119 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions + +name: valgrind incremental checks + +permissions: + # Grant read permissions to repository in case it is not a forked public + # repository, but a private repository that was created manually. + contents: read + + # Grant read permissions to private container images. + packages: read + +on: + pull_request: + paths: + - '**' + - '!**.md' + - '!**/.clang-format' + - '!**/COPYING' + - '!**/LICENSE' + - '!.github/**' + - '.github/workflows/valgrind.yml' + - '!.gitignore' + - '!cmake/manifests/**' + - 'cmake/manifests/linux/**' + - '!container/**' + - '!docs/**' + - '!scripts/**' + +jobs: + build: + runs-on: ubuntu-20.04 + + container: + image: ghcr.io/intel/fpga-runtime-for-opencl/ubuntu-20.04-dev:main + + env: + TMP_DIR: /home/build/ + + steps: + - name: change ownership of workspace to current user + run: sudo chown -R build:build . + + - name: install valgrind + run: sudo apt-get install -y valgrind gawk + + - name: checkout current branch + uses: actions/checkout@v2 + + - name: copy necessary suppression files + run: cp ./scripts/valgrind_suppression.supp "$TMP_DIR/valgrind_suppression_base.supp" + + - name: checkout main branch + uses: actions/checkout@v2 + with: + ref: ${{ github.base_ref }} + + - name: create build directories + run: mkdir "$TMP_DIR/child_build" "$TMP_DIR/parent_build" + + - name: create parent build files + run: cd "$TMP_DIR/parent_build" && cmake -G Ninja "$GITHUB_WORKSPACE" -DCMAKE_BUILD_TYPE=Debug + + - name: build parent runtime + run: cd "$TMP_DIR/parent_build" && ninja -v -k0 + + - name: parent runtime valgrind scan + run: cd "$TMP_DIR/parent_build" && ctest --overwrite MemoryCheckCommandOptions="--leak-check=full --show-reachable=yes --error-limit=no --gen-suppressions=all" -T memcheck + + - name: checkout current branch + uses: actions/checkout@v2 + + - name: get parent valgrind suppression file + run: | + cat "$TMP_DIR/parent_build/Testing/Temporary/MemoryChecker.6.log" | ./scripts/parse_valgrind_suppressions.sh > "$TMP_DIR/valgrind_suppression.supp" + cat "$TMP_DIR/valgrind_suppression_base.supp" >> "$TMP_DIR/valgrind_suppression.supp" + + - name: upload valgrind report as manifest + uses: actions/upload-artifact@v2 + with: + name: suppression-${{ github.run_id }} + path: /home/build/valgrind_suppression.supp + + - name: create child build files + run: cd "$TMP_DIR/child_build" && cmake -G Ninja "$GITHUB_WORKSPACE" -DCMAKE_BUILD_TYPE=Debug + + - name: build child runtime + run: cd "$TMP_DIR/child_build" && ninja -v -k0 + + - name: child runtime valgrind scan + run: cd "$TMP_DIR/child_build" && ctest --overwrite MemoryCheckCommandOptions="--leak-check=full --show-reachable=yes --error-limit=no" --overwrite MemoryCheckSuppressionFile="$TMP_DIR/valgrind_suppression.supp" -T memcheck + + - name: upload parent valgrind report as manifest + uses: actions/upload-artifact@v2 + with: + name: parent-valgrind-${{ github.run_id }} + path: /home/build/parent_build/Testing/Temporary/MemoryChecker.6.log + + - name: upload child valgrind report as manifest + uses: actions/upload-artifact@v2 + with: + name: valgrind-${{ github.run_id }} + path: /home/build/child_build/Testing/Temporary/MemoryChecker.6.log + + - name: valgrind incremental + run: | + if [ -s "$TMP_DIR/child_build/Testing/Temporary/MemoryChecker.6.log" ]; then + # The valgrind log after suppressing all parent log is not empty + cat "$TMP_DIR/child_build/Testing/Temporary/MemoryChecker.6.log" + exit 1 + fi + + + - name: revert ownership of workspace to root + run: sudo chown -R root:root . + if: always() \ No newline at end of file diff --git a/scripts/delta_valgrind.sh b/scripts/delta_valgrind.sh new file mode 100755 index 00000000..f13155a9 --- /dev/null +++ b/scripts/delta_valgrind.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause + +# Install Intel FPGA SDK for OpenCL Pro Edition +# https://fpgasoftware.intel.com/ + +set -eu -o pipefail + +parent_valgrind_report="$1" +child_valgrind_report="$2" + +# remove ==29048== patern from beginning +echo 'Removing line prefix' +sed -i -E "s/==.*== //" ${parent_valgrind_report} +sed -i -E "s/==.*== //" ${child_valgrind_report} +# remove memory address +echo 'Removing memory address' +sed -i -E "s/0x.*: //" ${parent_valgrind_report} +sed -i -E "s/0x.*: //" ${child_valgrind_report} +# remove file header +echo 'Removing file header' +sed -i -e '1,/Parent PID/ d' ${parent_valgrind_report} +sed -i -e '1,/Parent PID/ d' ${child_valgrind_report} +# remove source code line number +echo 'Removing source code line number' +sed -i -E "s/\(([^)]*)\)[^(]*$//" ${parent_valgrind_report} +sed -i -E "s/\(([^)]*)\)[^(]*$//" ${child_valgrind_report} +# remove file ending +echo 'Removing file ending' +sed -i -e '/LEAK SUMMARY/Q' ${parent_valgrind_report} +sed -i -e '/LEAK SUMMARY/Q' ${child_valgrind_report} +# check what is added to second file +echo 'Delta check' +comm -13 ${parent_valgrind_report} ${child_valgrind_report} diff --git a/scripts/parse_valgrind_suppressions.sh b/scripts/parse_valgrind_suppressions.sh new file mode 100755 index 00000000..6364ff36 --- /dev/null +++ b/scripts/parse_valgrind_suppressions.sh @@ -0,0 +1,57 @@ +#! /usr/bin/gawk -f +# A script to extract the actual suppression info from the output of (for example) valgrind --leak-check=full --show-reachable=yes --error-limit=no --gen-suppressions=all ./minimal +# The desired bits are between ^{ and ^} (including the braces themselves). +# The combined output should either be appended to /usr/lib/valgrind/default.supp, or placed in a .supp of its own +# If the latter, either tell valgrind about it each time with --suppressions=, or add that line to ~/.valgrindrc + +# NB This script uses the |& operator, which I believe is gawk-specific. In case of failure, check that you're using gawk rather than some other awk + +# The script looks for suppressions. When it finds one it stores it temporarily in an array, +# and also feeds it line by line to the external app 'md5sum' which generates a unique checksum for it. +# The checksum is used as an index in a different array. If an item with that index already exists the suppression must be a duplicate and is discarded. + +BEGIN { suppression=0; md5sum = "md5sum" } + # If the line begins with '{', it's the start of a supression; so set the var and initialise things + /^{/ { + suppression=1; i=0; next + } + # If the line begins with '}' its the end of a suppression + /^}/ { + if (suppression) + { suppression=0; + close(md5sum, "to") # We've finished sending data to md5sum, so close that part of the pipe + ProcessInput() # Do the slightly-complicated stuff in functions + delete supparray # We don't want subsequent suppressions to append to it! + } + } + # Otherwise, it's a normal line. If we're inside a supression, store it, and pipe it to md5sum. Otherwise it's cruft, so ignore it + { if (suppression) + { + supparray[++i] = $0 + print |& md5sum + } + } + + + function ProcessInput() + { + # Pipe the result from md5sum, then close it + md5sum |& getline result + close(md5sum) + # gawk can't cope with enormous ints like $result would be, so stringify it first by prefixing a definite string + resultstring = "prefix"result + + if (! (resultstring in chksum_array) ) + { chksum_array[resultstring] = 0; # This checksum hasn't been seen before, so add it to the array + OutputSuppression() # and output the contents of the suppression + } + } + + function OutputSuppression() + { + # A suppression is surrounded by '{' and '}'. Its data was stored line by line in the array + print "{" + for (n=1; n <= i; ++n) + { print supparray[n] } + print "}" + } \ No newline at end of file diff --git a/scripts/valgrind_suppression.supp b/scripts/valgrind_suppression.supp new file mode 100755 index 00000000..06d0d1f8 --- /dev/null +++ b/scripts/valgrind_suppression.supp @@ -0,0 +1,56 @@ +{ + getenv-Cond + Memcheck:Cond + ... + fun:getenv + ... +} +{ + getenv-Addr1 + Memcheck:Addr1 + ... + fun:getenv + ... +} +{ + getenv-Addr2 + Memcheck:Addr2 + ... + fun:getenv + ... +} +{ + getenv-Addr4 + Memcheck:Addr4 + ... + fun:getenv + ... +} +{ + getenv-Addr8 + Memcheck:Addr8 + ... + fun:getenv + ... +} +{ + getenv-Addr16 + Memcheck:Addr16 + ... + fun:getenv + ... +} +{ + setenv + Memcheck:Cond + ... + fun:setenv + ... +} +{ + unsetenv + Memcheck:Cond + ... + fun:unsetenv + ... +}