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

标签 c linux gcc arm cpu-architecture

在我正在写的可执行文件中,我有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并从辅助 vector 中读取这些位。

我发现最有效的是:
  • 尝试读取getauxval以获得拱形和特征
  • 如果SIGILL失败,请使用getauxval探针
  • SIGILL探针很昂贵。您设置了SIGILL处理程序,然后尝试使用ARMv5或ARMv7指令。如果捕获到SIGILL,则说明该指令不可用。
    SIGILL探针由Crypto++和OpenSSL使用。例如,在ARMv7中添加了movwmovt。这是使用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 Blog和Runtime Detection of CPU Features on an armv8-a CPU。 (非付费墙版本here)。

    不要在iOS设备上执行基于SIGILL的功能探针。苹果设备破坏内存。对于Apple设备,请使用How to get device make and model on iOS?之类的东西。

    您还需要基于编译器选项启用代码路径。那是蠕虫的全部“另一 jar ”。对于该问题,请参见Detect ARM NEON availability in the preprocessor?

    有关其他一些要检查的源代码,请参见Crypto++中的 cpu.cpp 。在这里Crypto++可以执行诸如getauxvalandroid_getCpuFamily()android_getCpuFeatures()之类的操作。

    Crypto++ SIGILL探针发生在特定的源文件中,因为源文件通常需要使用编译器选项来启用拱形,例如ARM的-march=armv7-a-fpu=neon。这就是在 neon_simd.cpp 中检测到ARMv7和NEON的原因。 (i686和x86_64,Altivec,PowerPC和Aarch64还有其他类似文件)。

    这是Crypto++中getauxvalandroid_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;
    }
    

    从以下源代码中反汇编了movwmovt的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)及更高级别,但是用户空间在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
    

    如果要使用movwmovt:

    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超出您的 protected 代码。

    我在x86上使用了错误的指令集而遇到了Clang。参见Crypto++ Issue 751。海湾合作委员会一定会跟随。在Clang情况下,我需要在源文件上使用-march=avx进行编译,以便可以使用AVX内部函数。 Clang在 protected 块之外生成了AVX代码,并在旧的Core2 Duo机器上崩溃了。 (Clang生成的不安全代码是std::string的初始化)。

    对于ARM,问题是,您需要-march=armv7才能通过movwmovt启用ISA,并且编译器认为它也可以使用ISA。这是编译器中的一个设计错误,其中用户的拱门和编译器的拱门被混合在一起。实际上,由于编译器的设计,您需要一个用户体系结构和一个单独的编译器体系结构。

    关于c - 如何在C代码中区分armhf(ARMv7)和armel(ARMv4)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59478463/

    相关文章:

    c - 连续内存空间中的链表

    c - &*NULL 在 C 语言中定义明确吗?

    缺少 Python.h header

    gcc - LLVM 与 GCC MIPS 代码生成,任何基准?

    linux - 如何更改 GCC 版本

    c - 在 C 中使用指针打印数组的值

    c - int 意外变回初始状态

    linux - 为什么wget引发错误403:禁止的错误?

    linux - 需要适用于 Linux 的文本转语音和语音识别工具

    Java 将 xml 文件发布到带有身份验证的 url