I spent some time debugging over the weekend and figured out the necessary changes for supporting Apple Silicon. I can try to restructure the code a bit and send in a pull request, but I just wanted to write down the things that I encountered.
W^X
Apple requires all pages to be W^X, and requires pages to be mapped in with the MAP_JIT flag.
- When applying detours, the relevant pages are already mapped in with the
MAP_JIT flag. We just need to do the following:
- Call
pthread_jit_write_protect_np(false) (to enable write and disable execute)
- Make the changes
- Call
pthread_jit_write_protect_np(true) (to disable write and enable execute)
- When creating the trampoline for the JIT hook, we need to
mmap in memory with the MAP_JIT flag instead of calling the equivalent of malloc.
The first item has to be done entirely in native code because calling pthread_jit_write_protect_np(false) from managed code causes a bus error -- this is because pthread_jit_write_protect_np controls every page!
libclrjit.dylib
libclrjit.dylib doesn't seem to be one of the modules loaded into the process, which means that the JIT hooks don't work. This is, however, a pretty easy change to make.
Virtual Method Function Pointers
Hooking virtual methods seems to be a bit fraught (with arm64, at least?) because the function pointers that we get with DetourRuntimeNETPlatform.GetFunctionPointer don't seem to related to the entries found in the vtable, and I'm not exactly sure why this isn't the case for amd64...
My quick and dirty solution was to just read the vtable to get the function pointer:
var methodTable = method.DeclaringType.TypeHandle.Value;
var methodDesc = method.MethodHandle.Value;
var m_wSlotNumber = (ushort) Marshal.ReadInt16((IntPtr) (methodDesc.ToInt64() + 4));
var m_wFlags = (ushort) Marshal.ReadInt16((IntPtr) (methodDesc.ToInt64() + 6));
if ((m_wFlags & 0x8000) == 0) {
m_wSlotNumber &= 0xff;
}
var chunkNumber = m_wSlotNumber / 8;
var chunkOffset = m_wSlotNumber % 8;
var vtablePtr = Marshal.ReadIntPtr((IntPtr) (methodTable.ToInt64() + 0x40 + 8 * chunkNumber));
var vtableSlotPtr = (IntPtr) (vtablePtr.ToInt64() + 8 * chunkOffset);
ptr = Marshal.ReadIntPtr(vtableSlotPtr);
Additionally, because the devirtualization code expects the entries to point to things that look like precode, I had to change the detour code a bit to match.
I spent some time debugging over the weekend and figured out the necessary changes for supporting Apple Silicon. I can try to restructure the code a bit and send in a pull request, but I just wanted to write down the things that I encountered.
W^X
Apple requires all pages to be W^X, and requires pages to be mapped in with the
MAP_JITflag.MAP_JITflag. We just need to do the following:pthread_jit_write_protect_np(false)(to enable write and disable execute)pthread_jit_write_protect_np(true)(to disable write and enable execute)mmapin memory with theMAP_JITflag instead of calling the equivalent ofmalloc.The first item has to be done entirely in native code because calling
pthread_jit_write_protect_np(false)from managed code causes a bus error -- this is becausepthread_jit_write_protect_npcontrols every page!libclrjit.dylib
libclrjit.dylibdoesn't seem to be one of the modules loaded into the process, which means that the JIT hooks don't work. This is, however, a pretty easy change to make.Virtual Method Function Pointers
Hooking virtual methods seems to be a bit fraught (with arm64, at least?) because the function pointers that we get with
DetourRuntimeNETPlatform.GetFunctionPointerdon't seem to related to the entries found in the vtable, and I'm not exactly sure why this isn't the case for amd64...My quick and dirty solution was to just read the vtable to get the function pointer:
Additionally, because the devirtualization code expects the entries to point to things that look like precode, I had to change the detour code a bit to match.