102 changes: 84 additions & 18 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "llvm/ProfileData/InstrProfReader.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MD5.h"

using namespace clang;
using namespace CodeGen;
Expand Down Expand Up @@ -439,6 +440,11 @@ void CodeGenModule::Release() {
getModule().addModuleFlag(llvm::Module::Error, "min_enum_size", EnumWidth);
}

if (CodeGenOpts.SanitizeCfiCrossDso) {
// Indicate that we want cross-DSO control flow integrity checks.
getModule().addModuleFlag(llvm::Module::Override, "Cross-DSO CFI", 1);
}

if (uint32_t PLevel = Context.getLangOpts().PICLevel) {
llvm::PICLevel::Level PL = llvm::PICLevel::Default;
switch (PLevel) {
Expand Down Expand Up @@ -736,6 +742,21 @@ void CodeGenModule::setFunctionDLLStorageClass(GlobalDecl GD, llvm::Function *F)
F->setDLLStorageClass(llvm::GlobalVariable::DefaultStorageClass);
}

llvm::ConstantInt *
CodeGenModule::CreateCfiIdForTypeMetadata(llvm::Metadata *MD) {
llvm::MDString *MDS = dyn_cast<llvm::MDString>(MD);
if (!MDS) return nullptr;

llvm::MD5 md5;
llvm::MD5::MD5Result result;
md5.update(MDS->getString());
md5.final(result);
uint64_t id = 0;
for (int i = 0; i < 8; ++i)
id |= static_cast<uint64_t>(result[i]) << (i * 8);
return llvm::ConstantInt::get(Int64Ty, id);
}

void CodeGenModule::setFunctionDefinitionAttributes(const FunctionDecl *D,
llvm::Function *F) {
setNonAliasAttributes(D, F);
Expand Down Expand Up @@ -928,6 +949,49 @@ static void setLinkageAndVisibilityForGV(llvm::GlobalValue *GV,
}
}

void CodeGenModule::CreateFunctionBitSetEntry(const FunctionDecl *FD,
llvm::Function *F) {
// Only if we are checking indirect calls.
if (!LangOpts.Sanitize.has(SanitizerKind::CFIICall))
return;

// Non-static class methods are handled via vtable pointer checks elsewhere.
if (isa<CXXMethodDecl>(FD) && !cast<CXXMethodDecl>(FD)->isStatic())
return;

// Additionally, if building with cross-DSO support...
if (CodeGenOpts.SanitizeCfiCrossDso) {
// Don't emit entries for function declarations. In cross-DSO mode these are
// handled with better precision at run time.
if (!FD->hasBody())
return;
// Skip available_externally functions. They won't be codegen'ed in the
// current module anyway.
if (getContext().GetGVALinkageForFunction(FD) == GVA_AvailableExternally)
return;
}

llvm::NamedMDNode *BitsetsMD =
getModule().getOrInsertNamedMetadata("llvm.bitsets");

llvm::Metadata *MD = CreateMetadataIdentifierForType(FD->getType());
llvm::Metadata *BitsetOps[] = {
MD, llvm::ConstantAsMetadata::get(F),
llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(Int64Ty, 0))};
BitsetsMD->addOperand(llvm::MDTuple::get(getLLVMContext(), BitsetOps));

// Emit a hash-based bit set entry for cross-DSO calls.
if (CodeGenOpts.SanitizeCfiCrossDso) {
if (auto TypeId = CreateCfiIdForTypeMetadata(MD)) {
llvm::Metadata *BitsetOps2[] = {
llvm::ConstantAsMetadata::get(TypeId),
llvm::ConstantAsMetadata::get(F),
llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(Int64Ty, 0))};
BitsetsMD->addOperand(llvm::MDTuple::get(getLLVMContext(), BitsetOps2));
}
}
}

void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, llvm::Function *F,
bool IsIncompleteFunction,
bool IsThunk) {
Expand Down Expand Up @@ -970,19 +1034,7 @@ void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, llvm::Function *F,
F->addAttribute(llvm::AttributeSet::FunctionIndex,
llvm::Attribute::NoBuiltin);

// If we are checking indirect calls and this is not a non-static member
// function, emit a bit set entry for the function type.
if (LangOpts.Sanitize.has(SanitizerKind::CFIICall) &&
!(isa<CXXMethodDecl>(FD) && !cast<CXXMethodDecl>(FD)->isStatic())) {
llvm::NamedMDNode *BitsetsMD =
getModule().getOrInsertNamedMetadata("llvm.bitsets");

llvm::Metadata *BitsetOps[] = {
CreateMetadataIdentifierForType(FD->getType()),
llvm::ConstantAsMetadata::get(F),
llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(Int64Ty, 0))};
BitsetsMD->addOperand(llvm::MDTuple::get(getLLVMContext(), BitsetOps));
}
CreateFunctionBitSetEntry(FD, F);
}

void CodeGenModule::addUsedGlobal(llvm::GlobalValue *GV) {
Expand Down Expand Up @@ -3874,14 +3926,28 @@ llvm::Metadata *CodeGenModule::CreateMetadataIdentifierForType(QualType T) {
return InternalId;
}

llvm::MDTuple *CodeGenModule::CreateVTableBitSetEntry(
llvm::GlobalVariable *VTable, CharUnits Offset, const CXXRecordDecl *RD) {
void CodeGenModule::CreateVTableBitSetEntry(llvm::NamedMDNode *BitsetsMD,
llvm::GlobalVariable *VTable,
CharUnits Offset,
const CXXRecordDecl *RD) {
llvm::Metadata *MD =
CreateMetadataIdentifierForType(QualType(RD->getTypeForDecl(), 0));
llvm::Metadata *BitsetOps[] = {
CreateMetadataIdentifierForType(QualType(RD->getTypeForDecl(), 0)),
llvm::ConstantAsMetadata::get(VTable),
MD, llvm::ConstantAsMetadata::get(VTable),
llvm::ConstantAsMetadata::get(
llvm::ConstantInt::get(Int64Ty, Offset.getQuantity()))};
return llvm::MDTuple::get(getLLVMContext(), BitsetOps);
BitsetsMD->addOperand(llvm::MDTuple::get(getLLVMContext(), BitsetOps));

if (CodeGenOpts.SanitizeCfiCrossDso) {
if (auto TypeId = CreateCfiIdForTypeMetadata(MD)) {
llvm::Metadata *BitsetOps2[] = {
llvm::ConstantAsMetadata::get(TypeId),
llvm::ConstantAsMetadata::get(VTable),
llvm::ConstantAsMetadata::get(
llvm::ConstantInt::get(Int64Ty, Offset.getQuantity()))};
BitsetsMD->addOperand(llvm::MDTuple::get(getLLVMContext(), BitsetOps2));
}
}
}

// Fills in the supplied string map with the set of target features for the
Expand Down
14 changes: 10 additions & 4 deletions clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1106,15 +1106,21 @@ class CodeGenModule : public CodeGenTypeCache {
void EmitVTableBitSetEntries(llvm::GlobalVariable *VTable,
const VTableLayout &VTLayout);

/// Generate a cross-DSO type identifier for type.
llvm::ConstantInt *CreateCfiIdForTypeMetadata(llvm::Metadata *MD);

/// Create a metadata identifier for the given type. This may either be an
/// MDString (for external identifiers) or a distinct unnamed MDNode (for
/// internal identifiers).
llvm::Metadata *CreateMetadataIdentifierForType(QualType T);

/// Create a bitset entry for the given vtable.
llvm::MDTuple *CreateVTableBitSetEntry(llvm::GlobalVariable *VTable,
CharUnits Offset,
const CXXRecordDecl *RD);
/// Create a bitset entry for the given function and add it to BitsetsMD.
void CreateFunctionBitSetEntry(const FunctionDecl *FD, llvm::Function *F);

/// Create a bitset entry for the given vtable and add it to BitsetsMD.
void CreateVTableBitSetEntry(llvm::NamedMDNode *BitsetsMD,
llvm::GlobalVariable *VTable, CharUnits Offset,
const CXXRecordDecl *RD);

/// \breif Get the declaration of std::terminate for the platform.
llvm::Constant *getTerminateFn();
Expand Down
13 changes: 5 additions & 8 deletions clang/lib/CodeGen/MicrosoftCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1523,15 +1523,14 @@ void MicrosoftCXXABI::emitVTableBitSetEntries(VPtrInfo *Info,

if (Info->PathToBaseWithVPtr.empty()) {
if (!CGM.IsCFIBlacklistedRecord(RD))
BitsetsMD->addOperand(
CGM.CreateVTableBitSetEntry(VTable, AddressPoint, RD));
CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, RD);
return;
}

// Add a bitset entry for the least derived base belonging to this vftable.
if (!CGM.IsCFIBlacklistedRecord(Info->PathToBaseWithVPtr.back()))
BitsetsMD->addOperand(CGM.CreateVTableBitSetEntry(
VTable, AddressPoint, Info->PathToBaseWithVPtr.back()));
CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint,
Info->PathToBaseWithVPtr.back());

// Add a bitset entry for each derived class that is laid out at the same
// offset as the least derived base.
Expand All @@ -1550,14 +1549,12 @@ void MicrosoftCXXABI::emitVTableBitSetEntries(VPtrInfo *Info,
if (!Offset.isZero())
return;
if (!CGM.IsCFIBlacklistedRecord(DerivedRD))
BitsetsMD->addOperand(
CGM.CreateVTableBitSetEntry(VTable, AddressPoint, DerivedRD));
CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, DerivedRD);
}

// Finally do the same for the most derived class.
if (Info->FullOffsetInMDC.isZero() && !CGM.IsCFIBlacklistedRecord(RD))
BitsetsMD->addOperand(
CGM.CreateVTableBitSetEntry(VTable, AddressPoint, RD));
CGM.CreateVTableBitSetEntry(BitsetsMD, VTable, AddressPoint, RD);
}

void MicrosoftCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT,
Expand Down
19 changes: 18 additions & 1 deletion clang/lib/Driver/SanitizerArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,12 @@ bool SanitizerArgs::needsUbsanRt() const {
return (Sanitizers.Mask & NeedsUbsanRt & ~TrapSanitizers.Mask) &&
!Sanitizers.has(Address) &&
!Sanitizers.has(Memory) &&
!Sanitizers.has(Thread);
!Sanitizers.has(Thread) &&
!CfiCrossDso;
}

bool SanitizerArgs::needsCfiRt() const {
return CfiCrossDso;
}

bool SanitizerArgs::requiresPIE() const {
Expand All @@ -184,6 +189,7 @@ void SanitizerArgs::clear() {
AsanFieldPadding = 0;
AsanSharedRuntime = false;
LinkCXXRuntimes = false;
CfiCrossDso = false;
}

SanitizerArgs::SanitizerArgs(const ToolChain &TC,
Expand Down Expand Up @@ -430,6 +436,14 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
TC.getTriple().getArch() == llvm::Triple::x86_64);
}

if (AllAddedKinds & CFI) {
CfiCrossDso = Args.hasFlag(options::OPT_fsanitize_cfi_cross_dso,
options::OPT_fno_sanitize_cfi_cross_dso, false);
// Without PIE, external function address may resolve to a PLT record, which
// can not be verified by the target module.
NeedPIE |= CfiCrossDso;
}

// Parse -f(no-)?sanitize-coverage flags if coverage is supported by the
// enabled sanitizers.
if (AllAddedKinds & SupportsCoverage) {
Expand Down Expand Up @@ -580,6 +594,9 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
if (MsanUseAfterDtor)
CmdArgs.push_back(Args.MakeArgString("-fsanitize-memory-use-after-dtor"));

if (CfiCrossDso)
CmdArgs.push_back(Args.MakeArgString("-fsanitize-cfi-cross-dso"));

if (AsanFieldPadding)
CmdArgs.push_back(Args.MakeArgString("-fsanitize-address-field-padding=" +
llvm::utostr(AsanFieldPadding)));
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Driver/Tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2792,6 +2792,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
}
if (SanArgs.needsSafeStackRt())
StaticRuntimes.push_back("safestack");
if (SanArgs.needsCfiRt())
StaticRuntimes.push_back("cfi");
}

// Should be called before we add system libraries (C++ ABI, libstdc++/libc++,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK,
getLastArgIntValue(Args, OPT_fsanitize_memory_track_origins_EQ, 0, Diags);
Opts.SanitizeMemoryUseAfterDtor =
Args.hasArg(OPT_fsanitize_memory_use_after_dtor);
Opts.SanitizeCfiCrossDso = Args.hasArg(OPT_fsanitize_cfi_cross_dso);
Opts.SSPBufferSize =
getLastArgIntValue(Args, OPT_stack_protector_buffer_size, 8, Diags);
Opts.StackRealignment = Args.hasArg(OPT_mstackrealign);
Expand Down
49 changes: 49 additions & 0 deletions clang/test/CodeGen/cfi-icall-cross-dso.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux -O1 -fsanitize=cfi-icall -fsanitize-cfi-cross-dso -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=ITANIUM %s
// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -O1 -fsanitize=cfi-icall -fsanitize-cfi-cross-dso -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=MS %s

void caller(void (*f)()) {
f();
}

static void g(void) {}
void h(void);

typedef void (*Fn)(void);
Fn g1() {
return &g;
}
Fn h1() {
return &h;
}

inline void foo() {}
void bar() { foo(); }

// ITANIUM: call i1 @llvm.bitset.test(i8* %{{.*}}, metadata !"_ZTSFvE"), !nosanitize
// ITANIUM: call void @__cfi_slowpath(i64 6588678392271548388, i8* %{{.*}}) {{.*}}, !nosanitize

// MS: call i1 @llvm.bitset.test(i8* %{{.*}}, metadata !"?6AX@Z"), !nosanitize
// MS: call void @__cfi_slowpath(i64 4195979634929632483, i8* %{{.*}}) {{.*}}, !nosanitize

// ITANIUM: define available_externally void @foo()
// MS: define linkonce_odr void @foo()

// Check that we emit both string and hash based bit set entries for static void g(),
// and don't emit them for the declaration of h().

// CHECK-NOT: !{!"{{.*}}", void ()* @h, i64 0}
// CHECK: !{!"{{.*}}", void ()* @g, i64 0}
// CHECK-NOT: !{!"{{.*}}", void ()* @h, i64 0}
// CHECK: !{i64 {{.*}}, void ()* @g, i64 0}
// CHECK-NOT: !{!"{{.*}}", void ()* @h, i64 0}

// ITANIUM-NOT: !{!{{.*}}, void ()* @foo,
// ITANIUM: !{!"_ZTSFvE", void ()* @bar, i64 0}
// ITANIUM-NOT: !{!{{.*}}, void ()* @foo,
// ITANIUM: !{i64 6588678392271548388, void ()* @bar, i64 0}
// ITANIUM-NOT: !{!{{.*}}, void ()* @foo,

// MS: !{!"?6AX@Z", void ()* @foo, i64 0}
// MS: !{i64 4195979634929632483, void ()* @foo, i64 0}

// CHECK: !{i32 4, !"Cross-DSO CFI", i32 1}
45 changes: 45 additions & 0 deletions clang/test/CodeGenCXX/cfi-cross-dso.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux -fsanitize=cfi-vcall -fsanitize-cfi-cross-dso -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=ITANIUM %s
// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fsanitize=cfi-vcall -fsanitize-cfi-cross-dso -emit-llvm -o - %s | FileCheck --check-prefix=CHECK --check-prefix=MS %s

struct A {
A();
virtual void f();
};

A::A() {}
void A::f() {}

void caller(A* a) {
a->f();
}

namespace {
struct B {
virtual void f();
};

void B::f() {}
} // namespace

void g() {
B b;
b.f();
}

// MS: @[[B_VTABLE:.*]] = private unnamed_addr constant [2 x i8*] {{.*}}@"\01??_R4B@?A@@6B@"{{.*}}@"\01?f@B@?A@@UEAAXXZ"

// CHECK: %[[VT:.*]] = load void (%struct.A*)**, void (%struct.A*)***
// CHECK: %[[VT2:.*]] = bitcast {{.*}}%[[VT]] to i8*, !nosanitize
// ITANIUM: %[[TEST:.*]] = call i1 @llvm.bitset.test(i8* %[[VT2]], metadata !"_ZTS1A"), !nosanitize
// MS: %[[TEST:.*]] = call i1 @llvm.bitset.test(i8* %[[VT2]], metadata !"?AUA@@"), !nosanitize
// CHECK: br i1 %[[TEST]], label %[[CONT:.*]], label %[[SLOW:.*]], {{.*}} !nosanitize
// CHECK: [[SLOW]]:
// ITANIUM: call void @__cfi_slowpath(i64 7004155349499253778, i8* %[[VT2]]) {{.*}} !nosanitize
// MS: call void @__cfi_slowpath(i64 -8005289897957287421, i8* %[[VT2]]) {{.*}} !nosanitize
// CHECK: br label %[[CONT]], !nosanitize
// CHECK: [[CONT]]:
// CHECK: call void %{{.*}}(%struct.A* %{{.*}})

// No hash-based bit set entry for (anonymous namespace)::B
// ITANIUM-NOT: !{i64 {{.*}}, [3 x i8*]* @_ZTVN12_GLOBAL__N_11BE,
// MS-NOT: !{i64 {{.*}}, [2 x i8*]* @[[B_VTABLE]],
9 changes: 9 additions & 0 deletions clang/test/Driver/fsanitize.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@
// CHECK-CFI-NOTRAP-WIN: -emit-llvm-bc
// CHECK-CFI-NOTRAP-WIN-NOT: -fsanitize-trap=cfi

// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fsanitize-cfi-cross-dso -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-CROSS-DSO
// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-NO-CROSS-DSO
// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fsanitize-cfi-cross-dso -fno-sanitize-cfi-cross-dso -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-NO-CROSS-DSO
// RUN: %clang -target x86_64-linux-gnu -fsanitize=cfi -fno-sanitize-cfi-cross-dso -fsanitize-cfi-cross-dso -flto -c %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-CFI-CROSS-DSO
// CHECK-CFI-CROSS-DSO: -emit-llvm-bc
// CHECK-CFI-CROSS-DSO: -fsanitize-cfi-cross-dso
// CHECK-CFI-NO-CROSS-DSO: -emit-llvm-bc
// CHECK-CFI-NO-CROSS-DSO-NOT: -fsanitize-cfi-cross-dso

// RUN: %clang_cl -fsanitize=address -c -MDd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL
// RUN: %clang_cl -fsanitize=address -c -MTd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL
// RUN: %clang_cl -fsanitize=address -c -LDd -### -- %s 2>&1 | FileCheck %s -check-prefix=CHECK-ASAN-DEBUGRTL
Expand Down
40 changes: 40 additions & 0 deletions clang/test/Driver/sanitizer-ld.c
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,46 @@
// CHECK-LSAN-ASAN-LINUX: libclang_rt.asan-x86_64
// CHECK-LSAN-ASAN-LINUX-NOT: libclang_rt.lsan

// CFI by itself does not link runtime libraries.
// RUN: %clang -fsanitize=cfi %s -### -o %t.o 2>&1 \
// RUN: -target x86_64-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: | FileCheck --check-prefix=CHECK-CFI-LINUX %s
// CHECK-CFI-LINUX: "{{.*}}ld{{(.exe)?}}"
// CHECK-CFI-LINUX-NOT: libclang_rt.

// CFI with diagnostics links the UBSan runtime.
// RUN: %clang -fsanitize=cfi -fno-sanitize-trap=cfi -fsanitize-recover=cfi \
// RUN: %s -### -o %t.o 2>&1\
// RUN: -target x86_64-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: | FileCheck --check-prefix=CHECK-CFI-DIAG-LINUX %s
// CHECK-CFI-DIAG-LINUX: "{{.*}}ld{{(.exe)?}}"
// CHECK-CFI-CROSS-DSO-LINUX-NOT: libclang_rt.
// CHECK-CFI-DIAG-LINUX: libclang_rt.ubsan
// CHECK-CFI-CROSS-DSO-LINUX-NOT: libclang_rt.

// Cross-DSO CFI links the CFI runtime.
// RUN: %clang -fsanitize=cfi -fsanitize-cfi-cross-dso %s -### -o %t.o 2>&1 \
// RUN: -target x86_64-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: | FileCheck --check-prefix=CHECK-CFI-CROSS-DSO-LINUX %s
// CHECK-CFI-CROSS-DSO-LINUX: "{{.*}}ld{{(.exe)?}}"
// CHECK-CFI-CROSS-DSO-LINUX-NOT: libclang_rt.
// CHECK-CFI-CROSS-DSO-LINUX: libclang_rt.cfi
// CHECK-CFI-CROSS-DSO-LINUX-NOT: libclang_rt.

// Cross-DSO CFI with diagnostics links just the CFI runtime.
// RUN: %clang -fsanitize=cfi -fsanitize-cfi-cross-dso %s -### -o %t.o 2>&1 \
// RUN: -fno-sanitize-trap=cfi -fsanitize-recover=cfi \
// RUN: -target x86_64-unknown-linux \
// RUN: --sysroot=%S/Inputs/basic_linux_tree \
// RUN: | FileCheck --check-prefix=CHECK-CFI-CROSS-DSO-DIAG-LINUX %s
// CHECK-CFI-CROSS-DSO-DIAG-LINUX: "{{.*}}ld{{(.exe)?}}"
// CHECK-CFI-CROSS-DSO-DIAG-LINUX-NOT: libclang_rt.
// CHECK-CFI-CROSS-DSO-DIAG-LINUX: libclang_rt.cfi
// CHECK-CFI-CROSS-DSO-DIAG-LINUX-NOT: libclang_rt.

// RUN: %clangxx -fsanitize=address %s -### -o %t.o 2>&1 \
// RUN: -mmacosx-version-min=10.6 \
// RUN: -target x86_64-apple-darwin13.4.0 \
Expand Down