From 6e4a97dfceb558e789dcbae737a57eb4c01d7fa4 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Tue, 17 Mar 2015 16:59:19 +0000 Subject: [PATCH] asan: optimization experiments The experiments can be used to evaluate potential optimizations that remove instrumentation (assess false negatives). Instead of completely removing some instrumentation, you set Exp to a non-zero value (mask of optimization experiments that want to remove instrumentation of this instruction). If Exp is non-zero, this pass will emit special calls into runtime (e.g. __asan_report_exp_load1 instead of __asan_report_load1). These calls make runtime terminate the program in a special way (with a different exit status). Then you run the new compiler on a buggy corpus, collect the special terminations (ideally, you don't see them at all -- no false negatives) and make the decision on the optimization. The exact reaction to experiments in runtime is not implemented in this patch. It will be defined and implemented in a subsequent patch. http://reviews.llvm.org/D8198 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@232502 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../Instrumentation/AddressSanitizer.cpp | 174 ++++++++++++------ .../AddressSanitizer/experiment-call.ll | 113 ++++++++++++ .../AddressSanitizer/experiment.ll | 113 ++++++++++++ 3 files changed, 340 insertions(+), 60 deletions(-) create mode 100644 test/Instrumentation/AddressSanitizer/experiment-call.ll create mode 100644 test/Instrumentation/AddressSanitizer/experiment.ll diff --git a/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/lib/Transforms/Instrumentation/AddressSanitizer.cpp index f46a1276f89..732b9a776c9 100644 --- a/lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ b/lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -83,8 +83,6 @@ static const char *const kAsanModuleCtorName = "asan.module_ctor"; static const char *const kAsanModuleDtorName = "asan.module_dtor"; static const uint64_t kAsanCtorAndDtorPriority = 1; static const char *const kAsanReportErrorTemplate = "__asan_report_"; -static const char *const kAsanReportLoadN = "__asan_report_load_n"; -static const char *const kAsanReportStoreN = "__asan_report_store_n"; static const char *const kAsanRegisterGlobalsName = "__asan_register_globals"; static const char *const kAsanUnregisterGlobalsName = "__asan_unregister_globals"; @@ -216,6 +214,11 @@ static cl::opt ClDynamicAllocaStack( cl::desc("Use dynamic alloca to represent stack variables"), cl::Hidden, cl::init(true)); +static cl::opt ClForceExperiment( + "asan-force-experiment", + cl::desc("Force optimization experiment (for testing)"), cl::Hidden, + cl::init(0)); + // Debug flags. static cl::opt ClDebug("asan-debug", cl::desc("debug"), cl::Hidden, cl::init(0)); @@ -412,12 +415,16 @@ struct AddressSanitizer : public FunctionPass { void instrumentPointerComparisonOrSubtraction(Instruction *I); void instrumentAddress(Instruction *OrigIns, Instruction *InsertBefore, Value *Addr, uint32_t TypeSize, bool IsWrite, - Value *SizeArgument, bool UseCalls); + Value *SizeArgument, bool UseCalls, uint32_t Exp); + void instrumentUnusualSizeOrAlignment(Instruction *I, Value *Addr, + uint32_t TypeSize, bool IsWrite, + Value *SizeArgument, bool UseCalls, + uint32_t Exp); Value *createSlowPathCmp(IRBuilder<> &IRB, Value *AddrLong, Value *ShadowValue, uint32_t TypeSize); Instruction *generateCrashCode(Instruction *InsertBefore, Value *Addr, bool IsWrite, size_t AccessSizeIndex, - Value *SizeArgument); + Value *SizeArgument, uint32_t Exp); void instrumentMemIntrinsic(MemIntrinsic *MI); Value *memToShadow(Value *Shadow, IRBuilder<> &IRB); bool runOnFunction(Function &F) override; @@ -445,11 +452,12 @@ struct AddressSanitizer : public FunctionPass { Function *AsanInitFunction; Function *AsanHandleNoReturnFunc; Function *AsanPtrCmpFunction, *AsanPtrSubFunction; - // This array is indexed by AccessIsWrite and log2(AccessSize). - Function *AsanErrorCallback[2][kNumberOfAccessSizes]; - Function *AsanMemoryAccessCallback[2][kNumberOfAccessSizes]; - // This array is indexed by AccessIsWrite. - Function *AsanErrorCallbackSized[2], *AsanMemoryAccessCallbackSized[2]; + // This array is indexed by AccessIsWrite, Experiment and log2(AccessSize). + Function *AsanErrorCallback[2][2][kNumberOfAccessSizes]; + Function *AsanMemoryAccessCallback[2][2][kNumberOfAccessSizes]; + // This array is indexed by AccessIsWrite and Experiment. + Function *AsanErrorCallbackSized[2][2]; + Function *AsanMemoryAccessCallbackSized[2][2]; Function *AsanMemmove, *AsanMemcpy, *AsanMemset; InlineAsm *EmptyAsm; GlobalsMetadata GlobalsMD; @@ -904,6 +912,19 @@ void AddressSanitizer::instrumentMop(ObjectSizeOffsetVisitor &ObjSizeVis, Value *Addr = isInterestingMemoryAccess(I, &IsWrite, &TypeSize, &Alignment); assert(Addr); + // Optimization experiments. + // The experiments can be used to evaluate potential optimizations that remove + // instrumentation (assess false negatives). Instead of completely removing + // some instrumentation, you set Exp to a non-zero value (mask of optimization + // experiments that want to remove instrumentation of this instruction). + // If Exp is non-zero, this pass will emit special calls into runtime + // (e.g. __asan_report_exp_load1 instead of __asan_report_load1). These calls + // make runtime terminate the program in a special way (with a different + // exit status). Then you run the new compiler on a buggy corpus, collect + // the special terminations (ideally, you don't see them at all -- no false + // negatives) and make the decision on the optimization. + uint32_t Exp = ClForceExperiment; + if (ClOpt && ClOptGlobals) { // If initialization order checking is disabled, a simple access to a // dynamically initialized global is always valid. @@ -935,23 +956,10 @@ void AddressSanitizer::instrumentMop(ObjectSizeOffsetVisitor &ObjSizeVis, if ((TypeSize == 8 || TypeSize == 16 || TypeSize == 32 || TypeSize == 64 || TypeSize == 128) && (Alignment >= Granularity || Alignment == 0 || Alignment >= TypeSize / 8)) - return instrumentAddress(I, I, Addr, TypeSize, IsWrite, nullptr, UseCalls); - // Instrument unusual size or unusual alignment. - // We can not do it with a single check, so we do 1-byte check for the first - // and the last bytes. We call __asan_report_*_n(addr, real_size) to be able - // to report the actual access size. - IRBuilder<> IRB(I); - Value *Size = ConstantInt::get(IntptrTy, TypeSize / 8); - Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy); - if (UseCalls) { - IRB.CreateCall2(AsanMemoryAccessCallbackSized[IsWrite], AddrLong, Size); - } else { - Value *LastByte = IRB.CreateIntToPtr( - IRB.CreateAdd(AddrLong, ConstantInt::get(IntptrTy, TypeSize / 8 - 1)), - Addr->getType()); - instrumentAddress(I, I, Addr, 8, IsWrite, Size, false); - instrumentAddress(I, I, LastByte, 8, IsWrite, Size, false); - } + return instrumentAddress(I, I, Addr, TypeSize, IsWrite, nullptr, UseCalls, + Exp); + instrumentUnusualSizeOrAlignment(I, Addr, TypeSize, IsWrite, nullptr, + UseCalls, Exp); } // Validate the result of Module::getOrInsertFunction called for an interface @@ -969,12 +977,26 @@ static Function *checkInterfaceFunction(Constant *FuncOrBitcast) { Instruction *AddressSanitizer::generateCrashCode(Instruction *InsertBefore, Value *Addr, bool IsWrite, size_t AccessSizeIndex, - Value *SizeArgument) { + Value *SizeArgument, + uint32_t Exp) { IRBuilder<> IRB(InsertBefore); - CallInst *Call = - SizeArgument - ? IRB.CreateCall2(AsanErrorCallbackSized[IsWrite], Addr, SizeArgument) - : IRB.CreateCall(AsanErrorCallback[IsWrite][AccessSizeIndex], Addr); + Value *ExpVal = Exp == 0 ? nullptr : ConstantInt::get(IRB.getInt32Ty(), Exp); + CallInst *Call = nullptr; + if (SizeArgument) { + if (Exp == 0) + Call = IRB.CreateCall2(AsanErrorCallbackSized[IsWrite][0], Addr, + SizeArgument); + else + Call = IRB.CreateCall3(AsanErrorCallbackSized[IsWrite][1], Addr, + SizeArgument, ExpVal); + } else { + if (Exp == 0) + Call = + IRB.CreateCall(AsanErrorCallback[IsWrite][0][AccessSizeIndex], Addr); + else + Call = IRB.CreateCall2(AsanErrorCallback[IsWrite][1][AccessSizeIndex], + Addr, ExpVal); + } // We don't do Call->setDoesNotReturn() because the BB already has // UnreachableInst at the end. @@ -1004,14 +1026,19 @@ Value *AddressSanitizer::createSlowPathCmp(IRBuilder<> &IRB, Value *AddrLong, void AddressSanitizer::instrumentAddress(Instruction *OrigIns, Instruction *InsertBefore, Value *Addr, uint32_t TypeSize, bool IsWrite, - Value *SizeArgument, bool UseCalls) { + Value *SizeArgument, bool UseCalls, + uint32_t Exp) { IRBuilder<> IRB(InsertBefore); Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy); size_t AccessSizeIndex = TypeSizeToSizeIndex(TypeSize); if (UseCalls) { - IRB.CreateCall(AsanMemoryAccessCallback[IsWrite][AccessSizeIndex], - AddrLong); + if (Exp == 0) + IRB.CreateCall(AsanMemoryAccessCallback[IsWrite][0][AccessSizeIndex], + AddrLong); + else + IRB.CreateCall2(AsanMemoryAccessCallback[IsWrite][1][AccessSizeIndex], + AddrLong, ConstantInt::get(IRB.getInt32Ty(), Exp)); return; } @@ -1046,10 +1073,36 @@ void AddressSanitizer::instrumentAddress(Instruction *OrigIns, } Instruction *Crash = generateCrashCode(CrashTerm, AddrLong, IsWrite, - AccessSizeIndex, SizeArgument); + AccessSizeIndex, SizeArgument, Exp); Crash->setDebugLoc(OrigIns->getDebugLoc()); } +// Instrument unusual size or unusual alignment. +// We can not do it with a single check, so we do 1-byte check for the first +// and the last bytes. We call __asan_report_*_n(addr, real_size) to be able +// to report the actual access size. +void AddressSanitizer::instrumentUnusualSizeOrAlignment( + Instruction *I, Value *Addr, uint32_t TypeSize, bool IsWrite, + Value *SizeArgument, bool UseCalls, uint32_t Exp) { + IRBuilder<> IRB(I); + Value *Size = ConstantInt::get(IntptrTy, TypeSize / 8); + Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy); + if (UseCalls) { + if (Exp == 0) + IRB.CreateCall2(AsanMemoryAccessCallbackSized[IsWrite][0], AddrLong, + Size); + else + IRB.CreateCall3(AsanMemoryAccessCallbackSized[IsWrite][1], AddrLong, Size, + ConstantInt::get(IRB.getInt32Ty(), Exp)); + } else { + Value *LastByte = IRB.CreateIntToPtr( + IRB.CreateAdd(AddrLong, ConstantInt::get(IntptrTy, TypeSize / 8 - 1)), + Addr->getType()); + instrumentAddress(I, I, Addr, 8, IsWrite, Size, false, Exp); + instrumentAddress(I, I, LastByte, 8, IsWrite, Size, false, Exp); + } +} + void AddressSanitizerModule::poisonOneInitializer(Function &GlobalInit, GlobalValue *ModuleName) { // Set up the arguments to our poison/unpoison functions. @@ -1342,33 +1395,34 @@ bool AddressSanitizerModule::runOnModule(Module &M) { void AddressSanitizer::initializeCallbacks(Module &M) { IRBuilder<> IRB(*C); // Create __asan_report* callbacks. - for (size_t AccessIsWrite = 0; AccessIsWrite <= 1; AccessIsWrite++) { - for (size_t AccessSizeIndex = 0; AccessSizeIndex < kNumberOfAccessSizes; - AccessSizeIndex++) { - // IsWrite and TypeSize are encoded in the function name. - std::string Suffix = - (AccessIsWrite ? "store" : "load") + itostr(1 << AccessSizeIndex); - AsanErrorCallback[AccessIsWrite][AccessSizeIndex] = - checkInterfaceFunction( - M.getOrInsertFunction(kAsanReportErrorTemplate + Suffix, - IRB.getVoidTy(), IntptrTy, nullptr)); - AsanMemoryAccessCallback[AccessIsWrite][AccessSizeIndex] = - checkInterfaceFunction( - M.getOrInsertFunction(ClMemoryAccessCallbackPrefix + Suffix, - IRB.getVoidTy(), IntptrTy, nullptr)); + // IsWrite, TypeSize and Exp are encoded in the function name. + for (int Exp = 0; Exp < 2; Exp++) { + for (size_t AccessIsWrite = 0; AccessIsWrite <= 1; AccessIsWrite++) { + const std::string TypeStr = AccessIsWrite ? "store" : "load"; + const std::string ExpStr = Exp ? "exp_" : ""; + const Type *ExpType = Exp ? Type::getInt32Ty(*C) : nullptr; + AsanErrorCallbackSized[AccessIsWrite][Exp] = + checkInterfaceFunction(M.getOrInsertFunction( + kAsanReportErrorTemplate + ExpStr + TypeStr + "_n", + IRB.getVoidTy(), IntptrTy, IntptrTy, ExpType, nullptr)); + AsanMemoryAccessCallbackSized[AccessIsWrite][Exp] = + checkInterfaceFunction(M.getOrInsertFunction( + ClMemoryAccessCallbackPrefix + ExpStr + TypeStr + "N", + IRB.getVoidTy(), IntptrTy, IntptrTy, ExpType, nullptr)); + for (size_t AccessSizeIndex = 0; AccessSizeIndex < kNumberOfAccessSizes; + AccessSizeIndex++) { + const std::string Suffix = TypeStr + itostr(1 << AccessSizeIndex); + AsanErrorCallback[AccessIsWrite][Exp][AccessSizeIndex] = + checkInterfaceFunction(M.getOrInsertFunction( + kAsanReportErrorTemplate + ExpStr + Suffix, IRB.getVoidTy(), + IntptrTy, ExpType, nullptr)); + AsanMemoryAccessCallback[AccessIsWrite][Exp][AccessSizeIndex] = + checkInterfaceFunction(M.getOrInsertFunction( + ClMemoryAccessCallbackPrefix + ExpStr + Suffix, IRB.getVoidTy(), + IntptrTy, ExpType, nullptr)); + } } } - AsanErrorCallbackSized[0] = checkInterfaceFunction(M.getOrInsertFunction( - kAsanReportLoadN, IRB.getVoidTy(), IntptrTy, IntptrTy, nullptr)); - AsanErrorCallbackSized[1] = checkInterfaceFunction(M.getOrInsertFunction( - kAsanReportStoreN, IRB.getVoidTy(), IntptrTy, IntptrTy, nullptr)); - - AsanMemoryAccessCallbackSized[0] = checkInterfaceFunction( - M.getOrInsertFunction(ClMemoryAccessCallbackPrefix + "loadN", - IRB.getVoidTy(), IntptrTy, IntptrTy, nullptr)); - AsanMemoryAccessCallbackSized[1] = checkInterfaceFunction( - M.getOrInsertFunction(ClMemoryAccessCallbackPrefix + "storeN", - IRB.getVoidTy(), IntptrTy, IntptrTy, nullptr)); AsanMemmove = checkInterfaceFunction(M.getOrInsertFunction( ClMemoryAccessCallbackPrefix + "memmove", IRB.getInt8PtrTy(), diff --git a/test/Instrumentation/AddressSanitizer/experiment-call.ll b/test/Instrumentation/AddressSanitizer/experiment-call.ll new file mode 100644 index 00000000000..0e339cc8041 --- /dev/null +++ b/test/Instrumentation/AddressSanitizer/experiment-call.ll @@ -0,0 +1,113 @@ +; Test optimization experiments. +; -asan-force-experiment flag turns all memory accesses into experiments. +; RUN: opt < %s -asan -asan-module -asan-force-experiment=42 -asan-instrumentation-with-call-threshold=0 -S | FileCheck %s +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-unknown-linux-gnu" + +define void @load1(i8* %p) sanitize_address { +entry: + %t = load i8, i8* %p, align 1 + ret void +; CHECK-LABEL: define void @load1 +; CHECK: __asan_exp_load1{{.*}} i32 42 +; CHECK: ret void +} + +define void @load2(i16* %p) sanitize_address { +entry: + %t = load i16, i16* %p, align 2 + ret void +; CHECK-LABEL: define void @load2 +; CHECK: __asan_exp_load2{{.*}} i32 42 +; CHECK: ret void +} + +define void @load4(i32* %p) sanitize_address { +entry: + %t = load i32, i32* %p, align 4 + ret void +; CHECK-LABEL: define void @load4 +; CHECK: __asan_exp_load4{{.*}} i32 42 +; CHECK: ret void +} + +define void @load8(i64* %p) sanitize_address { +entry: + %t = load i64, i64* %p, align 8 + ret void +; CHECK-LABEL: define void @load8 +; CHECK: __asan_exp_load8{{.*}} i32 42 +; CHECK: ret void +} + +define void @load16(i128* %p) sanitize_address { +entry: + %t = load i128, i128* %p, align 16 + ret void +; CHECK-LABEL: define void @load16 +; CHECK: __asan_exp_load16{{.*}} i32 42 +; CHECK: ret void +} + +define void @loadN(i48* %p) sanitize_address { +entry: + %t = load i48, i48* %p, align 1 + ret void +; CHECK-LABEL: define void @loadN +; CHECK: __asan_exp_loadN{{.*}} i32 42 +; CHECK: ret void +} + +define void @store1(i8* %p) sanitize_address { +entry: + store i8 1, i8* %p, align 1 + ret void +; CHECK-LABEL: define void @store1 +; CHECK: __asan_exp_store1{{.*}} i32 42 +; CHECK: ret void +} + +define void @store2(i16* %p) sanitize_address { +entry: + store i16 1, i16* %p, align 2 + ret void +; CHECK-LABEL: define void @store2 +; CHECK: __asan_exp_store2{{.*}} i32 42 +; CHECK: ret void +} + +define void @store4(i32* %p) sanitize_address { +entry: + store i32 1, i32* %p, align 4 + ret void +; CHECK-LABEL: define void @store4 +; CHECK: __asan_exp_store4{{.*}} i32 42 +; CHECK: ret void +} + +define void @store8(i64* %p) sanitize_address { +entry: + store i64 1, i64* %p, align 8 + ret void +; CHECK-LABEL: define void @store8 +; CHECK: __asan_exp_store8{{.*}} i32 42 +; CHECK: ret void +} + +define void @store16(i128* %p) sanitize_address { +entry: + store i128 1, i128* %p, align 16 + ret void +; CHECK-LABEL: define void @store16 +; CHECK: __asan_exp_store16{{.*}} i32 42 +; CHECK: ret void +} + +define void @storeN(i48* %p) sanitize_address { +entry: + store i48 1, i48* %p, align 1 + ret void +; CHECK-LABEL: define void @storeN +; CHECK: __asan_exp_storeN{{.*}} i32 42 +; CHECK: ret void +} diff --git a/test/Instrumentation/AddressSanitizer/experiment.ll b/test/Instrumentation/AddressSanitizer/experiment.ll new file mode 100644 index 00000000000..aaa125f5d40 --- /dev/null +++ b/test/Instrumentation/AddressSanitizer/experiment.ll @@ -0,0 +1,113 @@ +; Test optimization experiments. +; -asan-force-experiment flag turns all memory accesses into experiments. +; RUN: opt < %s -asan -asan-module -asan-force-experiment=42 -S | FileCheck %s +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-unknown-linux-gnu" + +define void @load1(i8* %p) sanitize_address { +entry: + %t = load i8, i8* %p, align 1 + ret void +; CHECK-LABEL: define void @load1 +; CHECK: __asan_report_exp_load1{{.*}} i32 42 +; CHECK: ret void +} + +define void @load2(i16* %p) sanitize_address { +entry: + %t = load i16, i16* %p, align 2 + ret void +; CHECK-LABEL: define void @load2 +; CHECK: __asan_report_exp_load2{{.*}} i32 42 +; CHECK: ret void +} + +define void @load4(i32* %p) sanitize_address { +entry: + %t = load i32, i32* %p, align 4 + ret void +; CHECK-LABEL: define void @load4 +; CHECK: __asan_report_exp_load4{{.*}} i32 42 +; CHECK: ret void +} + +define void @load8(i64* %p) sanitize_address { +entry: + %t = load i64, i64* %p, align 8 + ret void +; CHECK-LABEL: define void @load8 +; CHECK: __asan_report_exp_load8{{.*}} i32 42 +; CHECK: ret void +} + +define void @load16(i128* %p) sanitize_address { +entry: + %t = load i128, i128* %p, align 16 + ret void +; CHECK-LABEL: define void @load16 +; CHECK: __asan_report_exp_load16{{.*}} i32 42 +; CHECK: ret void +} + +define void @loadN(i48* %p) sanitize_address { +entry: + %t = load i48, i48* %p, align 1 + ret void +; CHECK-LABEL: define void @loadN +; CHECK: __asan_report_exp_load_n{{.*}} i32 42 +; CHECK: ret void +} + +define void @store1(i8* %p) sanitize_address { +entry: + store i8 1, i8* %p, align 1 + ret void +; CHECK-LABEL: define void @store1 +; CHECK: __asan_report_exp_store1{{.*}} i32 42 +; CHECK: ret void +} + +define void @store2(i16* %p) sanitize_address { +entry: + store i16 1, i16* %p, align 2 + ret void +; CHECK-LABEL: define void @store2 +; CHECK: __asan_report_exp_store2{{.*}} i32 42 +; CHECK: ret void +} + +define void @store4(i32* %p) sanitize_address { +entry: + store i32 1, i32* %p, align 4 + ret void +; CHECK-LABEL: define void @store4 +; CHECK: __asan_report_exp_store4{{.*}} i32 42 +; CHECK: ret void +} + +define void @store8(i64* %p) sanitize_address { +entry: + store i64 1, i64* %p, align 8 + ret void +; CHECK-LABEL: define void @store8 +; CHECK: __asan_report_exp_store8{{.*}} i32 42 +; CHECK: ret void +} + +define void @store16(i128* %p) sanitize_address { +entry: + store i128 1, i128* %p, align 16 + ret void +; CHECK-LABEL: define void @store16 +; CHECK: __asan_report_exp_store16{{.*}} i32 42 +; CHECK: ret void +} + +define void @storeN(i48* %p) sanitize_address { +entry: + store i48 1, i48* %p, align 1 + ret void +; CHECK-LABEL: define void @storeN +; CHECK: __asan_report_exp_store_n{{.*}} i32 42 +; CHECK: ret void +}