Тест на инструкции AES-NI от C#
Я хочу знать, есть ли способ проверить наличие AES-NI в ЦП хост-системы из C#.NET.
Позвольте мне сразу сказать, что этот вопрос не касается того, как использовать AES-NI из.NET. Получается просто используя AESCryptoServiceProvider
будет использовать AES-NI, если он доступен. Этот результат основан на независимых тестах, которые я сделал, сравнивая выступления AESCryptoServiceProvider
по сравнению с эталонами, представленными в TrueCrypt, который действительно поддерживает AES-NI. Результаты были удивительно похожи на обеих машинах с AES-NI и без него.
Причина, по которой я хочу проверить его, заключается в том, чтобы указать пользователю, что его компьютер поддерживает AES-NI. Это было бы актуально, так как это уменьшило бы количество инцидентов поддержки, связанных с такими вопросами, как "но у моего друга также есть Core i5, но он намного быстрее!" Если пользовательский интерфейс программы может указывать пользователю, что его система поддерживает или не поддерживает AES-NI, также можно было бы указать, что "более низкая производительность является нормальной, поскольку эта система не поддерживает AES-NI".
(Мы можем поблагодарить Intel за всю путаницу с различными степенями процессора!:-))
Есть ли способ обнаружить эту информацию, возможно, через WMI?
2 ответа
Похоже, что с SO такой же вопрос: встроенный код сборки для получения идентификатора процессора с отличным ответом.
Но этот ответ требует некоторых корректировок в соответствии с вашими потребностями.
Во-первых, как я понимаю, 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 */
};
Насколько я вижу, это все изменения.
Ответ от Марка выше фантастический и заставил меня работать хорошо, однако я заметил, что если приложение запускается в 32-битном режиме, регистр ecx не извлекается в коде x86, что не приводит к обнаружению AES-NI,
Я добавил одну строку и изменил другую, в основном применяя изменения, внесенные Марком в код x64, в код x86. Это позволяет вам видеть бит AES-NI в 32-битном режиме. Не уверен, поможет ли это кому-то, но я думал, что выложу это.
РЕДАКТИРОВАТЬ: В то время как я проводил некоторое тестирование, я заметил, что регистры, возвращаемые кодом x64, были неправильными. EDX возвращался со смещением 0x4, 0x8 и 0xC, кроме того, регистры ECX и EDX имели разные смещения с кодом x86, поэтому вам нужно чаще проверять 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);