如何在C代码中区分armhf(ARMv7)和armel(ARMv4)?

pts*_*pts 6 c linux gcc arm cpu-architecture

在我正在编写的可执行文件中,我有 2 个相同功能的实现,一个用于 armhf(快速),另一个用于 armel(慢)。在运行时,我想检测 CPU 类型,并在检测到 armhf 时调用 armhf 实现。如何检测CPU?我在 C 代码中需要这样的东西:

int is_cpu_armhf(void) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

代码可能包含内联汇编,但最好不要包含对库函数的调用或系统调用,因为它应该适用于多个库和多个操作系统。

我找到了https://github.com/pytorch/cpuinfo/tree/master/src/arm,但它似乎没有使用任何内联程序集,但它依赖于操作系统来获取 CPU 信息。

jww*_*jww 6

...我有两种相同功能的实现,一种用于 armhf(快速),一种用于 armel(慢)。在运行时,我想检测 CPU 类型,并在检测到 armhf 时调用 armhf 实现。如何检测CPU?我需要在 C 代码中这样的东西......

正如@Ruslan 指出的那样,CPU 功能主要在 ARM 上享有特权。如果您是 root,那么您可以读取功能掩码的 MRS 寄存器。最新的内核为 ARM 伪造了一个 cpuid,但它仅适用于最新的内核。

在运行时,您可以/proc/cpuinfo在 Linux上解析CPU 架构和功能。您也可以getauxval从辅助向量中调用和读取位。

我发现效果最好的是:

  1. 尝试阅读getauxvalarch 和 feature
  2. SIGILL如果getauxval失败,请使用探针

SIGILL探测器是昂贵的。您设置了一个SIGILL处理程序并尝试使用 ARMv5 或 ARMv7 指令。如果你抓住了,SIGILL你就知道指令不可用。

SIGILLCrypto++ 和 OpenSSL 使用探针。例如,movwmovt在 ARMv7 中添加。这是使用Crypto++ 中的movwmovt指令探测 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;
}
Run Code Online (Sandbox Code Playgroud)

volatiles需要在探针。另请参阅这些损坏的变量警告有什么意义?

在 Android 上,您应该使用android_getCpuFamily()andandroid_getCpuFeatures()而不是getauxval.

ARM的人说,你应该解析/proc/cpuinfo。另请参阅 ARM 博客和armv8-a CPU 上 CPU 功能的运行时检测。(此处为非付费专区版本)。

不要SIGILL在 iOS 设备上执行基于特征的探测。苹果设备垃圾内存。对于 Apple 设备,请使用诸如如何在 iOS 上获取设备品牌和型号之类的内容?.

您还需要根据编译器选项启用代码路径。那是一整罐“蠕虫”。对于该问题,请参阅在预处理器中检测 ARM NEON 可用性?

有关要检查的其他一些源代码,请参阅cpu.cppCrypto++。这是其中的Crypto ++确实喜欢召唤物的地方getauxvalandroid_getCpuFamily()android_getCpuFeatures()

的加密++SIGILL探针发生在特定的源文件,因为一个源文件通常需要编译器选项以使拱形,像-march=armv7-a-fpu=neon用于ARM。这就是为什么在neon_simd.cpp. (对于 i686 和 x86_64、Altivec、PowerPC 和 Aarch64,还有其他类似的文件)。


这是Crypto++ 中agetauxval和的android_getCpuFamily()样子。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;
}
Run Code Online (Sandbox Code Playgroud)

用于movw和的 ARM 指令movt是从以下源代码反汇编的:

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
Run Code Online (Sandbox Code Playgroud)

这是阅读 MRS 的样子。这与在 x86 上获取 cpuid 位掩码非常相似。下面的代码可用于获取 Aarch64 的加密功能,但它需要 root 权限。

代码需要异常级别 1 (EL1) 及更高级别,但用户空间运行在 EL0。尝试从用户空间运行代码会导致 aSIGILL和终止。

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
Run Code Online (Sandbox Code Playgroud)

自己发出指令的好处是,编译源文件时不需要arch选项:

#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
Run Code Online (Sandbox Code Playgroud)

您可以在没有 arch 选项的情况下编译上述代码:

gcc cpu-test.c -o cpu-test.o
Run Code Online (Sandbox Code Playgroud)

如果您要使用movwmovt

    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");
Run Code Online (Sandbox Code Playgroud)

那么你的编译器需要支持 ARMv7,你需要使用 arch 选项:

gcc -march=armv7 cpu-test.c -o cpu-test.o
Run Code Online (Sandbox Code Playgroud)

并且 GCC 可以在整个源文件中使用 ARMv7,这可能会导致SIGILL受保护代码的外部。

我在 x86 上使用错误的指令集体验​​过 Clang。请参阅Crypto++ 问题 751。海湾合作委员会肯定会跟进。在 Clang 的情况下,我需要-march=avx在源文件上进行编译,以便我可以使用 AVX 内在函数。Clang 在我的受保护块之外生成了 AVX 代码,它在旧的 Core2 Duo 机器上崩溃了。(Clang 生成的不安全代码是 a 的初始化std::string)。

在 ARM 的情况下,问题是,您需要-march=armv7使用movw和启用 ISA,并且movt编译器认为它也可以使用 ISA。这是编译器中的一个设计错误,其中用户的架构和编译器的架构被混为一谈。实际上,由于编译器设计的原因,您需要一个用户 arch 和一个单独的编译器 arch。