繁体   English   中英

如何在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 信息。

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

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

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

我发现效果最好的是:

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

SIGILL探针很昂贵。 您设置了一个SIGILL处理程序并尝试使用 ARMv5 或 ARMv7 指令。 如果您收到SIGILL您就知道该指令不可用。

SIGILL探针由 Crypto++ 和 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;
}

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

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

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

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

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

有关要检查的其他源代码,请参阅 Crypto++ 中的cpu.cpp 它是 Crypto++ 执行诸如调用getauxvalandroid_getCpuFamily()android_getCpuFeatures()

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


这是 Crypto++ 中getauxvalandroid_getCpuFamily()样子。 CPU_QueryARMv7使用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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM