dorisoy_HardwareMonitor/LibreHardwareMonitorLib/Hardware/OpCode.cs
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace LibreHardwareMonitor.Hardware
{
    internal static class OpCode
    {
        public static CpuidDelegate CpuId;
        public static RdtscDelegate Rdtsc;

        private static IntPtr _codeBuffer;
        private static ulong _size;

        // void __stdcall cpuidex(unsigned int index, unsigned int ecxValue,
        //   unsigned int* eax, unsigned int* ebx, unsigned int* ecx,
        //   unsigned int* edx)
        // {
        //   int info[4];
        //   __cpuidex(info, index, ecxValue);
        //   *eax = info[0];
        //   *ebx = info[1];
        //   *ecx = info[2];
        //   *edx = info[3];
        // }

        private static readonly byte[] CpuId32 =
        {
            0x55, // push ebp
            0x8B,
            0xEC, // mov ebp, esp
            0x83,
            0xEC,
            0x10, // sub esp, 10h
            0x8B,
            0x45,
            0x08, // mov eax, dword ptr [ebp+8]
            0x8B,
            0x4D,
            0x0C, // mov ecx, dword ptr [ebp+0Ch]
            0x53, // push ebx
            0x0F,
            0xA2, // cpuid
            0x56, // push esi
            0x8D,
            0x75,
            0xF0, // lea esi, [info]
            0x89,
            0x06, // mov dword ptr [esi],eax
            0x8B,
            0x45,
            0x10, // mov eax, dword ptr [eax]
            0x89,
            0x5E,
            0x04, // mov dword ptr [esi+4], ebx
            0x89,
            0x4E,
            0x08, // mov dword ptr [esi+8], ecx
            0x89,
            0x56,
            0x0C, // mov dword ptr [esi+0Ch], edx
            0x8B,
            0x4D,
            0xF0, // mov ecx, dword ptr [info]
            0x89,
            0x08, // mov dword ptr [eax], ecx
            0x8B,
            0x45,
            0x14, // mov eax, dword ptr [ebx]
            0x8B,
            0x4D,
            0xF4, // mov ecx, dword ptr [ebp-0Ch]
            0x89,
            0x08, // mov dword ptr [eax], ecx
            0x8B,
            0x45,
            0x18, // mov eax, dword ptr [ecx]
            0x8B,
            0x4D,
            0xF8, // mov ecx, dword ptr [ebp-8]
            0x89,
            0x08, // mov dword ptr [eax], ecx
            0x8B,
            0x45,
            0x1C, // mov eax, dword ptr [edx]
            0x8B,
            0x4D,
            0xFC, // mov ecx, dword ptr [ebp-4]
            0x5E, // pop esi
            0x89,
            0x08, // mov dword ptr [eax], ecx
            0x5B, // pop ebx
            0xC9, // leave
            0xC2,
            0x18,
            0x00 // ret 18h
        };

        private static readonly byte[] CpuId64Linux =
        {
            0x49,
            0x89,
            0xD2, // mov r10, rdx
            0x49,
            0x89,
            0xCB, // mov r11, rcx
            0x53, // push rbx
            0x89,
            0xF8, // mov eax, edi
            0x89,
            0xF1, // mov ecx, esi
            0x0F,
            0xA2, // cpuid
            0x41,
            0x89,
            0x02, // mov dword ptr [r10], eax
            0x41,
            0x89,
            0x1B, // mov dword ptr [r11], ebx
            0x41,
            0x89,
            0x08, // mov dword ptr [r8], ecx
            0x41,
            0x89,
            0x11, // mov dword ptr [r9], edx
            0x5B, // pop rbx
            0xC3 // ret
        };

        private static readonly byte[] CpuId64Windows =
        {
            0x48,
            0x89,
            0x5C,
            0x24,
            0x08, // mov qword ptr [rsp+8], rbx
            0x8B,
            0xC1, // mov eax, ecx
            0x8B,
            0xCA, // mov ecx, edx
            0x0F,
            0xA2, // cpuid
            0x41,
            0x89,
            0x00, // mov dword ptr [r8], eax
            0x48,
            0x8B,
            0x44,
            0x24,
            0x28, // mov rax, qword ptr [rsp+28h]
            0x41,
            0x89,
            0x19, // mov dword ptr [r9], ebx
            0x48,
            0x8B,
            0x5C,
            0x24,
            0x08, // mov rbx, qword ptr [rsp+8]
            0x89,
            0x08, // mov dword ptr [rax], ecx
            0x48,
            0x8B,
            0x44,
            0x24,
            0x30, // mov rax, qword ptr [rsp+30h]
            0x89,
            0x10, // mov dword ptr [rax], edx
            0xC3 // ret
        };

        // unsigned __int64 __stdcall rdtsc() {
        //   return __rdtsc();
        // }

        private static readonly byte[] Rdtsc32 =
        {
            0x0F,
            0x31, // rdtsc
            0xC3 // ret
        };

        private static readonly byte[] Rdtsc64 =
        {
            0x0F,
            0x31, // rdtsc
            0x48,
            0xC1,
            0xE2,
            0x20, // shl rdx, 20h
            0x48,
            0x0B,
            0xC2, // or rax, rdx
            0xC3 // ret
        };

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate bool CpuidDelegate(uint index, uint ecxValue, out uint eax, out uint ebx, out uint ecx, out uint edx);

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate ulong RdtscDelegate();

        public static void Open()
        {
            byte[] rdTscCode;
            byte[] cpuidCode;
            if (IntPtr.Size == 4)
            {
                rdTscCode = Rdtsc32;
                cpuidCode = CpuId32;
            }
            else
            {
                rdTscCode = Rdtsc64;

                cpuidCode = Software.OperatingSystem.IsUnix ? CpuId64Linux : CpuId64Windows;
            }

            _size = (ulong)(rdTscCode.Length + cpuidCode.Length);

            if (Software.OperatingSystem.IsUnix)
            {
                Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756");
                Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall");
                MethodInfo mmap = sysCall.GetMethod("mmap");

                Type mmapProts = assembly.GetType("Mono.Unix.Native.MmapProts");
                object mmapProtsParam = Enum.ToObject(mmapProts,
                                                      (int)mmapProts.GetField("PROT_READ").GetValue(null) |
                                                      (int)mmapProts.GetField("PROT_WRITE").GetValue(null) |
                                                      (int)mmapProts.GetField("PROT_EXEC").GetValue(null));

                Type mmapFlags = assembly.GetType("Mono.Unix.Native.MmapFlags");
                object mmapFlagsParam = Enum.ToObject(mmapFlags,
                                                      (int)mmapFlags.GetField("MAP_ANONYMOUS").GetValue(null) |
                                                      (int)mmapFlags.GetField("MAP_PRIVATE").GetValue(null));

                if (mmap != null)
                    _codeBuffer = (IntPtr)mmap.Invoke(null, new[] { IntPtr.Zero, _size, mmapProtsParam, mmapFlagsParam, -1, 0 });
            }
            else
            {
                _codeBuffer = Interop.Kernel32.VirtualAlloc(IntPtr.Zero,
                                                            (UIntPtr)_size,
                                                            Interop.Kernel32.MEM.MEM_COMMIT | Interop.Kernel32.MEM.MEM_RESERVE,
                                                            Interop.Kernel32.PAGE.PAGE_EXECUTE_READWRITE);
            }

            Marshal.Copy(rdTscCode, 0, _codeBuffer, rdTscCode.Length);
            Rdtsc = Marshal.GetDelegateForFunctionPointer(_codeBuffer, typeof(RdtscDelegate)) as RdtscDelegate;
            IntPtr cpuidAddress = (IntPtr)((long)_codeBuffer + rdTscCode.Length);
            Marshal.Copy(cpuidCode, 0, cpuidAddress, cpuidCode.Length);
            CpuId = Marshal.GetDelegateForFunctionPointer(cpuidAddress, typeof(CpuidDelegate)) as CpuidDelegate;
        }

        public static void Close()
        {
            Rdtsc = null;
            CpuId = null;

            if (Software.OperatingSystem.IsUnix)
            {
                Assembly assembly = Assembly.Load("Mono.Posix, Version=2.0.0.0, Culture=neutral, " + "PublicKeyToken=0738eb9f132ed756");
                Type sysCall = assembly.GetType("Mono.Unix.Native.Syscall");
                MethodInfo method = sysCall.GetMethod("munmap");
                method?.Invoke(null, new object[] { _codeBuffer, _size });
            }
            else
            {
                Interop.Kernel32.VirtualFree(_codeBuffer, UIntPtr.Zero, Interop.Kernel32.MEM.MEM_RELEASE);
            }
        }

        public static bool CpuIdTx(uint index, uint ecxValue, out uint eax, out uint ebx, out uint ecx, out uint edx, GroupAffinity affinity)
        {
            GroupAffinity previousAffinity = ThreadAffinity.Set(affinity);
            if (previousAffinity == GroupAffinity.Undefined)
            {
                eax = ebx = ecx = edx = 0;
                return false;
            }

            CpuId(index, ecxValue, out eax, out ebx, out ecx, out edx);
            ThreadAffinity.Set(previousAffinity);
            return true;
        }
    }
}
