diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 55c6940fb34c1c..ca2aa0d09aaf9c 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -286,6 +286,8 @@ CODEGENOPT(SanitizeCoverageNoPrune, 1, 0) ///< Disable coverage pruning. CODEGENOPT(SanitizeCoverageStackDepth, 1, 0) ///< Enable max stack depth tracing CODEGENOPT(SanitizeCoverageTraceLoads, 1, 0) ///< Enable tracing of loads. CODEGENOPT(SanitizeCoverageTraceStores, 1, 0) ///< Enable tracing of stores. +CODEGENOPT(SanitizeBinaryMetadataCovered, 1, 0) ///< Emit PCs for covered functions. +CODEGENOPT(SanitizeBinaryMetadataAtomics, 1, 0) ///< Emit PCs for atomic operations. CODEGENOPT(SanitizeStats , 1, 0) ///< Collect statistics for sanitizers. CODEGENOPT(SimplifyLibCalls , 1, 1) ///< Set when -fbuiltin is enabled. CODEGENOPT(SoftFloat , 1, 0) ///< -soft-float. diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 7fffb383388899..0504012badb04d 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -485,6 +485,11 @@ class CodeGenOptions : public CodeGenOptionsBase { SanitizeCoverageTraceCmp || SanitizeCoverageTraceLoads || SanitizeCoverageTraceStores; } + + // Check if any one of SanitizeBinaryMetadata* is enabled. + bool hasSanitizeBinaryMetadata() const { + return SanitizeBinaryMetadataCovered || SanitizeBinaryMetadataAtomics; + } }; } // end namespace clang diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index f9dfb8104395ee..cfc0994d69d4eb 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1673,6 +1673,12 @@ def fsanitize_coverage_ignorelist : Joined<["-"], "fsanitize-coverage-ignorelist HelpText<"Disable sanitizer coverage instrumentation for modules and functions " "that match the provided special case list, even the allowed ones">, MarshallingInfoStringVector>; +def fexperimental_sanitize_metadata_EQ : CommaJoined<["-"], "fexperimental-sanitize-metadata=">, + Group, + HelpText<"Specify the type of metadata to emit for binary analysis sanitizers">; +def fno_experimental_sanitize_metadata_EQ : CommaJoined<["-"], "fno-experimental-sanitize-metadata=">, + Group, Flags<[CoreOption]>, + HelpText<"Disable emitting metadata for binary analysis sanitizers">; def fsanitize_memory_track_origins_EQ : Joined<["-"], "fsanitize-memory-track-origins=">, Group, HelpText<"Enable origins tracking in MemorySanitizer">, @@ -5497,6 +5503,14 @@ def fsanitize_coverage_trace_stores : Flag<["-"], "fsanitize-coverage-trace-stores">, HelpText<"Enable tracing of stores">, MarshallingInfoFlag>; +def fexperimental_sanitize_metadata_EQ_covered + : Flag<["-"], "fexperimental-sanitize-metadata=covered">, + HelpText<"Emit PCs for code covered with binary analysis sanitizers">, + MarshallingInfoFlag>; +def fexperimental_sanitize_metadata_EQ_atomics + : Flag<["-"], "fexperimental-sanitize-metadata=atomics">, + HelpText<"Emit PCs for atomic operations used by binary analysis sanitizers">, + MarshallingInfoFlag>; def fpatchable_function_entry_offset_EQ : Joined<["-"], "fpatchable-function-entry-offset=">, MetaVarName<"">, HelpText<"Generate M NOPs before function entry">, diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 1b29b1151224fd..65677f79742bc3 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -31,6 +31,7 @@ class SanitizerArgs { std::vector CoverageAllowlistFiles; std::vector CoverageIgnorelistFiles; int CoverageFeatures = 0; + int BinaryMetadataFeatures = 0; int MsanTrackOrigins = 0; bool MsanUseAfterDtor = true; bool MsanParamRetval = false; diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 1bb734863c7df4..ccde687eba09c0 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -73,6 +73,7 @@ #include "llvm/Transforms/Instrumentation/MemProfiler.h" #include "llvm/Transforms/Instrumentation/MemorySanitizer.h" #include "llvm/Transforms/Instrumentation/SanitizerCoverage.h" +#include "llvm/Transforms/Instrumentation/SanitizerBinaryMetadata.h" #include "llvm/Transforms/Instrumentation/ThreadSanitizer.h" #include "llvm/Transforms/ObjCARC.h" #include "llvm/Transforms/Scalar.h" @@ -218,6 +219,14 @@ getSancovOptsFromCGOpts(const CodeGenOptions &CGOpts) { return Opts; } +static SanitizerBinaryMetadataOptions +getSanitizerBinaryMetadataOptions(const CodeGenOptions &CGOpts) { + SanitizerBinaryMetadataOptions Opts; + Opts.Covered = CGOpts.SanitizeBinaryMetadataCovered; + Opts.Atomics = CGOpts.SanitizeBinaryMetadataAtomics; + return Opts; +} + // Check if ASan should use GC-friendly instrumentation for globals. // First of all, there is no point if -fdata-sections is off (expect for MachO, // where this is not a factor). Also, on ELF this feature requires an assembler @@ -632,6 +641,11 @@ static void addSanitizers(const Triple &TargetTriple, CodeGenOpts.SanitizeCoverageIgnorelistFiles)); } + if (CodeGenOpts.hasSanitizeBinaryMetadata()) { + MPM.addPass(SanitizerBinaryMetadataPass( + getSanitizerBinaryMetadataOptions(CodeGenOpts))); + } + auto MSanPass = [&](SanitizerMask Mask, bool CompileKernel) { if (LangOpts.Sanitize.has(Mask)) { int TrackOrigins = CodeGenOpts.SanitizeMemoryTrackOrigins; diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index b6ebc8ad1842b2..6319e3c433ab1f 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -100,6 +100,11 @@ enum CoverageFeature { CoverageTraceStores = 1 << 17, }; +enum BinaryMetadataFeature { + BinaryMetadataCovered = 1 << 0, + BinaryMetadataAtomics = 1 << 1, +}; + /// Parse a -fsanitize= or -fno-sanitize= argument's values, diagnosing any /// invalid components. Returns a SanitizerMask. static SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A, @@ -110,6 +115,11 @@ static SanitizerMask parseArgValues(const Driver &D, const llvm::opt::Arg *A, static int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A, bool DiagnoseErrors); +/// Parse -f(no-)?sanitize-metadata= flag values, diagnosing any invalid +/// components. Returns OR of members of \c BinaryMetadataFeature enumeration. +static int parseBinaryMetadataFeatures(const Driver &D, const llvm::opt::Arg *A, + bool DiagnoseErrors); + /// Produce an argument string from ArgList \p Args, which shows how it /// provides some sanitizer kind from \p Mask. For example, the argument list /// "-fsanitize=thread,vptr -fsanitize=address" with mask \c NeedsUbsanRt @@ -834,6 +844,22 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, DiagnoseErrors); } + // Parse -f(no-)?sanitize-metadata. + for (const auto *Arg : + Args.filtered(options::OPT_fexperimental_sanitize_metadata_EQ, + options::OPT_fno_experimental_sanitize_metadata_EQ)) { + if (Arg->getOption().matches( + options::OPT_fexperimental_sanitize_metadata_EQ)) { + Arg->claim(); + BinaryMetadataFeatures |= + parseBinaryMetadataFeatures(D, Arg, DiagnoseErrors); + } else { + Arg->claim(); + BinaryMetadataFeatures &= + ~parseBinaryMetadataFeatures(D, Arg, DiagnoseErrors); + } + } + SharedRuntime = Args.hasFlag(options::OPT_shared_libsan, options::OPT_static_libsan, TC.getTriple().isAndroid() || TC.getTriple().isOSFuchsia() || @@ -1095,6 +1121,17 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, addSpecialCaseListOpt(Args, CmdArgs, "-fsanitize-coverage-ignorelist=", CoverageIgnorelistFiles); + // Translate available BinaryMetadataFeatures to corresponding clang-cc1 + // flags. Does not depend on any other sanitizers. + const std::pair BinaryMetadataFlags[] = { + std::make_pair(BinaryMetadataCovered, "covered"), + std::make_pair(BinaryMetadataAtomics, "atomics")}; + for (const auto &F : BinaryMetadataFlags) { + if (BinaryMetadataFeatures & F.first) + CmdArgs.push_back( + Args.MakeArgString("-fexperimental-sanitize-metadata=" + F.second)); + } + if (TC.getTriple().isOSWindows() && needsUbsanRt()) { // Instruct the code generator to embed linker directives in the object file // that cause the required runtime libraries to be linked. @@ -1341,6 +1378,28 @@ int parseCoverageFeatures(const Driver &D, const llvm::opt::Arg *A, return Features; } +int parseBinaryMetadataFeatures(const Driver &D, const llvm::opt::Arg *A, + bool DiagnoseErrors) { + assert( + A->getOption().matches(options::OPT_fexperimental_sanitize_metadata_EQ) || + A->getOption().matches( + options::OPT_fno_experimental_sanitize_metadata_EQ)); + int Features = 0; + for (int i = 0, n = A->getNumValues(); i != n; ++i) { + const char *Value = A->getValue(i); + int F = llvm::StringSwitch(Value) + .Case("covered", BinaryMetadataCovered) + .Case("atomics", BinaryMetadataAtomics) + .Case("all", ~0) + .Default(0); + if (F == 0 && DiagnoseErrors) + D.Diag(clang::diag::err_drv_unsupported_option_argument) + << A->getOption().getName() << Value; + Features |= F; + } + return Features; +} + std::string lastArgumentForMask(const Driver &D, const llvm::opt::ArgList &Args, SanitizerMask Mask) { for (llvm::opt::ArgList::const_reverse_iterator I = Args.rbegin(), diff --git a/clang/test/CodeGen/sanitize-metadata.c b/clang/test/CodeGen/sanitize-metadata.c new file mode 100644 index 00000000000000..58d47ff84f5170 --- /dev/null +++ b/clang/test/CodeGen/sanitize-metadata.c @@ -0,0 +1,31 @@ +// RUN: %clang_cc1 -O -fexperimental-sanitize-metadata=atomics -triple x86_64-gnu-linux -x c -S -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,ATOMICS +// RUN: %clang_cc1 -O -fexperimental-sanitize-metadata=atomics -triple aarch64-gnu-linux -x c -S -emit-llvm %s -o - | FileCheck %s --check-prefixes=CHECK,ATOMICS + +int x, y; + +void empty() { +// CHECK-NOT: define dso_local void @empty() {{.*}} !pcsections +} + +int atomics() { +// ATOMICS-LABEL: define dso_local i32 @atomics() +// ATOMICS-SAME: !pcsections ![[ATOMICS_COVERED:[0-9]+]] +// ATOMICS-NEXT: entry: +// ATOMICS-NEXT: atomicrmw add {{.*}} !pcsections ![[ATOMIC_OP:[0-9]+]] +// ATOMICS-NOT: load {{.*}} !pcsections + __atomic_fetch_add(&x, 1, __ATOMIC_RELAXED); + return y; +} +// ATOMICS-LABEL: __sanitizer_metadata_atomics.module_ctor +// ATOMICS: call void @__sanitizer_metadata_atomics_add(i32 1, ptr @__start_sanmd_atomics, ptr @__stop_sanmd_atomics) +// ATOMICS-LABEL: __sanitizer_metadata_atomics.module_dtor +// ATOMICS: call void @__sanitizer_metadata_atomics_del(i32 1, ptr @__start_sanmd_atomics, ptr @__stop_sanmd_atomics) + +// CHECK-LABEL: __sanitizer_metadata_covered.module_ctor +// CHECK: call void @__sanitizer_metadata_covered_add(i32 1, ptr @__start_sanmd_covered, ptr @__stop_sanmd_covered) +// CHECK-LABEL: __sanitizer_metadata_covered.module_dtor +// CHECK: call void @__sanitizer_metadata_covered_del(i32 1, ptr @__start_sanmd_covered, ptr @__stop_sanmd_covered) + +// ATOMICS: ![[ATOMICS_COVERED]] = !{!"sanmd_covered", ![[ATOMICS_COVERED_AUX:[0-9]+]]} +// ATOMICS: ![[ATOMICS_COVERED_AUX]] = !{i32 1} +// ATOMICS: ![[ATOMIC_OP]] = !{!"sanmd_atomics"} diff --git a/clang/test/Driver/fsanitize-metadata.c b/clang/test/Driver/fsanitize-metadata.c new file mode 100644 index 00000000000000..ce572fe73ea385 --- /dev/null +++ b/clang/test/Driver/fsanitize-metadata.c @@ -0,0 +1,23 @@ +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all -fno-experimental-sanitize-metadata=all %s -### 2>&1 | FileCheck %s +// CHECK-NOT: -fexperimental-sanitize-metadata + +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=bad_arg %s -### 2>&1 | FileCheck -check-prefix=CHECK-INVALID %s +// CHECK-INVALID: error: unsupported argument 'bad_arg' to option '-fexperimental-sanitize-metadata=' + +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered %s -### 2>&1 | FileCheck -check-prefix=CHECK-COVERED %s +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=atomics -fno-experimental-sanitize-metadata=atomics -fexperimental-sanitize-metadata=covered %s -### 2>&1 | FileCheck -check-prefix=CHECK-COVERED %s +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all -fno-experimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-COVERED %s +// CHECK-COVERED: "-fexperimental-sanitize-metadata=covered" +// CHECK-COVERED-NOT: "-fexperimental-sanitize-metadata=atomics" + +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ATOMICS %s +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered -fno-experimental-sanitize-metadata=covered -fexperimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ATOMICS %s +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all -fno-experimental-sanitize-metadata=covered %s -### 2>&1 | FileCheck -check-prefix=CHECK-ATOMICS %s +// CHECK-ATOMICS: "-fexperimental-sanitize-metadata=atomics" +// CHECK-ATOMICS-NOT: "-fexperimental-sanitize-metadata=covered" + +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered,atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ALL %s +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=covered -fexperimental-sanitize-metadata=atomics %s -### 2>&1 | FileCheck -check-prefix=CHECK-ALL %s +// RUN: %clang --target=x86_64-linux-gnu -fexperimental-sanitize-metadata=all %s -### 2>&1 | FileCheck -check-prefix=CHECK-ALL %s +// CHECK-ALL: "-fexperimental-sanitize-metadata=covered" +// CHECK-ALL: "-fexperimental-sanitize-metadata=atomics"