using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Python.Runtime.Platform
{
class NativeCodePageHelper
{
///
/// Initialized by InitializeNativeCodePage.
///
/// This points to a page of memory allocated using mmap or VirtualAlloc
/// (depending on the system), and marked read and execute (not write).
/// Very much on purpose, the page is *not* released on a shutdown and
/// is instead leaked. See the TestDomainReload test case.
///
/// The contents of the page are two native functions: one that returns 0,
/// one that returns 1.
///
/// If python didn't keep its gc list through a Py_Finalize we could remove
/// this entire section.
///
internal static IntPtr NativeCodePage = IntPtr.Zero;
///
/// Structure to describe native code.
///
/// Use NativeCode.Active to get the native code for the current platform.
///
/// Generate the code by creating the following C code:
///
/// int Return0() { return 0; }
/// int Return1() { return 1; }
///
/// Then compiling on the target platform, e.g. with gcc or clang:
/// cc -c -fomit-frame-pointer -O2 foo.c
/// And then analyzing the resulting functions with a hex editor, e.g.:
/// objdump -disassemble foo.o
///
internal class NativeCode
{
///
/// The code, as a string of bytes.
///
public byte[] Code { get; private set; }
///
/// Where does the "return 0" function start?
///
public int Return0 { get; private set; }
///
/// Where does the "return 1" function start?
///
public int Return1 { get; private set; }
public static NativeCode Active
{
get
{
switch (RuntimeInformation.ProcessArchitecture)
{
case Architecture.X86:
return I386;
case Architecture.X64:
return X86_64;
default:
return null;
}
}
}
///
/// Code for x86_64. See the class comment for how it was generated.
///
public static readonly NativeCode X86_64 = new NativeCode()
{
Return0 = 0x10,
Return1 = 0,
Code = new byte[]
{
// First Return1:
0xb8, 0x01, 0x00, 0x00, 0x00, // movl $1, %eax
0xc3, // ret
// Now some padding so that Return0 can be 16-byte-aligned.
// I put Return1 first so there's not as much padding to type in.
0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, // nop
// Now Return0.
0x31, 0xc0, // xorl %eax, %eax
0xc3, // ret
}
};
///
/// Code for X86.
///
/// It's bitwise identical to X86_64, so we just point to it.
///
///
public static readonly NativeCode I386 = X86_64;
}
///
/// Platform-dependent mmap and mprotect.
///
internal interface IMemoryMapper
{
///
/// Map at least numBytes of memory. Mark the page read-write (but not exec).
///
IntPtr MapWriteable(int numBytes);
///
/// Sets the mapped memory to be read-exec (but not write).
///
void SetReadExec(IntPtr mappedMemory, int numBytes);
}
class WindowsMemoryMapper : IMemoryMapper
{
const UInt32 MEM_COMMIT = 0x1000;
const UInt32 MEM_RESERVE = 0x2000;
const UInt32 PAGE_READWRITE = 0x04;
const UInt32 PAGE_EXECUTE_READ = 0x20;
[DllImport("kernel32.dll")]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, UInt32 flAllocationType, UInt32 flProtect);
[DllImport("kernel32.dll")]
static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, UInt32 flNewProtect, out UInt32 lpflOldProtect);
public IntPtr MapWriteable(int numBytes)
{
return VirtualAlloc(IntPtr.Zero, new IntPtr(numBytes),
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
}
public void SetReadExec(IntPtr mappedMemory, int numBytes)
{
UInt32 _;
VirtualProtect(mappedMemory, new IntPtr(numBytes), PAGE_EXECUTE_READ, out _);
}
}
class UnixMemoryMapper : IMemoryMapper
{
const int PROT_READ = 0x1;
const int PROT_WRITE = 0x2;
const int PROT_EXEC = 0x4;
const int MAP_PRIVATE = 0x2;
int MAP_ANONYMOUS
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return 0x20;
}
else
{
// OSX, FreeBSD
return 0x1000;
}
}
}
[DllImport("libc")]
static extern IntPtr mmap(IntPtr addr, IntPtr len, int prot, int flags, int fd, IntPtr offset);
[DllImport("libc")]
static extern int mprotect(IntPtr addr, IntPtr len, int prot);
public IntPtr MapWriteable(int numBytes)
{
// MAP_PRIVATE must be set on linux, even though MAP_ANON implies it.
// It doesn't hurt on darwin, so just do it.
return mmap(IntPtr.Zero, new IntPtr(numBytes), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, IntPtr.Zero);
}
public void SetReadExec(IntPtr mappedMemory, int numBytes)
{
mprotect(mappedMemory, new IntPtr(numBytes), PROT_READ | PROT_EXEC);
}
}
internal static IMemoryMapper CreateMemoryMapper()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return new WindowsMemoryMapper();
}
else
{
// Linux, OSX, FreeBSD
return new UnixMemoryMapper();
}
}
///
/// Initializes the native code page.
///
/// Safe to call if we already initialized (this function is idempotent).
///
///
internal static void InitializeNativeCodePage()
{
// Do nothing if we already initialized.
if (NativeCodePage != IntPtr.Zero)
{
return;
}
// Allocate the page, write the native code into it, then set it
// to be executable.
IMemoryMapper mapper = CreateMemoryMapper();
int codeLength = NativeCode.Active.Code.Length;
NativeCodePage = mapper.MapWriteable(codeLength);
Marshal.Copy(NativeCode.Active.Code, 0, NativeCodePage, codeLength);
mapper.SetReadExec(NativeCodePage, codeLength);
}
}
}