运行时特性测试、setjmp、longjmp和信号掩码



根据开放组基本规范和跳远文档:

未指定longjmp()是恢复信号掩码,保持信号掩码不变,还是将其恢复到调用setjmp(()时的值。

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

我不相信代码属于文档调用的未定义行为。例如,不使用嵌套的信号处理程序,同一线程执行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
}

最新更新