从C#进行AES-NI指令的测试

6
我想知道是否有一种方法可以从C#.NET检测主机系统CPU中AES-NI的存在。请注意,这个问题并不是关于如何使用.NET的AES-NI。简单地使用AESCryptoServiceProvider将使用AES-NI(如果可用),这个结果是基于我所做的独立基准测试之后得出的。将AESCryptoServiceProvider的性能与TrueCrypt提供的基准测试进行比较,TrueCrypt确实支持AES-NI。在有和没有AES-NI的两台机器上,结果非常相似。
我想要能够测试它的原因是能够向用户指示他们的计算机是否支持AES-NI。这将是相关的,因为它将减少支持事件,涉及像“但我的朋友也有一个Core i5处理器,但他的比我的快很多!”这样的问题。如果程序的用户界面可以向用户指示他们的系统是否支持AES-NI,那么也可以指出“慢速性能是正常的,因为此系统不支持AES-NI”。
(我们应该感谢英特尔为所有不同处理器版本带来的困惑! :-) ) 是否有一种方法可以检测此信息,例如通过WMI?

不知道答案,但我会首先查找一些操作系统 WMI 调用。 - Scott Chamberlain
与论坛网站不同,我们在 [so] 上不使用“谢谢”、“感激任何帮助”或签名。请参阅“应该从帖子中删除‘Hi’、‘thanks’、标语和问候语吗?”文章。 - John Saunders
从看起来的情况来看,您需要调用装配指令CPUID,并将EAX设置为1,这将输出处理器功能中EDX和ECX中的一些位标志。您应该关心ECX中的第25位被设置为1,这意味着它支持AES-NI。问题在于,我无法找到通过托管代码或WMI获取CPUID的ECX值的方法。我最接近的是WMI调用Win32_ProcessorProcessorID属性,它是CPUID调用后EAX和EDX寄存器的位掩码,但不包括您关心的ECX部分。 - Scott Chamberlain
2个回答

2

看起来在SO上有类似的问题:Inline Assembly Code to Get CPU ID,并且有一个很好的答案。

但是这个答案需要一些调整才能满足您的需求。

首先,据我所知,AES-NI只存在于64位处理器上,对吗?那么您可以忽略上面答案中的所有32位代码。

其次,您需要ECX寄存器或者它的第25位,因此您必须稍微更改一下代码:

private static bool IsAESNIPresent()
{
    byte[] sn = new byte[16]; // !!! Here were 8 bytes

    if (!ExecuteCode(ref sn))
        return false;

    var ecx = BitConverter.ToUInt32(sn, 8);
    return (ecx & (1 << 25)) != 0;
}

最后,您需要将ECX寄存器存储在数组中:
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], ebx !!! changed */
    0x41, 0x89, 0x50, 0x08,                   /* mov [r8+0x8], ecx !!! added */
    0x41, 0x89, 0x50, 0x0C,                   /* mov [r8+0xC], edx !!! added*/
    0x5b,                                     /* pop rbx */
    0xc3,                                     /* ret */
};

据我所见,这就是所有的变化。

我该如何在 Windows 上使用 C++(GCC)实现相同的功能? - Rahim Khoja
@Canadian_Republican,GCC支持CPUID:https://dev59.com/questions/323Xa4cB1Zd3GeqPag8W - Mark Shevchenko

1
上面Mark的回答非常好,让我成功地解决了问题。但是我注意到,如果应用程序以32位模式运行,则x86代码中没有提取ecx寄存器,因此无法检测AES-NI。
我添加了一行并更改了另一行,基本上将Mark对x64代码所做的更改应用于x86代码。这样可以在32位模式下查看AES-NI位。不确定是否会有帮助,但我想发布一下。
编辑:在进行一些测试时,我注意到x64代码返回的寄存器不正确。 EDX在0x4、0x8和0xC处被返回,此外,x86代码中的ECX和EDX寄存器位于不同的偏移量,因此您需要更频繁地检查IntPtr.Size,以使两个环境都正常工作。为简化事情,我将ECX寄存器放置在0x4处,将EDX放置在0x8处,这样数据就排列正确了。
如果有人请求,我可以发布整个类,它是从这篇文章和其他文章中学到的可工作示例。
public static bool ExecuteCode(ref byte[] result) {
    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, 0x4F, 0x04,          /* mov  [edi+0x4], ecx Changed */
        0x89, 0x57, 0x08,          /* mov  [edi+0x8], edx Changed */
        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, 0x48, 0x04,                   /* mov [r8+0x4], ecx Changed */
        0x41, 0x89, 0x50, 0x08,                   /* mov [r8+0x8], edx Changed*/
        0x5B,                                     /* pop rbx */
        0xC3,                                     /* ret */
    };

    int num;
    byte[] code = (IntPtr.Size == 4) ? code_x86 : code_x64;
    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 (ExecuteNativeCode(code, IntPtr.Zero, 0, result, ptr) != IntPtr.Zero);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接