Skip to content

Commit 75a32b8

Browse files
authored
[NativeAOT] Implement thunk page generation and mapping for iOS-like 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
1 parent cabaab5 commit 75a32b8

File tree

6 files changed

+328
-4
lines changed

6 files changed

+328
-4
lines changed

src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Unix.targets

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ The .NET Foundation licenses this file to you under the MIT license.
158158
<LinkerArg Include="-L/usr/local/lib -lgssapi_krb5" Condition="'$(_targetOS)' == 'freebsd'" />
159159
<!-- FreeBSD's inotify is an installed package and not found in default libraries -->
160160
<LinkerArg Include="-L/usr/local/lib -linotify" Condition="'$(_targetOS)' == 'freebsd'" />
161+
<LinkerArg Include="-Wl,-segprot,__THUNKS,rx,rx" Condition="'$(_IsiOSLikePlatform)' == 'true'" />
161162

162163
<LinkerArg Include="@(NativeFramework->'-framework %(Identity)')" Condition="'$(_IsApplePlatform)' == 'true'" />
163164
</ItemGroup>

src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ project(Runtime)
66
# Include auto-generated files on include path
77
set(CMAKE_INCLUDE_CURRENT_DIR ON)
88

9-
add_definitions(-DFEATURE_RX_THUNKS)
9+
if (CLR_CMAKE_TARGET_APPLE AND NOT CLR_CMAKE_TARGET_OSX)
10+
list(APPEND RUNTIME_SOURCES_ARCH_ASM
11+
${ARCH_SOURCES_DIR}/ThunkPoolThunks.${ASM_SUFFIX}
12+
)
13+
else()
14+
add_definitions(-DFEATURE_RX_THUNKS)
15+
endif()
1016

1117
if (CLR_CMAKE_TARGET_WIN32)
1218
if (CLR_CMAKE_HOST_ARCH_ARM OR CLR_CMAKE_HOST_ARCH_ARM64)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
.intel_syntax noprefix
5+
#include <unixasmmacros.inc>
6+
7+
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DATA SECTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
8+
9+
#define THUNK_CODESIZE 0x10 // 3 instructions, 4 bytes each (and we also have 4 bytes of padding)
10+
#define THUNK_DATASIZE 0x10 // 2 qwords
11+
12+
#define POINTER_SIZE 0x08
13+
14+
#define THUNKS_MAP_SIZE 0x8000
15+
16+
#define PAGE_SIZE 0x1000
17+
#define PAGE_SIZE_LOG2 12
18+
19+
// THUNK_POOL_NUM_THUNKS_PER_PAGE = min(PAGE_SIZE / THUNK_CODESIZE, (PAGE_SIZE - POINTER_SIZE) / THUNK_DATASIZE)
20+
#define THUNK_POOL_NUM_THUNKS_PER_PAGE 0xff
21+
22+
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Thunk Pages ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
23+
24+
.macro THUNKS_PAGE_BLOCK
25+
IN_PAGE_INDEX = 0
26+
.rept THUNK_POOL_NUM_THUNKS_PER_PAGE
27+
28+
.p2align 4
29+
30+
// Set r10 to the address of the current thunk's data block.
31+
lea r10, [rip + THUNKS_MAP_SIZE - 7]
32+
33+
// jump to the location pointed at by the last qword in the data page
34+
jmp qword ptr[r10 + PAGE_SIZE - POINTER_SIZE - (THUNK_DATASIZE * IN_PAGE_INDEX)]
35+
36+
IN_PAGE_INDEX = IN_PAGE_INDEX + 1
37+
.endr
38+
.endm
39+
40+
#ifdef TARGET_APPLE
41+
// Create two segments in the Mach-O file:
42+
// __THUNKS with executable permissions
43+
// __THUNKS_DATA with read/write permissions
44+
45+
.section __THUNKS,__thunks,regular,pure_instructions
46+
.p2align PAGE_SIZE_LOG2
47+
PATCH_LABEL ThunkPool
48+
.rept (THUNKS_MAP_SIZE / PAGE_SIZE)
49+
.p2align PAGE_SIZE_LOG2
50+
THUNKS_PAGE_BLOCK
51+
.endr
52+
.p2align PAGE_SIZE_LOG2
53+
.section __THUNKS_DATA,__thunks,regular
54+
.p2align PAGE_SIZE_LOG2
55+
.space THUNKS_MAP_SIZE
56+
.p2align PAGE_SIZE_LOG2
57+
#else
58+
#error Unsupported OS
59+
#endif
60+
61+
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; General Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
62+
63+
//
64+
// IntPtr RhpGetThunksBase()
65+
//
66+
LEAF_ENTRY RhpGetThunksBase
67+
// Return the address of the first thunk pool to the caller (this is really the base address)
68+
lea rax, [rip + C_FUNC(ThunkPool)]
69+
ret
70+
LEAF_END RhpGetThunksBase
71+
72+
//
73+
// int RhpGetNumThunksPerBlock()
74+
//
75+
LEAF_ENTRY RhpGetNumThunksPerBlock
76+
mov rax, THUNK_POOL_NUM_THUNKS_PER_PAGE
77+
ret
78+
LEAF_END RhpGetNumThunksPerBlock
79+
80+
//
81+
// int RhpGetThunkSize()
82+
//
83+
LEAF_ENTRY RhpGetThunkSize
84+
mov rax, THUNK_CODESIZE
85+
ret
86+
LEAF_END RhpGetThunkSize
87+
88+
//
89+
// int RhpGetNumThunkBlocksPerMapping()
90+
//
91+
LEAF_ENTRY RhpGetNumThunkBlocksPerMapping
92+
mov rax, (THUNKS_MAP_SIZE / PAGE_SIZE)
93+
ret
94+
LEAF_END RhpGetNumThunkBlocksPerMapping
95+
96+
//
97+
// int RhpGetThunkBlockSize
98+
//
99+
LEAF_ENTRY RhpGetThunkBlockSize
100+
mov rax, PAGE_SIZE
101+
ret
102+
LEAF_END RhpGetThunkBlockSize
103+
104+
//
105+
// IntPtr RhpGetThunkDataBlockAddress(IntPtr thunkStubAddress)
106+
//
107+
LEAF_ENTRY RhpGetThunkDataBlockAddress
108+
mov rax, rdi
109+
mov rdi, PAGE_SIZE - 1
110+
not rdi
111+
and rax, rdi
112+
add rax, THUNKS_MAP_SIZE
113+
ret
114+
LEAF_END RhpGetThunkDataBlockAddress
115+
116+
//
117+
// IntPtr RhpGetThunkStubsBlockAddress(IntPtr thunkDataAddress)
118+
//
119+
LEAF_ENTRY RhpGetThunkStubsBlockAddress
120+
mov rax, rdi
121+
mov rdi, PAGE_SIZE - 1
122+
not rdi
123+
and rax, rdi
124+
sub rax, THUNKS_MAP_SIZE
125+
ret
126+
LEAF_END RhpGetThunkStubsBlockAddress
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#include <unixasmmacros.inc>
5+
6+
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; DATA SECTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7+
8+
#define THUNK_CODESIZE 0x10 // 3 instructions, 4 bytes each (and we also have 4 bytes of padding)
9+
#define THUNK_DATASIZE 0x10 // 2 qwords
10+
11+
#define POINTER_SIZE 0x08
12+
13+
#define THUNKS_MAP_SIZE 0x8000
14+
15+
#ifdef TARGET_APPLE
16+
#define PAGE_SIZE 0x4000
17+
#define PAGE_SIZE_LOG2 14
18+
#else
19+
#error Unsupported OS
20+
#endif
21+
22+
// THUNK_POOL_NUM_THUNKS_PER_PAGE = min(PAGE_SIZE / THUNK_CODESIZE, (PAGE_SIZE - POINTER_SIZE) / THUNK_DATASIZE)
23+
#define THUNK_POOL_NUM_THUNKS_PER_PAGE 0x3ff
24+
25+
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Thunk Pages ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
26+
27+
.macro THUNKS_PAGE_BLOCK
28+
IN_PAGE_INDEX = 0
29+
.rept THUNK_POOL_NUM_THUNKS_PER_PAGE
30+
31+
// Set xip0 to the address of the current thunk's data block.
32+
adr xip0, THUNKS_MAP_SIZE
33+
34+
// start : xip0 points to the current thunks first data cell in the data page
35+
// set xip0 to beginning of data page : xip0 <- xip0 - (THUNK_DATASIZE * current thunk's index)
36+
// fix offset to point to last QWROD in page : xip1 <- [xip0 + PAGE_SIZE - POINTER_SIZE]
37+
// tailcall to the location pointed at by the last qword in the data page
38+
ldr xip1, [xip0, #(PAGE_SIZE - POINTER_SIZE - (THUNK_DATASIZE * IN_PAGE_INDEX))]
39+
br xip1
40+
41+
brk 0xf000 // Stubs need to be 16-byte aligned for CFG table. Filling padding with a
42+
// deterministic brk instruction, instead of having it just filled with zeros.
43+
44+
IN_PAGE_INDEX = IN_PAGE_INDEX + 1
45+
.endr
46+
.endm
47+
48+
#ifdef TARGET_APPLE
49+
// Create two segments in the Mach-O file:
50+
// __THUNKS with executable permissions
51+
// __THUNKS_DATA with read/write permissions
52+
53+
.section __THUNKS,__thunks,regular,pure_instructions
54+
.p2align PAGE_SIZE_LOG2
55+
PATCH_LABEL ThunkPool
56+
.rept (THUNKS_MAP_SIZE / PAGE_SIZE)
57+
.p2align PAGE_SIZE_LOG2
58+
THUNKS_PAGE_BLOCK
59+
.endr
60+
.p2align PAGE_SIZE_LOG2
61+
.section __THUNKS_DATA,__thunks,regular
62+
.p2align PAGE_SIZE_LOG2
63+
.space THUNKS_MAP_SIZE
64+
.p2align PAGE_SIZE_LOG2
65+
#else
66+
#error Unsupported OS
67+
#endif
68+
69+
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; General Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
70+
71+
//
72+
// IntPtr RhpGetThunksBase()
73+
//
74+
LEAF_ENTRY RhpGetThunksBase
75+
// Return the address of the first thunk pool to the caller (this is really the base address)
76+
adrp x0, C_FUNC(ThunkPool)@PAGE
77+
add x0, x0, C_FUNC(ThunkPool)@PAGEOFF
78+
ret
79+
LEAF_END RhpGetThunksBase
80+
81+
//
82+
// int RhpGetNumThunksPerBlock()
83+
//
84+
LEAF_ENTRY RhpGetNumThunksPerBlock
85+
mov x0, THUNK_POOL_NUM_THUNKS_PER_PAGE
86+
ret
87+
LEAF_END RhpGetNumThunksPerBlock
88+
89+
//
90+
// int RhpGetThunkSize()
91+
//
92+
LEAF_ENTRY RhpGetThunkSize
93+
mov x0, THUNK_CODESIZE
94+
ret
95+
LEAF_END RhpGetThunkSize
96+
97+
//
98+
// int RhpGetNumThunkBlocksPerMapping()
99+
//
100+
LEAF_ENTRY RhpGetNumThunkBlocksPerMapping
101+
mov x0, (THUNKS_MAP_SIZE / PAGE_SIZE)
102+
ret
103+
LEAF_END RhpGetNumThunkBlocksPerMapping
104+
105+
//
106+
// int RhpGetThunkBlockSize
107+
//
108+
LEAF_ENTRY RhpGetThunkBlockSize
109+
mov x0, PAGE_SIZE
110+
ret
111+
LEAF_END RhpGetThunkBlockSize
112+
113+
//
114+
// IntPtr RhpGetThunkDataBlockAddress(IntPtr thunkStubAddress)
115+
//
116+
LEAF_ENTRY RhpGetThunkDataBlockAddress
117+
mov x12, PAGE_SIZE - 1
118+
bic x0, x0, x12
119+
mov x12, THUNKS_MAP_SIZE
120+
add x0, x0, x12
121+
ret
122+
LEAF_END RhpGetThunkDataBlockAddress
123+
124+
//
125+
// IntPtr RhpGetThunkStubsBlockAddress(IntPtr thunkDataAddress)
126+
//
127+
LEAF_ENTRY RhpGetThunkStubsBlockAddress
128+
mov x12, PAGE_SIZE - 1
129+
bic x0, x0, x12
130+
mov x12, THUNKS_MAP_SIZE
131+
sub x0, x0, x12
132+
ret
133+
LEAF_END RhpGetThunkStubsBlockAddress

src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp

+59-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@
5454
#include <time.h>
5555
#endif
5656

57+
#ifdef TARGET_APPLE
58+
#include <minipal/getexepath.h>
59+
#include <mach-o/getsect.h>
60+
#endif
61+
5762
using std::nullptr_t;
5863

5964
#define PalRaiseFailFastException RaiseFailFastException
@@ -488,14 +493,63 @@ extern "C" bool PalDetachThread(void* thread)
488493
}
489494

490495
#if !defined(USE_PORTABLE_HELPERS) && !defined(FEATURE_RX_THUNKS)
496+
497+
#ifdef TARGET_APPLE
498+
static const struct section_64 *thunks_section;
499+
static const struct section_64 *thunks_data_section;
500+
#endif
501+
491502
REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDLE hTemplateModule, uint32_t templateRva, size_t templateSize, void** newThunksOut)
492503
{
504+
#ifdef TARGET_APPLE
505+
int f;
506+
Dl_info info;
507+
508+
int st = dladdr((const void*)hTemplateModule, &info);
509+
if (st == 0)
510+
{
511+
return UInt32_FALSE;
512+
}
513+
514+
f = open(info.dli_fname, O_RDONLY);
515+
if (f < 0)
516+
{
517+
return UInt32_FALSE;
518+
}
519+
520+
// NOTE: We ignore templateRva since we would need to convert it to file offset
521+
// and templateSize is useless too. Instead we read the sections from the
522+
// executable and determine the size from them.
523+
if (thunks_section == NULL)
524+
{
525+
const struct mach_header_64 *hdr = (const struct mach_header_64 *)hTemplateModule;
526+
thunks_section = getsectbynamefromheader_64(hdr, "__THUNKS", "__thunks");
527+
thunks_data_section = getsectbynamefromheader_64(hdr, "__THUNKS_DATA", "__thunks");
528+
}
529+
530+
*newThunksOut = mmap(
531+
NULL,
532+
thunks_section->size + thunks_data_section->size,
533+
PROT_READ | PROT_EXEC,
534+
MAP_PRIVATE,
535+
f,
536+
thunks_section->offset);
537+
close(f);
538+
539+
return *newThunksOut == NULL ? UInt32_FALSE : UInt32_TRUE;
540+
#else
493541
PORTABILITY_ASSERT("UNIXTODO: Implement this function");
542+
#endif
494543
}
495544

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

@@ -506,7 +560,11 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalMarkThunksAsValidCallTargets(
506560
int thunkBlockSize,
507561
int thunkBlocksPerMapping)
508562
{
509-
return UInt32_TRUE;
563+
int ret = mprotect(
564+
(void*)((uintptr_t)virtualAddress + (thunkBlocksPerMapping * OS_PAGE_SIZE)),
565+
thunkBlocksPerMapping * OS_PAGE_SIZE,
566+
PROT_READ | PROT_WRITE);
567+
return ret == 0 ? UInt32_TRUE : UInt32_FALSE;
510568
}
511569

512570
REDHAWK_PALEXPORT void REDHAWK_PALAPI PalSleep(uint32_t milliseconds)

src/tests/Directory.Build.targets

+2-2
Original file line numberDiff line numberDiff line change
@@ -641,8 +641,8 @@
641641
<PropertyGroup Condition="'$(TestBuildMode)' == 'nativeaot'">
642642
<!-- NativeAOT compiled output is placed into a 'native' subdirectory: we need to tweak
643643
rpath so that the test can load its native library dependencies if there's any -->
644-
<IlcRPath Condition="'$(TargetOS)' != 'osx'">$ORIGIN/..</IlcRPath>
645-
<IlcRPath Condition="'$(TargetOS)' == 'osx'">@executable_path/..</IlcRPath>
644+
<IlcRPath Condition="'$(TargetOS)' == 'osx' or '$(TargetsAppleMobile)' == 'true'">@executable_path/..</IlcRPath>
645+
<IlcRPath Condition="'$(IlcRPath)' == ''">$ORIGIN/..</IlcRPath>
646646

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

0 commit comments

Comments
 (0)