views:

558

answers:

2

I would like to add cpuid functionality to my C# app. I found this interesting blog post online. I will probably need MASM to compile this but:

  1. How should I start?
  2. I suspect that I will have to compile a dll for both X86 and X64 but again I don't have a clue about how to go about such a thing (and I am a bit pressed for time).

So any help would be more than welcome!

A: 

I think you can build a native CLR assembly employing inline assembler syntax (__asm) in C++ code.

Nikolai N Fetissov
I seem to remember this not working in X64 mode.
Kris
Oh, it's been a while since I messed with C#/C++ on windows, and that was certainly win32.
Nikolai N Fetissov
+2  A: 

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;
}
StarPacker
Now this is a useful answer :)You are the first person that mentions there might be a real difference between 32 and 64 bit. Can you give some additional directions on where these differences lie?Your general strategy seems to line up with this:http://devpinoy.org/blogs/cvega/archive/2006/04/07/2658.aspxMight you have any idea how I could compile this properly in either bit mode?
Kris
I just realized that the code in your answer can probably only be used in 32 bit because X64 bit compilation does not allow inline asm?
Kris
The code would work on a 64-bit machine running a 32-bit OS (that's probably not what you are asking though). Truly supporting 64-bit is more complicated... First, you have to decide if you will support IA64 or only x64/AMD64/whatever you want to call it. Then, you would need a different version of the byte array (call it CPUID_64) and code that ensures that the correct version of the byte array is executed when you want to call CPUID. This example however is not using "inline assembly" per se. The x86 is being dynamically loaded and executed, it is not an executable part of the binary.
StarPacker
uph, why o why does it lways have to be soooo complicaed with these things. One would think that it might be useful to always have access to this information....
Kris
Actually the code I linked runs very well on my Win7 X64... If only I could figure out how to compile the thing :D
Kris