diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
index cf6622bddddd4..44513b4b7e2dc 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets
@@ -158,6 +158,7 @@ The .NET Foundation licenses this file to you under the MIT license.
+
diff --git a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
index 22af5f44eba20..f2ca92a8aab85 100644
--- a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@@ -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)
diff --git a/src/coreclr/nativeaot/Runtime/amd64/ThunkPoolThunks.S b/src/coreclr/nativeaot/Runtime/amd64/ThunkPoolThunks.S
new file mode 100644
index 0000000000000..259d698002b49
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/amd64/ThunkPoolThunks.S
@@ -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
+
+//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 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
diff --git a/src/coreclr/nativeaot/Runtime/arm64/ThunkPoolThunks.S b/src/coreclr/nativeaot/Runtime/arm64/ThunkPoolThunks.S
new file mode 100644
index 0000000000000..d93d9f959506e
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/arm64/ThunkPoolThunks.S
@@ -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
+
+//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 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
diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
index b658171ee7771..136be651f4462 100644
--- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
+++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp
@@ -54,6 +54,11 @@
#include
#endif
+#ifdef TARGET_APPLE
+#include
+#include
+#endif
+
using std::nullptr_t;
#define PalRaiseFailFastException RaiseFailFastException
@@ -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
@@ -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)
diff --git a/src/tests/Directory.Build.targets b/src/tests/Directory.Build.targets
index 0a26b5826c4bf..6d84421d94698 100644
--- a/src/tests/Directory.Build.targets
+++ b/src/tests/Directory.Build.targets
@@ -641,8 +641,8 @@
- $ORIGIN/..
- @executable_path/..
+ @executable_path/..
+ $ORIGIN/..