如何在M1 mac上捕获浮点异常?



捕获浮点异常的方法取决于体系结构。这是我在Intel (x86) Mac上成功测试过的代码:它取负数的平方根两次,一次在之前,一次在之后,启用浮点异常捕获。第二次调用fpe_signal_handler()

#include <cmath>        // for sqrt()                                           
#include <csignal>      // for signal()                                         
#include <iostream>
#include <xmmintrin.h>  // for _mm_setcsr                                       
void fpe_signal_handler(int /*signal*/) {
std::cerr << "Floating point exception!n";
exit(1);
}
void enable_floating_point_exceptions() {
_mm_setcsr(_MM_MASK_MASK & ~_MM_MASK_INVALID);
signal(SIGFPE, fpe_signal_handler);
}
int main() {
const double x{-1.0};
std::cout << sqrt(x) << "n";
enable_floating_point_exceptions();
std::cout << sqrt(x) << "n";
}

使用Xcode提供的apple-clang编译器编译

clang++ -g -std=c++17 -o fpe fpe.cpp

和运行给出以下预期输出:

nan
Floating point exception!

我想写一个类似的程序,在M1 (arm64) Mac上做与上述程序相同的事情。我尝试了以下操作:

#include <cfenv>        // for std::fenv_t                                      
#include <cmath>        // for sqrt()                                           
#include <csignal>      // for signal()                                         
#include <fenv.h>       // for fegetenv(), fesetenv()                           
#include <iostream>
void fpe_signal_handler(int /*signal*/) {
std::cerr << "Floating point exception!n";
exit(1);
}
void enable_floating_point_exceptions() {
std::fenv_t env;
fegetenv(&env);
env.__fpcr = env.__fpcr | __fpcr_trap_invalid;
fesetenv(&env);
signal(SIGFPE, fpe_signal_handler);
}
int main() {
const double x{-1.0};
std::cout << sqrt(x) << "n";
enable_floating_point_exceptions();
std::cout << sqrt(x) << "n";
}
在使用Xcode提供的apple-clang编译器编译后
clang++ -g -std=c++17 -o fpe fpe.cpp

得到以下输出:

nan
zsh: illegal hardware instruction  ./fpe

我试着添加-fexceptions标志,但这并没有什么不同。我注意到ARM编译器工具链"不支持AArch64目标的浮点异常捕获",但我不确定这是否适用于带有苹果工具链的M1 mac电脑。

我是否正确,M1 Mac硬件只是不支持浮点异常捕获?或者有一种方法来修改这个程序,使它捕获第二个浮点异常,然后调用fpe_signal_handler()?


在同一线程内同步测试异常确实工作得很好,使用来自<fenv.h>的ISO Cfetestexcept,就像在cppreference示例中一样。这里的问题是让FP异常实际捕获,以便操作系统提供SIGFPE,而不仅仅是在FP环境中设置sticky标志。

根据下面的长时间讨论(非常感谢@Peter Cordes的耐心),似乎在Aarch64上的MacOS中,在SIGILL而不是SIGFPE中揭开FP异常然后生成相应的错误FP数学结果。可以在处理程序中检测到信号代码ILL_ILLTRP。

#include <fenv.h>    
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
static void
fpe_signal_handler( int sig, siginfo_t *sip, void *scp )
{
int fe_code = sip->si_code;
printf("In signal handler : ");
if (fe_code == ILL_ILLTRP)
printf("Illegal trap detectedn");
else
printf("Code detected : %dn",fe_code);
abort();
}
void enable_floating_point_exceptions()
{
fenv_t env;
fegetenv(&env);
env.__fpcr = env.__fpcr | __fpcr_trap_invalid;
fesetenv(&env);
struct sigaction act;
act.sa_sigaction = fpe_signal_handler;
sigemptyset (&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGILL, &act, NULL);
}
void main()
{
const double x = -1;    
printf("y = %fn",sqrt(x));
enable_floating_point_exceptions();
printf("y = %fn",sqrt(x));
}

结果是:

y = nan
In signal handler : Illegal trap detected
Abort trap: 6

其他浮点异常可以用类似的方式检测,例如,取消__fpcr_trap_divbyzero的掩码,然后生成double x=0; double y=1/x。如果异常未被揭开,则程序正常终止。

但是,如果没有SIGFPE,似乎不可能准确地检测到哪个浮点操作触发了信号处理程序。可以想象揭开所有异常(例如使用FE_ALL_EXCEPT)。然后,当一个错误的数学操作生成SIGILL时,我们在处理程序中没有足够的信息来知道是什么操作发送了信号。在处理程序中揭开所有异常的面纱并稍微摆弄一下fetestexcept并不能产生非常可靠的结果。这可能需要更多的调查。

您可以通过将sigaction处理程序的第三个参数强制转换为struct ucontext*,然后在处理程序中解码scp->uc_mcontext->__es.esr来解码SIGILL处理程序中FPU异常的性质。这是ESR_ELx, Exception Syndrome Register (ELx)寄存器的值。如果它的前6位(EC)是0b101100,那么信号是由捕获AArch64 FPU操作触发的。如果在这种情况下,寄存器(TFV)的第23位也是1,那么寄存器的低7位将与fpsr寄存器的低7位相匹配,以防陷阱被禁用(即,由指令触发的FPU异常)。

请参阅ARMv8架构手册的D17.2.37节获取更多信息("ESR_EL1,异常综合征寄存器(EL1)")。

相关内容

  • 没有找到相关文章

最新更新