c++ - 运行时功能测试、setjmp、longjmp 和信号掩码

标签 c++ arm signals setjmp

根据 The Open Group 基本规范和 longjump docs :

It is unspecified whether longjmp() restores the signal mask, leaves the signal mask unchanged, or restores it to its value at the time setjmp() was called.

我想我在 ARMv8 Mustang board 上遇到了这个警告。 。我们通过捕获 SIGILL 来执行运行时功能检测。测试 CRC32 扩展时,第一个 SIGILL 陷阱按预期执行。在测试 AES 扩展时,第二个 SIGILL 陷阱按预期执行。下面是调试器下的样子。

相信代码会陷入文档所指出的未定义行为。例如,不使用嵌套信号处理程序,同一线程执行 setjmplongjmp 魔术等。

我的问题是,如何安全地多次执行运行时功能测试?


gdb ./test.exe
...

(gdb) b TryCRC32() 
Breakpoint 1 at 0x401034: file test.cc, line 92.
(gdb) b TryAES() 
Breakpoint 2 at 0x401120: file test.cc, line 120.
...

(gdb) r
Starting program: /home/cryptopp/test.exe v

Breakpoint 1, TryCRC32 () at test.cc:92
92      volatile bool result = true;
(gdb) n
94      SigHandler oldHandler = signal(SIGILL, SigIllHandlerCRC32);
(gdb) 
95      if (oldHandler == SIG_ERR)
(gdb) 
98      if (setjmp(s_jmpNoCRC32))
(gdb) 
102         word32 w=0, x=0; word16 y=0; byte z=0;
(gdb) 
103         w = __crc32cw(w,x);
(gdb) 

Program received signal SIGILL, Illegal instruction.
0x00000000004010b4 in __crc32cw (__b=0, __a=0)
    at /usr/lib/gcc/aarch64-linux-gnu/4.9/include/arm_acle.h:57
57    return __builtin_aarch64_crc32cw (__a, __b);
(gdb) c
Continuing.

Breakpoint 2, TryAES () at test.cc:120
120     volatile bool result = true;
(gdb) n
122     SigHandler oldHandler = signal(SIGILL, SigIllHandlerAES);
(gdb) 
123     if (oldHandler == SIG_ERR)
(gdb) 
126     if (setjmp(s_jmpNoAES))
(gdb) 
130         uint8x16_t data = vdupq_n_u8(0), key = vdupq_n_u8(0);
(gdb) 
131         uint8x16_t r1 = vaeseq_u8(data, key);
(gdb) 

Program received signal SIGILL, Illegal instruction.
0x0000000000400a64 in vaeseq_u8 (data=..., key=...)
    at /usr/lib/gcc/aarch64-linux-gnu/4.9/include/arm_neon.h:13731
13731     return __builtin_aarch64_crypto_aesev16qi_uuu (data, key);
(gdb) c
Continuing.

Program terminated with signal SIGILL, Illegal instruction.
The program no longer exists.

这是测试程序。它是用以下内容编译的:

$ export CXXFLAGS="-g3 -O0 -march=armv8-a+crc+crypto"
$ g++ $CXXFLAGS test.cc -o test.exe

-march=armv8-a+crc+crypto 表示定义了 __ARM_NEON__ARM_FEATURE_CRYPTO 等预处理器符号。

诸如 static volatile bool TryNEON() 之类的声明是 Mustang 暴露的另一个问题(即,GCC 正在优化检查)。它导致程序因 SIGILL 而终止。不要因此而分心,因为这只是目前的权宜之计。

#include <signal.h>
#include <setjmp.h>

#include <stdint.h>
#include <arm_neon.h>
#include <arm_acle.h>

#include <iostream>

#define UNUSED(x) ((void)(x))

typedef uint8_t byte;
typedef uint16_t word16;
typedef uint32_t word32;
typedef uint64_t word64;

typedef void (*SigHandler)(int);

extern "C" {

    static jmp_buf s_jmpNoNEON;
    static void SigIllHandlerNEON(int)
    {
        longjmp(s_jmpNoNEON, 1);
    }

    static jmp_buf s_jmpNoCRC32;
    static void SigIllHandlerCRC32(int)
    {
        longjmp(s_jmpNoCRC32, 1);
    }

    static jmp_buf s_jmpNoAES;
    static void SigIllHandlerAES(int)
    {
        longjmp(s_jmpNoAES, 1);
    }

    static jmp_buf s_jmpNoSHA1;
    static void SigIllHandlerSHA1(int)
    {
        longjmp(s_jmpNoSHA1, 1);
    }

    static jmp_buf s_jmpNoSHA2;
    static void SigIllHandlerSHA2(int)
    {
        longjmp(s_jmpNoSHA2, 1);
    }
};

static volatile bool TryNEON()
{
#if defined(__ARM_NEON)
    volatile bool result = true;

    SigHandler oldHandler = signal(SIGILL, SigIllHandlerNEON);
    if (oldHandler == SIG_ERR)
        result = false;

    if (setjmp(s_jmpNoNEON))
        result = false;
    else
    {
        uint32_t v1[4] = {1,1,1,1};
        uint32x4_t x1 = vld1q_u32(v1);
        uint64_t v2[2] = {1,1};
        uint64x2_t x2 = vld1q_u64(v2);

        uint32x4_t x3 = vdupq_n_u32(0);
        x3 = vsetq_lane_u32(vgetq_lane_u32(x1,0),x3,0);
        x3 = vsetq_lane_u32(vgetq_lane_u32(x1,3),x3,3);
        uint64x2_t x4 = vdupq_n_u64(0);
        x4 = vsetq_lane_u64(vgetq_lane_u64(x2,0),x4,0);
        x4 = vsetq_lane_u64(vgetq_lane_u64(x2,1),x4,1);
    }

    signal(SIGILL, oldHandler);
    return result;
#else
    return false;
#endif  // __ARM_NEON
}

static volatile bool TryCRC32()
{
#if defined(__ARM_FEATURE_CRC32)
    volatile bool result = true;

    SigHandler oldHandler = signal(SIGILL, SigIllHandlerCRC32);
    if (oldHandler == SIG_ERR)
        result = false;

    if (setjmp(s_jmpNoCRC32))
        result = false;
    else
    {
        word32 w=0, x=0; word16 y=0; byte z=0;
        w = __crc32cw(w,x);
        w = __crc32ch(w,y);
        w = __crc32cb(w,z);
    }

    signal(SIGILL, oldHandler);
    return result;
#else
    return false;
#endif  // __ARM_FEATURE_CRC32
}

static volatile bool TryAES()
{
#if defined(__ARM_FEATURE_CRYPTO)
    volatile bool result = true;

    SigHandler oldHandler = signal(SIGILL, SigIllHandlerAES);
    if (oldHandler == SIG_ERR)
        result = false;

    if (setjmp(s_jmpNoAES))
        result = false;
    else
    {
        uint8x16_t data = vdupq_n_u8(0), key = vdupq_n_u8(0);
        uint8x16_t r1 = vaeseq_u8(data, key);
        uint8x16_t r2 = vaesdq_u8(data, key);
        UNUSED(r1), UNUSED(r2);
    }

    signal(SIGILL, oldHandler);
    return result;
#else
    return false;
#endif  // __ARM_FEATURE_CRYPTO
}

static volatile bool TrySHA1()
{
#if defined(__ARM_FEATURE_CRYPTO)
    volatile bool result = true;

    SigHandler oldHandler = signal(SIGILL, SigIllHandlerSHA1);
    if (oldHandler == SIG_ERR)
        result = false;

    if (setjmp(s_jmpNoSHA1))
        result = false;
    else
    {
        uint32x4_t data = vdupq_n_u32(0);
        uint32_t hash = 0x0;

        uint32x4_t r1 = vsha1cq_u32 (data, hash, data);
        uint32x4_t r2 = vsha1mq_u32 (data, hash, data);
        uint32x4_t r3 = vsha1pq_u32 (data, hash, data);
        UNUSED(r1), UNUSED(r2), UNUSED(r3);
    }

    signal(SIGILL, oldHandler);
    return result;
#else
    return false;
#endif  // __ARM_FEATURE_CRYPTO
}

static volatile bool TrySHA2()
{
#if defined(__ARM_FEATURE_CRYPTO)
    volatile bool result = true;

    SigHandler oldHandler = signal(SIGILL, SigIllHandlerSHA2);
    if (oldHandler == SIG_ERR)
        result = false;

    if (setjmp(s_jmpNoSHA2))
        result = false;
    else
    {
        uint32x4_t data = vdupq_n_u32(0);
        uint32x4_t hash = vdupq_n_u32(0);

        uint32x4_t r1 = vsha256hq_u32 (hash, hash, data);
        uint32x4_t r2 = vsha256h2q_u32 (hash, hash, data);
        uint32x4_t r3 = vsha256su0q_u32 (data, data);
        uint32x4_t r4 = vsha256su1q_u32 (data, data, data);
        UNUSED(r1), UNUSED(r2), UNUSED(r3), UNUSED(r4);
    }

    signal(SIGILL, oldHandler);
    return result;
#else
    return false;
#endif  // __ARM_FEATURE_CRYPTO
}

bool hasNEON = TryNEON();
bool hasCRC32 = TryCRC32();
bool hasAES = TryAES();
bool hasSHA1 = TrySHA1();
bool hasSHA2 = TrySHA2();

int main(int argc, char* argv[])
{
    std::cout << "Has NEON: " << hasNEON << std::endl;
    std::cout << "Has CRC32: " << hasCRC32 << std::endl;
    std::cout << "Has AES: " << hasAES << std::endl;
    std::cout << "Has SHA1: " << hasSHA1 << std::endl;
    std::cout << "Has SHA2: " << hasSHA2 << std::endl;

    return 0;
}

最佳答案

代码存在两个问题。首先,所有变量都变得不稳定。其次,需要保存和恢复过程掩码。第二个问题仅在某个功能不存在以及第二次(或后续)功能测试失败时才出现。如果该功能可用,则问题不会出现。

这是一个例子:

static bool TryNEON()
{
#if defined(__ARM_NEON)
    volatile bool result = true;
    volatile SigHandler oldHandler = signal(SIGILL, SigIllHandlerNEON);
    if (oldHandler == SIG_ERR)
        return false;

    volatile sigset_t oldMask;
    if (sigprocmask(0, NULL, (sigset_t*)&oldMask))
        return false;

    if (setjmp(s_jmpNoNEON))
        result = false;
    else
    {
        uint32_t v1[4] = {1,1,1,1};
        uint32x4_t x1 = vld1q_u32(v1);
        uint64_t v2[2] = {1,1};
        uint64x2_t x2 = vld1q_u64(v2);

        uint32x4_t x3 = {0,0,0,0};
        x3 = vsetq_lane_u32(vgetq_lane_u32(x1,0),x3,0);
        x3 = vsetq_lane_u32(vgetq_lane_u32(x1,3),x3,3);
        uint64x2_t x4 = {0,0};
        x4 = vsetq_lane_u64(vgetq_lane_u64(x2,0),x4,0);
        x4 = vsetq_lane_u64(vgetq_lane_u64(x2,1),x4,1);

        // Hack... GCC optimizes away the code and returns true
        result = !!(vgetq_lane_u32(x3,0) | vgetq_lane_u64(x4,1));
    }

    sigprocmask(SIG_SETMASK, (sigset_t*)&oldMask, NULL);
    signal(SIGILL, oldHandler);
    return result;
#else
    return false;
#endif  // __ARM_NEON
}

关于c++ - 运行时功能测试、setjmp、longjmp 和信号掩码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37516057/

相关文章:

C 中的 CRC 计算 - 向 Ross Williams 库添加单位函数

operating-system - 处理器如何处理除以零的情况

python - 为什么这个python程序有时会退出失败?

c++ - 双重选择对数组进行排序 - honeSTLy stumped

C++ 编译时程序范围内的唯一编号

android - 如何为android实现mbtowc? (或者,理想情况下,如何不这样做?)

c - Unix 中的信号交互

c++ - 什么时候成员函数应该同时是 const 和 volatile?

c++ - 等待句柄/事件异步或在同一线程内回调

linux - 多核处理器之间的区别