...我有两个相同功能的实现,一个用于 armhf(快),一个用于 armel(慢)。在运行时,我想检测 CPU 类型,如果检测到 armhf,则调用 armhf 实现。如何检测 CPU?我在 C 代码中需要这样的东西...
正如@Ruslan 所说,cpu 功能主要在 ARM 上具有特权。如果您是 root,那么您可以读取 MRS 寄存器以获取功能掩码。最新的内核为 ARM 伪造了一个 cpuid,但它仅适用于最新的内核。
在运行时,您可能能够在 Linux 上解析 /proc/cpuinfo 以获得 cpu 架构和功能。您也可以调用getauxval 并从辅助向量中读取位。
我发现效果最好的是:
- 尝试阅读
getauxval 了解拱门和功能
- 如果
getauxval 失败,请使用SIGILL 探测
SIGILL 探针很昂贵。您设置了SIGILL 处理程序并尝试使用 ARMv5 或 ARMv7 指令。如果您看到SIGILL,您就知道该指令不可用。
SIGILL 探针被 Crypto++ 和 OpenSSL 使用。例如,movw 和 movt 是在 ARMv7 中添加的。这是使用 Crypto++ 中的movw and movt instructions 探测 ARMv7 的代码。 OpenSSL 在crypto/armcap.c 中的表现类似。
bool CPU_ProbeARMv7()
{
volatile bool result = true;
volatile SigHandler oldHandler = signal(SIGILL, SigIllHandler);
if (oldHandler == SIG_ERR)
return false;
volatile sigset_t oldMask;
if (sigprocmask(0, NULLPTR, (sigset_t*)&oldMask))
return false;
if (setjmp(s_jmpSIGILL))
result = false;
else
{
unsigned int a;
asm volatile (
#if defined(__thumb__)
".inst.n 0xf241, 0x2034 \n\t" // movw r0, 0x1234
".inst.n 0xf2c1, 0x2034 \n\t" // movt r0, 0x1234
"mov %0, r0 \n\t" // mov [a], r0
#else
".inst 0xe3010234 \n\t" // movw r0, 0x1234
".inst 0xe3410234 \n\t" // movt r0, 0x1234
"mov %0, r0 \n\t" // mov [a], r0
#endif
: "=r" (a) : : "r0");
result = (a == 0x12341234);
}
sigprocmask(SIG_SETMASK, (sigset_t*)&oldMask, NULLPTR);
signal(SIGILL, oldHandler);
return result;
}
探针中需要volatiles。另见What sense do these clobbered variable warnings make?
在 Android 上,您应该使用 android_getCpuFamily() 和 android_getCpuFeatures() 而不是 getauxval。
ARM 的人说你应该不解析/proc/cpuinfo。另请参阅 ARM 博客和 Runtime Detection of CPU Features on an armv8-a CPU。 (非付费版here)。
请勿在 iOS 设备上执行基于 SIGILL 的功能探测。苹果设备垃圾内存。对于 Apple 设备,请使用 How to get device make and model on iOS? 之类的内容。
您还需要根据编译器选项启用代码路径。那是一个完整的“另一罐蠕虫”。对于这个问题,请参阅Detect ARM NEON availability in the preprocessor?
有关要检查的其他源代码,请参阅 Crypto++ 中的 cpu.cpp。它是 Crypto++ 进行调用getauxval、android_getCpuFamily() 和android_getCpuFeatures() 之类的地方。
Crypto++ SIGILL 探测出现在特定的源文件中,因为源文件通常需要编译器选项来启用架构,例如用于 ARM 的 -march=armv7-a 和 -fpu=neon。这就是在 neon_simd.cpp 中检测到 ARMv7 和 NEON 的原因。 (对于 i686 和 x86_64、Altivec、PowerPC 和 Aarch64,还有其他类似的文件)。
这是 getauxval 和 android_getCpuFamily() 在 Crypto++ 中的样子。首先使用CPU_QueryARMv7。如果CPU_QueryARMv7 失败,则使用SIGILL 功能探测。
inline bool CPU_QueryARMv7()
{
#if defined(__ANDROID__) && defined(__arm__)
if (((android_getCpuFamily() & ANDROID_CPU_FAMILY_ARM) != 0) &&
((android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_ARMv7) != 0))
return true;
#elif defined(__linux__) && defined(__arm__)
if ((getauxval(AT_HWCAP) & HWCAP_ARMv7) != 0 ||
(getauxval(AT_HWCAP) & HWCAP_NEON) != 0)
return true;
#elif defined(__APPLE__) && defined(__arm__)
// Apple hardware is ARMv7 or above.
return true;
#endif
return false;
}
movw 和 movt 的 ARM 指令从以下源代码反汇编:
int a;
asm volatile("movw %0,%1 \n"
"movt %0,%1 \n"
: "=r"(a) : "i"(0x1234));
00000010 <_Z5test2v>: // ARM
10: e3010234 movw r0, #4660 ; 0x1234
14: e3410234 movt r0, #4660 ; 0x1234
18: e12fff1e bx lr
0000001c <_Z5test3v>: // Thumb
1c: f241 2034 movw r0, #4660 ; 0x1234
20: f2c1 2034 movt r0, #4660 ; 0x1234
24: e12fff1e bx lr
这就是阅读 MRS 的样子。这与在 x86 上获取 cpuid 位掩码非常相似。以下代码可用于获取 Aarch64 的 Crypto 功能,但需要 root 权限。
代码需要异常级别 1 (EL1) 及以上,但用户空间运行在 EL0。尝试从用户空间运行代码会导致 SIGILL 并终止。
#if defined(__arm64__) || defined(__aarch64__)
uint64_t caps = 0; // Read ID_AA64ISAR0_EL1
__asm __volatile("mrs %0, " "id_aa64isar0_el1" : "=r" (caps));
#elif defined(__arm__) || defined(__aarch32__)
uint32_t caps = 0; // Read ID_ISAR5_EL1
__asm __volatile("mrs %0, " "id_isar5_el1" : "=r" (caps));
#endif
自己发指令的好处是,编译源文件时不需要arch选项:
unsigned int a;
asm volatile (
#if defined(__thumb__)
".inst.n 0xf241, 0x2034 \n\t" // movw r0, 0x1234
".inst.n 0xf2c1, 0x2034 \n\t" // movt r0, 0x1234
"mov %0, r0 \n\t" // mov [a], r0
#else
".inst 0xe3010234 \n\t" // movw r0, 0x1234
".inst 0xe3410234 \n\t" // movt r0, 0x1234
"mov %0, r0 \n\t" // mov [a], r0
#endif
: "=r" (a) : : "r0");
你可以不使用arch选项编译上面的代码:
gcc cpu-test.c -o cpu-test.o
如果您要使用movw 和movt:
int a;
asm volatile("movw %0,%1 \n"
"movt %0,%1 \n"
: "=r"(a) : "i"(0x1234));
那么您的编译器需要支持 ARMv7,并且您需要使用 arch 选项:
gcc -march=armv7 cpu-test.c -o cpu-test.o
而且 GCC 可以在整个源文件中使用 ARMv7,这可能会导致您的受保护代码之外出现 SIGILL。
我曾体验过 Clang 在 x86 上使用错误的指令集。见Crypto++ Issue 751。 GCC 肯定会紧随其后。在 Clang 的情况下,我需要在源文件上使用 -march=avx 进行编译,这样我才能使用 AVX 内部函数。 Clang 在我的受保护块之外生成了 AVX 代码,它在旧的 Core2 Duo 机器上崩溃了。 (Clang 生成的不安全代码是 std::string 的初始化)。
在 ARM 的情况下,问题是,您需要 -march=armv7 来启用具有 movw 和 movt 的 ISA,并且编译器认为它也可以使用 ISA。这是编译器中的一个设计错误,其中用户的拱门和编译器的拱门混为一谈。实际上,由于编译器的设计,您需要一个用户架构和一个单独的编译器架构。