X Tutup
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); } } }
X Tutup