Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement SanitizerCoverage support (Refs. halide#6513)
Browse files Browse the repository at this point in the history
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.
LebedevRI committed Dec 25, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 1d1f06a commit c907e23
Showing 10 changed files with 211 additions and 3 deletions.
1 change: 1 addition & 0 deletions python_bindings/src/PyEnums.cpp
Original file line number Diff line number Diff line change
@@ -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("SANCOV", Target::Feature::SANCOV)
.value("FeatureEnd", Target::Feature::FeatureEnd);

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

OptimizationLevel level = OptimizationLevel::O3;

if (get_target().has_feature(Target::SANCOV)) {
pb.registerOptimizerLastEPCallback(
[](ModulePassManager &mpm, OptimizationLevel level) {
SanitizerCoverageOptions sancov_options;
// Mirror what -fsanitize=fuzzer-no-link would enable.
sancov_options.CoverageType = SanitizerCoverageOptions::SCK_Edge;
sancov_options.IndirectCalls = true;
sancov_options.TraceCmp = true;
sancov_options.Inline8bitCounters = true;
sancov_options.PCTable = true;
sancov_options.StackDepth = true;
mpm.addPass(ModuleSanitizerCoveragePass(sancov_options));
});
}

if (get_target().has_feature(Target::ASAN)) {
pb.registerPipelineStartEPCallback([&](ModulePassManager &mpm, OptimizationLevel) {
mpm.addPass(RequireAnalysisPass<ASanGlobalsMetadataAnalysis, llvm::Module>());
1 change: 1 addition & 0 deletions src/LLVM_Headers.h
Original file line number Diff line number Diff line change
@@ -91,6 +91,7 @@
#include <llvm/Transforms/Instrumentation.h>
#include <llvm/Transforms/Instrumentation/AddressSanitizer.h>
#include <llvm/Transforms/Instrumentation/ThreadSanitizer.h>
#include <llvm/Transforms/Instrumentation/SanitizerCoverage.h>
#include <llvm/Transforms/Scalar/GVN.h>
#include <llvm/Transforms/Utils/ModuleUtils.h>
#include <llvm/Transforms/Utils/SymbolRewriter.h>
3 changes: 2 additions & 1 deletion src/Module.cpp
Original file line number Diff line number Diff line change
@@ -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,
@@ -846,6 +846,7 @@ void compile_multitarget(const std::string &fn_name,
Target::MSAN,
Target::NoRuntime,
Target::TSAN,
Target::SANCOV,
Target::UserContext,
}};
for (auto f : must_match_features) {
9 changes: 7 additions & 2 deletions src/Target.cpp
Original file line number Diff line number Diff line change
@@ -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},
{"sancov", Target::SANCOV},
// NOTE: When adding features to this map, be sure to update PyEnums.cpp as well.
};

@@ -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::SANCOV);
#endif
#endif
string target = Internal::get_env_variable("HL_JIT_TARGET");
if (target.empty()) {
@@ -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,
@@ -1004,6 +1008,7 @@ bool Target::get_runtime_compatible_target(const Target &other, Target &result)
SoftFloatABI,
TSAN,
WasmThreads,
SANCOV,
}};
// clang-format on

@@ -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, SANCOV\n"
<< " this: " << *this << "\n"
<< " other: " << other << "\n";
return false;
1 change: 1 addition & 0 deletions src/Target.h
Original file line number Diff line number Diff line change
@@ -131,6 +131,7 @@ struct Target {
LLVMLargeCodeModel = halide_llvm_large_code_model,
RVV = halide_target_feature_rvv,
ARMv81a = halide_target_feature_armv81a,
SANCOV = halide_target_feature_sancov,
FeatureEnd = halide_target_feature_end
};
Target() = default;
1 change: 1 addition & 0 deletions src/runtime/HalideRuntime.h
Original file line number Diff line number Diff line change
@@ -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_sancov, ///< 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;

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

# sancov_aottest.cpp
# sancov_generator.cpp
halide_define_aot_test(sancov FEATURES sancov)

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

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

using namespace std;
using namespace Halide::Runtime;

bool seen_8bit_counters_init = false;
bool seen_pcs_init = false;
bool seen_trace_cmp1 = false;
bool seen_trace_cmp4 = false;
bool seen_trace_cmp8 = false;
bool seen_trace_const_cmp1 = false;
bool seen_trace_const_cmp2 = false;
bool seen_trace_const_cmp4 = false;
bool seen_trace_const_cmp8 = false;
bool seen_trace_pc_indir = false;
bool seen_trace_switch = false;

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

extern "C" void __sanitizer_cov_8bit_counters_init(uint8_t *Start, uint8_t *Stop) {
seen_8bit_counters_init = true;
}

extern "C" void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg,
const uintptr_t *pcs_end) {
seen_pcs_init = true;
}

extern "C" void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) {
seen_trace_cmp1 = true;
}

extern "C" void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) {
seen_trace_cmp4 = true;
}

extern "C" void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) {
seen_trace_cmp8 = true;
}

extern "C" void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2) {
seen_trace_const_cmp1 = true;
}

extern "C" void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2) {
seen_trace_const_cmp2 = true;
}

extern "C" void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2) {
seen_trace_const_cmp4 = true;
}

extern "C" void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2) {
seen_trace_const_cmp8 = true;
}

extern "C" void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases) {
seen_trace_switch = true;
}

extern "C" void __sanitizer_cov_trace_pc_indir(uintptr_t Callee) {
seen_trace_pc_indir = true;
}

template<typename T>
void clear_out(T &image) {
image.for_each_element([&](int x, int y, int c) {
image(x, y, c) = -42;
});
}

template<typename T>
void verify_out(const 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);
}
});
}

void verify_coverage(bool should_have_seen) {
if (seen_8bit_counters_init != true) {
fprintf(stderr, "Failure @ seen_8bit_counters_init");
exit(-1);
}
if (seen_pcs_init != true) {
fprintf(stderr, "Failure @ seen_pcs_init");
exit(-1);
}
if (seen_trace_cmp1 != false) {
fprintf(stderr, "Failure @ seen_trace_cmp1");
exit(-1);
}
if (seen_trace_cmp4 != false) {
fprintf(stderr, "Failure @ seen_trace_cmp4");
exit(-1);
}
if (seen_trace_cmp8 != false) {
fprintf(stderr, "Failure @ seen_trace_cmp8");
exit(-1);
}
if (seen_trace_const_cmp1 != false) {
fprintf(stderr, "Failure @ seen_trace_const_cmp1");
exit(-1);
}
if (seen_trace_const_cmp2 != false) {
fprintf(stderr, "Failure @ seen_trace_const_cmp2");
exit(-1);
}
if (seen_trace_const_cmp4 != should_have_seen) {
fprintf(stderr, "Failure @ seen_trace_const_cmp4");
exit(-1);
}
if (seen_trace_const_cmp8 != should_have_seen) {
fprintf(stderr, "Failure @ seen_trace_const_cmp8");
exit(-1);
}
if (seen_trace_pc_indir != false) {
fprintf(stderr, "Failure @ seen_trace_pc_indir");
exit(-1);
}
if (seen_trace_switch != should_have_seen) {
fprintf(stderr, "Failure @ seen_trace_switch");
exit(-1);
}
}

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

int main() {
auto out = Buffer<uint8_t>(4, 4, 3);
clear_out(out);
verify_coverage(/*should_have_seen=*/false);
if (sancov(out) != 0) {
fprintf(stderr, "Failure!\n");
exit(-1);
}
verify_coverage(/*should_have_seen=*/true);
verify_out(out);

printf("Success!\n");
return 0;
}
25 changes: 25 additions & 0 deletions test/generator/sancov_generator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "Halide.h"

namespace {

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

void generate() {
// Currently the test just exercises Target::SANCOV
output(x, y, c) = cast<uint8_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::SANCOV
Var x, y, c;
};

} // namespace

HALIDE_REGISTER_GENERATOR(SANCOV, sancov)

0 comments on commit c907e23

Please sign in to comment.