Support for building with sanitizers (#149)

* Add support for building with sanitizers

* CI Enable building with ASAN+UBSAN sanitizers in Debug builds

* Add directions to README

* Better CI reporting and matrix for sanitizer runs
This commit is contained in:
Eric Kilmer 2021-03-15 21:50:35 -04:00 committed by GitHub
parent 401743fd4b
commit 4286f109b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 195 additions and 0 deletions

View File

@ -44,6 +44,10 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: 'true'
- name: Enable ASan+UBSan Sanitizers
if: matrix.build-type == 'Debug'
run: |
echo "SANITIZER_FLAG=-DPEPARSE_USE_SANITIZER=Address,Undefined" >> $GITHUB_ENV
- name: build
env:
CC: ${{ matrix.compiler.CC }}
@ -55,6 +59,7 @@ jobs:
-DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \
-DBUILD_SHARED_LIBS=${{ matrix.build-shared }} \
-DPEPARSE_ENABLE_TESTING=ON \
${SANITIZER_FLAG} \
..
cmake --build .
- name: test

View File

@ -94,6 +94,20 @@ You can build the (catch2-based) tests by adding `-DPEPARSE_ENABLE_TESTING=ON` d
To run the full test suite with the [Corkami test suite](https://github.com/corkami/pocs/tree/master/PE), you must clone the submodule with `git submodule update --init`.
## Building with Sanitizers
If you are familiar with C++ sanitizers and any specific development environment requirements for them (compiler, instrumented standard library, etc.), you can choose to compile with any of the following sanitizers: `Address`, `HWAddress`, `Undefined`, `Memory`, `MemoryWithOrigins`, `Leak`, `Address,Undefined`.
For example, to compile with both `Address` and `Undefined` sanitizers, use the following (recommended for development and testing, and tested in CI):
```bash
mkdir build-san
cd build-san
cmake -DCMAKE_BUILD_TYPE=Debug -DPEPARSE_ENABLE_TESTING=ON -DPEPARSE_USE_SANITIZER=Address,Undefined ..
cmake --build .
```
## Using the library
Once the library is installed, linking to it is easy! Add the following lines in your CMake project:

View File

@ -2,6 +2,9 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
include("cmake/sanitizers.cmake")
process_sanitizer(PEPARSE)
if (MSVC)
list(APPEND DEFAULT_CXX_FLAGS /W4 /analyze)

173
cmake/sanitizers.cmake Normal file
View File

@ -0,0 +1,173 @@
# Enable C/C++ sanitizers in your CMake project by `include`ing this file
# somewhere in your CMakeLists.txt.
#
# Example:
#
# include("cmake/sanitizers.cmake")
# process_sanitizer(MY_PROJECT)
#
# Example CMake configuration usage for `MY_PROJECT`:
#
# cmake -Bbuild-asan-ubsan -H. -DMY_PROJECT_USE_SANITIZER=Address,Undefined
#
# where "MY_PROJECT" can by any arbitrary text that will be prepended to some
# expected CMake variables--see below.
#
# This file expects the following variables to be set during CMake
# configuration, where the prefix is set by the caller of `process_sanitizer`:
#
# - *_USE_SANITIZER
# - A string value that is one of the following:
# - Address
# - HWAddress
# - Memory
# - MemoryWithOrigins
# - Undefined
# - Thread
# - DataFlow
# - Leak
# - Address,Undefined
#
# - *_OPTIMIZE_SANITIZED_BUILDS
# - A boolean value to set whether a higher optimization is used in debug
# builds
#
# - *_BLACKLIST_FILE
# - A filepath to a sanitizer blacklist file.
function(append value)
foreach(variable ${ARGN})
set(${variable}
"${${variable}} ${value}"
PARENT_SCOPE)
endforeach(variable)
endfunction()
macro(append_common_sanitizer_flags prefix)
if (NOT MSVC)
# Append -fno-omit-frame-pointer and turn on debug info to get better
# stack traces.
append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
append("-fno-optimize-sibling-calls" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
append("-gline-tables-only" CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZE_REL)
# Use -O1 even in debug mode, otherwise sanitizers slowdown is too large.
if (${prefix}_OPTIMIZE_SANITIZED_BUILDS)
message(STATUS "Optimizing sanitized Debug build")
append("-O1" CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
endif()
else()
# Keep frame pointers around.
append("/Oy-" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Always ask the linker to produce symbols with asan.
append("/Zi" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Workaround for incompatible (warning-producing) default CMake flag
# https://docs.microsoft.com/en-us/cpp/sanitizers/asan-known-issues
# https://gitlab.kitware.com/cmake/cmake/-/issues/19084
string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
append("/debug" CMAKE_EXE_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS)
endif()
endmacro()
# Main logic
macro(process_sanitizer prefix)
# Add options for the project to use sanitizers
option(${prefix}_USE_SANITIZER "Enable building with sanitizer support. Options are: Address, HWAddress, Memory, MemoryWithOrigins, Undefined, Thread, DataFlow, Leak, 'Address,Undefined'" false)
if (UNIX)
option(${prefix}_OPTIMIZE_SANITIZED_BUILDS "Optimize builds that use sanitization" false)
option(${prefix}_BLACKLIST_FILE "Path to blacklist file for sanitizers" "")
option(${prefix}_USE_SANITIZE_COVERAGE "Set for libFuzzer-required instrumentation, no linking." false)
endif()
if (${prefix}_USE_SANITIZER)
if(UNIX)
if(${prefix}_USE_SANITIZER STREQUAL "Address")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "HWAddress")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=hwaddress" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER MATCHES "Memory(WithOrigins)?")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=memory" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
if(${prefix}_USE_SANITIZER STREQUAL "MemoryWithOrigins")
message(STATUS "Building with MemoryWithOrigins sanitizer")
append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(STATUS "Building with Memory sanitizer")
endif()
elseif(${prefix}_USE_SANITIZER STREQUAL "Undefined")
message(STATUS "Building with Undefined sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Execution error on undefined detection. Could be optional to add this
append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "Thread")
message(STATUS "Building with Thread sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "DataFlow")
message(STATUS "Building with DataFlow sanitizer")
append("-fsanitize=dataflow" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "Leak")
message(STATUS "Building with Leak sanitizer")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
elseif(${prefix}_USE_SANITIZER STREQUAL "Address,Undefined"
OR ${prefix}_USE_SANITIZER STREQUAL "Undefined,Address")
message(STATUS "Building with Address, Undefined sanitizers")
append_common_sanitizer_flags(${prefix})
append("-fsanitize=address,undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
# Execution error on undefined detection. Could be optional to add this
append("-fno-sanitize-recover=all" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(
FATAL_ERROR "Unsupported value of ${prefix}_USE_SANITIZER: '${${prefix}_USE_SANITIZER}'")
endif()
elseif(MSVC)
if(${prefix}_USE_SANITIZER STREQUAL "Address")
message(STATUS "Building with Address sanitizer")
append_common_sanitizer_flags(${prefix})
append("/fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
else()
message(FATAL_ERROR "This sanitizer is not yet supported in the MSVC environment: '${${prefix}_USE_SANITIZER}'")
endif()
else()
message(FATAL_ERROR "${prefix}_USE_SANITIZER is not supported on this platform.")
endif()
# If specified, use a blacklist file
if (EXISTS "${${prefix}_BLACKLIST_FILE}")
message(STATUS "Using sanitizer blacklist file: ${${prefix}_BLACKLIST_FILE}")
append("-fsanitize-blacklist=${${prefix}_BLACKLIST_FILE}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
# Set a once non-default option for more detection
if (${prefix}_USE_SANITIZER MATCHES "(Undefined,)?Address(,Undefined)?")
if (UNIX)
append("-fsanitize-address-use-after-scope" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
endif()
# Set for libFuzzer-required instrumentation, no linking.
if (${prefix}_USE_SANITIZE_COVERAGE)
message(STATUS "Setting up sanitizer for coverage support with 'fuzzer-no-link'")
append("-fsanitize=fuzzer-no-link" CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
endif()
endif()
endmacro()