Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit d289ccc

Browse files
authored
Port to Release/3.1 - enabling MHR support (#26803)
* Move JIT_WriteBarrier that is modified at runtime to a dynamically allocated memory instead of making a page in libcoreclr.dylib RWX. * Fix JIT_Stelem_Ref calls to JIT_WriteBarrier * Update PAL to add MEM_JIT flag for allocations and reservations of executable memory. * Update native runtime in EH and stack unwinding areas so that it can unwind from the write barrier copy. That code has no unwind info, so without special handling, runtime would not be able to unwind from it.
1 parent 281a383 commit d289ccc

File tree

13 files changed

+240
-39
lines changed

13 files changed

+240
-39
lines changed

clrdefinitions.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,7 @@ if(WIN32)
241241
add_definitions(-DFEATURE_DATABREAKPOINT)
242242
endif(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386)
243243
endif(WIN32)
244+
245+
if(CLR_CMAKE_PLATFORM_DARWIN)
246+
add_definitions(-DFEATURE_WRITEBARRIER_COPY)
247+
endif(CLR_CMAKE_PLATFORM_DARWIN)

src/pal/src/include/pal/utils.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ void UTIL_SetLastErrorFromMach(kern_return_t MachReturn);
182182

183183
#endif //HAVE_VM_ALLOCATE
184184

185+
BOOL IsRunningOnMojaveHardenedRuntime();
186+
185187
#ifdef __cplusplus
186188
}
187189
#endif // __cplusplus

src/pal/src/map/map.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,17 +2157,36 @@ MAPmmapAndRecord(
21572157
_ASSERTE(pPEBaseAddress != NULL);
21582158

21592159
PAL_ERROR palError = NO_ERROR;
2160-
LPVOID pvBaseAddress = NULL;
2161-
21622160
off_t adjust = offset & (GetVirtualPageSize() - 1);
2161+
LPVOID pvBaseAddress = static_cast<char *>(addr) - adjust;
21632162

2164-
pvBaseAddress = mmap(static_cast<char *>(addr) - adjust, len + adjust, prot, flags, fd, offset - adjust);
2165-
if (MAP_FAILED == pvBaseAddress)
2163+
#ifdef __APPLE__
2164+
if ((prot & PROT_EXEC) != 0 && IsRunningOnMojaveHardenedRuntime())
21662165
{
2167-
ERROR_(LOADER)( "mmap failed with code %d: %s.\n", errno, strerror( errno ) );
2168-
palError = FILEGetLastErrorFromErrno();
2166+
// Mojave hardened runtime doesn't allow executable mappings of a file. So we have to create an
2167+
// anonymous mapping and read the file contents into it instead.
2168+
2169+
// Set the requested mapping with forced PROT_WRITE to ensure data from the file can be read there,
2170+
// read the data in and finally remove the forced PROT_WRITE
2171+
if ((mprotect(pvBaseAddress, len + adjust, prot | PROT_WRITE) == -1) ||
2172+
(pread(fd, pvBaseAddress, len + adjust, offset - adjust) == -1) ||
2173+
(((prot & PROT_WRITE) == 0) && mprotect(pvBaseAddress, len + adjust, prot) == -1))
2174+
{
2175+
palError = FILEGetLastErrorFromErrno();
2176+
}
21692177
}
21702178
else
2179+
#endif
2180+
{
2181+
pvBaseAddress = mmap(static_cast<char *>(addr) - adjust, len + adjust, prot, flags, fd, offset - adjust);
2182+
if (MAP_FAILED == pvBaseAddress)
2183+
{
2184+
ERROR_(LOADER)( "mmap failed with code %d: %s.\n", errno, strerror( errno ) );
2185+
palError = FILEGetLastErrorFromErrno();
2186+
}
2187+
}
2188+
2189+
if (NO_ERROR == palError)
21712190
{
21722191
palError = MAPRecordMapping(pMappingObject, pPEBaseAddress, pvBaseAddress, len, prot);
21732192
if (NO_ERROR != palError)
@@ -2359,7 +2378,14 @@ void * MAPMapPEFile(HANDLE hFile)
23592378
#endif // FEATURE_ENABLE_NO_ADDRESS_SPACE_RANDOMIZATION
23602379
// MAC64 requires we pass MAP_SHARED (or MAP_PRIVATE) flags - otherwise, the call is failed.
23612380
// Refer to mmap documentation at http://www.manpagez.com/man/2/mmap/ for details.
2362-
loadedBase = mmap(usedBaseAddr, virtualSize, PROT_NONE, MAP_ANON|MAP_PRIVATE, -1, 0);
2381+
int mapFlags = MAP_ANON|MAP_PRIVATE;
2382+
#ifdef __APPLE__
2383+
if (IsRunningOnMojaveHardenedRuntime())
2384+
{
2385+
mapFlags |= MAP_JIT;
2386+
}
2387+
#endif // __APPLE__
2388+
loadedBase = mmap(usedBaseAddr, virtualSize, PROT_NONE, mapFlags, -1, 0);
23632389
}
23642390

23652391
if (MAP_FAILED == loadedBase)

src/pal/src/map/virtual.cpp

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,10 @@ static LPVOID VIRTUALReserveMemory(
916916
if (pRetVal == NULL)
917917
{
918918
// Try to reserve memory from the OS
919+
if ((flProtect & 0xff) == PAGE_EXECUTE_READWRITE)
920+
{
921+
flAllocationType |= MEM_RESERVE_EXECUTABLE;
922+
}
919923
pRetVal = ReserveVirtualMemory(pthrCurrent, (LPVOID)StartBoundary, MemSize, flAllocationType);
920924
}
921925

@@ -969,24 +973,7 @@ static LPVOID ReserveVirtualMemory(
969973

970974
// Most platforms will only commit memory if it is dirtied,
971975
// so this should not consume too much swap space.
972-
int mmapFlags = 0;
973-
974-
#if HAVE_VM_ALLOCATE
975-
// Allocate with vm_allocate first, then map at the fixed address.
976-
int result = vm_allocate(mach_task_self(),
977-
&StartBoundary,
978-
MemSize,
979-
((LPVOID) StartBoundary != nullptr) ? FALSE : TRUE);
980-
981-
if (result != KERN_SUCCESS)
982-
{
983-
ERROR("vm_allocate failed to allocated the requested region!\n");
984-
pthrCurrent->SetLastError(ERROR_INVALID_ADDRESS);
985-
return nullptr;
986-
}
987-
988-
mmapFlags |= MAP_FIXED;
989-
#endif // HAVE_VM_ALLOCATE
976+
int mmapFlags = MAP_ANON | MAP_PRIVATE;
990977

991978
if ((fAllocationType & MEM_LARGE_PAGES) != 0)
992979
{
@@ -1001,7 +988,12 @@ static LPVOID ReserveVirtualMemory(
1001988
#endif
1002989
}
1003990

1004-
mmapFlags |= MAP_ANON | MAP_PRIVATE;
991+
#ifdef __APPLE__
992+
if ((fAllocationType & MEM_RESERVE_EXECUTABLE) && IsRunningOnMojaveHardenedRuntime())
993+
{
994+
mmapFlags |= MAP_JIT;
995+
}
996+
#endif
1005997

1006998
LPVOID pRetVal = mmap((LPVOID) StartBoundary,
1007999
MemSize,
@@ -1014,10 +1006,6 @@ static LPVOID ReserveVirtualMemory(
10141006
{
10151007
ERROR( "Failed due to insufficient memory.\n" );
10161008

1017-
#if HAVE_VM_ALLOCATE
1018-
vm_deallocate(mach_task_self(), StartBoundary, MemSize);
1019-
#endif // HAVE_VM_ALLOCATE
1020-
10211009
pthrCurrent->SetLastError(ERROR_NOT_ENOUGH_MEMORY);
10221010
return nullptr;
10231011
}
@@ -2160,7 +2148,7 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory()
21602148
// Do actual memory reservation.
21612149
do
21622150
{
2163-
m_startAddress = ReserveVirtualMemory(pthrCurrent, (void*)preferredStartAddress, sizeOfAllocation, 0 /* fAllocationType */);
2151+
m_startAddress = ReserveVirtualMemory(pthrCurrent, (void*)preferredStartAddress, sizeOfAllocation, MEM_RESERVE_EXECUTABLE);
21642152
if (m_startAddress != nullptr)
21652153
{
21662154
break;
@@ -2190,7 +2178,7 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory()
21902178
// - The code heap allocator for the JIT can allocate from this address space. Beyond this reservation, one can use
21912179
// the COMPlus_CodeHeapReserveForJumpStubs environment variable to reserve space for jump stubs.
21922180
sizeOfAllocation = MaxExecutableMemorySize;
2193-
m_startAddress = ReserveVirtualMemory(pthrCurrent, nullptr, sizeOfAllocation, 0 /* fAllocationType */);
2181+
m_startAddress = ReserveVirtualMemory(pthrCurrent, nullptr, sizeOfAllocation, MEM_RESERVE_EXECUTABLE);
21942182
if (m_startAddress == nullptr)
21952183
{
21962184
return;

src/pal/src/misc/utils.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ SET_DEFAULT_DEBUG_CHANNEL(MISC); // some headers have code with asserts, so do t
2626
#include <mach/message.h>
2727
#endif //HAVE_VM_ALLOCATE
2828

29+
#include <sys/mman.h>
30+
2931
#include "pal/utils.h"
3032
#include "pal/file.h"
3133

@@ -323,3 +325,41 @@ void UTIL_SetLastErrorFromMach(kern_return_t MachReturn)
323325
}
324326
#endif //HAVE_VM_ALLOCATE
325327

328+
#ifdef __APPLE__
329+
330+
/*++
331+
Function:
332+
IsRunningOnMojaveHardenedRuntime() - Test if the current process is running on Mojave hardened runtime
333+
--*/
334+
BOOL IsRunningOnMojaveHardenedRuntime()
335+
{
336+
static volatile int isRunningOnMojaveHardenedRuntime = -1;
337+
338+
if (isRunningOnMojaveHardenedRuntime == -1)
339+
{
340+
BOOL mhrDetected = FALSE;
341+
int pageSize = sysconf(_SC_PAGE_SIZE);
342+
// Try to map a page with read-write-execute protection. It should fail on Mojave hardened runtime.
343+
void* testPage = mmap(NULL, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
344+
if (testPage == MAP_FAILED && (errno == EACCES))
345+
{
346+
// The mapping has failed with EACCES, check if making the same mapping with MAP_JIT flag works
347+
testPage = mmap(NULL, pageSize, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_JIT, -1, 0);
348+
if (testPage != MAP_FAILED)
349+
{
350+
mhrDetected = TRUE;
351+
}
352+
}
353+
354+
if (testPage != MAP_FAILED)
355+
{
356+
munmap(testPage, pageSize);
357+
}
358+
359+
isRunningOnMojaveHardenedRuntime = (int)mhrDetected;
360+
}
361+
362+
return (BOOL)isRunningOnMojaveHardenedRuntime;
363+
}
364+
365+
#endif // __APPLE__

src/vm/amd64/jithelpers_fast.S

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ LEAF_ENTRY JIT_CheckedWriteBarrier, _TEXT
3737
.byte 0x72, 0x0e
3838
PREPARE_EXTERNAL_VAR g_highest_address, rax
3939
cmp rdi, [rax]
40+
41+
#ifdef FEATURE_WRITEBARRIER_COPY
42+
// jnb NotInHeap
43+
.byte 0x73, 0x06
44+
jmp [rip + C_FUNC(JIT_WriteBarrier_Loc)]
45+
#else
4046
// jnb NotInHeap
4147
.byte 0x73, 0x02
42-
43-
// call C_FUNC(JIT_WriteBarrier)
48+
// jmp C_FUNC(JIT_WriteBarrier)
4449
.byte 0xeb, 0x05
50+
#endif
4551

4652
NotInHeap:
4753
// See comment above about possible AV
@@ -388,6 +394,13 @@ LEAF_ENTRY JIT_ByRefWriteBarrier, _TEXT
388394
ret
389395
LEAF_END_MARKED JIT_ByRefWriteBarrier, _TEXT
390396

397+
#ifdef FEATURE_WRITEBARRIER_COPY
398+
// When JIT_WriteBarrier is copied into an allocated page, the JIT_Stelem_Ref and its
399+
// helpers use this global variable to jump to it. This variable is set in InitThreadManager.
400+
.global _JIT_WriteBarrier_Loc
401+
.zerofill __DATA,__common,_JIT_WriteBarrier_Loc,8,3
402+
#endif // FEATURE_WRITEBARRIER_COPY
403+
391404
// TODO: put definition for this in asmconstants.h
392405
#define CanCast 1
393406

@@ -424,7 +437,11 @@ LEAF_ENTRY JIT_Stelem_Ref, _TEXT
424437
mov rsi, rdx
425438

426439
// JIT_WriteBarrier(Object** dst, Object* src)
440+
#ifdef FEATURE_WRITEBARRIER_COPY
441+
jmp [rip + C_FUNC(JIT_WriteBarrier_Loc)]
442+
#else
427443
jmp C_FUNC(JIT_WriteBarrier)
444+
#endif
428445

429446
LOCAL_LABEL(AssigningNull):
430447
// write barrier is not needed for assignment of NULL references
@@ -478,7 +495,11 @@ LEAF_ENTRY JIT_Stelem_Ref__ObjIsInstanceOfNoGC_Helper, _TEXT
478495
mov rsi, rdx
479496

480497
// JIT_WriteBarrier(Object** dst, Object* src)
498+
#ifdef FEATURE_WRITEBARRIER_COPY
499+
jmp [rip + C_FUNC(JIT_WriteBarrier_Loc)]
500+
#else
481501
jmp C_FUNC(JIT_WriteBarrier)
502+
#endif
482503

483504
LOCAL_LABEL(NeedCheck):
484505
jmp C_FUNC(JIT_Stelem_Ref__ArrayStoreCheck_Helper)
@@ -510,5 +531,9 @@ LEAF_ENTRY JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT
510531
RESET_FRAME_WITH_RBP
511532
512533
// JIT_WriteBarrier(Object** dst, Object* src)
534+
#ifdef FEATURE_WRITEBARRIER_COPY
535+
jmp [rip + C_FUNC(JIT_WriteBarrier_Loc)]
536+
#else
513537
jmp C_FUNC(JIT_WriteBarrier)
538+
#endif
514539
LEAF_END JIT_Stelem_Ref__ArrayStoreCheck_Helper, _TEXT

src/vm/amd64/jitinterfaceamd64.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,10 @@ PBYTE WriteBarrierManager::CalculatePatchLocation(LPVOID base, LPVOID label, int
275275
// the label should always come after the entrypoint for this funtion
276276
_ASSERTE_ALL_BUILDS("clr/src/VM/AMD64/JITinterfaceAMD64.cpp", (LPBYTE)label > (LPBYTE)base);
277277

278-
return ((LPBYTE)GetEEFuncEntryPoint(JIT_WriteBarrier) + ((LPBYTE)GetEEFuncEntryPoint(label) - (LPBYTE)GetEEFuncEntryPoint(base) + offset));
278+
return (GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier) + ((LPBYTE)GetEEFuncEntryPoint(label) - (LPBYTE)GetEEFuncEntryPoint(base) + offset));
279279
}
280280

281+
281282
int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier, bool isRuntimeSuspended)
282283
{
283284
GCX_MAYBE_COOP_NO_THREAD_BROKEN((!isRuntimeSuspended && GetThread() != NULL));
@@ -293,7 +294,7 @@ int WriteBarrierManager::ChangeWriteBarrierTo(WriteBarrierType newWriteBarrier,
293294

294295
// the memcpy must come before the switch statment because the asserts inside the switch
295296
// are actually looking into the JIT_WriteBarrier buffer
296-
memcpy((PVOID)JIT_WriteBarrier, (LPVOID)GetCurrentWriteBarrierCode(), GetCurrentWriteBarrierSize());
297+
memcpy(GetWriteBarrierCodeLocation((void*)JIT_WriteBarrier), (LPVOID)GetCurrentWriteBarrierCode(), GetCurrentWriteBarrierSize());
297298

298299
switch (newWriteBarrier)
299300
{
@@ -722,7 +723,7 @@ int StompWriteBarrierResize(bool isRuntimeSuspended, bool bReqUpperBoundsCheck)
722723

723724
void FlushWriteBarrierInstructionCache()
724725
{
725-
FlushInstructionCache(GetCurrentProcess(), (PVOID)JIT_WriteBarrier, g_WriteBarrierManager.GetCurrentWriteBarrierSize());
726+
FlushInstructionCache(GetCurrentProcess(), GetWriteBarrierCodeLocation((PVOID)JIT_WriteBarrier), g_WriteBarrierManager.GetCurrentWriteBarrierSize());
726727
}
727728

728729
#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP

src/vm/ceemain.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,12 +510,24 @@ void InitGSCookie()
510510

511511
GSCookie * pGSCookiePtr = GetProcessGSCookiePtr();
512512

513+
#ifdef FEATURE_PAL
514+
// On Unix, the GS cookie is stored in a read only data segment
515+
DWORD newProtection = PAGE_READWRITE;
516+
#else // FEATURE_PAL
517+
DWORD newProtection = PAGE_EXECUTE_READWRITE;
518+
#endif // !FEATURE_PAL
519+
513520
DWORD oldProtection;
514-
if(!ClrVirtualProtect((LPVOID)pGSCookiePtr, sizeof(GSCookie), PAGE_EXECUTE_READWRITE, &oldProtection))
521+
if(!ClrVirtualProtect((LPVOID)pGSCookiePtr, sizeof(GSCookie), newProtection, &oldProtection))
515522
{
516523
ThrowLastError();
517524
}
518525

526+
#ifdef FEATURE_PAL
527+
// PAL layer is unable to extract old protection for regions that were not allocated using VirtualAlloc
528+
oldProtection = PAGE_READONLY;
529+
#endif // FEATURE_PAL
530+
519531
#ifndef FEATURE_PAL
520532
// The GSCookie cannot be in a writeable page
521533
assert(((oldProtection & (PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READWRITE|

src/vm/excep.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6792,6 +6792,17 @@ AdjustContextForWriteBarrier(
67926792
{
67936793
WRAPPER_NO_CONTRACT;
67946794

6795+
PCODE ip = GetIP(pContext);
6796+
6797+
#ifdef FEATURE_WRITEBARRIER_COPY
6798+
if (IsIPInWriteBarrierCodeCopy(ip))
6799+
{
6800+
// Pretend we were executing the barrier function at its original location so that the unwinder can unwind the frame
6801+
ip = AdjustWriteBarrierIP(ip);
6802+
SetIP(pContext, ip);
6803+
}
6804+
#endif // FEATURE_WRITEBARRIER_COPY
6805+
67956806
#ifdef FEATURE_DATABREAKPOINT
67966807

67976808
// If pExceptionRecord is null, it means it is called from EEDbgInterfaceImpl::AdjustContextForWriteBarrierForDebugger()
@@ -6802,7 +6813,6 @@ AdjustContextForWriteBarrier(
68026813

68036814
if (pExceptionRecord == nullptr)
68046815
{
6805-
PCODE ip = GetIP(pContext);
68066816
#if defined(_TARGET_X86_)
68076817
bool withinWriteBarrierGroup = ((ip >= (PCODE) JIT_WriteBarrierGroup) && (ip <= (PCODE) JIT_WriteBarrierGroup_End));
68086818
bool withinPatchedWriteBarrierGroup = ((ip >= (PCODE) JIT_PatchedWriteBarrierGroup) && (ip <= (PCODE) JIT_PatchedWriteBarrierGroup_End));

0 commit comments

Comments
 (0)