using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Python.Runtime.Platform
{
class NativeCodePageHelper
{
///
/// Gets the operating system as reported by python's platform.system().
///
public static OperatingSystemType OperatingSystem { get; private set; }
///
/// Gets the operating system as reported by python's platform.system().
///
[Obsolete]
public static string OperatingSystemName => PythonEngine.Platform;
///
/// Gets the machine architecture as reported by python's platform.machine().
///
public static MachineType Machine { get; private set; }/* set in Initialize using python's platform.machine */
///
/// Gets the machine architecture as reported by python's platform.machine().
///
[Obsolete]
public static string MachineName { get; private set; }
///
/// 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;
static readonly Dictionary OperatingSystemTypeMapping = new Dictionary()
{
{ "Windows", OperatingSystemType.Windows },
{ "Darwin", OperatingSystemType.Darwin },
{ "Linux", OperatingSystemType.Linux },
};
///
/// Map lower-case version of the python machine name to the processor
/// type. There are aliases, e.g. x86_64 and amd64 are two names for
/// the same thing. Make sure to lower-case the search string, because
/// capitalization can differ.
///
static readonly Dictionary MachineTypeMapping = new Dictionary()
{
["i386"] = MachineType.i386,
["i686"] = MachineType.i386,
["x86"] = MachineType.i386,
["x86_64"] = MachineType.x86_64,
["amd64"] = MachineType.x86_64,
["x64"] = MachineType.x86_64,
["em64t"] = MachineType.x86_64,
["armv7l"] = MachineType.armv7l,
["armv8"] = MachineType.armv8,
["aarch64"] = MachineType.aarch64,
};
///
/// 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 (Machine)
{
case MachineType.i386:
return I386;
case MachineType.x86_64:
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
{
switch (OperatingSystem)
{
case OperatingSystemType.Darwin:
return 0x1000;
case OperatingSystemType.Linux:
return 0x20;
default:
throw new NotImplementedException($"mmap is not supported on {OperatingSystemName}");
}
}
}
[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);
}
}
///
/// Initializes the data about platforms.
///
/// This must be the last step when initializing the runtime:
/// GetManagedString needs to have the cached values for types.
/// But it must run before initializing anything outside the runtime
/// because those rely on the platform data.
///
public static void InitializePlatformData()
{
MachineName = SystemInfo.GetArchitecture();
Machine = SystemInfo.GetMachineType();
OperatingSystem = SystemInfo.GetSystemType();
}
internal static IMemoryMapper CreateMemoryMapper()
{
switch (OperatingSystem)
{
case OperatingSystemType.Darwin:
case OperatingSystemType.Linux:
return new UnixMemoryMapper();
case OperatingSystemType.Windows:
return new WindowsMemoryMapper();
default:
throw new NotImplementedException($"No support for {OperatingSystemName}");
}
}
///
/// 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);
}
}
}