【问题标题】:How to distinguish armhf (ARMv7) and armel (ARMv4) in C code?如何区分 C 代码中的 armhf (ARMv7) 和 armel (ARMv4)?
【发布时间】:2019-12-25 12:37:09
【问题描述】:

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

int is_cpu_armhf(void) {
  ...
}

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

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

【问题讨论】:

  • AFAIK,在 ARM 上,此信息是特权信息,因此您必须直接询问操作系统,或者尝试执行一些在 ARMv4 上未定义但为 ARMv7 定义的指令并处理产生的异常.
  • @Ruslan:你能指出这样一个实现吗(它试图执行一条指令,并在它结束时捕获异常)?
  • 好吧,不是用于 ARM,而是用于 x86,也不是用于检测 CPU,而是尝试用零指令跳过错误除法,但可能对您有所帮助:pastebin.com/uQLuAm2U。还要记住,您可能会得到SIGILL 或其他一些信号,而不是在此示例中处理的SIGFPE。实际信号将取决于您选择作为判别式的指令。另请注意,ucontext_t 也是平台相关的。
  • 只是好奇,但为什么在运行时需要它?你为什么架构编译?
  • 你会在哪里遇到这两种架构重叠的情况(外部仿真)? armv6、armv7 以及根据您将使用 CPUID 寄存器的平台的优化执行不同代码路径可能需要的所有子细节,它有很多不同的选项,如果您需要深入了解这些选项有一些理由关心...从应用程序授予您很可能无法在 cpuid 寄存器中获得,并且会使用下面的答案作为起点。

标签: c linux gcc arm cpu-architecture


【解决方案1】:

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

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

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

我发现效果最好的是:

  1. 尝试阅读 getauxval 了解拱门和功能
  2. 如果getauxval 失败,请使用SIGILL 探测

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

SIGILL 探针被 Crypto++ 和 OpenSSL 使用。例如,movwmovt 是在 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++ 进行调用getauxvalandroid_getCpuFamily()android_getCpuFeatures() 之类的地方。

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


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

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 的 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

如果您要使用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

我曾体验过 Clang 在 x86 上使用错误的指令集。见Crypto++ Issue 751。 GCC 肯定会紧随其后。在 Clang 的情况下,我需要在源文件上使用 -march=avx 进行编译,这样我才能使用 AVX 内部函数。 Clang 在我的受保护块之外生成了 AVX 代码,它在旧的 Core2 Duo 机器上崩溃了。 (Clang 生成的不安全代码是 std::string 的初始化)。

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

【讨论】:

    猜你喜欢
    • 2013-11-05
    • 2017-12-28
    • 2016-03-07
    • 1970-01-01
    • 2020-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多