Skip to content

Commit

Permalink
W^X support (#54954)
Browse files Browse the repository at this point in the history
* W^X support

This change is the last part of enabling the W^X support. It adds the
actual executable allocator that handles all double mapped memory
allocations and creating the writeable mappings.

The platform specific functionality is placed in a new minipal that is
going to be a basis for future removal of Windows APIs usage from the
native runtime.

The last state of the change was tested on all the platforms that we
support using coreclr pri 1 tests with both the W^X enabled and disabled
using the COMPlus_EnableWriteXorExecute variable.

The debugger changes were tested using the managed debugger testing
suite on Windows x64, x86 and on Apple Silicon so far. Further testing
on other platforms is in progress.

* Replace LeafLock in UMEntryThunkFreeList by a new lock

* Also allocate LoaderHeapFreeBlock from regular heap.

* Set the W^X default to disabled
  • Loading branch information
janvorli authored Jul 11, 2021
1 parent 83a4d3c commit 6a47ecf
Show file tree
Hide file tree
Showing 70 changed files with 2,258 additions and 786 deletions.
3 changes: 3 additions & 0 deletions src/coreclr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ add_subdirectory(pal/prebuilt/inc)

add_subdirectory(debug/debug-pal)

add_subdirectory(minipal)

if(CLR_CMAKE_TARGET_WIN32)
add_subdirectory(gc/sample)
endif()
Expand Down Expand Up @@ -171,6 +173,7 @@ include_directories("classlibnative/cryptography")
include_directories("classlibnative/inc")
include_directories("${GENERATED_INCLUDE_DIR}")
include_directories("hosts/inc")
include_directories("minipal")

if(CLR_CMAKE_TARGET_WIN32 AND FEATURE_EVENT_TRACE)
include_directories("${GENERATED_INCLUDE_DIR}/etw")
Expand Down
4 changes: 0 additions & 4 deletions src/coreclr/clrdefinitions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,6 @@ if(CLR_CMAKE_TARGET_WIN32)
endif(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386)
endif(CLR_CMAKE_TARGET_WIN32)

if(CLR_CMAKE_TARGET_OSX)
add_definitions(-DFEATURE_WRITEBARRIER_COPY)
endif(CLR_CMAKE_TARGET_OSX)

if (NOT CLR_CMAKE_TARGET_ARCH_I386 OR NOT CLR_CMAKE_TARGET_WIN32)
add_compile_definitions($<$<NOT:$<BOOL:$<TARGET_PROPERTY:IGNORE_DEFAULT_TARGET_ARCH>>>:FEATURE_EH_FUNCLETS>)
endif (NOT CLR_CMAKE_TARGET_ARCH_I386 OR NOT CLR_CMAKE_TARGET_WIN32)
Expand Down
9 changes: 8 additions & 1 deletion src/coreclr/debug/ee/arm64/arm64walker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,14 @@ BYTE* NativeWalker::SetupOrSimulateInstructionForPatchSkip(T_CONTEXT * context,
{
CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, 0xd503201f); //Add Nop in buffer

m_pSharedPatchBypassBuffer->RipTargetFixup = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook
#if defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<UINT_PTR> ripTargetFixupWriterHolder(&m_pSharedPatchBypassBuffer->RipTargetFixup, sizeof(UINT_PTR));
UINT_PTR *pRipTargetFixupRW = ripTargetFixupWriterHolder.GetRW();
#else // HOST_OSX && HOST_ARM64
UINT_PTR *pRipTargetFixupRW = &m_pSharedPatchBypassBuffer->RipTargetFixup;
#endif // HOST_OSX && HOST_ARM64

*pRipTargetFixupRW = ip; //Control Flow simulation alone is done DebuggerPatchSkip::TriggerExceptionHook
LOG((LF_CORDB, LL_INFO100000, "Arm64Walker::Simulate opcode: %x is a Control Flow instr \n", opcode));

if (walk == WALK_CALL) //initialize Lr
Expand Down
47 changes: 30 additions & 17 deletions src/coreclr/debug/ee/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,13 @@ SharedPatchBypassBuffer* DebuggerControllerPatch::GetOrCreateSharedPatchBypassBu
if (m_pSharedPatchBypassBuffer == NULL)
{
void *pSharedPatchBypassBufferRX = g_pDebugger->GetInteropSafeExecutableHeap()->Alloc(sizeof(SharedPatchBypassBuffer));
#if defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<SharedPatchBypassBuffer> sharedPatchBypassBufferWriterHolder((SharedPatchBypassBuffer*)pSharedPatchBypassBufferRX, sizeof(SharedPatchBypassBuffer));
new (sharedPatchBypassBufferWriterHolder.GetRW()) SharedPatchBypassBuffer();
void *pSharedPatchBypassBufferRW = sharedPatchBypassBufferWriterHolder.GetRW();
#else // HOST_OSX && HOST_ARM64
void *pSharedPatchBypassBufferRW = pSharedPatchBypassBufferRX;
#endif // HOST_OSX && HOST_ARM64
new (pSharedPatchBypassBufferRW) SharedPatchBypassBuffer();
m_pSharedPatchBypassBuffer = (SharedPatchBypassBuffer*)pSharedPatchBypassBufferRX;

_ASSERTE(m_pSharedPatchBypassBuffer);
Expand Down Expand Up @@ -4351,7 +4356,15 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread,
//

m_pSharedPatchBypassBuffer = patch->GetOrCreateSharedPatchBypassBuffer();
BYTE* patchBypass = m_pSharedPatchBypassBuffer->PatchBypass;
#if defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<SharedPatchBypassBuffer> sharedPatchBypassBufferWriterHolder((SharedPatchBypassBuffer*)m_pSharedPatchBypassBuffer, sizeof(SharedPatchBypassBuffer));
SharedPatchBypassBuffer *pSharedPatchBypassBufferRW = sharedPatchBypassBufferWriterHolder.GetRW();
#else // HOST_OSX && HOST_ARM64
SharedPatchBypassBuffer *pSharedPatchBypassBufferRW = m_pSharedPatchBypassBuffer;
#endif // HOST_OSX && HOST_ARM64

BYTE* patchBypassRX = m_pSharedPatchBypassBuffer->PatchBypass;
BYTE* patchBypassRW = pSharedPatchBypassBufferRW->PatchBypass;
LOG((LF_CORDB, LL_INFO10000, "DPS::DPS: Patch skip for opcode 0x%.4x at address %p buffer allocated at 0x%.8x\n", patch->opcode, patch->address, m_pSharedPatchBypassBuffer));

// Copy the instruction block over to the patch skip
Expand All @@ -4367,19 +4380,19 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread,
// the 2nd skip executes the new jump-stamp code and not the original method prologue code. Copying
// the code every time ensures that we have the most up-to-date version of the code in the buffer.
_ASSERTE( patch->IsBound() );
CopyInstructionBlock(patchBypass, (const BYTE *)patch->address);
CopyInstructionBlock(patchBypassRW, (const BYTE *)patch->address);

// Technically, we could create a patch skipper for an inactive patch, but we rely on the opcode being
// set here.
_ASSERTE( patch->IsActivated() );
CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypass, patch->opcode);
CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)patchBypassRW, patch->opcode);

LOG((LF_CORDB, LL_EVERYTHING, "SetInstruction was called\n"));
//
// Look at instruction to get some attributes
//

NativeWalker::DecodeInstructionForPatchSkip(patchBypass, &(m_instrAttrib));
NativeWalker::DecodeInstructionForPatchSkip(patchBypassRX, &(m_instrAttrib));

#if defined(TARGET_AMD64)

Expand All @@ -4395,33 +4408,33 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread,
// Populate the RIP-relative buffer with the current value if needed
//

BYTE* bufferBypass = m_pSharedPatchBypassBuffer->BypassBuffer;
BYTE* bufferBypassRW = pSharedPatchBypassBufferRW->BypassBuffer;

// Overwrite the *signed* displacement.
int dwOldDisp = *(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]);
int dwOldDisp = *(int*)(&patchBypassRX[m_instrAttrib.m_dwOffsetToDisp]);
int dwNewDisp = offsetof(SharedPatchBypassBuffer, BypassBuffer) -
(offsetof(SharedPatchBypassBuffer, PatchBypass) + m_instrAttrib.m_cbInstr);
*(int*)(&patchBypass[m_instrAttrib.m_dwOffsetToDisp]) = dwNewDisp;
*(int*)(&patchBypassRW[m_instrAttrib.m_dwOffsetToDisp]) = dwNewDisp;

// This could be an LEA, which we'll just have to change into a MOV
// and copy the original address
if (((patchBypass[0] == 0x4C) || (patchBypass[0] == 0x48)) && (patchBypass[1] == 0x8d))
if (((patchBypassRX[0] == 0x4C) || (patchBypassRX[0] == 0x48)) && (patchBypassRX[1] == 0x8d))
{
patchBypass[1] = 0x8b; // MOV reg, mem
patchBypassRW[1] = 0x8b; // MOV reg, mem
_ASSERTE((int)sizeof(void*) <= SharedPatchBypassBuffer::cbBufferBypass);
*(void**)bufferBypass = (void*)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
*(void**)bufferBypassRW = (void*)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
}
else
{
_ASSERTE(m_instrAttrib.m_cOperandSize <= SharedPatchBypassBuffer::cbBufferBypass);
// Copy the data into our buffer.
memcpy(bufferBypass, patch->address + m_instrAttrib.m_cbInstr + dwOldDisp, m_instrAttrib.m_cOperandSize);
memcpy(bufferBypassRW, patch->address + m_instrAttrib.m_cbInstr + dwOldDisp, m_instrAttrib.m_cOperandSize);

if (m_instrAttrib.m_fIsWrite)
{
// save the actual destination address and size so when we TriggerSingleStep() we can update the value
m_pSharedPatchBypassBuffer->RipTargetFixup = (UINT_PTR)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
m_pSharedPatchBypassBuffer->RipTargetFixupSize = m_instrAttrib.m_cOperandSize;
pSharedPatchBypassBufferRW->RipTargetFixup = (UINT_PTR)(patch->address + m_instrAttrib.m_cbInstr + dwOldDisp);
pSharedPatchBypassBufferRW->RipTargetFixupSize = m_instrAttrib.m_cOperandSize;
}
}
}
Expand Down Expand Up @@ -4490,17 +4503,17 @@ DebuggerPatchSkip::DebuggerPatchSkip(Thread *thread,
#else // FEATURE_EMULATE_SINGLESTEP

#ifdef TARGET_ARM64
patchBypass = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode);
patchBypassRX = NativeWalker::SetupOrSimulateInstructionForPatchSkip(context, m_pSharedPatchBypassBuffer, (const BYTE *)patch->address, patch->opcode);
#endif //TARGET_ARM64

//set eip to point to buffer...
SetIP(context, (PCODE)patchBypass);
SetIP(context, (PCODE)patchBypassRX);

if (context ==(T_CONTEXT*) &c)
thread->SetThreadContext(&c);


LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypass, patch->opcode));
LOG((LF_CORDB, LL_INFO10000, "DPS::DPS Bypass at 0x%p for opcode %p \n", patchBypassRX, patch->opcode));

//
// Turn on single step (if the platform supports it) so we can
Expand Down
18 changes: 16 additions & 2 deletions src/coreclr/debug/ee/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,28 @@ class SharedPatchBypassBuffer

LONG AddRef()
{
LONG newRefCount = InterlockedIncrement(&m_refCount);
#if !defined(DACCESS_COMPILE) && defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<LONG> refCountWriterHolder(&m_refCount, sizeof(LONG));
LONG *pRefCountRW = refCountWriterHolder.GetRW();
#else // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64
LONG *pRefCountRW = &m_refCount;
#endif // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64

LONG newRefCount = InterlockedIncrement(pRefCountRW);
_ASSERTE(newRefCount > 0);
return newRefCount;
}

LONG Release()
{
LONG newRefCount = InterlockedDecrement(&m_refCount);
#if !DACCESS_COMPILE && HOST_OSX && HOST_ARM64
ExecutableWriterHolder<LONG> refCountWriterHolder(&m_refCount, sizeof(LONG));
LONG *pRefCountRW = refCountWriterHolder.GetRW();
#else // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64
LONG *pRefCountRW = &m_refCount;
#endif // !DACCESS_COMPILE && HOST_OSX && HOST_ARM64

LONG newRefCount = InterlockedDecrement(pRefCountRW);
_ASSERTE(newRefCount >= 0);

if (newRefCount == 0)
Expand Down
30 changes: 24 additions & 6 deletions src/coreclr/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1317,13 +1317,19 @@ DebuggerEval::DebuggerEval(CONTEXT * pContext, DebuggerIPCE_FuncEvalInfo * pEval

// Allocate the breakpoint instruction info in executable memory.
void *bpInfoSegmentRX = g_pDebugger->GetInteropSafeExecutableHeap()->Alloc(sizeof(DebuggerEvalBreakpointInfoSegment));

#if !defined(DBI_COMPILE) && !defined(DACCESS_COMPILE) && defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<DebuggerEvalBreakpointInfoSegment> bpInfoSegmentWriterHolder((DebuggerEvalBreakpointInfoSegment*)bpInfoSegmentRX, sizeof(DebuggerEvalBreakpointInfoSegment));
new (bpInfoSegmentWriterHolder.GetRW()) DebuggerEvalBreakpointInfoSegment(this);
DebuggerEvalBreakpointInfoSegment *bpInfoSegmentRW = bpInfoSegmentWriterHolder.GetRW();
#else // !DBI_COMPILE && !DACCESS_COMPILE && HOST_OSX && HOST_ARM64
DebuggerEvalBreakpointInfoSegment *bpInfoSegmentRW = (DebuggerEvalBreakpointInfoSegment*)bpInfoSegmentRX;
#endif // !DBI_COMPILE && !DACCESS_COMPILE && HOST_OSX && HOST_ARM64
new (bpInfoSegmentRW) DebuggerEvalBreakpointInfoSegment(this);
m_bpInfoSegment = (DebuggerEvalBreakpointInfoSegment*)bpInfoSegmentRX;

// This must be non-zero so that the saved opcode is non-zero, and on IA64 we want it to be 0x16
// so that we can have a breakpoint instruction in any slot in the bundle.
bpInfoSegmentWriterHolder.GetRW()->m_breakpointInstruction[0] = 0x16;
bpInfoSegmentRW->m_breakpointInstruction[0] = 0x16;
#if defined(TARGET_ARM)
USHORT *bp = (USHORT*)&m_bpInfoSegment->m_breakpointInstruction;
*bp = CORDbg_BREAK_INSTRUCTION;
Expand Down Expand Up @@ -16234,6 +16240,7 @@ void Debugger::ReleaseDebuggerDataLock(Debugger *pDebugger)
}
#endif // DACCESS_COMPILE

#ifndef DACCESS_COMPILE
/* ------------------------------------------------------------------------ *
* Functions for DebuggerHeap executable memory allocations
* ------------------------------------------------------------------------ */
Expand Down Expand Up @@ -16378,6 +16385,7 @@ void* DebuggerHeapExecutableMemoryAllocator::GetPointerToChunkWithUsageUpdate(De

return page->GetPointerToChunk(chunkNumber);
}
#endif // DACCESS_COMPILE

/* ------------------------------------------------------------------------ *
* DebuggerHeap impl
Expand Down Expand Up @@ -16412,7 +16420,7 @@ void DebuggerHeap::Destroy()
m_hHeap = NULL;
}
#endif
#ifndef HOST_WINDOWS
#if !defined(HOST_WINDOWS) && !defined(DACCESS_COMPILE)
if (m_execMemAllocator != NULL)
{
delete m_execMemAllocator;
Expand All @@ -16439,6 +16447,8 @@ HRESULT DebuggerHeap::Init(BOOL fExecutable)
}
CONTRACTL_END;

#ifndef DACCESS_COMPILE

// Have knob catch if we don't want to lazy init the debugger.
_ASSERTE(!g_DbgShouldntUseDebugger);
m_fExecutable = fExecutable;
Expand Down Expand Up @@ -16472,7 +16482,9 @@ HRESULT DebuggerHeap::Init(BOOL fExecutable)
return E_OUTOFMEMORY;
}
}
#endif
#endif

#endif // !DACCESS_COMPILE

return S_OK;
}
Expand Down Expand Up @@ -16549,7 +16561,10 @@ void *DebuggerHeap::Alloc(DWORD size)
size += sizeof(InteropHeapCanary);
#endif

void *ret;
void *ret = NULL;

#ifndef DACCESS_COMPILE

#ifdef USE_INTEROPSAFE_HEAP
_ASSERTE(m_hHeap != NULL);
ret = ::HeapAlloc(m_hHeap, HEAP_ZERO_MEMORY, size);
Expand Down Expand Up @@ -16585,7 +16600,7 @@ void *DebuggerHeap::Alloc(DWORD size)
InteropHeapCanary * pCanary = InteropHeapCanary::GetFromRawAddr(ret);
ret = pCanary->GetUserAddr();
#endif

#endif // !DACCESS_COMPILE
return ret;
}

Expand Down Expand Up @@ -16638,6 +16653,8 @@ void DebuggerHeap::Free(void *pMem)
}
CONTRACTL_END;

#ifndef DACCESS_COMPILE

#ifdef USE_INTEROPSAFE_CANARY
// Check for canary

Expand Down Expand Up @@ -16673,6 +16690,7 @@ void DebuggerHeap::Free(void *pMem)
#endif // HOST_WINDOWS
}
#endif
#endif // !DACCESS_COMPILE
}

#ifndef DACCESS_COMPILE
Expand Down
32 changes: 26 additions & 6 deletions src/coreclr/debug/ee/debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,8 @@ constexpr uint64_t CHUNKS_PER_DEBUGGERHEAP=(DEBUGGERHEAP_PAGESIZE / EXPECTED_CHU
constexpr uint64_t MAX_CHUNK_MASK=((1ull << CHUNKS_PER_DEBUGGERHEAP) - 1);
constexpr uint64_t BOOKKEEPING_CHUNK_MASK (1ull << (CHUNKS_PER_DEBUGGERHEAP - 1));

#ifndef DACCESS_COMPILE

// Forward declaration
struct DebuggerHeapExecutableMemoryPage;

Expand Down Expand Up @@ -1110,8 +1112,13 @@ struct DECLSPEC_ALIGN(DEBUGGERHEAP_PAGESIZE) DebuggerHeapExecutableMemoryPage

inline void SetNextPage(DebuggerHeapExecutableMemoryPage* nextPage)
{
#if defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<DebuggerHeapExecutableMemoryPage> debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage));
debuggerHeapPageWriterHolder.GetRW()->chunks[0].bookkeeping.nextPage = nextPage;
DebuggerHeapExecutableMemoryPage *pHeapPageRW = debuggerHeapPageWriterHolder.GetRW();
#else
DebuggerHeapExecutableMemoryPage *pHeapPageRW = this;
#endif
pHeapPageRW->chunks[0].bookkeeping.nextPage = nextPage;
}

inline uint64_t GetPageOccupancy() const
Expand All @@ -1124,8 +1131,13 @@ struct DECLSPEC_ALIGN(DEBUGGERHEAP_PAGESIZE) DebuggerHeapExecutableMemoryPage
// Can't unset the bookmark chunk!
ASSERT((newOccupancy & BOOKKEEPING_CHUNK_MASK) != 0);
ASSERT(newOccupancy <= MAX_CHUNK_MASK);
#if defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<DebuggerHeapExecutableMemoryPage> debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage));
debuggerHeapPageWriterHolder.GetRW()->chunks[0].bookkeeping.pageOccupancy = newOccupancy;
DebuggerHeapExecutableMemoryPage *pHeapPageRW = debuggerHeapPageWriterHolder.GetRW();
#else
DebuggerHeapExecutableMemoryPage *pHeapPageRW = this;
#endif
pHeapPageRW->chunks[0].bookkeeping.pageOccupancy = newOccupancy;
}

inline void* GetPointerToChunk(int chunkNum) const
Expand All @@ -1136,14 +1148,18 @@ struct DECLSPEC_ALIGN(DEBUGGERHEAP_PAGESIZE) DebuggerHeapExecutableMemoryPage

DebuggerHeapExecutableMemoryPage()
{
ExecutableWriterHolder<DebuggerHeapExecutableMemoryPage> debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage));

SetPageOccupancy(BOOKKEEPING_CHUNK_MASK); // only the first bit is set.
#if defined(HOST_OSX) && defined(HOST_ARM64)
ExecutableWriterHolder<DebuggerHeapExecutableMemoryPage> debuggerHeapPageWriterHolder(this, sizeof(DebuggerHeapExecutableMemoryPage));
DebuggerHeapExecutableMemoryPage *pHeapPageRW = debuggerHeapPageWriterHolder.GetRW();
#else
DebuggerHeapExecutableMemoryPage *pHeapPageRW = this;
#endif
for (uint8_t i = 1; i < CHUNKS_PER_DEBUGGERHEAP; i++)
{
ASSERT(i != 0);
debuggerHeapPageWriterHolder.GetRW()->chunks[i].data.startOfPage = this;
debuggerHeapPageWriterHolder.GetRW()->chunks[i].data.chunkNumber = i;
pHeapPageRW->chunks[i].data.startOfPage = this;
pHeapPageRW->chunks[i].data.chunkNumber = i;
}
}

Expand Down Expand Up @@ -1190,6 +1206,8 @@ class DebuggerHeapExecutableMemoryAllocator
Crst m_execMemAllocMutex;
};

#endif // DACCESS_COMPILE

// ------------------------------------------------------------------------ *
// DebuggerHeap class
// For interop debugging, we need a heap that:
Expand All @@ -1201,6 +1219,8 @@ class DebuggerHeapExecutableMemoryAllocator
#define USE_INTEROPSAFE_HEAP
#endif

class DebuggerHeapExecutableMemoryAllocator;

class DebuggerHeap
{
public:
Expand Down
Loading

0 comments on commit 6a47ecf

Please sign in to comment.