【发布时间】:2023-02-01 14:17:16
【问题描述】:
为了在 MacOS 上捕获浮点异常,我使用了一个提供 feenableexcept 功能的扩展。原始扩展(写于 2009 年)在这里
http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c
笔记:如果您看到这篇文章以了解如何在 MacOS(使用 Intel 或 Apple 芯片)上捕获浮点异常,您可能想跳过汇编讨论到细节以下。
我现在想为 Apple silicon 更新此扩展,并可能删除一些过时的代码。翻遍fenv.h,很清楚如何更新Apple silicon的例程feenableexcept、fegetexcept和fedisableexcept。但是,不太清楚如何处理 2009 扩展中提供的汇编代码,或者为什么甚至包含此代码。
上面链接中提供的扩展很长,所以我只提取涉及程序集的片段:
#if DEFINED_INTEL
// x87 fpu
#define getx87cr(x) __asm ("fnstcw %0" : "=m" (x));
#define setx87cr(x) __asm ("fldcw %0" : "=m" (x));
#define getx87sr(x) __asm ("fnstsw %0" : "=m" (x));
// SIMD, gcc with Intel Core 2 Duo uses SSE2(4)
#define getmxcsr(x) __asm ("stmxcsr %0" : "=m" (x));
#define setmxcsr(x) __asm ("ldmxcsr %0" : "=m" (x));
#endif // DEFINED_INTEL
此代码用于 sigaction 机制的处理程序中,该机制用于报告捕获的浮点异常类型。
fhdl ( int sig, siginfo_t *sip, ucontext_t *scp )
{
int fe_code = sip->si_code;
unsigned int excepts = fetestexcept (FE_ALL_EXCEPT);
/* ... see complete code in link above ... */
if ( sig == SIGFPE )
{
#if DEFINED_INTEL
unsigned short x87cr,x87sr;
unsigned int mxcsr;
getx87cr (x87cr);
getx87sr (x87sr);
getmxcsr (mxcsr);
printf ("X87CR: 0x%04X\n", x87cr);
printf ("X87SR: 0x%04X\n", x87sr);
printf ("MXCSR: 0x%08X\n", mxcsr);
#endif
// ....
}
printf ("signal: SIGFPE with code %s\n", fe_code_name[fe_code]);
printf ("invalid flag: 0x%04X\n", excepts & FE_INVALID);
printf ("divByZero flag: 0x%04X\n", excepts & FE_DIVBYZERO);
}
else printf ("Signal is not SIGFPE, it's %i.\n", sig);
abort();
}
提供了一个捕获异常并通过sigaction处理异常的例子。对 feenableexcept 的调用将是具有 feenableexcept 定义的系统(例如非 Apple 硬件)的本机实现,或者是上面链接的扩展中提供的实现。
int main (int argc, char **argv)
{
double s;
struct sigaction act;
act.sa_sigaction = (void(*))fhdl;
sigemptyset (&act.sa_mask);
act.sa_flags = SA_SIGINFO;
// printf ("Old divByZero exception: 0x%08X\n", feenableexcept (FE_DIVBYZERO));
printf ("Old invalid exception: 0x%08X\n", feenableexcept (FE_INVALID));
printf ("New fp exception: 0x%08X\n", fegetexcept ());
// set handler
if (sigaction(SIGFPE, &act, (struct sigaction *)0) != 0)
{
perror("Yikes");
exit(-1);
}
// s = 1.0 / 0.0; // FE_DIVBYZERO
s = 0.0 / 0.0; // FE_INVALID
return 0;
}
当我在基于 Intel 的 Mac 上运行它时,我得到;
Old invalid exception: 0x0000003F
New fp exception: 0x0000003E
X87CR: 0x037F
X87SR: 0x0000
MXCSR: 0x00001F80
signal: SIGFPE with code FPE_FLTINV
invalid flag: 0x0000
divByZero flag: 0x0000
Abort trap: 6
我的问题是:
-
为什么汇编代码和对
fetestexcept的调用都包含在处理程序中?是否都需要报告被捕获的异常类型? -
FE_INVALID异常被处理程序捕获。那么,为什么excepts & FE_INVALID为零? -
sigaction处理程序在 Apple 芯片上被完全忽略。它应该工作吗?还是我不了解有关使用sigaction进行信号处理工作的更基本的知识,而不是引发 FP 异常时会发生什么?
我正在用 gcc 和 clang 编译。
细节:这是从原始代码中提取的一个最小示例,该示例提炼了我上面的问题。在此示例中,我为基于 Intel 或 Apple 芯片的 MacOS 提供了缺失的 feeableexcept 功能。然后我测试有没有sigaction。
#include <fenv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(__APPLE__)
#if defined(__arm) || defined(__arm64) || defined(__aarch64__)
#define DEFINED_ARM 1
#define FE_EXCEPT_SHIFT 8
#endif
void feenableexcept(unsigned int excepts)
{
fenv_t env;
fegetenv(&env);
#if (DEFINED_ARM==1)
env.__fpcr = env.__fpcr | (excepts << FE_EXCEPT_SHIFT);
#else
/* assume Intel */
env.__control = env.__control & ~excepts;
env.__mxcsr = env.__mxcsr & ~(excepts << 7);
#endif
fesetenv(&env);
}
#else
/* Linux may or may not have feenableexcept. */
#endif
static void
fhdl ( int sig, siginfo_t *sip, ucontext_t *scp )
{
int fe_code = sip->si_code;
unsigned int excepts = fetestexcept (FE_ALL_EXCEPT);
if (fe_code == FPE_FLTDIV)
printf("In signal handler : Division by zero. Flag is : 0x%04X\n", excepts & FE_DIVBYZERO);
abort();
}
void main()
{
#ifdef HANDLE_SIGNAL
struct sigaction act;
act.sa_sigaction = (void(*))fhdl;
sigemptyset (&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGFPE, &act, NULL);
#endif
feenableexcept(FE_DIVBYZERO);
double x = 0;
double y = 1/x;
}
没有信号的结果
关于英特尔:
% gcc -o stack_except stack_except.c
% stack_except
Floating point exception: 8
在苹果硅上:
% gcc -o stack_except stack_except.c
% stack_except
Illegal instruction: 4
上面的代码按预期工作,当遇到被零除时代码终止。
sigaction 的结果
英特尔的结果:
% gcc -o stack_signal stack_signal.c -DHANDLE_SIGNAL
% stack_signal
In signal handler : Division by zero. Flag is : 0x0000
Abort trap: 6
该代码在 Intel 上按预期工作。然而,
-
fetestexcept(从信号处理程序调用)的返回值为零。为什么是这样?之前是否清除了异常 正在由处理程序处理?
苹果硅的结果:
% gcc -o stack_signal stack_signal.c -DHANDLE_SIGNAL
% stack_signal
Illegal instruction: 4
信号处理程序被完全忽略。为什么是这样?我是否遗漏了有关信号处理方式的一些基本知识?
在原始代码中使用程序集(请参阅帖子顶部的链接)
我的最后一个问题是关于在帖子顶部发布的原始示例中使用 assembly 。为什么程序集用于查询信号处理程序中的标志?用fetestexcept还不够吗?或者查看siginfo.si_code?可能的答案:fetestexcept,在处理程序内部使用时未检测到异常(?)。 (这就是为什么只从处理程序内部打印 0x0000 的原因吗?。)
这是有类似问题的相关帖子。 How to trap floating-point exceptions on M1 Macs?
【问题讨论】:
-
#define setx87cr(x) __asm ("fldcw %0" : "=m" (x));超级坏。它告诉编译器x是一个纯输出(由 asm 模板编写),但实际上运行的是从中读取的 asm 指令。我希望它能在除调试版本之外的任何地方中断(因为死存储消除)。ldmxcsr包装器也一样,它更没用,因为#include <immintrin.h>有_mm_setcsr -
除非 AArch64 也像 x86(x87 和 SSE)一样有两个单独的 FP 异常掩码/状态,否则我看不出有任何理由需要自定义函数/宏而不是 ISO C fenv.h 函数。
fetestexcept(FE_DIVBYZERO)等应该可以解决问题。 en.cppreference.com/w/c/numeric/fenv/fetestexcept -
是的 -
fetestexcept将测试是否发生了异常,但只有在发生异常之后。因此,必须为每一行可疑代码调用它。而feenableexcept是一个方便的函数,(出于某种原因,OSX 没有提供)它只使用 fegetenv 和 fesetenv 来设置环境以在发生异常时终止执行 - 对 gdb 非常有用。 -
我的意思是在您的异常处理程序中使用
fetestexcept而不是getmxcsr。您不需要任何 mxcsr 或 x87 的 AArch64 端口。 -
fetestexcept会测试任何一个x87 或 SSE 异常,具体取决于默认情况下用于 FP 数学的编译器。 (x86-64 的 SSE2,long double使用 x87 除外...)因此有理由要检查两者以确保它与 fetestexcept 匹配。此外,x87 状态字具有精度控制位(使其始终舍入到与double或float相同的尾数精度,而不是完整的 80 位),并且 MXCSR 具有 DAZ / FTZ(非正规数为零) / flush to zero) 以禁用逐渐下溢,因为如果发生下溢,它会很慢。 fenv 不会轻易暴露这一点。
标签: c macos x86 arm64 floating-point-exceptions