C代码中如何区分armhf(ARMv7)和armel(ARMv4)?
How to distinguish armhf (ARMv7) and armel (ARMv4) in C code?
在我编写的可执行文件中,我有 2 个相同功能的实现,一个用于 armhf(快),一个用于 armel(慢)。在运行时,我想检测 CPU 类型,如果检测到 armhf,则调用 armhf 实现。如何检测 CPU?我在 C 代码中需要这样的东西:
int is_cpu_armhf(void) {
...
}
代码可以包含内联汇编,但最好不要包含对库函数或系统调用的调用,因为它应该适用于多个库和多个操作系统。
我找到了 https://github.com/pytorch/cpuinfo/tree/master/src/arm ,但它似乎没有使用任何内联汇编,而是依赖操作系统来获取 CPU 信息。
... I have two implementations of the same function, one for armhf (fast) and one for armel (slow). At runtime I'd like to detect the CPU type, and call the armhf implementation if armhf was detected. How do I detect the CPU? I need something like this in C code ...
正如@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 中添加的。这是使用 movw
and movt
instructions in Crypto++. OpenSSL performs similar in crypto/armcap.c
.
探测 ARMv7 的代码
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. (Non-paywall version here)。
不要 在 iOS 设备上执行基于 SIGILL
的特征探测。苹果设备垃圾内存。对于 Apple 设备,请使用类似 How to get device make and model on iOS?.
的内容
您还需要根据编译器选项启用代码路径。那是一大堆蠕虫。对于该问题,请参阅
要查看一些额外的源代码,请参阅 Crypto++ 中的 cpu.cpp
。这是 Crypto++ 执行调用 getauxval
、android_getCpuFamily()
和 android_getCpuFeatures()
.
等操作的地方
Crypto++ SIGILL
探测发生在特定的源文件中,因为源文件通常需要一个编译器选项来启用 arch,例如 -march=armv7-a
和 -fpu=neon
用于 ARM。这就是为什么在 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 的加密功能,但它需要 root 权限。
代码需要异常级别 1 (EL1) 及更高级别,但用户 space 运行s 在 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。这是编译器中的一个设计错误,用户的架构和编译器的架构被混淆了。实际上,由于编译器设计,您需要一个用户架构和一个单独的编译器架构。
在我编写的可执行文件中,我有 2 个相同功能的实现,一个用于 armhf(快),一个用于 armel(慢)。在运行时,我想检测 CPU 类型,如果检测到 armhf,则调用 armhf 实现。如何检测 CPU?我在 C 代码中需要这样的东西:
int is_cpu_armhf(void) {
...
}
代码可以包含内联汇编,但最好不要包含对库函数或系统调用的调用,因为它应该适用于多个库和多个操作系统。
我找到了 https://github.com/pytorch/cpuinfo/tree/master/src/arm ,但它似乎没有使用任何内联汇编,而是依赖操作系统来获取 CPU 信息。
... I have two implementations of the same function, one for armhf (fast) and one for armel (slow). At runtime I'd like to detect the CPU type, and call the armhf implementation if armhf was detected. How do I detect the CPU? I need something like this in C code ...
正如@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 中添加的。这是使用 movw
and movt
instructions in Crypto++. OpenSSL performs similar in 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. (Non-paywall version here)。
不要 在 iOS 设备上执行基于 SIGILL
的特征探测。苹果设备垃圾内存。对于 Apple 设备,请使用类似 How to get device make and model on iOS?.
您还需要根据编译器选项启用代码路径。那是一大堆蠕虫。对于该问题,请参阅
要查看一些额外的源代码,请参阅 Crypto++ 中的 cpu.cpp
。这是 Crypto++ 执行调用 getauxval
、android_getCpuFamily()
和 android_getCpuFeatures()
.
Crypto++ SIGILL
探测发生在特定的源文件中,因为源文件通常需要一个编译器选项来启用 arch,例如 -march=armv7-a
和 -fpu=neon
用于 ARM。这就是为什么在 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 的加密功能,但它需要 root 权限。
代码需要异常级别 1 (EL1) 及更高级别,但用户 space 运行s 在 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。这是编译器中的一个设计错误,用户的架构和编译器的架构被混淆了。实际上,由于编译器设计,您需要一个用户架构和一个单独的编译器架构。