Skip to content

Commit

Permalink
Implement SanitizerCoverage support (Refs. #6513) (#6517)
Browse files Browse the repository at this point in the history
* Implement SanitizerCoverage support (Refs. #6513)

Please refer to https://clang.llvm.org/docs/SanitizerCoverage.html

TLDR: `ModuleSanitizerCoveragePass` instruments the IR by inserting
calls to callbacks at certain constructs. What the callbacks should do
is up to the implementation. They are effectively required for fuzzing
to be effective, and are provided by e.g. libfuzzer.

One huge caveat is `SanitizerCoverageOptions` which controls
which which callbacks should actually be inserted.
I just don't know what to do about it. Right now i have hardcoded
the set that would have been enabled by `-fsanitize=fuzzer-no-link`,
because the alternative, due to halide unflexibility,
would be to introduce ~16 suboptions to control each one.

* Simplify test

* sancov test: avoid potential signedness warnings.

* Rename all instances of sancov to sanitizecoverage

* Adjust spelling of "SanitizerCoverage" in some places

* Actually adjust the feature name in build system for the test

* Hopefully fix Makefile build

Co-authored-by: Steven Johnson <srj@google.com>
  • Loading branch information
LebedevRI and steven-johnson authored Jan 4, 2022
1 parent 7eb9949 commit f11d820
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 3 deletions.
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/cxx_mangling_define_
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/define_extern_opencl.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/matlab.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/msan.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/sanitizercoverage.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/multitarget.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/nested_externs.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
GENERATOR_BUILD_RUNGEN_TESTS := $(filter-out $(FILTERS_DIR)/tiled_blur.rungen,$(GENERATOR_BUILD_RUNGEN_TESTS))
Expand Down Expand Up @@ -1475,6 +1476,10 @@ $(FILTERS_DIR)/msan.a: $(BIN_DIR)/msan.generator
@mkdir -p $(@D)
$(CURDIR)/$< -g msan -f msan $(GEN_AOT_OUTPUTS) -o $(CURDIR)/$(FILTERS_DIR) target=$(TARGET)-msan

$(FILTERS_DIR)/sanitizercoverage.a: $(BIN_DIR)/sanitizercoverage.generator
@mkdir -p $(@D)
$(CURDIR)/$< -g sanitizercoverage -f sanitizercoverage $(GEN_AOT_OUTPUTS) -o $(CURDIR)/$(FILTERS_DIR) target=$(TARGET)-sanitizer_coverage

# user_context needs to be generated with user_context as the first argument to its calls
$(FILTERS_DIR)/user_context.a: $(BIN_DIR)/user_context.generator
@mkdir -p $(@D)
Expand Down Expand Up @@ -1586,6 +1591,11 @@ $(BIN_DIR)/$(TARGET)/generator_aot_msan: $(ROOT_DIR)/test/generator/msan_aottest
@mkdir -p $(@D)
$(CXX) $(GEN_AOT_CXX_FLAGS) $(filter-out %.h,$^) $(GEN_AOT_INCLUDES) $(GEN_AOT_LD_FLAGS) -o $@

# SanitizerCoverage test doesn't use the standard runtime
$(BIN_DIR)/$(TARGET)/generator_aot_sanitizercoverage: $(ROOT_DIR)/test/generator/sanitizercoverage_aottest.cpp $(FILTERS_DIR)/sanitizercoverage.a $(FILTERS_DIR)/sanitizercoverage.h $(RUNTIME_EXPORTED_INCLUDES)
@mkdir -p $(@D)
$(CXX) $(GEN_AOT_CXX_FLAGS) $(filter-out %.h,$^) $(GEN_AOT_INCLUDES) $(GEN_AOT_LD_FLAGS) -o $@

# alias has additional deps to link in
$(BIN_DIR)/$(TARGET)/generator_aot_alias: $(ROOT_DIR)/test/generator/alias_aottest.cpp $(FILTERS_DIR)/alias.a $(FILTERS_DIR)/alias_with_offset_42.a $(RUNTIME_EXPORTED_INCLUDES) $(BIN_DIR)/$(TARGET)/runtime.a
@mkdir -p $(@D)
Expand Down
1 change: 1 addition & 0 deletions python_bindings/src/PyEnums.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ void define_enums(py::module &m) {
.value("LLVMLargeCodeModel", Target::Feature::LLVMLargeCodeModel)
.value("RVV", Target::Feature::RVV)
.value("ARMv81a", Target::Feature::ARMv81a)
.value("SanitizerCoverage", Target::Feature::SanitizerCoverage)
.value("FeatureEnd", Target::Feature::FeatureEnd);

py::enum_<halide_type_code_t>(m, "TypeCode")
Expand Down
19 changes: 19 additions & 0 deletions src/CodeGen_LLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,25 @@ void CodeGen_LLVM::optimize_module() {

OptimizationLevel level = OptimizationLevel::O3;

if (get_target().has_feature(Target::SanitizerCoverage)) {
pb.registerOptimizerLastEPCallback(
[&](ModulePassManager &mpm, OptimizationLevel level) {
SanitizerCoverageOptions sanitizercoverage_options;
// Mirror what -fsanitize=fuzzer-no-link would enable.
// See https://github.com/halide/Halide/issues/6528
sanitizercoverage_options.CoverageType = SanitizerCoverageOptions::SCK_Edge;
sanitizercoverage_options.IndirectCalls = true;
sanitizercoverage_options.TraceCmp = true;
sanitizercoverage_options.Inline8bitCounters = true;
sanitizercoverage_options.PCTable = true;
// Due to TLS differences, stack depth tracking is only enabled on Linux
if (get_target().os == Target::OS::Linux) {
sanitizercoverage_options.StackDepth = true;
}
mpm.addPass(ModuleSanitizerCoveragePass(sanitizercoverage_options));
});
}

if (get_target().has_feature(Target::ASAN)) {
pb.registerPipelineStartEPCallback([&](ModulePassManager &mpm, OptimizationLevel) {
mpm.addPass(RequireAnalysisPass<ASanGlobalsMetadataAnalysis, llvm::Module>());
Expand Down
1 change: 1 addition & 0 deletions src/LLVM_Headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
#include <llvm/Transforms/Instrumentation.h>
#include <llvm/Transforms/Instrumentation/AddressSanitizer.h>
#include <llvm/Transforms/Instrumentation/SanitizerCoverage.h>
#include <llvm/Transforms/Instrumentation/ThreadSanitizer.h>
#include <llvm/Transforms/Scalar/GVN.h>
#include <llvm/Transforms/Utils/ModuleUtils.h>
Expand Down
3 changes: 2 additions & 1 deletion src/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ void compile_multitarget(const std::string &fn_name,
user_error << "All Targets must have matching arch-bits-os for compile_multitarget.\n";
}
// Some features must match across all targets.
static const std::array<Target::Feature, 9> must_match_features = {{
static const std::array<Target::Feature, 10> must_match_features = {{
Target::ASAN,
Target::CPlusPlusMangling,
Target::Debug,
Expand All @@ -846,6 +846,7 @@ void compile_multitarget(const std::string &fn_name,
Target::MSAN,
Target::NoRuntime,
Target::TSAN,
Target::SanitizerCoverage,
Target::UserContext,
}};
for (auto f : must_match_features) {
Expand Down
9 changes: 7 additions & 2 deletions src/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ const std::map<std::string, Target::Feature> feature_name_map = {
{"llvm_large_code_model", Target::LLVMLargeCodeModel},
{"rvv", Target::RVV},
{"armv81a", Target::ARMv81a},
{"sanitizer_coverage", Target::SanitizerCoverage},
// NOTE: When adding features to this map, be sure to update PyEnums.cpp as well.
};

Expand Down Expand Up @@ -420,6 +421,9 @@ Target get_jit_target_from_environment() {
#if __has_feature(thread_sanitizer)
host.set_feature(Target::TSAN);
#endif
#if __has_feature(coverage_sanitizer)
host.set_feature(Target::SanitizerCoverage);
#endif
#endif
string target = Internal::get_env_variable("HL_JIT_TARGET");
if (target.empty()) {
Expand Down Expand Up @@ -994,7 +998,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result)
// clang-format on

// clang-format off
const std::array<Feature, 12> matching_features = {{
const std::array<Feature, 10> matching_features = {{
ASAN,
Debug,
HexagonDma,
Expand All @@ -1004,6 +1008,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result)
SoftFloatABI,
TSAN,
WasmThreads,
SanitizerCoverage,
}};
// clang-format on

Expand Down Expand Up @@ -1032,7 +1037,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result)
}

if ((features & matching_mask) != (other.features & matching_mask)) {
Internal::debug(1) << "runtime targets must agree on SoftFloatABI, Debug, TSAN, ASAN, MSAN, HVX, HexagonDma, and HVX_shared_object\n"
Internal::debug(1) << "runtime targets must agree on SoftFloatABI, Debug, TSAN, ASAN, MSAN, HVX, HexagonDma, HVX_shared_object, SanitizerCoverage\n"
<< " this: " << *this << "\n"
<< " other: " << other << "\n";
return false;
Expand Down
1 change: 1 addition & 0 deletions src/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ struct Target {
LLVMLargeCodeModel = halide_llvm_large_code_model,
RVV = halide_target_feature_rvv,
ARMv81a = halide_target_feature_armv81a,
SanitizerCoverage = halide_target_feature_sanitizer_coverage,
FeatureEnd = halide_target_feature_end
};
Target() = default;
Expand Down
1 change: 1 addition & 0 deletions src/runtime/HalideRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,7 @@ typedef enum halide_target_feature_t {
halide_llvm_large_code_model, ///< Use the LLVM large code model to compile
halide_target_feature_rvv, ///< Enable RISCV "V" Vector Extension
halide_target_feature_armv81a, ///< Enable ARMv8.1-a instructions
halide_target_feature_sanitizer_coverage, ///< Enable hooks for SanitizerCoverage support.
halide_target_feature_end ///< A sentinel. Every target is considered to have this feature, and setting this feature does nothing.
} halide_target_feature_t;

Expand Down
7 changes: 7 additions & 0 deletions test/generator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ add_halide_library(metadata_tester_ucon
# msan_generator.cpp
halide_define_aot_test(msan FEATURES msan)

# (Doesn't build/link properly on windows / under wasm)
if (NOT Halide_TARGET MATCHES "windows" AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows" AND NOT Halide_TARGET MATCHES "wasm")
# sanitizercoverage_aottest.cpp
# sanitizercoverage_generator.cpp
halide_define_aot_test(sanitizercoverage FEATURES sanitizer_coverage)
endif()

# multitarget_aottest.cpp
# multitarget_generator.cpp
halide_define_aot_test(multitarget
Expand Down
115 changes: 115 additions & 0 deletions test/generator/sanitizercoverage_aottest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "HalideBuffer.h"
#include "HalideRuntime.h"
#include "sanitizercoverage.h"

#include <iostream>
#include <limits>
#include <type_traits>
#include <vector>

using namespace std;
using namespace Halide::Runtime;

bool enable_callbacks = false;

#if defined(__linux__)
// Used by -fsanitize-coverage=stack-depth to track stack depth
__attribute__((tls_model("initial-exec"))) thread_local uintptr_t __sancov_lowest_stack;
#endif

extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *Start, uint8_t *Stop) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_8bit_counters_init. Success!\n");
}

extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
const uintptr_t *pcs_end) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_pcs_init. Success!\n");
}

extern "C" void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_cmp1. Success!\n");
}

extern "C" void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_cmp4. Success!\n");
}

extern "C" void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_cmp8. Success!\n");
}

extern "C" void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_const_cmp1. Success!\n");
}

extern "C" void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_const_cmp2. Success!\n");
}

extern "C" void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_const_cmp4. Success!\n");
}

extern "C" void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_const_cmp8. Success!\n");
}

extern "C" void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_switch. Success!\n");
}

extern "C" void __sanitizer_cov_trace_pc_indir(uintptr_t Callee) {
if (!enable_callbacks) return;
printf("Hit __sanitizer_cov_trace_pc_indir. Success!\n");
}

template<typename T>
void clear_out(T &image) {
image.fill(-42);
}

void verify_out(const Buffer<int8_t> &image) {
image.for_each_element([&](int x, int y, int c) {
int expected = 42 + c;
int actual = image(x, y, c);
if (actual != expected) {
fprintf(stderr, "Failure @ %d %d %d: expected %d, got %d\n", x, y, c, expected, actual);
exit(-1);
}
});
}

//-----------------------------------------------------------------------------

auto sanitizercoverage_wrapper(struct halide_buffer_t *out) {
enable_callbacks = true;
auto status = sanitizercoverage(out);
enable_callbacks = false;
return status;
}

int main() {
fprintf(stderr, "Entering main().\n");
auto out = Buffer<int8_t>(4, 4, 3);
fprintf(stderr, "Clearing output buffer.\n");
clear_out(out);
fprintf(stderr, "Performing the transformation.\n");
if (sanitizercoverage_wrapper(out) != 0) {
fprintf(stderr, "Failure!\n");
exit(-1);
}
fprintf(stderr, "Verifying the transformation.\n");
verify_out(out);
// We rely on the callbacks being called and printing Success.
return 0;
}
25 changes: 25 additions & 0 deletions test/generator/sanitizercoverage_generator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "Halide.h"

namespace {

class SanitizerCoverage : public Halide::Generator<SanitizerCoverage> {
public:
Output<Buffer<int8_t>> output{"output", 3};

void generate() {
// Currently the test just exercises Target::SanitizerCoverage
output(x, y, c) = cast<int8_t>(42 + c);
}

void schedule() {
output.dim(0).set_stride(Expr()).set_extent(4).dim(1).set_extent(4).dim(2).set_extent(3);
}

private:
// Currently the test just exercises Target::SanitizerCoverage
Var x, y, c;
};

} // namespace

HALIDE_REGISTER_GENERATOR(SanitizerCoverage, sanitizercoverage)

0 comments on commit f11d820

Please sign in to comment.