C# 中的 x86/x64 CPUID

Posted

技术标签:

【中文标题】C# 中的 x86/x64 CPUID【英文标题】:x86/x64 CPUID in C# 【发布时间】:2011-03-14 02:12:16 【问题描述】:

与my other question相关,请帮我调试“未知模块中发生'System.AccessViolationException'类型的未处理异常。附加信息:试图读取或写入受保护的内存。这通常表明其他内存已损坏。”单步执行代码,一切正常,直到实际调用 del() 并在该行中失败。

此代码基于此article's sample 和此python code 在python 中工作。我也无法让代码示例按原样工作(同样的例外),但我希望它只是有点过时了。

编辑:如果您关心我们是如何到达这里的,请查看编辑历史记录,这很无趣。

完成的工作版本:

public static class CpuID

    public static byte[] Invoke(int level)
    
        IntPtr codePointer = IntPtr.Zero;
        try
        
            // compile
            byte[] codeBytes;
            if (IntPtr.Size == 4)
            
                codeBytes = x86CodeBytes;
            
            else
            
                codeBytes = x64CodeBytes;
            

            codePointer = VirtualAlloc(
                IntPtr.Zero,
                new UIntPtr((uint)codeBytes.Length),
                AllocationType.COMMIT | AllocationType.RESERVE,
                MemoryProtection.EXECUTE_READWRITE
            );

            Marshal.Copy(codeBytes, 0, codePointer, codeBytes.Length);

            CpuIDDelegate cpuIdDelg = (CpuIDDelegate)Marshal.GetDelegateForFunctionPointer(codePointer, typeof(CpuIDDelegate));

            // invoke
            GCHandle handle = default(GCHandle);
            var buffer = new byte[16];

            try
            
                handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                cpuIdDelg(level, buffer);
            
            finally
            
                if (handle != default(GCHandle))
                
                    handle.Free();
                
            

            return buffer;
        
        finally
        
            if (codePointer != IntPtr.Zero)
            
                VirtualFree(codePointer, 0, 0x8000);
                codePointer = IntPtr.Zero;
            
        
    

    [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
    private delegate void CpuIDDelegate(int level, byte[] buffer);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType,
        MemoryProtection flProtect);

    [DllImport("kernel32")]
    private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, UInt32 dwFreeType);

    [Flags()]
    private enum AllocationType : uint
    
        COMMIT = 0x1000,
        RESERVE = 0x2000,
        RESET = 0x80000,
        LARGE_PAGES = 0x20000000,
        PHYSICAL = 0x400000,
        TOP_DOWN = 0x100000,
        WRITE_WATCH = 0x200000
    

    [Flags()]
    private enum MemoryProtection : uint
    
        EXECUTE = 0x10,
        EXECUTE_READ = 0x20,
        EXECUTE_READWRITE = 0x40,
        EXECUTE_WRITECOPY = 0x80,
        NOACCESS = 0x01,
        READONLY = 0x02,
        READWRITE = 0x04,
        WRITECOPY = 0x08,
        GUARD_Modifierflag = 0x100,
        NOCACHE_Modifierflag = 0x200,
        WRITECOMBINE_Modifierflag = 0x400
    

    // Basic ASM strategy --
    // void x86CpuId(int level, byte* buffer) 
    // 
    //    eax = level
    //    cpuid
    //    buffer[0] = eax
    //    buffer[4] = ebx
    //    buffer[8] = ecx
    //    buffer[12] = edx
    // 

    private readonly static byte[] x86CodeBytes = 
        0x55,                   // push        ebp  
        0x8B, 0xEC,             // mov         ebp,esp
        0x53,                   // push        ebx  
        0x57,                   // push        edi

        0x8B, 0x45, 0x08,       // mov         eax, dword ptr [ebp+8] (move level into eax)
        0x0F, 0xA2,              // cpuid

        0x8B, 0x7D, 0x0C,       // mov         edi, dword ptr [ebp+12] (move address of buffer into edi)
        0x89, 0x07,             // mov         dword ptr [edi+0], eax  (write eax, ... to buffer)
        0x89, 0x5F, 0x04,       // mov         dword ptr [edi+4], ebx 
        0x89, 0x4F, 0x08,       // mov         dword ptr [edi+8], ecx 
        0x89, 0x57, 0x0C,       // mov         dword ptr [edi+12],edx 

        0x5F,                   // pop         edi  
        0x5B,                   // pop         ebx  
        0x8B, 0xE5,             // mov         esp,ebp  
        0x5D,                   // pop         ebp 
        0xc3                    // ret
    ;

    private readonly static byte[] x64CodeBytes = 
        0x53,                       // push rbx    this gets clobbered by cpuid

        // rcx is level
        // rdx is buffer.
        // Need to save buffer elsewhere, cpuid overwrites rdx
        // Put buffer in r8, use r8 to reference buffer later.

        // Save rdx (buffer addy) to r8
        0x49, 0x89, 0xd0,           // mov r8,  rdx

        // Move ecx (level) to eax to call cpuid, call cpuid
        0x89, 0xc8,                 // mov eax, ecx
        0x0F, 0xA2,                 // cpuid

        // Write eax et al to buffer
        0x41, 0x89, 0x40, 0x00,     // mov    dword ptr [r8+0],  eax
        0x41, 0x89, 0x58, 0x04,     // mov    dword ptr [r8+4],  ebx
        0x41, 0x89, 0x48, 0x08,     // mov    dword ptr [r8+8],  ecx
        0x41, 0x89, 0x50, 0x0c,     // mov    dword ptr [r8+12], edx

        0x5b,                       // pop rbx
        0xc3                        // ret
    ;

注意CPUID0需要按正确的顺序读取:

//a twelve character ASCII string stored in EBX, EDX, ECX - in that order
var cpuid0s = new string(ASCIIEncoding.ASCII.GetChars(
    cpuid0.Skip(4).Take(4).Concat(
    cpuid0.Skip(12).Take(4)).Concat(
    cpuid0.Skip(8).Take(4)).ToArray()));

【问题讨论】:

如果我错了,请纠正我,但是C#代码不是编译成MSIL代码,类似于java字节码吗?这意味着您不能输入 x86 汇编语言,因为 .NET 框架出于所有意图和目的不知道 x86 操作码是什么?只有MSIL,对吧? icemanind - 我只能说atrevido.net/blog/2005/01/28/Inline+X86+ASM+In+C.aspx 很有说服力。 是的,他们通过本质上“欺骗”.net 解释器来做到这一点。我认为更好的方法是将您的汇编语言程序创建为 DLL,并使用 PInvoke 执行 DLL 中的方法。见这篇文章:codeproject.com/KB/cs/unmanage.aspx 我正在同时进行这项工作。见my other post 哇,我曾经从 DynamicMethod 的 _methodPtrAux 字段中得到一个函数指针,看看我是否可以用 MSIL calli 指令调用它,我认为 很荒谬... 【参考方案1】:

我很确定你被DEP 屏蔽了。 x_CPUIDy_INSNS 字节数组位于标记为数据且不可执行的内存段中。

编辑:

话虽如此,我已经得到了一个可以编译和运行的版本,但我认为没有得到正确的值。也许这会让你一路走好。

编辑 2:

我认为我现在回归了正确的价值观。随意验证。

namespace CPUID

    using System;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Text;

    internal static class Program
    
        [Flags]
        private enum AllocationTypes : uint
        
            Commit = 0x1000,
            Reserve = 0x2000,
            Reset = 0x80000,
            LargePages = 0x20000000,
            Physical = 0x400000,
            TopDown = 0x100000,
            WriteWatch = 0x200000
        

        [Flags]
        private enum MemoryProtections : uint
        
            Execute = 0x10,
            ExecuteRead = 0x20,
            ExecuteReadWrite = 0x40,
            ExecuteWriteCopy = 0x80,
            NoAccess = 0x01,
            ReadOnly = 0x02,
            ReadWrite = 0x04,
            WriteCopy = 0x08,
            GuartModifierflag = 0x100,
            NoCacheModifierflag = 0x200,
            WriteCombineModifierflag = 0x400
        

        [Flags]
        private enum FreeTypes : uint
        
            Decommit = 0x4000,
            Release = 0x8000
        

        [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
        private unsafe delegate void CPUID0Delegate(byte* buffer);

        [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
        private unsafe delegate void CPUID1Delegate(byte* buffer);

        private static void Main()
        
            Console.WriteLine("CPUID0: 0", string.Join(", ", CPUID0().Select(x => x.ToString("X2", CultureInfo.InvariantCulture))));
            Console.WriteLine("CPUID0: 0", new string(ASCIIEncoding.ASCII.GetChars(CPUID0())));
            Console.WriteLine("CPUID1: 0", string.Join(", ", CPUID1().Select(x => x.ToString("X2", CultureInfo.InvariantCulture))));
            Console.ReadLine();
        

        private static unsafe byte[] CPUID0()
        
            byte[] buffer = new byte[12];

            if (IntPtr.Size == 4)
            
                IntPtr p = NativeMethods.VirtualAlloc(
                    IntPtr.Zero,
                    new UIntPtr((uint)x86_CPUID0_INSNS.Length),
                    AllocationTypes.Commit | AllocationTypes.Reserve,
                    MemoryProtections.ExecuteReadWrite);
                try
                
                    Marshal.Copy(x86_CPUID0_INSNS, 0, p, x86_CPUID0_INSNS.Length);

                    CPUID0Delegate del = (CPUID0Delegate)Marshal.GetDelegateForFunctionPointer(p, typeof(CPUID0Delegate));

                    fixed (byte* newBuffer = &buffer[0])
                    
                        del(newBuffer);
                    
                
                finally
                
                    NativeMethods.VirtualFree(p, 0, FreeTypes.Release);
                
            
            else if (IntPtr.Size == 8)
            
                IntPtr p = NativeMethods.VirtualAlloc(
                    IntPtr.Zero,
                    new UIntPtr((uint)x64_CPUID0_INSNS.Length),
                    AllocationTypes.Commit | AllocationTypes.Reserve,
                    MemoryProtections.ExecuteReadWrite);
                try
                
                    Marshal.Copy(x64_CPUID0_INSNS, 0, p, x64_CPUID0_INSNS.Length);

                    CPUID0Delegate del = (CPUID0Delegate)Marshal.GetDelegateForFunctionPointer(p, typeof(CPUID0Delegate));

                    fixed (byte* newBuffer = &buffer[0])
                    
                        del(newBuffer);
                    
                
                finally
                
                    NativeMethods.VirtualFree(p, 0, FreeTypes.Release);
                
            

            return buffer;
        

        private static unsafe byte[] CPUID1()
        
            byte[] buffer = new byte[12];

            if (IntPtr.Size == 4)
            
                IntPtr p = NativeMethods.VirtualAlloc(
                    IntPtr.Zero,
                    new UIntPtr((uint)x86_CPUID1_INSNS.Length),
                    AllocationTypes.Commit | AllocationTypes.Reserve,
                    MemoryProtections.ExecuteReadWrite);
                try
                
                    Marshal.Copy(x86_CPUID1_INSNS, 0, p, x86_CPUID1_INSNS.Length);

                    CPUID1Delegate del = (CPUID1Delegate)Marshal.GetDelegateForFunctionPointer(p, typeof(CPUID1Delegate));

                    fixed (byte* newBuffer = &buffer[0])
                    
                        del(newBuffer);
                    
                
                finally
                
                    NativeMethods.VirtualFree(p, 0, FreeTypes.Release);
                
            
            else if (IntPtr.Size == 8)
            
                IntPtr p = NativeMethods.VirtualAlloc(
                    IntPtr.Zero,
                    new UIntPtr((uint)x64_CPUID1_INSNS.Length),
                    AllocationTypes.Commit | AllocationTypes.Reserve,
                    MemoryProtections.ExecuteReadWrite);
                try
                
                    Marshal.Copy(x64_CPUID1_INSNS, 0, p, x64_CPUID1_INSNS.Length);

                    CPUID1Delegate del = (CPUID1Delegate)Marshal.GetDelegateForFunctionPointer(p, typeof(CPUID1Delegate));

                    fixed (byte* newBuffer = &buffer[0])
                    
                        del(newBuffer);
                    
                
                finally
                
                    NativeMethods.VirtualFree(p, 0, FreeTypes.Release);
                
            

            return buffer;
        

        private static class NativeMethods
        
            [DllImport("kernel32.dll", SetLastError = true)]
            internal static extern IntPtr VirtualAlloc(
                IntPtr lpAddress,
                UIntPtr dwSize,
                AllocationTypes flAllocationType,
                MemoryProtections flProtect);

            [DllImport("kernel32")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool VirtualFree(
                IntPtr lpAddress,
                uint dwSize,
                FreeTypes flFreeType);
        

        #region ASM
        private static readonly byte[] x86_CPUID0_INSNS = new byte[]
            
                0x53,                      // push   %ebx
                0x31, 0xc0,                // xor    %eax,%eax
                0x0f, 0xa2,                // cpuid
                0x8b, 0x44, 0x24, 0x08,    // mov    0x8(%esp),%eax
                0x89, 0x18,                // mov    %ebx,0x0(%eax)
                0x89, 0x50, 0x04,          // mov    %edx,0x4(%eax)
                0x89, 0x48, 0x08,          // mov    %ecx,0x8(%eax)
                0x5b,                      // pop    %ebx
                0xc3                       // ret
            ;

        private static readonly byte[] x86_CPUID1_INSNS = new byte[]
            
                0x53,                   // push   %ebx
                0x31, 0xc0,             // xor    %eax,%eax
                0x40,                   // inc    %eax
                0x0f, 0xa2,             // cpuid
                0x5b,                   // pop    %ebx
                0xc3                    // ret
            ;

        private static readonly byte[] x64_CPUID0_INSNS = new byte[]
            
                0x49, 0x89, 0xd8,       // mov    %rbx,%r8
                0x49, 0x89, 0xc9,       // mov    %rcx,%r9
                0x48, 0x31, 0xc0,       // xor    %rax,%rax
                0x0f, 0xa2,             // cpuid
                0x4c, 0x89, 0xc8,       // mov    %r9,%rax
                0x89, 0x18,             // mov    %ebx,0x0(%rax)
                0x89, 0x50, 0x04,       // mov    %edx,0x4(%rax)
                0x89, 0x48, 0x08,       // mov    %ecx,0x8(%rax)
                0x4c, 0x89, 0xc3,       // mov    %r8,%rbx
                0xc3                    // retq
            ;

        private static readonly byte[] x64_CPUID1_INSNS = new byte[]
            
                0x53,                     // push   %rbx
                0x48, 0x31, 0xc0,         // xor    %rax,%rax
                0x48, 0xff, 0xc0,         // inc    %rax
                0x0f, 0xa2,               // cpuid
                0x5b,                     // pop    %rbx
                0xc3                      // retq
            ;
        #endregion
    

【讨论】:

那不会也阻止python脚本吗?它似乎使用了类似的机制。 很有可能,是的。您可以尝试在“性能选项”对话框的“数据执行保护”选项卡中排除 Python 解释器(可从“系统属性”小程序的“高级”选项卡访问)并查看是否有效。但是,对于 .NET 应用程序,您必须花很多心思才能绕过它。 我添加了一些至少不会崩溃的代码。虽然似乎没有得到正确的价值观,但我希望它能让你朝着正确的方向前进。 是的,我全是零。但我喜欢你的改变。有效地将其复制到可执行段中,对吗? 我刚刚进行了更多更改。是的,将其复制到可执行段中。 “更多更改”是创建内存指针委托并将指针正确传递给缓冲区的一种更简洁的方法。【参考方案2】:

我可以推荐以下页面吗:http://community.devpinoy.org/blogs/cvega/archive/2006/04/07/2658.aspx

本页将向您展示 CPUID 的汇编源代码、如何将其编译为 DLL 以及如何从 C# 中调用它。

另外如果你需要其他硬件识别程序,我可以推荐这个页面:http://www.codeproject.com/KB/system/GetHardwareInformation.aspx

此页面显示如何获取主板信息、硬盘信息、cpu 信息、显卡信息等信息。

【讨论】:

【参考方案3】:

我决定改进您的答案。它不再需要 unsafe 来编译,它只需要两个汇编块就可以读取任何和所有 cpuid 块,因为它只是将 eax/ebx/ecx/edx 写入 16 字节字节数组。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;

namespace CpuID

    public class CpuID : IDisposable
    
        [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
        public delegate void CpuIDDelegate(int level, byte[] buffer);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr VirtualAlloc(
            IntPtr lpAddress,
            UIntPtr dwSize,
            AllocationType flAllocationType, 
            MemoryProtection flProtect
        );

        [DllImport("kernel32")]
        private static extern bool VirtualFree(
                IntPtr lpAddress,
                UInt32 dwSize,
                UInt32 dwFreeType
        );

        [Flags()]
        public enum AllocationType : uint
        
            COMMIT = 0x1000,
            RESERVE = 0x2000,
            RESET = 0x80000,
            LARGE_PAGES = 0x20000000,
            PHYSICAL = 0x400000,
            TOP_DOWN = 0x100000,
            WRITE_WATCH = 0x200000
        

        [Flags()]
        public enum MemoryProtection : uint
        
            EXECUTE = 0x10,
            EXECUTE_READ = 0x20,
            EXECUTE_READWRITE = 0x40,
            EXECUTE_WRITECOPY = 0x80,
            NOACCESS = 0x01,
            READONLY = 0x02,
            READWRITE = 0x04,
            WRITECOPY = 0x08,
            GUARD_Modifierflag = 0x100,
            NOCACHE_Modifierflag = 0x200,
            WRITECOMBINE_Modifierflag = 0x400
        

        private CpuIDDelegate cpuIdDelg;

        private IntPtr codePointer;

        // void x86CpuId(int level, byte* buffer) 
        // 
        //    eax = level
        //    cpuid
        //    buffer[0] = eax
        //    buffer[4] = ebx
        //    buffer[8] = ecx
        //    buffer[12] = edx
        // 
        private byte[] x86CodeBytes = 
        
            0x55,                   // push        ebp  
            0x8B, 0xEC,             // mov         ebp,esp
            0x53,                   // push        ebx  
            0x57,                   // push        edi

            0x8B, 0x45, 0x08,       // mov         eax, dword ptr [ebp+8] (move level into eax)
            0x0F, 0xA2,              // cpuid

            0x8B, 0x7D, 0x0C,       // mov         edi, dword ptr [ebp+12] (move address of buffer into edi)
            0x89, 0x07,             // mov         dword ptr [edi+0], eax  (write eax, ... to buffer)
            0x89, 0x5F, 0x04,       // mov         dword ptr [edi+4], ebx 
            0x89, 0x4F, 0x08,       // mov         dword ptr [edi+8], ecx 
            0x89, 0x57, 0x0C,       // mov         dword ptr [edi+12],edx 

            0x5F,                   // pop         edi  
            0x5B,                   // pop         ebx  
            0x8B, 0xE5,             // mov         esp,ebp  
            0x5D,                   // pop         ebp 
            0xc3                    // ret
        ;

        private byte[] x64CodeBytes = 
        
            0x53,                       // push rbx    this gets clobbered by cpuid

            // rcx is level
            // rdx is buffer.
            // Need to save buffer elsewhere, cpuid overwrites rdx
            // Put buffer in r8, use r8 to reference buffer later.

            // Save rdx (buffer addy) to r8
            0x49, 0x89, 0xd0,           // mov r8,  rdx

            // Move ecx (level) to eax to call cpuid, call cpuid
            0x89, 0xc8,                 // mov eax, ecx
            0x0F, 0xA2,                 // cpuid

            // Write eax et al to buffer
            0x41, 0x89, 0x40, 0x00,     // mov    dword ptr [r8+0],  eax
            0x41, 0x89, 0x58, 0x04,     // mov    dword ptr [r8+4],  ebx
            0x41, 0x89, 0x48, 0x08,     // mov    dword ptr [r8+8],  ecx
            0x41, 0x89, 0x50, 0x0c,     // mov    dword ptr [r8+12], edx

            0x5b,                       // pop rbx
            0xc3                        // ret
        ;

        public CpuID()
        
            Compile();
        

        ~CpuID()
        
            Dispose(false);
        

        private void Compile()
        
            byte[] codeBytes;

            if (IntPtr.Size == 4)
            
                codeBytes = x86CodeBytes;
            
            else
            
                codeBytes = x64CodeBytes;
            

            this.codePointer = VirtualAlloc(
                IntPtr.Zero,
                new UIntPtr((uint)codeBytes.Length),
                AllocationType.COMMIT | AllocationType.RESERVE,
                MemoryProtection.EXECUTE_READWRITE
            );

            Marshal.Copy(codeBytes, 0, this.codePointer, codeBytes.Length);

            this.cpuIdDelg = (CpuIDDelegate)Marshal.GetDelegateForFunctionPointer(this.codePointer, typeof(CpuIDDelegate));
        

        public void Invoke(int level, byte[] buffer)
        
            GCHandle handle = default(GCHandle);
            if (buffer.Length < 16)
            
                throw new ArgumentException("buffer must be at least 16 bytes long");
            

            try
            
                handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

                this.cpuIdDelg(level, buffer);
            
            finally
            
                if (handle != default(GCHandle))
                
                    handle.Free();
                
            
        

        public void Dispose()
        
            Dispose(true);
        

        public void Dispose(bool disposing)
        
            if (this.codePointer != IntPtr.Zero)
            
                VirtualFree(this.codePointer, 0, 0x8000);
                this.codePointer = IntPtr.Zero;
            
        

    

【讨论】:

对于我和@Julien,GenuineIntel 出现故障。我在 x64 机器上测试过。我更新了我的原始帖子,对订单进行了更正。我希望 CPUID1 需要类似地重新排列。【参考方案4】:

我将@antiduh 的代码重构为静态方法,因此没有对象生命周期需要管理。这比较慢,因为在调用 Invoke() 之间没有重用 ASM 代码,但是为了简单而权衡速度对我的用例来说是有意义的。这个新版本可以在我的机器上在 15 毫秒内调用 CPUID 1000 次。

感谢出色的代码家伙!

public static class CpuID 

    public static byte[] Invoke(int level) 
        IntPtr codePointer = IntPtr.Zero;
        try 
            // compile
            byte[] codeBytes;
            if (IntPtr.Size == 4) 
                codeBytes = x86CodeBytes;
             else 
                codeBytes = x64CodeBytes;
            

            codePointer = VirtualAlloc(
                IntPtr.Zero,
                new UIntPtr((uint)codeBytes.Length),
                AllocationType.COMMIT | AllocationType.RESERVE,
                MemoryProtection.EXECUTE_READWRITE
            );

            Marshal.Copy(codeBytes, 0, codePointer, codeBytes.Length);

            CpuIDDelegate cpuIdDelg = (CpuIDDelegate)Marshal.GetDelegateForFunctionPointer(codePointer, typeof(CpuIDDelegate));

            // invoke
            GCHandle handle = default(GCHandle);
            var buffer = new byte[16];

            try 
                handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                cpuIdDelg(level, buffer);
             finally 
                if (handle != default(GCHandle)) 
                    handle.Free();
                
            

            return buffer;
         finally 
            if (codePointer != IntPtr.Zero) 
                VirtualFree(codePointer, 0, 0x8000);
                codePointer = IntPtr.Zero;
            
        
    

    [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
    private delegate void CpuIDDelegate(int level, byte[] buffer);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType,
        MemoryProtection flProtect);

    [DllImport("kernel32")]
    private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, UInt32 dwFreeType);

    [Flags()]
    private enum AllocationType : uint 
        COMMIT = 0x1000,
        RESERVE = 0x2000,
        RESET = 0x80000,
        LARGE_PAGES = 0x20000000,
        PHYSICAL = 0x400000,
        TOP_DOWN = 0x100000,
        WRITE_WATCH = 0x200000
    

    [Flags()]
    private enum MemoryProtection : uint 
        EXECUTE = 0x10,
        EXECUTE_READ = 0x20,
        EXECUTE_READWRITE = 0x40,
        EXECUTE_WRITECOPY = 0x80,
        NOACCESS = 0x01,
        READONLY = 0x02,
        READWRITE = 0x04,
        WRITECOPY = 0x08,
        GUARD_Modifierflag = 0x100,
        NOCACHE_Modifierflag = 0x200,
        WRITECOMBINE_Modifierflag = 0x400
    

    // Basic ASM strategy --
    // void x86CpuId(int level, byte* buffer) 
    // 
    //    eax = level
    //    cpuid
    //    buffer[0] = eax
    //    buffer[4] = ebx
    //    buffer[8] = ecx
    //    buffer[12] = edx
    // 

    private readonly static byte[] x86CodeBytes = 
        0x55,                   // push        ebp  
        0x8B, 0xEC,             // mov         ebp,esp
        0x53,                   // push        ebx  
        0x57,                   // push        edi

        0x8B, 0x45, 0x08,       // mov         eax, dword ptr [ebp+8] (move level into eax)
        0x0F, 0xA2,              // cpuid

        0x8B, 0x7D, 0x0C,       // mov         edi, dword ptr [ebp+12] (move address of buffer into edi)
        0x89, 0x07,             // mov         dword ptr [edi+0], eax  (write eax, ... to buffer)
        0x89, 0x5F, 0x04,       // mov         dword ptr [edi+4], ebx 
        0x89, 0x4F, 0x08,       // mov         dword ptr [edi+8], ecx 
        0x89, 0x57, 0x0C,       // mov         dword ptr [edi+12],edx 

        0x5F,                   // pop         edi  
        0x5B,                   // pop         ebx  
        0x8B, 0xE5,             // mov         esp,ebp  
        0x5D,                   // pop         ebp 
        0xc3                    // ret
    ;

    private readonly static byte[] x64CodeBytes = 
        0x53,                       // push rbx    this gets clobbered by cpuid

        // rcx is level
        // rdx is buffer.
        // Need to save buffer elsewhere, cpuid overwrites rdx
        // Put buffer in r8, use r8 to reference buffer later.

        // Save rdx (buffer addy) to r8
        0x49, 0x89, 0xd0,           // mov r8,  rdx

        // Move ecx (level) to eax to call cpuid, call cpuid
        0x89, 0xc8,                 // mov eax, ecx
        0x0F, 0xA2,                 // cpuid

        // Write eax et al to buffer
        0x41, 0x89, 0x40, 0x00,     // mov    dword ptr [r8+0],  eax
        0x41, 0x89, 0x58, 0x04,     // mov    dword ptr [r8+4],  ebx
        0x41, 0x89, 0x48, 0x08,     // mov    dword ptr [r8+8],  ecx
        0x41, 0x89, 0x50, 0x0c,     // mov    dword ptr [r8+12], edx

        0x5b,                       // pop rbx
        0xc3                        // ret
    ;

【讨论】:

这是一段不错的代码。只是想知道为什么将结果放在byte[] 而不是uint[]:后者更类似于 32 位寄存器。 PS:如果你想要一个“干净”的解决方案,你不妨将内在的__cpuid 与 C++/CLI 结合使用。 如果您不针对纯速度进行优化,为什么还要编写 ASM?【参考方案5】:

此外,要获得 CPUID4,还需要一个参数。 以下是获取 CPUID0、CPUID1、CPUID2、CPUID4 的方法。

byte[] cpuid0 = Invoke(0, 0);
byte[] cpuid1 = Invoke(1, 0);
byte[] cpuid2 = Invoke(2, 0);

List<byte[]> cpuid4L = new List<byte[]>();
for (int i = 0; true; i++)

    byte[] cpuid4 = Invoke(4, (uint)i);
    if ( (cpuid4[0] & 0x0F) == 0)
        break;
    cpuid4L.Add(cpuid4);


private static byte[] Invoke(uint functionNum, uint ecx)

    IntPtr codePointer = IntPtr.Zero;

    try
    
        // Select a code
        byte[] codeBytes;
        if (IntPtr.Size == 4)
            codeBytes = x86CodeBytes;
        else
            codeBytes = x64CodeBytes;

        codePointer = NativeMethods.VirtualAlloc(IntPtr.Zero, new UIntPtr((uint)codeBytes.Length), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        Marshal.Copy(codeBytes, 0, codePointer, codeBytes.Length);
        CpuIdDelegate cpuIdDelg = (CpuIdDelegate)Marshal.GetDelegateForFunctionPointer(codePointer, typeof(CpuIdDelegate));

        // Invoke the code
        GCHandle handle = default(GCHandle);
        var buffer = new byte[16];

        try
        
            handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            cpuIdDelg(ecx, functionNum, buffer);  // Run the assembly code.
        
        finally
        
            if (handle != default(GCHandle))
            
                handle.Free();
            
        

        return buffer;
    
    finally
    
        if (codePointer != IntPtr.Zero)
        
            NativeMethods.VirtualFree(codePointer, (UIntPtr) 0, MEM_RELEASE);
            codePointer = IntPtr.Zero;
        
    


private readonly static byte[] x86CodeBytes = 
    0x55,                   
    0x8B, 0xEC,             
    0x53,                   
    0x57,                   
    0x8B, 0x4D, 0x08,
    0x8B, 0x45, 0x0C,
    0x0F, 0xA2,      
    0x8B, 0x7D, 0x10,
    0x89, 0x07,      
    0x89, 0x5F, 0x04,
    0x89, 0x4F, 0x08,
    0x89, 0x57, 0x0C,
    0x5F,                   
    0x5B,                   
    0x8B, 0xE5,             
    0x5D,                   
    0xc3                    
;

private readonly static byte[] x64CodeBytes = 
    0x53,
    0x89, 0xD0,
    0x0F, 0xA2,
    0x41, 0x89, 0x40, 0x00,
    0x41, 0x89, 0x58, 0x04,
    0x41, 0x89, 0x48, 0x08,
    0x41, 0x89, 0x50, 0x0c,
    0x5b,
    0xc3
;

【讨论】:

【参考方案6】:

感谢@antiduh 的解决方案。 为了更好的可用性,我会稍微更改 Invoke 签名,如下所示,因此您无需将 get 结果分配为一组寄存器

    // This is a modification to https://***.com/a/7964376/725903
    [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
    private delegate void CpuIDDelegate(int level, IntPtr ptr);

    [StructLayout(LayoutKind.Sequential, Size = 16)]
    public struct CpuIdResult
    
        public int Eax;
        public int Ebx;
        public int Ecx;
        public int Edx;
    

    public CpuIdResult Invoke(int level)
    
        CpuIdResult result;
        IntPtr buffer = Marshal.AllocHGlobal(16);
        try
        
            this.cpuIdDelg(level, buffer);
            result = (CpuIdResult)Marshal.PtrToStructure(buffer, typeof(CpuIdResult));
        
        finally
        
            Marshal.FreeHGlobal(buffer);
        
        return result;
    

【讨论】:

【参考方案7】:

我知道这个帖子很旧,但我非常喜欢这个帖子。 编码后,我发现在“EAX = 7,ECX = 0”时获取数据存在问题 所以,我在 x64CodeBytes 中添加了“mov ecx, 0”。

private readonly static byte[] x64CodeBytes = 
    0x53,                         // push rbx    this gets clobbered by cpuid

    // rcx is level
    // rdx is buffer.
    // Need to save buffer elsewhere, cpuid overwrites rdx
    // Put buffer in r8, use r8 to reference buffer later.        

    // Save rdx (buffer addy) to r8
    0x49, 0x89, 0xd0,             // mov r8,  rdx

    // Move ecx (level) to eax to call cpuid, call cpuid
    0x89, 0xc8,                   // mov eax, ecx
    0xB9, 0x00, 0x00, 0x00, 0x00, // mov ecx, 0
    0x0F, 0xA2,                   // cpuid

    // Write eax et al to buffer
    0x41, 0x89, 0x40, 0x00,       // mov    dword ptr [r8+0],  eax
    0x41, 0x89, 0x58, 0x04,       // mov    dword ptr [r8+4],  ebx
    0x41, 0x89, 0x48, 0x08,       // mov    dword ptr [r8+8],  ecx
    0x41, 0x89, 0x50, 0x0c,       // mov    dword ptr [r8+12], edx

    0x5b,                         // pop rbx
    0xc3                          // ret
    ;

【讨论】:

xor ecx, ecx 也可以,代码是0x31, 0xC9【参考方案8】:

对于 .NET 5,现在有一个内置的内部函数:https://docs.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.x86.x86base.cpuid?view=net-5.0

var (eax, ebx, ecx, edx) = X86Base.CpuId(functionId, subFunctionId);

【讨论】:

以上是关于C# 中的 x86/x64 CPUID的主要内容,如果未能解决你的问题,请参考以下文章

X.86 X64 汇编器中的正确堆栈操作

关于C#编写x86与x64程序的分析

Dll注入:X86/X64 远程线程CreateRemoteThread 注入

conan入门(十七):支持android NDK (armv7,armv8,x86,x86_64)交叉编译的统一profile jinja2模板

x86/x64 添加置换寻址

在 x86/x86_64 处理器上使用 LFENCE 指令是不是有意义?