Skip to content

[llvm] Implement address sanitizer on AIX #129926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ static const uint64_t kNetBSD_ShadowOffset64 = 1ULL << 46;
static const uint64_t kNetBSDKasan_ShadowOffset64 = 0xdfff900000000000;
static const uint64_t kPS_ShadowOffset64 = 1ULL << 40;
static const uint64_t kWindowsShadowOffset32 = 3ULL << 28;
static const uint64_t kAIXShadowOffset32 = 0x40000000;
// 64-BIT AIX is not yet ready.
static const uint64_t kAIXShadowOffset64 = 0x0a01000000000000ULL;
static const uint64_t kWebAssemblyShadowOffset = 0;

// The shadow memory space is dynamically allocated.
Expand All @@ -128,6 +131,8 @@ static const size_t kMaxStackMallocSize = 1 << 16; // 64K
static const uintptr_t kCurrentStackFrameMagic = 0x41B58AB3;
static const uintptr_t kRetiredStackFrameMagic = 0x45E0360E;

static const uint32_t kAIXHighBits = 6;

const char kAsanModuleCtorName[] = "asan.module_ctor";
const char kAsanModuleDtorName[] = "asan.module_dtor";
static const uint64_t kAsanCtorAndDtorPriority = 1;
Expand Down Expand Up @@ -463,11 +468,14 @@ namespace {

/// This struct defines the shadow mapping using the rule:
/// shadow = (mem >> Scale) ADD-or-OR Offset.
/// However, on 64-bit AIX, we use HighBits to reduce the mapped address space:
/// shadow = ((mem << HighBits) >> (HighBits + Scale)) + Offset
/// If InGlobal is true, then
/// extern char __asan_shadow[];
/// shadow = (mem >> Scale) + &__asan_shadow
struct ShadowMapping {
int Scale;
int HighBits;
uint64_t Offset;
bool OrShadowOffset;
bool InGlobal;
Expand All @@ -487,6 +495,7 @@ static ShadowMapping getShadowMapping(const Triple &TargetTriple, int LongSize,
bool IsLinux = TargetTriple.isOSLinux();
bool IsPPC64 = TargetTriple.getArch() == Triple::ppc64 ||
TargetTriple.getArch() == Triple::ppc64le;
bool IsAIX = TargetTriple.isOSAIX();
bool IsSystemZ = TargetTriple.getArch() == Triple::systemz;
bool IsX86_64 = TargetTriple.getArch() == Triple::x86_64;
bool IsMIPSN32ABI = TargetTriple.isABIN32();
Expand Down Expand Up @@ -525,6 +534,8 @@ static ShadowMapping getShadowMapping(const Triple &TargetTriple, int LongSize,
Mapping.Offset = kDynamicShadowSentinel;
else if (IsWindows)
Mapping.Offset = kWindowsShadowOffset32;
else if (IsAIX)
Mapping.Offset = kAIXShadowOffset32;
else if (IsWasm)
Mapping.Offset = kWebAssemblyShadowOffset;
else
Expand All @@ -534,7 +545,9 @@ static ShadowMapping getShadowMapping(const Triple &TargetTriple, int LongSize,
// space is always available.
if (IsFuchsia)
Mapping.Offset = 0;
else if (IsPPC64)
else if (IsAIX)
Mapping.Offset = kAIXShadowOffset64;
else if (IsPPC64 && !IsAIX)
Mapping.Offset = kPPC64_ShadowOffset64;
else if (IsSystemZ)
Mapping.Offset = kSystemZ_ShadowOffset64;
Expand Down Expand Up @@ -596,13 +609,16 @@ static ShadowMapping getShadowMapping(const Triple &TargetTriple, int LongSize,
// SystemZ, we could OR the constant in a single instruction, but it's more
// efficient to load it once and use indexed addressing.
Mapping.OrShadowOffset = !IsAArch64 && !IsPPC64 && !IsSystemZ && !IsPS &&
!IsRISCV64 && !IsLoongArch64 &&
!IsRISCV64 && !IsLoongArch64 && !IsAIX &&
!(Mapping.Offset & (Mapping.Offset - 1)) &&
Mapping.Offset != kDynamicShadowSentinel;
bool IsAndroidWithIfuncSupport =
IsAndroid && !TargetTriple.isAndroidVersionLT(21);
Mapping.InGlobal = ClWithIfunc && IsAndroidWithIfuncSupport && IsArmOrThumb;

if (IsAIX && LongSize == 64)
Mapping.HighBits = kAIXHighBits;

return Mapping;
}

Expand Down Expand Up @@ -1380,7 +1396,11 @@ static bool isUnsupportedAMDGPUAddrspace(Value *Addr) {

Value *AddressSanitizer::memToShadow(Value *Shadow, IRBuilder<> &IRB) {
// Shadow >> scale
Shadow = IRB.CreateLShr(Shadow, Mapping.Scale);
if (TargetTriple.isOSAIX() && TargetTriple.getArch() == Triple::ppc64)
Shadow = IRB.CreateLShr(IRB.CreateShl(Shadow, Mapping.HighBits),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will ShadowBase has HighBits set?
It possible to avoid that so you don't need to diverge from default implementation?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, does this mean that 0x090100000f000000 will have same shadow as 0x000100000f000000?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I think we have to diverge from the default implementation because the address ranges on 64-bit AIX are large, resulting in a large shadow region, so we use HIGH_BITS to limit it. More details on the mapping:
https://github.com/llvm/llvm-project/blob/e9e590e350df5877d717bb667f20bbc60a9de0ee/compiler-rt/lib/asan/asan_mapping_aix64.h

0x90100000f000000 is mapped to 0x20200001e00000 while 0x100000f000000 is mapped to 0x200001e00000

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still confused with HIGH_BITS purpose.
If we do just:

#define MEM_TO_SHADOW(mem)                                       \
  ((((mem)) >> ((ASAN_SHADOW_SCALE))) + \
   ASAN_SHADOW_OFFSET)

Mapping should be

// Default AIX64 mapping:
// || `[0x0fffff8000000000, 0x0fffffffffffffff]` || HighMem    ||
// || `[0x0c00fff000000000, 0x0c00ffffffffffff]` || HighShadow ||
// || `[0x0b41000000000000, 0x0b41003fffffffff]` || MidShadow  ||
// || `[0x0b21020000000000, 0x0b21020fffffffff]` || Mid2Shadow ||
// || `[0x0b01020000000000, 0x0b01020fffffffff]` || Mid3Shadow ||
// || `[0x0a01000000000000, 0x0a01000fffffffff]` || LowShadow  ||
// || `[0x0a00000000000000, 0x0a0001ffffffffff]` || MidMem     ||
// || `[0x0900100000000000, 0x0900107fffffffff]` || Mid2Mem    ||
// || `[0x0800100000000000, 0x0800107fffffffff]` || Mid3Mem    ||
// || `[0x0000000000000000, 0x0000007fffffffff]` || LowMem     ||

Same amount of shadow. Why this does not work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A large part of the address space is reserved by the OS and not available to the compiler/runtime. The mapping scheme Jake is using is because we have to map the shadow memory into the mmap range from 0x0a00 0000 0000 0000 to 0x0aff ffff ffff ffff.

Mapping.Scale + Mapping.HighBits);
else
Shadow = IRB.CreateLShr(Shadow, Mapping.Scale);
if (Mapping.Offset == 0) return Shadow;
// (Shadow >> scale) | offset
Value *ShadowBase;
Expand Down
21 changes: 21 additions & 0 deletions llvm/test/Instrumentation/AddressSanitizer/mapping-aix.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
; Test shadow memory mapping on AIX

; RUN: opt -passes=asan -mtriple=powerpc64-ibm-aix -S < %s | FileCheck %s -check-prefix=CHECK-64
; RUN: opt -passes=asan -mtriple=powerpc-ibm-aix -S < %s | FileCheck %s -check-prefix=CHECK-32

; CHECK: @test
; On 64-bit AIX, we expect a left shift of 6 (HIGH_BITS) followed by a right shift of 9 (HIGH_BITS
; + ASAN_SHADOW_SCALE) and an offset of 0x0a01000000000000.
; CHECK-64: shl {{.*}} 6
; CHECK-64-NEXT: lshr {{.*}} 9
; CHECK-64-NEXT: add {{.*}} 720857415355990016
; On 32-bit AIX, we expect just a right shift of 3 and an offset of 0x40000000.
; CHECK-32: lshr {{.*}} 3
; CHECK-32-NEXT: add {{.*}} 1073741824
; CHECK: ret

define i32 @test(i32* %a) sanitize_address {
entry:
%tmp1 = load i32, i32* %a, align 4
ret i32 %tmp1
}