Skip to content

Commit

Permalink
[NativeAOT] Implement thunk page generation and mapping for iOS-like …
Browse files Browse the repository at this point in the history
…platforms (#82317)

* NativeAOT: Implement thunk page generation and mapping for iOS-like platforms

* Use minipal_getexepath instead of libproc

* Reimplement PalAllocateThunksFromTemplate to work inside shared libraries

* Specify correct rpath in tests for all Apple platforms
  • Loading branch information
filipnavara authored Feb 25, 2023
1 parent cabaab5 commit 75a32b8
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<LinkerArg Include="-L/usr/local/lib -lgssapi_krb5" Condition="'$(_targetOS)' == 'freebsd'" />
<!-- FreeBSD's inotify is an installed package and not found in default libraries -->
<LinkerArg Include="-L/usr/local/lib -linotify" Condition="'$(_targetOS)' == 'freebsd'" />
<LinkerArg Include="-Wl,-segprot,__THUNKS,rx,rx" Condition="'$(_IsiOSLikePlatform)' == 'true'" />

<LinkerArg Include="@(NativeFramework->'-framework %(Identity)')" Condition="'$(_IsApplePlatform)' == 'true'" />
</ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ project(Runtime)
# Include auto-generated files on include path
set(CMAKE_INCLUDE_CURRENT_DIR ON)

add_definitions(-DFEATURE_RX_THUNKS)
if (CLR_CMAKE_TARGET_APPLE AND NOT CLR_CMAKE_TARGET_OSX)
list(APPEND RUNTIME_SOURCES_ARCH_ASM
${ARCH_SOURCES_DIR}/ThunkPoolThunks.${ASM_SUFFIX}
)
else()
add_definitions(-DFEATURE_RX_THUNKS)
endif()

if (CLR_CMAKE_TARGET_WIN32)
if (CLR_CMAKE_HOST_ARCH_ARM OR CLR_CMAKE_HOST_ARCH_ARM64)
Expand Down
126 changes: 126 additions & 0 deletions src/coreclr/nativeaot/Runtime/amd64/ThunkPoolThunks.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.intel_syntax noprefix
#include <unixasmmacros.inc>

//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DATA SECTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

#define THUNK_CODESIZE 0x10 // 3 instructions, 4 bytes each (and we also have 4 bytes of padding)
#define THUNK_DATASIZE 0x10 // 2 qwords

#define POINTER_SIZE 0x08

#define THUNKS_MAP_SIZE 0x8000

#define PAGE_SIZE 0x1000
#define PAGE_SIZE_LOG2 12

// THUNK_POOL_NUM_THUNKS_PER_PAGE = min(PAGE_SIZE / THUNK_CODESIZE, (PAGE_SIZE - POINTER_SIZE) / THUNK_DATASIZE)
#define THUNK_POOL_NUM_THUNKS_PER_PAGE 0xff

//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Thunk Pages ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

.macro THUNKS_PAGE_BLOCK
IN_PAGE_INDEX = 0
.rept THUNK_POOL_NUM_THUNKS_PER_PAGE

.p2align 4

// Set r10 to the address of the current thunk's data block.
lea r10, [rip + THUNKS_MAP_SIZE - 7]

// jump to the location pointed at by the last qword in the data page
jmp qword ptr[r10 + PAGE_SIZE - POINTER_SIZE - (THUNK_DATASIZE * IN_PAGE_INDEX)]

IN_PAGE_INDEX = IN_PAGE_INDEX + 1
.endr
.endm

#ifdef TARGET_APPLE
// Create two segments in the Mach-O file:
// __THUNKS with executable permissions
// __THUNKS_DATA with read/write permissions

.section __THUNKS,__thunks,regular,pure_instructions
.p2align PAGE_SIZE_LOG2
PATCH_LABEL ThunkPool
.rept (THUNKS_MAP_SIZE / PAGE_SIZE)
.p2align PAGE_SIZE_LOG2
THUNKS_PAGE_BLOCK
.endr
.p2align PAGE_SIZE_LOG2
.section __THUNKS_DATA,__thunks,regular
.p2align PAGE_SIZE_LOG2
.space THUNKS_MAP_SIZE
.p2align PAGE_SIZE_LOG2
#else
#error Unsupported OS
#endif

//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; General Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

//
// IntPtr RhpGetThunksBase()
//
LEAF_ENTRY RhpGetThunksBase
// Return the address of the first thunk pool to the caller (this is really the base address)
lea rax, [rip + C_FUNC(ThunkPool)]
ret
LEAF_END RhpGetThunksBase

//
// int RhpGetNumThunksPerBlock()
//
LEAF_ENTRY RhpGetNumThunksPerBlock
mov rax, THUNK_POOL_NUM_THUNKS_PER_PAGE
ret
LEAF_END RhpGetNumThunksPerBlock

//
// int RhpGetThunkSize()
//
LEAF_ENTRY RhpGetThunkSize
mov rax, THUNK_CODESIZE
ret
LEAF_END RhpGetThunkSize

//
// int RhpGetNumThunkBlocksPerMapping()
//
LEAF_ENTRY RhpGetNumThunkBlocksPerMapping
mov rax, (THUNKS_MAP_SIZE / PAGE_SIZE)
ret
LEAF_END RhpGetNumThunkBlocksPerMapping

//
// int RhpGetThunkBlockSize
//
LEAF_ENTRY RhpGetThunkBlockSize
mov rax, PAGE_SIZE
ret
LEAF_END RhpGetThunkBlockSize

//
// IntPtr RhpGetThunkDataBlockAddress(IntPtr thunkStubAddress)
//
LEAF_ENTRY RhpGetThunkDataBlockAddress
mov rax, rdi
mov rdi, PAGE_SIZE - 1
not rdi
and rax, rdi
add rax, THUNKS_MAP_SIZE
ret
LEAF_END RhpGetThunkDataBlockAddress

//
// IntPtr RhpGetThunkStubsBlockAddress(IntPtr thunkDataAddress)
//
LEAF_ENTRY RhpGetThunkStubsBlockAddress
mov rax, rdi
mov rdi, PAGE_SIZE - 1
not rdi
and rax, rdi
sub rax, THUNKS_MAP_SIZE
ret
LEAF_END RhpGetThunkStubsBlockAddress
133 changes: 133 additions & 0 deletions src/coreclr/nativeaot/Runtime/arm64/ThunkPoolThunks.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include <unixasmmacros.inc>

//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DATA SECTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

#define THUNK_CODESIZE 0x10 // 3 instructions, 4 bytes each (and we also have 4 bytes of padding)
#define THUNK_DATASIZE 0x10 // 2 qwords

#define POINTER_SIZE 0x08

#define THUNKS_MAP_SIZE 0x8000

#ifdef TARGET_APPLE
#define PAGE_SIZE 0x4000
#define PAGE_SIZE_LOG2 14
#else
#error Unsupported OS
#endif

// THUNK_POOL_NUM_THUNKS_PER_PAGE = min(PAGE_SIZE / THUNK_CODESIZE, (PAGE_SIZE - POINTER_SIZE) / THUNK_DATASIZE)
#define THUNK_POOL_NUM_THUNKS_PER_PAGE 0x3ff

//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Thunk Pages ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

.macro THUNKS_PAGE_BLOCK
IN_PAGE_INDEX = 0
.rept THUNK_POOL_NUM_THUNKS_PER_PAGE

// Set xip0 to the address of the current thunk's data block.
adr xip0, THUNKS_MAP_SIZE

// start : xip0 points to the current thunks first data cell in the data page
// set xip0 to beginning of data page : xip0 <- xip0 - (THUNK_DATASIZE * current thunk's index)
// fix offset to point to last QWROD in page : xip1 <- [xip0 + PAGE_SIZE - POINTER_SIZE]
// tailcall to the location pointed at by the last qword in the data page
ldr xip1, [xip0, #(PAGE_SIZE - POINTER_SIZE - (THUNK_DATASIZE * IN_PAGE_INDEX))]
br xip1

brk 0xf000 // Stubs need to be 16-byte aligned for CFG table. Filling padding with a
// deterministic brk instruction, instead of having it just filled with zeros.

IN_PAGE_INDEX = IN_PAGE_INDEX + 1
.endr
.endm

#ifdef TARGET_APPLE
// Create two segments in the Mach-O file:
// __THUNKS with executable permissions
// __THUNKS_DATA with read/write permissions

.section __THUNKS,__thunks,regular,pure_instructions
.p2align PAGE_SIZE_LOG2
PATCH_LABEL ThunkPool
.rept (THUNKS_MAP_SIZE / PAGE_SIZE)
.p2align PAGE_SIZE_LOG2
THUNKS_PAGE_BLOCK
.endr
.p2align PAGE_SIZE_LOG2
.section __THUNKS_DATA,__thunks,regular
.p2align PAGE_SIZE_LOG2
.space THUNKS_MAP_SIZE
.p2align PAGE_SIZE_LOG2
#else
#error Unsupported OS
#endif

//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; General Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

//
// IntPtr RhpGetThunksBase()
//
LEAF_ENTRY RhpGetThunksBase
// Return the address of the first thunk pool to the caller (this is really the base address)
adrp x0, C_FUNC(ThunkPool)@PAGE
add x0, x0, C_FUNC(ThunkPool)@PAGEOFF
ret
LEAF_END RhpGetThunksBase

//
// int RhpGetNumThunksPerBlock()
//
LEAF_ENTRY RhpGetNumThunksPerBlock
mov x0, THUNK_POOL_NUM_THUNKS_PER_PAGE
ret
LEAF_END RhpGetNumThunksPerBlock

//
// int RhpGetThunkSize()
//
LEAF_ENTRY RhpGetThunkSize
mov x0, THUNK_CODESIZE
ret
LEAF_END RhpGetThunkSize

//
// int RhpGetNumThunkBlocksPerMapping()
//
LEAF_ENTRY RhpGetNumThunkBlocksPerMapping
mov x0, (THUNKS_MAP_SIZE / PAGE_SIZE)
ret
LEAF_END RhpGetNumThunkBlocksPerMapping

//
// int RhpGetThunkBlockSize
//
LEAF_ENTRY RhpGetThunkBlockSize
mov x0, PAGE_SIZE
ret
LEAF_END RhpGetThunkBlockSize

//
// IntPtr RhpGetThunkDataBlockAddress(IntPtr thunkStubAddress)
//
LEAF_ENTRY RhpGetThunkDataBlockAddress
mov x12, PAGE_SIZE - 1
bic x0, x0, x12
mov x12, THUNKS_MAP_SIZE
add x0, x0, x12
ret
LEAF_END RhpGetThunkDataBlockAddress

//
// IntPtr RhpGetThunkStubsBlockAddress(IntPtr thunkDataAddress)
//
LEAF_ENTRY RhpGetThunkStubsBlockAddress
mov x12, PAGE_SIZE - 1
bic x0, x0, x12
mov x12, THUNKS_MAP_SIZE
sub x0, x0, x12
ret
LEAF_END RhpGetThunkStubsBlockAddress
60 changes: 59 additions & 1 deletion src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
#include <time.h>
#endif

#ifdef TARGET_APPLE
#include <minipal/getexepath.h>
#include <mach-o/getsect.h>
#endif

using std::nullptr_t;

#define PalRaiseFailFastException RaiseFailFastException
Expand Down Expand Up @@ -488,14 +493,63 @@ extern "C" bool PalDetachThread(void* thread)
}

#if !defined(USE_PORTABLE_HELPERS) && !defined(FEATURE_RX_THUNKS)

#ifdef TARGET_APPLE
static const struct section_64 *thunks_section;
static const struct section_64 *thunks_data_section;
#endif

REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDLE hTemplateModule, uint32_t templateRva, size_t templateSize, void** newThunksOut)
{
#ifdef TARGET_APPLE
int f;
Dl_info info;

int st = dladdr((const void*)hTemplateModule, &info);
if (st == 0)
{
return UInt32_FALSE;
}

f = open(info.dli_fname, O_RDONLY);
if (f < 0)
{
return UInt32_FALSE;
}

// NOTE: We ignore templateRva since we would need to convert it to file offset
// and templateSize is useless too. Instead we read the sections from the
// executable and determine the size from them.
if (thunks_section == NULL)
{
const struct mach_header_64 *hdr = (const struct mach_header_64 *)hTemplateModule;
thunks_section = getsectbynamefromheader_64(hdr, "__THUNKS", "__thunks");
thunks_data_section = getsectbynamefromheader_64(hdr, "__THUNKS_DATA", "__thunks");
}

*newThunksOut = mmap(
NULL,
thunks_section->size + thunks_data_section->size,
PROT_READ | PROT_EXEC,
MAP_PRIVATE,
f,
thunks_section->offset);
close(f);

return *newThunksOut == NULL ? UInt32_FALSE : UInt32_TRUE;
#else
PORTABILITY_ASSERT("UNIXTODO: Implement this function");
#endif
}

REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalFreeThunksFromTemplate(void *pBaseAddress)
{
#ifdef TARGET_APPLE
int ret = munmap(pBaseAddress, thunks_section->size + thunks_data_section->size);
return ret == 0 ? UInt32_TRUE : UInt32_FALSE;
#else
PORTABILITY_ASSERT("UNIXTODO: Implement this function");
#endif
}
#endif // !USE_PORTABLE_HELPERS && !FEATURE_RX_THUNKS

Expand All @@ -506,7 +560,11 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalMarkThunksAsValidCallTargets(
int thunkBlockSize,
int thunkBlocksPerMapping)
{
return UInt32_TRUE;
int ret = mprotect(
(void*)((uintptr_t)virtualAddress + (thunkBlocksPerMapping * OS_PAGE_SIZE)),
thunkBlocksPerMapping * OS_PAGE_SIZE,
PROT_READ | PROT_WRITE);
return ret == 0 ? UInt32_TRUE : UInt32_FALSE;
}

REDHAWK_PALEXPORT void REDHAWK_PALAPI PalSleep(uint32_t milliseconds)
Expand Down
4 changes: 2 additions & 2 deletions src/tests/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,8 @@
<PropertyGroup Condition="'$(TestBuildMode)' == 'nativeaot'">
<!-- NativeAOT compiled output is placed into a 'native' subdirectory: we need to tweak
rpath so that the test can load its native library dependencies if there's any -->
<IlcRPath Condition="'$(TargetOS)' != 'osx'">$ORIGIN/..</IlcRPath>
<IlcRPath Condition="'$(TargetOS)' == 'osx'">@executable_path/..</IlcRPath>
<IlcRPath Condition="'$(TargetOS)' == 'osx' or '$(TargetsAppleMobile)' == 'true'">@executable_path/..</IlcRPath>
<IlcRPath Condition="'$(IlcRPath)' == ''">$ORIGIN/..</IlcRPath>

<!-- Works around "Error: Native compilation can run on x64 and arm64 hosts only"
Microsoft.NETCore.Native.targets expect IlcHostArch to be set but it doesn't have to -->
Expand Down

0 comments on commit 75a32b8

Please sign in to comment.