开发者

x86/x64 CPUID in C#

开发者 https://www.devze.com 2023-01-06 03:58 出处:网络
Related to my other question, please help me debug \"An unhandled exception of type \'System.AccessViolationException\' occurred in Unknown Module.Additional information: Attempted to read or write pr

Related to my other question, please help me debug "An unhandled exception of type 'System.AccessViolationException' occurred in Unknown Module. Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt." Stepping through the code, everything works up until the actual call to del() and fails in that line.

This code is based on this article's sample and this python code which works in python. I can't get the code example as-is to work either (same exception), but I'm hopeful that it's just a little outdated or something.

EDIT: See the edit history if you care about how we got here, which is uninteresting.

Finished working version:

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开发者_JAVA百科    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
    };
}

Note that CPUID0 needs to be read in the right order:

//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()));


I'm fairly certain you're being blocked by DEP. The x_CPUIDy_INSNS byte arrays are in a segment of memory marked as data and non-executable.

EDIT:

That being said, I've gotten a version that compiles and runs, but I don't think gets the right values. Perhaps this will get you along your way.

EDIT 2:

I think I have the right values coming back now. Feel free to validate.

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
    }
}


I decided to improve your answer. It does not need unsafe to compile anymore, and it only needs two assembly blocks in order to be able to read out any and all cpuid blocks, because it just writes eax/ebx/ecx/edx to a 16-byte byte array.

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;
            }
        }

    }
}


I took @antiduh's code and refactored it into a static method, so there is no object lifecycle to manage. This is slower because the ASM code isn't reused between calls to Invoke(), but the tradeoff of speed for simplicity makes sense for my use case. This new version can call CPUID 1000 times in 15 milliseconds on my machine.

Thanks for the wonderful code guys!

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
    };
}


i know this thread is old, but i like this thread very much. after coding, I figure out there is problem to get data while "EAX=7,ECX=0" so, I add "mov ecx, 0" in x64CodeBytes.

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
    };


Can I suggest the following page: http://community.devpinoy.org/blogs/cvega/archive/2006/04/07/2658.aspx

This page will show you the assembly source code for CPUID, how to compile it into a DLL and how to call it from C#.

Also if you need other hardware identification procedures, may I suggest this page: http://www.codeproject.com/KB/system/GetHardwareInformation.aspx

This page shows how to get information like motherboard info, hard drive info, cpu info, video card info, etc etc.


Additionally, to obtain CPUID4, one more argument is needed. Here is how you get 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
};


With .NET 5, there is now a built-in intrinsic for this: https://learn.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);


Thanks to @antiduh for his solution. I'd change the Invoke signature a little bit for better usability as follows, so you don't need to allocate an get the result as a set of registers

    // This is a modification to https://stackoverflow.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;
    }
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号