C# 获取CPU ID的内联程序集代码
我发现了一段很好的代码,它使用API调用执行ASM指令,以获取CPU的序列号:C# 获取CPU ID的内联程序集代码,c#,.net,winapi,assembly,64-bit,C#,.net,Winapi,Assembly,64 Bit,我发现了一段很好的代码,它使用API调用执行ASM指令,以获取CPU的序列号: using System; using System.Text; using System.Runtime.InteropServices; namespace ConsoleApplication1 { class Program { [DllImport("user32", EntryPoint = "CallWindowProcW", CharSet = CharSet.Uni
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 from 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);
}
}
}
我测试过了,对我来说效果很好。但我仍然有一些与之相关的问题:
1) 我想在一个可以在x86和x64环境中运行的应用程序中实现这段代码。如果在64x环境中运行此代码,则会得到AccessViolationException。代码的作者说,通过实现包含x64指令(RAX、RBX、RCX、RDX等)的字节码数组,可以很容易地实现这一点。我的问题是,我完全不知道如何将86x字节码转换成x64字节码,事实上我甚至不知道ASM。是否有任何转换表或实用程序可以这样做
2) 此代码段对任何类型的处理器都有效吗?我在使用英特尔内核的笔记本电脑上测试了它,它可以工作。。。但以AMD为例呢
3) 我不确定我得到的值是否正确。如果我运行以下代码:
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();
}
我得到的结果是“BFEBFBFF000306A9”。代码片段的结果是“F0B2FF0CA0000”。为什么?哪一个是正确的?您发布的代码似乎调用了
CPUID
函数#2(由EAX
寄存器给出,在PUSH 2;POP EAX
之后)。根据不用于查询序列号的英特尔指令集参考:
当CPUID在EAX设置为2的情况下执行时,处理器返回
有关处理器内部TLB、缓存和预取的信息
EAX、EBX、ECX和EDX寄存器中的硬件
另请注意,此功能在AMD处理器上不可用,但代码执行时应无错误。以下是修改后的代码,以获得与x64和x86上相同的结果: 我在没有编译代码的情况下对C#部分进行了微不足道的修改(我目前没有Windows开发机器设置),因此如果出现语法错误,请进行明显的修复 我想强调一个非常重要的点:您最初读取的代码不是CPU序列号:
cpuid_opcodes = [ 0x55, 0x8b, 0xec, 0x8b, ... ]
open('cpuid-x86.bin', 'w').write(''.join(chr(x) for x in cpuid_opcodes))
#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;
}
Set objProc = GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'")
WScript.echo objProc.ProcessorId
- 您使用了CPUID函数2(在执行CPUID指令之前,将2放入EAX)。如果您阅读了和CPUID应用程序说明,您将看到这将读回缓存和TLB硬件配置,并且仅在Intel上受支持
- 我修改了您的代码以使用CPUID函数1,它读回CPU的步进、型号和系列。这与
- 现代x86 CPU没有一个序列号,在“从装配线滚出”的其他相同单元中是唯一的。处理器序列号仅通过CPUID函数3在奔腾3上可用
__stdcall CpuIdWindowProc(hWnd, Msg, wParam, lParam);
是32位Windows API使用的特殊函数调用约定
汇编代码使用函数的第三个参数0x10(%ebp)作为字符数组来存储CPUID指令的输出。(在x86上的标准函数序言之后,8(%ebp)是第一个参数。0xc(%ebp)是第二个4字节参数,0x10(%ebp)是第三个参数)上面的窗口过程函数原型中的第三个参数是wParam。它用作out参数,是汇编代码中使用的唯一参数
关于汇编代码的最后一件有趣的事情是,它在不保存寄存器的情况下对寄存器EDI和EBX进行了重击,违反了\uu stdcall调用约定。当通过CallWindowProc()调用函数时,此错误显然是潜在的,但如果您试图用C编写自己的主函数来测试汇编代码(cpuid main.C),则会暴露出来:
符号名称\u cpuid\u wind_proc@16是32位窗口上函数名被损坏的方式。@16是参数占用的字节数。(在32位窗口上,四个参数各占用四个字节,总计16个)
现在我准备将代码移植到x64
- 通过查阅,我发现前四个参数在RCX、RDX、R8和R9中传递,因此wParam在R8中
- 英特尔文档告诉我,CPUID指令
#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; }
.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
.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
Set objProc = GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'") WScript.echo objProc.ProcessorId
wscript cpuid.vbs
string.Format("{0}{1}", BitConverter.ToUInt32(sn, 4).ToString("X8"), BitConverter.ToUInt32(sn, 0).ToString("X8"));