Skip to content

Commit

Permalink
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.
  • Loading branch information
LebedevRI committed Dec 25, 2021
1 parent 1d1f06a commit c907e23
Show file tree
Hide file tree
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
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("SANCOV", Target::Feature::SANCOV)
.value("FeatureEnd", Target::Feature::FeatureEnd);

py::enum_<halide_type_code_t>(m, "TypeCode")
Expand Down
15 changes: 15 additions & 0 deletions src/CodeGen_LLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>());
Expand Down
1 change: 1 addition & 0 deletions src/LLVM_Headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
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::SANCOV,
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},
{"sancov", Target::SANCOV},
// 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::SANCOV);
#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,
SANCOV,
}};
// 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, SANCOV\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,
SANCOV = halide_target_feature_sancov,
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_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;

Expand Down
4 changes: 4 additions & 0 deletions test/generator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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.