c# - Inline Assembly Code to Get CPU ID -


i found nice piece of code here executes asm instructions using api calls in order obtain serial number of cpu:

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 executenativecode([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 = cpu32_serialnumber();             console.writeline("cpu serial-number: " + s);             console.readline();         }          private static string cpu32_serialnumber()         {             byte[] sn = new byte[12];              if (!executecode32(ref sn))                 return "nd";              return string.format("{0}{1}{2}", bitconverter.touint32(sn, 0).tostring("x"), bitconverter.touint32(sn, 4).tostring("x"), bitconverter.touint32(sn, 8).tostring("x"));         }          private static bool executecode32(ref byte[] result)         {             // cpu 32bit serialnumber -> asm x86 c# (c) 2003-2011 cantelmo software             // 55               push ebp             // 8bec             mov ebp,esp             // 8b7d 10          mov edi,dword ptr ss:[ebp+10]             // 6a 02            push 2             // 58               pop eax             // 0fa2             cpuid             // 891f             mov dword ptr ds:[edi],ebx             // 894f 04          mov dword ptr ds:[edi+4],ecx             // 8957 08          mov dword ptr ds:[edi+8],edx             // 8be5             mov esp,ebp             // 5d               pop ebp             // c2 1000          retn 10              int num;              byte[] code_32bit = new byte[] { 0x55, 0x8b, 0xec, 0x8b, 0x7d, 0x10, 0x6a, 2, 0x58, 15, 0xa2, 0x89, 0x1f, 0x89, 0x4f, 4, 0x89, 0x57, 8, 0x8b, 0xe5, 0x5d, 0xc2, 0x10, 0 };             intptr ptr = new intptr(code_32bit.length);              if (!virtualprotect(code_32bit, ptr, page_execute_readwrite, out num))                 marshal.throwexceptionforhr(marshal.gethrforlastwin32error());              ptr = new intptr(result.length);              return (executenativecode(code_32bit, intptr.zero, 0, result, ptr) != intptr.zero);         }     } } 

i tested , it's working fine me. still have questions , problems related it:

1) implement code inside application can run in both x86 , x64 environment. if run code 64x environment, accessviolationexception. author of code said can achieved implementing bytecode array contains x64 instructions (rax, rbx, rcx, rdx, ...). problem absolutely don't know how convert 86x byte code x64 byte code, don't know asm in fact. there conversion table or utility can this?

2) code snippet valid type of processor? tested on laptop uses intel core , works... amd example?

3) i'm not sure value i'm obtaining correct one. if run following code:

string cpuinfo = string.empty;  system.management.managementclass mc = new system.management.managementclass("win32_processor"); system.management.managementobjectcollection moc = mc.getinstances();  foreach (system.management.managementobject mo in moc) {     if (cpuinfo == string.empty)         cpuinfo = mo.properties["processorid"].value.tostring(); } 

the result "bfebfbff000306a9". result of code snippet "f0b2ff0ca0000". why? 1 correct?

here's code modified same result 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;              /* opcodes below implement c function signature:              * __stdcall cpuidwindowproc(hwnd, msg, wparam, lparam);              * wparam interpreted pointer pointing 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 c# part without compiling code (i don't have windows dev machine setup @ moment) if there syntax errors please make obvious fix.

i want stress 1 important point: what original code reading not cpu serial number:

  • you used cpuid function 2 (by placing 2 in eax before executing cpuid instruction). if read intel , amd cpuid application notes you'll see reads cache , tlb hardware configuration , supported on intel.
  • i modified code use cpuid function 1, reads stepping, model, , family of cpu. matches behavior of win32_processor.processorid
  • modern x86 cpus don't have serial number unique among otherwise identical units "rolling off assembly line". processor serial numbers available on pentium 3's through cpuid function 3.

i'll explain process , tools used.

paste array of opcodes python script write opcodes in binary file (cpuid-x86.bin):

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

disassemble cpuid-x86.bin. used udcli 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 stands out why use "push $0x2; pop %eax" move value 2 eax when simple "mov $0x2, %eax" do?

my guess instruction encoding "push $0x2", 6a02, easier modify in hexadecimal form. both hand , programmatically. i'd guess somewhere tried use cpuid function 3 processor serial number , found wasn't supported switched using function 2.

the "ret $0x10" @ end unusual. ret imm16 form of ret instruction returns caller pops imm16 bytes off stack. fact callee responsible popping arguments off stack after function return implies not using standard x86 calling convention.

indeed, quick peek c# code reveals it's using callwindowproc() invoke assembly function. documentation callwindowproc() shows assembly code implementing c function signature like:

__stdcall cpuidwindowproc(hwnd, msg, wparam, lparam); 

__stdcall special function calling convention used 32 bit windows apis.

the assembly code uses 0x10(%ebp), third argument function, character array store output cpuid instruction. (after standard function prologue on x86, 8(%ebp) first argument. 0xc(%ebp) second 4-byte argument , 0x10(%ebp) third) third parameter in our window procedure function prototype above wparam. it's used out parameter , parameter used in assembly code.

the last interesting thing assembly code clobbers registers edi , ebx without saving them, violating __stdcall calling convention. bug apparently latent when calling function through callwindowproc() reveal if try write own main function in c test 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, /* 2 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 assembly fixed save , restore edi, ebx , use cpuid function 1 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 how __stdcall function names mangled on 32 bit windows. @16 number of bytes parameters take up. (four parameters each taking 4 bytes on 32 bit windows adds 16)

now i'm ready port code x64.

  • by consulting this handy abi table see first 4 parameters passed in rcx, rdx, r8, , r9 wparam in r8.
  • the intel documentation tells me cpuid instruction clobbers eax, ebx, ecx, , edx. ebx lower half of rbx saved gpr in abi ("saved gpr" here means general purpose register should retain contents across function call) made sure save rbx before executing cpuid instruction , restore rbx afterwards.

here's 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 can see x64 version shorter , easier write. there's 1 function calling convention on x64 don't have worry __stdcall.

build x64 assembly function along cpuid-main.c , compare output 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 outputs match. (i cross compiled mingw-w64 on linux , ran program under wine64 emulation while doing c , assembly work till point.)

with x64 assembly cpuid function working, i'm ready integrate code c#.

  • disassemble cpuid-x64.exe opcodes , paste them new byte array (code_x64).
  • change executecode() determine whether run x86 or x64 version of cpuid code testing intptr.size == 8 in isx64process().

finally, change processorid() produce hexadecimal string with:

string.format("{0}{1}", bitconverter.touint32(sn, 4).tostring("x8"), bitconverter.touint32(sn, 0).tostring("x8")); 

using "x8" instead of "x" ensures uint32 formatted 8 digit hexadecimal value 0 padding. otherwise, can't tell digits came edx , eax when concatenate them single string.

and that's it.


Comments

Popular posts from this blog

linux - xterm copying to CLIPBOARD using copy-selection causes automatic updating of CLIPBOARD upon mouse selection -

c++ - qgraphicsview horizontal scrolling always has a vertical delta -