X86/X64 Cpuid in C#

x86/x64 CPUID in C#

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

Compiling assembly for X86/X64 for use with C#

CPUID is a giant pain and I would advise against going down that path if you can avoid it. CPUID results are different between Intel and AMD processors (at least for the interesting stuff like hyperthreading and cache topology) and are not particularly stable across different processor versions. (Newer Intel i7 processors introduce a new CPUID value (eax=0xb) which supercedes the information supported by CPUID on earlier processors).

If you can get away with it, your best bet would be to use WMI (see Win32_Processor) or GetLogicalProcessorInformation.

Either of these will be a vastly simpler and more manageable solution if they are supported on your platform (to get logical processor information either way requires WinXP sp3 or newer on the client side or Windows Server 2008 or newer on the server side).

If you really want to try your luck with CPUID, what I would recommend doing would be to create a simple stub which is capable of executing CPUID and returning the results to managed code (you will need different versions for 32-bit and 64-bit) and execute those within the context of your managed application. I do this by compiling a native application and then extracting the raw instruction bytes of my CPUID method into a byte array which can be executed from managed code.

This should get you started for 32-bit support only:

using System;
using System.Runtime.InteropServices;

static class Program {
static void Main() {
//Allocate the executable buffer on a distinct page
// rather than just pinning it in place because we
// need to mark the page as executable.
// Failing to do this would cause NX-enabled machines
// to have access violations attempting to execute.
IntPtr pExecutableBuffer = VirtualAlloc(
IntPtr.Zero,
new IntPtr(CPUID_32.Length),
AllocationType.MEM_COMMIT | AllocationType.MEM_RESERVE,
MemoryProtection.PAGE_EXECUTE_READWRITE
);

Marshal.Copy(CPUID_32, 0, pExecutableBuffer, CPUID_32.Length);
CPUID executeHandler = (CPUID)Marshal.GetDelegateForFunctionPointer(
pExecutableBuffer, typeof(CPUID));
CPUID_Args args = new CPUID_Args();
args.eax = 0;
executeHandler(ref args);
Console.WriteLine("eax: {0} ebx: {1} ecx: {2} edx: {3}",
args.eax,
args.ebx,
args.ecx,
args.edx);
VirtualFree(
pExecutableBuffer,
IntPtr.Zero,
FreeType.MEM_RELEASE);
}

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate void CPUID(ref CPUID_Args args);

private static readonly byte[] CPUID_32 = new byte[] {
0x53, // push ebx
0x57, // push edi
0x8B, 0x7C, 0x24, 0x0C, // mov edi,dword ptr [esp+0Ch]
0x8B, 0x07, // mov eax,dword ptr [edi]
0x8B, 0x4F, 0x08, // mov ecx,dword ptr [edi+8]
0x0F, 0xA2, // cpuid
0x89, 0x07, // mov dword ptr [edi],eax
0x89, 0x5F, 0x04, // mov dword ptr [edi+4],ebx
0x89, 0x4F, 0x08 , // movdword ptr [edi+8],ecx
0x89, 0x57, 0x0C , // mov dword ptr [edi+0Ch],edx
0x5F, // pop edi
0x5B, // pop ebx
0xC2, 0x04, 0x00 // ret
};

[Flags]
enum AllocationType {
MEM_COMMIT = 0x1000,
MEM_RESERVE = 0x2000,
}

[Flags]
enum MemoryProtection {
PAGE_EXECUTE_READWRITE = 0x40,
}

[Flags]
enum FreeType {
MEM_RELEASE = 0x8000
}

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

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool VirtualFree(
IntPtr lpAddress,
IntPtr dwSize,
FreeType dwFreeType);
}

[StructLayout(LayoutKind.Sequential)]
struct CPUID_Args {
public uint eax;
public uint ebx;
public uint ecx;
public uint edx;
}

Identifying the CPU architecture type using C#

You could also try (only works if it's not manipulated):

System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")

I am trying to get an unique CPU ID

I came to the conclusion that this is just not a good method to get unique ID's.
So instead of this method, I wrote a much larger piece of code in which I requested much more hardware identifiers of the system to build an unique identifier.

Its way better as this method would even produce different numbers if I would run it on cloned hardware, or if hardware gets changed. There is a lot of WMI info around and I think that's the place where people should look for creating something unique and not use ProcessorID.

Inline Assembly Code to Get CPU ID

Here's your code modified to get the same result as Win32_Processor.ProcessorId on both x64 and x86:

using System;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
class Program
{
[DllImport("user32", EntryPoint = "CallWindowProcW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] private static extern IntPtr CallWindowProcW([In] byte[] bytes, IntPtr hWnd, int msg, [In, Out] byte[] wParam, IntPtr lParam);

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool VirtualProtect([In] byte[] bytes, IntPtr size, int newProtect, out int oldProtect);

const int PAGE_EXECUTE_READWRITE = 0x40;

static void Main(string[] args)
{
string s = ProcessorId();
Console.WriteLine("ProcessorId: " + s);
Console.ReadLine();
}

private static string ProcessorId()
{
byte[] sn = new byte[8];

if (!ExecuteCode(ref sn))
return "ND";

return string.Format("{0}{1}", BitConverter.ToUInt32(sn, 4).ToString("X8"), BitConverter.ToUInt32(sn, 0).ToString("X8"));
}

private static bool ExecuteCode(ref byte[] result)
{
int num;

/* The opcodes below implement a C function with the signature:
* __stdcall CpuIdWindowProc(hWnd, Msg, wParam, lParam);
* with wParam interpreted as a pointer pointing to an 8 byte unsigned character buffer.
* */

byte[] code_x86 = new byte[] {
0x55, /* push ebp */
0x89, 0xe5, /* mov ebp, esp */
0x57, /* push edi */
0x8b, 0x7d, 0x10, /* mov edi, [ebp+0x10] */
0x6a, 0x01, /* push 0x1 */
0x58, /* pop eax */
0x53, /* push ebx */
0x0f, 0xa2, /* cpuid */
0x89, 0x07, /* mov [edi], eax */
0x89, 0x57, 0x04, /* mov [edi+0x4], edx */
0x5b, /* pop ebx */
0x5f, /* pop edi */
0x89, 0xec, /* mov esp, ebp */
0x5d, /* pop ebp */
0xc2, 0x10, 0x00, /* ret 0x10 */
};
byte[] code_x64 = new byte[] {
0x53, /* push rbx */
0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, /* mov rax, 0x1 */
0x0f, 0xa2, /* cpuid */
0x41, 0x89, 0x00, /* mov [r8], eax */
0x41, 0x89, 0x50, 0x04, /* mov [r8+0x4], edx */
0x5b, /* pop rbx */
0xc3, /* ret */
};

ref byte[] code;

if (IsX64Process())
code = ref code_x64;
else
code = ref code_x86;

IntPtr ptr = new IntPtr(code.Length);

if (!VirtualProtect(code, ptr, PAGE_EXECUTE_READWRITE, out num))
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

ptr = new IntPtr(result.Length);

return (CallWindowProcW(code, IntPtr.Zero, 0, result, ptr) != IntPtr.Zero);
}

private static bool IsX64Process()
{
return IntPtr.Size == 8;
}
}
}

I made trivial modifications to the C# part without compiling the code (I don't have a Windows dev machine setup at the moment) so if there are syntax errors please just make the obvious fix.

I want to stress one very important point: what your original code was reading back was NOT a CPU serial number:

  • You used CPUID function 2 (by placing 2 in EAX before executing the CPUID instruction). If you read the Intel and AMD CPUID application notes you'll see that this reads back the cache and TLB hardware configuration and is only supported on Intel.
  • I modified your code to use CPUID function 1, which reads back the stepping, model, and family of a CPU. This matches the behavior of WIN32_Processor.ProcessorID
  • Modern x86 CPUs don't have a serial number that is unique among otherwise identical units "rolling off the assembly line". Processor serial numbers were only available on Pentium 3's through CPUID function 3.

I'll now explain the process and tools I used.

Paste the array of opcodes into a Python script that will then write the opcodes in a binary file (cpuid-x86.bin):

cpuid_opcodes = [ 0x55, 0x8b, 0xec, 0x8b, ... ]
open('cpuid-x86.bin', 'w').write(''.join(chr(x) for x in cpuid_opcodes))

Disassemble cpuid-x86.bin. I used udcli from udis86.

$ udcli -att cpuid-x86.bin
0000000000000000 55 push %ebp
0000000000000001 8bec mov %esp, %ebp
0000000000000003 8b7d10 mov 0x10(%ebp), %edi
0000000000000006 6a02 push $0x2
0000000000000008 58 pop %eax
0000000000000009 0fa2 cpuid
000000000000000b 891f mov %ebx, (%edi)
000000000000000d 894f04 mov %ecx, 0x4(%edi)
0000000000000010 895708 mov %edx, 0x8(%edi)
0000000000000013 8be5 mov %ebp, %esp
0000000000000015 5d pop %ebp
0000000000000016 c21000 ret $0x10

One thing that immediately stands out is why use "push $0x2; pop %eax" to move the value 2 into EAX when a simple "mov $0x2, %eax" will do?

My guess is that the instruction encoding for "push $0x2", 6a02, is easier to modify in hexadecimal form. Both by hand and programmatically. I'd guess somebody somewhere tried to use CPUID function 3 to get the processor serial number and found that it wasn't supported then switched to using function 2.

The "ret $0x10" at the end is also unusual. The RET IMM16 form of the RET instruction returns to the caller then pops IMM16 bytes off the stack. The fact that the callee is responsible for popping arguments off the stack after function return implies that this is not using the standard x86 calling convention.

Indeed, a quick peek into the C# code reveals that it's using CallWindowProc() to invoke the assembly function. The documentation for CallWindowProc() shows that the assembly code is implementing a C function with a signature like:

__stdcall CpuIdWindowProc(hWnd, Msg, wParam, lParam);

__stdcall is the special function calling convention used by 32 bit Windows APIs.

The assembly code uses 0x10(%ebp), which is the third argument to the function, as a character array to store the output from the CPUID instruction. (After a standard function prologue on x86, 8(%ebp) is the first argument. 0xc(%ebp) is the second 4-byte argument and 0x10(%ebp) is the third) The third parameter in our window procedure function prototype above is wParam. It's used as an out parameter and is the only parameter used in the assembly code.

The last interesting thing about the assembly code is that it clobbers the registers EDI and EBX without saving them, violating the __stdcall calling convention. This bug is apparently latent when calling the function through CallWindowProc() but will reveal itself if you try to write your own main function in C to test the assembly code (cpuid-main.c):

#include <stdio.h>
#include <stdint.h>

void __stdcall cpuid_wind_proc(uint32_t hWnd, uint32_t msg, uint8_t *wparam, uint32_t lparam);

enum {
RESULT_SIZE = 2 * 4, /* Two 32-bit registers: EAX, EDX */
};

static unsigned int form_word_le(uint8_t a[])
{
return (a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0];
}

int main()
{
uint8_t r[RESULT_SIZE];
memset(r, 0, sizeof(r));

cpuid_wind_proc(0, 0, r, 0);

printf("%08x%08x\n", form_word_le(r + 4), form_word_le(r));
return 0;
}

A version of the assembly fixed to save and restore EDI, EBX and use CPUID function 1 is like this:

    .section .text
.global _cpuid_wind_proc@16
_cpuid_wind_proc@16:
push %ebp
mov %esp, %ebp
push %edi
mov 16(%ebp), %edi
push $1
pop %eax
push %ebx
cpuid
mov %eax, (%edi)
mov %edx, 0x4(%edi)
pop %ebx
pop %edi
mov %ebp, %esp
pop %ebp
ret $16

The symbol name _cpuid_wind_proc@16 is how __stdcall function names are mangled on 32 bit Windows. The @16 is the number of bytes the parameters take up. (Four parameters each taking four bytes on 32 bit Windows adds up to 16)

Now I'm ready to port the code to x64.

  • By consulting this handy ABI table I see that the first four parameters are passed in RCX, RDX, R8, and R9 so wParam is in R8.
  • The Intel documentation tells me that the CPUID instruction clobbers EAX, EBX, ECX, and EDX. EBX is the lower half of RBX which is a saved GPR in the ABI ("saved GPR" here means a general purpose register that should retain its contents across a function call) so I made sure to save RBX before executing the CPUID instruction and restore RBX afterwards.

Here's the x64 assembly:

    .section .text
.global cpuid_wind_proc
cpuid_wind_proc:
push %rbx
mov $1, %rax
cpuid
movl %eax, (%r8)
movl %edx, 4(%r8)
pop %rbx
ret

As you can see the x64 version is shorter and easier to write. There's only one function calling convention on x64 so we don't have to worry about __stdcall.

Build the x64 assembly function along with cpuid-main.c and compare its output with this VBScript (cpuid.vbs):

Set objProc = GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'")
WScript.echo objProc.ProcessorId

Run cpuid.vbs with

wscript cpuid.vbs

and verify the outputs match. (I actually cross compiled with MinGW-w64 on Linux and ran the program under Wine64 emulation while doing the C and assembly work up till this point.)

With the x64 assembly CPUID function working, I'm now ready to integrate the code back into C#.

  • Disassemble cpuid-x64.exe to get the opcodes and paste them as a new byte array (code_x64).
  • Change ExecuteCode() to determine whether to run the x86 or x64 version of the CPUID code by testing for IntPtr.Size == 8 in IsX64Process().

Finally, change ProcessorId() to produce the hexadecimal string with:

string.Format("{0}{1}", BitConverter.ToUInt32(sn, 4).ToString("X8"), BitConverter.ToUInt32(sn, 0).ToString("X8"));

Using "X8" instead of just "X" ensures that the UInt32 is formatted as an 8 digit hexadecimal value with zero padding. Otherwise, you can't tell which digits came from EDX and which from EAX when you concatenate them into a single string.

And that's it.

How to determine if processor supports 64bit on a 32bit OS

It may not be ideal, but it's relatively straightforward to create a (native) DLL using VC++ or the like and query the processor's features directly. This method could then be PInvoked from your C# application.

The following C++ method would return true when run on a 64 bit capable processor, and false on a 32 bit only processor (whether the OS is 32 or 64 bit):

bool __declspec(naked) IsCPU64BitCapable()
{
__asm
{
// Save EBX since it's affected by CPUID
push ebx
// Determine whether the CPU supports retrieving extended feature data
mov eax, 0x80000000
cpuid
cmp eax, 0x80000000
// No extended data => no 64 bit
jbe no_extended_data
// Request extended feature data
mov eax, 0x80000001
cpuid
// Bit 29 of EDX will now indicate whether the CPU is 64 bit capable
mov eax, edx
shr eax, 29
and eax, 1
jmp extended_data
no_extended_data:
xor eax,eax
extended_data:
// Restore EBX
pop ebx
ret
}
}

This method can then be used from C# using:

[DllImport("Test64Bit.dll")]
private static extern bool IsCPU64BitCapable();


Related Topics



Leave a reply



Submit