C# 中的 x86/x64 CPUID
Posted
技术标签:
【中文标题】C# 中的 x86/x64 CPUID【英文标题】:x86/x64 CPUID in C# 【发布时间】:2011-03-14 02:12:16 【问题描述】:与my other question相关,请帮我调试“未知模块中发生'System.AccessViolationException'类型的未处理异常。附加信息:试图读取或写入受保护的内存。这通常表明其他内存已损坏。”单步执行代码,一切正常,直到实际调用 del() 并在该行中失败。
此代码基于此article's sample 和此python code 在python 中工作。我也无法让代码示例按原样工作(同样的例外),但我希望它只是有点过时了。
编辑:如果您关心我们是如何到达这里的,请查看编辑历史记录,这很无趣。
完成的工作版本:
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
;
注意CPUID0需要按正确的顺序读取:
//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()));
【问题讨论】:
如果我错了,请纠正我,但是C#代码不是编译成MSIL代码,类似于java字节码吗?这意味着您不能输入 x86 汇编语言,因为 .NET 框架出于所有意图和目的不知道 x86 操作码是什么?只有MSIL,对吧? icemanind - 我只能说atrevido.net/blog/2005/01/28/Inline+X86+ASM+In+C.aspx 很有说服力。 是的,他们通过本质上“欺骗”.net 解释器来做到这一点。我认为更好的方法是将您的汇编语言程序创建为 DLL,并使用 PInvoke 执行 DLL 中的方法。见这篇文章:codeproject.com/KB/cs/unmanage.aspx 我正在同时进行这项工作。见my other post 哇,我曾经从 DynamicMethod 的_methodPtrAux
字段中得到一个函数指针,看看我是否可以用 MSIL calli 指令调用它,我认为 那 很荒谬...
【参考方案1】:
我很确定你被DEP 屏蔽了。 x_CPUIDy_INSNS
字节数组位于标记为数据且不可执行的内存段中。
编辑:
话虽如此,我已经得到了一个可以编译和运行的版本,但我认为没有得到正确的值。也许这会让你一路走好。
编辑 2:
我认为我现在回归了正确的价值观。随意验证。
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
【讨论】:
那不会也阻止python脚本吗?它似乎使用了类似的机制。 很有可能,是的。您可以尝试在“性能选项”对话框的“数据执行保护”选项卡中排除 Python 解释器(可从“系统属性”小程序的“高级”选项卡访问)并查看是否有效。但是,对于 .NET 应用程序,您必须花很多心思才能绕过它。 我添加了一些至少不会崩溃的代码。虽然似乎没有得到正确的价值观,但我希望它能让你朝着正确的方向前进。 是的,我全是零。但我喜欢你的改变。有效地将其复制到可执行段中,对吗? 我刚刚进行了更多更改。是的,将其复制到可执行段中。 “更多更改”是创建内存指针委托并将指针正确传递给缓冲区的一种更简洁的方法。【参考方案2】:我可以推荐以下页面吗:http://community.devpinoy.org/blogs/cvega/archive/2006/04/07/2658.aspx
本页将向您展示 CPUID 的汇编源代码、如何将其编译为 DLL 以及如何从 C# 中调用它。
另外如果你需要其他硬件识别程序,我可以推荐这个页面:http://www.codeproject.com/KB/system/GetHardwareInformation.aspx
此页面显示如何获取主板信息、硬盘信息、cpu 信息、显卡信息等信息。
【讨论】:
【参考方案3】:我决定改进您的答案。它不再需要 unsafe 来编译,它只需要两个汇编块就可以读取任何和所有 cpuid 块,因为它只是将 eax/ebx/ecx/edx 写入 16 字节字节数组。
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;
【讨论】:
对于我和@Julien,GenuineIntel 出现故障。我在 x64 机器上测试过。我更新了我的原始帖子,对订单进行了更正。我希望 CPUID1 需要类似地重新排列。【参考方案4】:我将@antiduh 的代码重构为静态方法,因此没有对象生命周期需要管理。这比较慢,因为在调用 Invoke() 之间没有重用 ASM 代码,但是为了简单而权衡速度对我的用例来说是有意义的。这个新版本可以在我的机器上在 15 毫秒内调用 CPUID 1000 次。
感谢出色的代码家伙!
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
;
【讨论】:
这是一段不错的代码。只是想知道为什么将结果放在byte[]
而不是uint[]
:后者更类似于 32 位寄存器。 PS:如果你想要一个“干净”的解决方案,你不妨将内在的__cpuid
与 C++/CLI 结合使用。
如果您不针对纯速度进行优化,为什么还要编写 ASM?【参考方案5】:
此外,要获得 CPUID4,还需要一个参数。 以下是获取 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
;
【讨论】:
【参考方案6】:感谢@antiduh 的解决方案。 为了更好的可用性,我会稍微更改 Invoke 签名,如下所示,因此您无需将 get 结果分配为一组寄存器
// This is a modification to https://***.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;
【讨论】:
【参考方案7】:我知道这个帖子很旧,但我非常喜欢这个帖子。 编码后,我发现在“EAX = 7,ECX = 0”时获取数据存在问题 所以,我在 x64CodeBytes 中添加了“mov ecx, 0”。
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
;
【讨论】:
xor ecx, ecx
也可以,代码是0x31, 0xC9
【参考方案8】:
对于 .NET 5,现在有一个内置的内部函数:https://docs.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);
【讨论】:
以上是关于C# 中的 x86/x64 CPUID的主要内容,如果未能解决你的问题,请参考以下文章
Dll注入:X86/X64 远程线程CreateRemoteThread 注入
conan入门(十七):支持android NDK (armv7,armv8,x86,x86_64)交叉编译的统一profile jinja2模板