From acf6f57e43cb90f49571bedb04f31588c382ac46 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 17 Feb 2023 21:12:09 +0100 Subject: [PATCH 1/4] NativeAOT: Implement thunk page generation and mapping for iOS-like platforms --- .../Microsoft.NETCore.Native.Unix.targets | 1 + .../nativeaot/Runtime/Full/CMakeLists.txt | 8 +- .../nativeaot/Runtime/amd64/ThunkPoolThunks.S | 126 +++++++++++++++++ .../nativeaot/Runtime/arm64/ThunkPoolThunks.S | 133 ++++++++++++++++++ .../nativeaot/Runtime/unix/PalRedhawkUnix.cpp | 62 +++++++- 5 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 src/coreclr/nativeaot/Runtime/amd64/ThunkPoolThunks.S create mode 100644 src/coreclr/nativeaot/Runtime/arm64/ThunkPoolThunks.S 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..1af35445a3e57 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -54,6 +54,12 @@ #include #endif +#ifdef TARGET_APPLE +#include +#include +#include +#endif + using std::nullptr_t; #define PalRaiseFailFastException RaiseFailFastException @@ -488,14 +494,64 @@ 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 + char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; + int ret; + int f; + + // NOTE: We ignore hTemplateModule, it is alwyas the current module + ret = proc_pidpath(getpid(), pathbuf, sizeof(pathbuf)); + if (ret <= 0) + { + return UInt32_FALSE; + } + + f = open(pathbuf, 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) + { + thunks_section = getsectbyname("__THUNKS", "__thunks"); + thunks_data_section = getsectbyname("__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 +562,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) From a1a6193f1095b4b42dc0318a1799d2d9eae44449 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 24 Feb 2023 18:40:31 +0100 Subject: [PATCH 2/4] Use minipal_getexepath instead of libproc --- .../nativeaot/Runtime/unix/PalRedhawkUnix.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 1af35445a3e57..85881dc347fc0 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -55,8 +55,7 @@ #endif #ifdef TARGET_APPLE -#include -#include +#include #include #endif @@ -503,18 +502,13 @@ static const struct section_64 *thunks_data_section; REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDLE hTemplateModule, uint32_t templateRva, size_t templateSize, void** newThunksOut) { #ifdef TARGET_APPLE - char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - int ret; int f; + char *exepath; - // NOTE: We ignore hTemplateModule, it is alwyas the current module - ret = proc_pidpath(getpid(), pathbuf, sizeof(pathbuf)); - if (ret <= 0) - { - return UInt32_FALSE; - } - - f = open(pathbuf, O_RDONLY); + // NOTE: We ignore hTemplateModule, it is always the current module + exepath = minipal_getexepath(); + f = open(exepath, O_RDONLY); + free(exepath); if (f < 0) { return UInt32_FALSE; From 49fb9f7ec339084a2b2ca8d6473e34ff6f4722f8 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 25 Feb 2023 09:48:14 +0100 Subject: [PATCH 3/4] Reimplement PalAllocateThunksFromTemplate to work inside shared libraries --- .../nativeaot/Runtime/unix/PalRedhawkUnix.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 85881dc347fc0..136be651f4462 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -503,12 +503,15 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDL { #ifdef TARGET_APPLE int f; - char *exepath; + Dl_info info; + + int st = dladdr((const void*)hTemplateModule, &info); + if (st == 0) + { + return UInt32_FALSE; + } - // NOTE: We ignore hTemplateModule, it is always the current module - exepath = minipal_getexepath(); - f = open(exepath, O_RDONLY); - free(exepath); + f = open(info.dli_fname, O_RDONLY); if (f < 0) { return UInt32_FALSE; @@ -519,8 +522,9 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDL // executable and determine the size from them. if (thunks_section == NULL) { - thunks_section = getsectbyname("__THUNKS", "__thunks"); - thunks_data_section = getsectbyname("__THUNKS_DATA", "__thunks"); + 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( From 6c447e3b8b8b461c5b9bf15f1bb8a8c8729d960a Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Sat, 18 Feb 2023 21:25:26 +0100 Subject: [PATCH 4/4] Specify correct rpath in tests for all Apple platforms --- src/tests/Directory.Build.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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/..