aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler-rt/lib/asan/CMakeLists.txt1
-rw-r--r--compiler-rt/lib/asan/asan_init_version.h7
-rw-r--r--compiler-rt/lib/asan/asan_linux.cc32
-rw-r--r--compiler-rt/lib/asan/asan_mapping.h8
-rw-r--r--compiler-rt/lib/asan/asan_premap_shadow.cc69
-rw-r--r--compiler-rt/lib/asan/asan_premap_shadow.h28
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_common.h1
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_flags.h4
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_fuchsia.cc4
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_linux.cc18
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_mac.cc4
-rw-r--r--compiler-rt/lib/sanitizer_common/sanitizer_win.cc4
-rw-r--r--llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp46
-rw-r--r--llvm/test/Instrumentation/AddressSanitizer/with-ifunc.ll30
14 files changed, 239 insertions, 17 deletions
diff --git a/compiler-rt/lib/asan/CMakeLists.txt b/compiler-rt/lib/asan/CMakeLists.txt
index 3342357..9291e71 100644
--- a/compiler-rt/lib/asan/CMakeLists.txt
+++ b/compiler-rt/lib/asan/CMakeLists.txt
@@ -21,6 +21,7 @@ set(ASAN_SOURCES
asan_memory_profile.cc
asan_poisoning.cc
asan_posix.cc
+ asan_premap_shadow.cc
asan_report.cc
asan_rtl.cc
asan_shadow_setup.cc
diff --git a/compiler-rt/lib/asan/asan_init_version.h b/compiler-rt/lib/asan/asan_init_version.h
index f48cc19..c49fcd7 100644
--- a/compiler-rt/lib/asan/asan_init_version.h
+++ b/compiler-rt/lib/asan/asan_init_version.h
@@ -15,6 +15,8 @@
#ifndef ASAN_INIT_VERSION_H
#define ASAN_INIT_VERSION_H
+#include "sanitizer_common/sanitizer_platform.h"
+
extern "C" {
// Every time the ASan ABI changes we also change the version number in the
// __asan_init function name. Objects built with incompatible ASan ABI
@@ -32,7 +34,12 @@ extern "C" {
// v6=>v7: added 'odr_indicator' to __asan_global
// v7=>v8: added '__asan_(un)register_image_globals' functions for dead
// stripping support on Mach-O platforms
+#if SANITIZER_WORDSIZE == 32 && SANITIZER_ANDROID
+ // v8=>v9: 32-bit Android switched to dynamic shadow
+ #define __asan_version_mismatch_check __asan_version_mismatch_check_v9
+#else
#define __asan_version_mismatch_check __asan_version_mismatch_check_v8
+#endif
}
#endif // ASAN_INIT_VERSION_H
diff --git a/compiler-rt/lib/asan/asan_linux.cc b/compiler-rt/lib/asan/asan_linux.cc
index a949a98..902c64c 100644
--- a/compiler-rt/lib/asan/asan_linux.cc
+++ b/compiler-rt/lib/asan/asan_linux.cc
@@ -17,6 +17,7 @@
#include "asan_interceptors.h"
#include "asan_internal.h"
+#include "asan_premap_shadow.h"
#include "asan_thread.h"
#include "sanitizer_common/sanitizer_flags.h"
#include "sanitizer_common/sanitizer_freebsd.h"
@@ -81,10 +82,37 @@ void *AsanDoesNotSupportStaticLinkage() {
return &_DYNAMIC; // defined in link.h
}
+#if ASAN_PREMAP_SHADOW
uptr FindDynamicShadowStart() {
- UNREACHABLE("FindDynamicShadowStart is not available");
- return 0;
+ uptr granularity = GetMmapGranularity();
+ uptr shadow_start = reinterpret_cast<uptr>(&__asan_shadow);
+ uptr shadow_size = PremapShadowSize();
+ UnmapOrDie((void *)(shadow_start - granularity), shadow_size + granularity);
+ // MmapNoAccess does not touch TotalMmap, but UnmapOrDie decreases it.
+ // Compensate.
+ IncreaseTotalMmap(shadow_size + granularity);
+ return shadow_start;
}
+#else
+uptr FindDynamicShadowStart() {
+ uptr granularity = GetMmapGranularity();
+ uptr alignment = granularity * 8;
+ uptr left_padding = granularity;
+ uptr shadow_size = kHighShadowEnd + left_padding;
+ uptr map_size = shadow_size + alignment;
+
+ uptr map_start = (uptr)MmapNoAccess(map_size);
+ CHECK_NE(map_start, ~(uptr)0);
+
+ uptr shadow_start = RoundUpTo(map_start, alignment);
+ UnmapOrDie((void *)map_start, map_size);
+ // MmapNoAccess does not touch TotalMmap, but UnmapOrDie decreases it.
+ // Compensate.
+ IncreaseTotalMmap(map_size);
+
+ return shadow_start;
+}
+#endif
void AsanApplyToGlobals(globals_op_fptr op, const void *needle) {
UNIMPLEMENTED();
diff --git a/compiler-rt/lib/asan/asan_mapping.h b/compiler-rt/lib/asan/asan_mapping.h
index fcd9511..febe090 100644
--- a/compiler-rt/lib/asan/asan_mapping.h
+++ b/compiler-rt/lib/asan/asan_mapping.h
@@ -156,7 +156,7 @@ static const u64 kWindowsShadowOffset32 = 3ULL << 28; // 0x30000000
# define SHADOW_OFFSET (0)
#elif SANITIZER_WORDSIZE == 32
# if SANITIZER_ANDROID
-# define SHADOW_OFFSET (0)
+# define SHADOW_OFFSET __asan_shadow_memory_dynamic_address
# elif defined(__mips__)
# define SHADOW_OFFSET kMIPS32_ShadowOffset32
# elif SANITIZER_FREEBSD
@@ -200,6 +200,12 @@ static const u64 kWindowsShadowOffset32 = 3ULL << 28; // 0x30000000
# endif
#endif
+#if SANITIZER_ANDROID && defined(__arm__)
+# define ASAN_PREMAP_SHADOW 1
+#else
+# define ASAN_PREMAP_SHADOW 0
+#endif
+
#define SHADOW_GRANULARITY (1ULL << SHADOW_SCALE)
#define MEM_TO_SHADOW(mem) (((mem) >> SHADOW_SCALE) + (SHADOW_OFFSET))
diff --git a/compiler-rt/lib/asan/asan_premap_shadow.cc b/compiler-rt/lib/asan/asan_premap_shadow.cc
new file mode 100644
index 0000000..0e66eee
--- /dev/null
+++ b/compiler-rt/lib/asan/asan_premap_shadow.cc
@@ -0,0 +1,69 @@
+//===-- asan_premap_shadow.cc ---------------------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of AddressSanitizer, an address sanity checker.
+//
+// Reserve shadow memory with an ifunc resolver.
+//===----------------------------------------------------------------------===//
+
+#include "asan_mapping.h"
+
+#if ASAN_PREMAP_SHADOW
+
+#include "asan_premap_shadow.h"
+#include "sanitizer_common/sanitizer_posix.h"
+
+namespace __asan {
+
+// The code in this file needs to run in an unrelocated binary. It may not
+// access any external symbol, including its own non-hidden globals.
+
+// Conservative upper limit.
+uptr PremapShadowSize() {
+ return GetMaxVirtualAddress() >> SHADOW_SCALE;
+}
+
+// Returns an address aligned to 8 pages, such that one page on the left and
+// PremapShadowSize() bytes on the right of it are mapped r/o.
+uptr PremapShadow() {
+ uptr granularity = GetMmapGranularity();
+ uptr alignment = granularity * 8;
+ uptr left_padding = granularity;
+ uptr shadow_size = PremapShadowSize();
+ uptr map_size = shadow_size + left_padding + alignment;
+
+ uptr map_start = (uptr)MmapNoAccess(map_size);
+ CHECK_NE(map_start, ~(uptr)0);
+
+ uptr shadow_start = RoundUpTo(map_start + left_padding, alignment);
+ uptr shadow_end = shadow_start + shadow_size;
+ internal_munmap(reinterpret_cast<void *>(map_start),
+ shadow_start - left_padding - map_start);
+ internal_munmap(reinterpret_cast<void *>(shadow_end),
+ map_start + map_size - shadow_end);
+ return shadow_start;
+}
+
+} // namespace __asan
+
+extern "C" {
+decltype(__asan_shadow)* __asan_premap_shadow() {
+ // The resolver may be called multiple times. Map the shadow just once.
+ static uptr premapped_shadow = 0;
+ if (!premapped_shadow) premapped_shadow = __asan::PremapShadow();
+ return reinterpret_cast<decltype(__asan_shadow)*>(premapped_shadow);
+}
+
+// __asan_shadow is a "function" that has the same address as the first byte of
+// the shadow mapping.
+INTERFACE_ATTRIBUTE __attribute__((ifunc("__asan_premap_shadow"))) void
+__asan_shadow();
+}
+
+#endif // ASAN_PREMAP_SHADOW
diff --git a/compiler-rt/lib/asan/asan_premap_shadow.h b/compiler-rt/lib/asan/asan_premap_shadow.h
new file mode 100644
index 0000000..6349e04
--- /dev/null
+++ b/compiler-rt/lib/asan/asan_premap_shadow.h
@@ -0,0 +1,28 @@
+//===-- asan_mapping.h ------------------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of AddressSanitizer, an address sanity checker.
+//
+// Premap shadow range with an ifunc resolver.
+//===----------------------------------------------------------------------===//
+
+
+#ifndef ASAN_PREMAP_SHADOW_H
+#define ASAN_PREMAP_SHADOW_H
+
+#if ASAN_PREMAP_SHADOW
+namespace __asan {
+// Conservative upper limit.
+uptr PremapShadowSize();
+}
+#endif
+
+extern "C" INTERFACE_ATTRIBUTE void __asan_shadow();
+
+#endif // ASAN_PREMAP_SHADOW_H
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h
index 9fb615f..746ba22 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h
@@ -73,6 +73,7 @@ INLINE uptr GetPageSizeCached() {
return PageSizeCached;
}
uptr GetMmapGranularity();
+uptr GetMaxVirtualAddress();
uptr GetMaxUserVirtualAddress();
// Threads
tid_t GetTid();
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flags.h b/compiler-rt/lib/sanitizer_common/sanitizer_flags.h
index c2ec29d..f42d219 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_flags.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_flags.h
@@ -34,7 +34,9 @@ struct CommonFlags {
};
// Functions to get/set global CommonFlags shared by all sanitizer runtimes:
-extern CommonFlags common_flags_dont_use;
+// FIXME: hidden needed for asan_premap_shadow. Consider building with
+// -fvisibility=hidden.
+extern CommonFlags common_flags_dont_use __attribute__((visibility("hidden")));
inline const CommonFlags *common_flags() {
return &common_flags_dont_use;
}
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_fuchsia.cc b/compiler-rt/lib/sanitizer_common/sanitizer_fuchsia.cc
index 97dff0d..c03c993 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_fuchsia.cc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_fuchsia.cc
@@ -191,6 +191,10 @@ uptr GetMaxUserVirtualAddress() {
return ShadowBounds.memory_limit - 1;
}
+uptr GetMaxVirtualAddress() {
+ return GetMaxUserVirtualAddress();
+}
+
static void *DoAnonymousMmapOrDie(uptr size, const char *mem_type,
bool raw_report, bool die_for_nomem) {
size = RoundUpTo(size, PAGE_SIZE);
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc
index ea1e795..6274b24 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cc
@@ -954,7 +954,7 @@ static uptr GetKernelAreaSize() {
}
#endif // SANITIZER_WORDSIZE == 32
-uptr GetMaxUserVirtualAddress() {
+uptr GetMaxVirtualAddress() {
#if SANITIZER_NETBSD && defined(__x86_64__)
return 0x7f7ffffff000ULL; // (0x00007f8000000000 - PAGE_SIZE)
#elif SANITIZER_WORDSIZE == 64
@@ -978,15 +978,21 @@ uptr GetMaxUserVirtualAddress() {
# if defined(__s390__)
return (1ULL << 31) - 1; // 0x7fffffff;
# else
- uptr res = (1ULL << 32) - 1; // 0xffffffff;
- if (!common_flags()->full_address_space)
- res -= GetKernelAreaSize();
- CHECK_LT(reinterpret_cast<uptr>(&res), res);
- return res;
+ return (1ULL << 32) - 1; // 0xffffffff;
# endif
#endif // SANITIZER_WORDSIZE
}
+uptr GetMaxUserVirtualAddress() {
+ uptr addr = GetMaxVirtualAddress();
+#if SANITIZER_WORDSIZE == 32 && !defined(__s390__)
+ if (!common_flags()->full_address_space)
+ addr -= GetKernelAreaSize();
+ CHECK_LT(reinterpret_cast<uptr>(&addr), addr);
+#endif
+ return addr;
+}
+
uptr GetPageSize() {
// Android post-M sysconf(_SC_PAGESIZE) crashes if called from .preinit_array.
#if SANITIZER_ANDROID
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cc b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cc
index 284b6cf..195a52c 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cc
@@ -864,6 +864,10 @@ uptr GetMaxUserVirtualAddress() {
#endif // SANITIZER_WORDSIZE
}
+uptr GetMaxVirtualAddress() {
+ return GetMaxUserVirtualAddress();
+}
+
uptr FindAvailableMemoryRange(uptr shadow_size,
uptr alignment,
uptr left_padding,
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_win.cc b/compiler-rt/lib/sanitizer_common/sanitizer_win.cc
index 1186971..dee1add 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_win.cc
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_win.cc
@@ -70,6 +70,10 @@ uptr GetMaxUserVirtualAddress() {
return (uptr)si.lpMaximumApplicationAddress;
}
+uptr GetMaxVirtualAddress() {
+ return GetMaxUserVirtualAddress();
+}
+
bool FileExists(const char *filename) {
return ::GetFileAttributesA(filename) != INVALID_FILE_ATTRIBUTES;
}
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 3d50007..33e0206 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -136,8 +136,8 @@ static const char *const kAsanUnregisterElfGlobalsName =
static const char *const kAsanPoisonGlobalsName = "__asan_before_dynamic_init";
static const char *const kAsanUnpoisonGlobalsName = "__asan_after_dynamic_init";
static const char *const kAsanInitName = "__asan_init";
-static const char *const kAsanVersionCheckName =
- "__asan_version_mismatch_check_v8";
+static const char *const kAsanVersionCheckNamePrefix =
+ "__asan_version_mismatch_check_v";
static const char *const kAsanPtrCmp = "__sanitizer_ptr_cmp";
static const char *const kAsanPtrSub = "__sanitizer_ptr_sub";
static const char *const kAsanHandleNoReturnName = "__asan_handle_no_return";
@@ -207,6 +207,12 @@ static cl::opt<bool> ClForceDynamicShadow(
cl::desc("Load shadow address into a local variable for each function"),
cl::Hidden, cl::init(false));
+static cl::opt<bool>
+ ClWithIfunc("asan-with-ifunc",
+ cl::desc("Access dynamic shadow through an ifunc global on "
+ "platforms that support this"),
+ cl::Hidden, cl::init(false));
+
// This flag limits the number of instructions to be instrumented
// in any given BB. Normally, this should be set to unlimited (INT_MAX),
// but due to http://llvm.org/bugs/show_bug.cgi?id=12652 we temporary
@@ -447,10 +453,14 @@ private:
/// This struct defines the shadow mapping using the rule:
/// shadow = (mem >> Scale) ADD-or-OR Offset.
+/// If InGlobal is true, then
+/// extern char __asan_shadow[];
+/// shadow = (mem >> Scale) + &__asan_shadow
struct ShadowMapping {
int Scale;
uint64_t Offset;
bool OrShadowOffset;
+ bool InGlobal;
};
} // end anonymous namespace
@@ -472,6 +482,7 @@ static ShadowMapping getShadowMapping(Triple &TargetTriple, int LongSize,
TargetTriple.getArch() == Triple::mipsel;
bool IsMIPS64 = TargetTriple.getArch() == Triple::mips64 ||
TargetTriple.getArch() == Triple::mips64el;
+ bool IsArmOrThumb = TargetTriple.isARM() || TargetTriple.isThumb();
bool IsAArch64 = TargetTriple.getArch() == Triple::aarch64;
bool IsWindows = TargetTriple.isOSWindows();
bool IsFuchsia = TargetTriple.isOSFuchsia();
@@ -479,10 +490,8 @@ static ShadowMapping getShadowMapping(Triple &TargetTriple, int LongSize,
ShadowMapping Mapping;
if (LongSize == 32) {
- // Android is always PIE, which means that the beginning of the address
- // space is always available.
if (IsAndroid)
- Mapping.Offset = 0;
+ Mapping.Offset = kDynamicShadowSentinel;
else if (IsMIPS32)
Mapping.Offset = kMIPS32_ShadowOffset32;
else if (IsFreeBSD)
@@ -550,6 +559,7 @@ static ShadowMapping getShadowMapping(Triple &TargetTriple, int LongSize,
Mapping.OrShadowOffset = !IsAArch64 && !IsPPC64 && !IsSystemZ && !IsPS4CPU &&
!(Mapping.Offset & (Mapping.Offset - 1)) &&
Mapping.Offset != kDynamicShadowSentinel;
+ Mapping.InGlobal = ClWithIfunc && IsAndroid && IsArmOrThumb;
return Mapping;
}
@@ -672,6 +682,7 @@ private:
DominatorTree *DT;
Function *AsanHandleNoReturnFunc;
Function *AsanPtrCmpFunction, *AsanPtrSubFunction;
+ Constant *AsanShadowGlobal;
// These arrays is indexed by AccessIsWrite, Experiment and log2(AccessSize).
Function *AsanErrorCallback[2][2][kNumberOfAccessSizes];
@@ -744,6 +755,7 @@ private:
size_t MinRedzoneSizeForGlobal() const {
return RedzoneSizeForScale(Mapping.Scale);
}
+ int GetAsanVersion(const Module &M) const;
GlobalsMetadata GlobalsMD;
bool CompileKernel;
@@ -1109,6 +1121,11 @@ Value *AddressSanitizer::memToShadow(Value *Shadow, IRBuilder<> &IRB) {
if (Mapping.Offset == 0) return Shadow;
// (Shadow >> scale) | offset
Value *ShadowBase;
+ if (Mapping.InGlobal)
+ return IRB.CreatePtrToInt(
+ IRB.CreateGEP(AsanShadowGlobal,
+ {ConstantInt::get(IntptrTy, 0), Shadow}),
+ IntptrTy);
if (LocalDynamicShadow)
ShadowBase = LocalDynamicShadow;
else
@@ -2156,6 +2173,16 @@ bool AddressSanitizerModule::InstrumentGlobals(IRBuilder<> &IRB, Module &M, bool
return true;
}
+int AddressSanitizerModule::GetAsanVersion(const Module &M) const {
+ int LongSize = M.getDataLayout().getPointerSizeInBits();
+ bool isAndroid = Triple(M.getTargetTriple()).isAndroid();
+ int Version = 8;
+ // 32-bit Android is one version ahead because of the switch to dynamic
+ // shadow.
+ Version += (LongSize == 32 && isAndroid);
+ return Version;
+}
+
bool AddressSanitizerModule::runOnModule(Module &M) {
C = &(M.getContext());
int LongSize = M.getDataLayout().getPointerSizeInBits();
@@ -2169,9 +2196,11 @@ bool AddressSanitizerModule::runOnModule(Module &M) {
// Create a module constructor. A destructor is created lazily because not all
// platforms, and not all modules need it.
+ std::string VersionCheckName =
+ kAsanVersionCheckNamePrefix + std::to_string(GetAsanVersion(M));
std::tie(AsanCtorFunction, std::ignore) = createSanitizerCtorAndInitFunctions(
M, kAsanModuleCtorName, kAsanInitName, /*InitArgTypes=*/{},
- /*InitArgs=*/{}, kAsanVersionCheckName);
+ /*InitArgs=*/{}, VersionCheckName);
bool CtorComdat = true;
bool Changed = false;
@@ -2270,6 +2299,9 @@ void AddressSanitizer::initializeCallbacks(Module &M) {
EmptyAsm = InlineAsm::get(FunctionType::get(IRB.getVoidTy(), false),
StringRef(""), StringRef(""),
/*hasSideEffects=*/true);
+ if (Mapping.InGlobal)
+ AsanShadowGlobal = M.getOrInsertGlobal("__asan_shadow",
+ ArrayType::get(IRB.getInt8Ty(), 0));
}
// virtual
@@ -2311,7 +2343,7 @@ bool AddressSanitizer::maybeInsertAsanInitAtFunctionEntry(Function &F) {
void AddressSanitizer::maybeInsertDynamicShadowAtFunctionEntry(Function &F) {
// Generate code only when dynamic addressing is needed.
- if (Mapping.Offset != kDynamicShadowSentinel)
+ if (Mapping.Offset != kDynamicShadowSentinel || Mapping.InGlobal)
return;
IRBuilder<> IRB(&F.front().front());
diff --git a/llvm/test/Instrumentation/AddressSanitizer/with-ifunc.ll b/llvm/test/Instrumentation/AddressSanitizer/with-ifunc.ll
new file mode 100644
index 0000000..bb7a9ca3
--- /dev/null
+++ b/llvm/test/Instrumentation/AddressSanitizer/with-ifunc.ll
@@ -0,0 +1,30 @@
+; Test -asan-force-dynamic-shadow flag.
+;
+; RUN: opt -asan -asan-module -S -asan-with-ifunc=1 < %s | FileCheck %s --check-prefixes=CHECK,CHECK-IFUNC
+; RUN: opt -asan -asan-module -S -asan-with-ifunc=0 < %s | FileCheck %s --check-prefixes=CHECK,CHECK-NOIFUNC
+
+target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
+target triple = "armv7--linux-android"
+
+; CHECK-IFUNC: @__asan_shadow = external global [0 x i8]
+; CHECK-NOIFUNC: @__asan_shadow_memory_dynamic_address = external global i32
+
+define i32 @test_load(i32* %a) sanitize_address {
+; First instrumentation in the function must be to load the dynamic shadow
+; address into a local variable.
+; CHECK-LABEL: @test_load
+; CHECK: entry:
+
+; CHECK-IFUNC-NEXT: %[[A:[^ ]*]] = ptrtoint i32* %a to i32
+; CHECK-IFUNC-NEXT: %[[B:[^ ]*]] = lshr i32 %[[A]], 3
+; CHECK-IFUNC-NEXT: %[[C:[^ ]*]] = getelementptr [0 x i8], [0 x i8]* @__asan_shadow, i32 0, i32 %[[B]]
+
+; CHECK-NOIFUNC-NEXT: %[[SHADOW:[^ ]*]] = load i32, i32* @__asan_shadow_memory_dynamic_address
+; CHECK-NOIFUNC-NEXT: %[[A:[^ ]*]] = ptrtoint i32* %a to i32
+; CHECK-NOIFUNC-NEXT: %[[B:[^ ]*]] = lshr i32 %[[A]], 3
+; CHECK-NOIFUNC-NEXT: %[[C:[^ ]*]] = add i32 %[[B]], %[[SHADOW]]
+
+entry:
+ %x = load i32, i32* %a, align 4
+ ret i32 %x
+}