TL:DR:你不能用#DE异常使硬件不出错,而且在跳过信号处理程序中的出错指令以“修复”这种情况后恢复执行也是很重要的。(您还需要将RDX和RAX设置为一些特殊值,如-1,0,或者一个值得注意的值,比如0xcccccccccccccccc。或者取决于什么可以让你的程序的其余部分安静地做一些有用的事情(?)而不是以不同的方式再次崩溃,也许是+1。) 对于实际的浮点异常,x87 FPU和SSE(通过MXCSR)默认具有异常掩码,因此在目标中无效操作(如被零除)时会得到NaN,没有硬件异常。POSIX要求,如果任何算术运算出现错误,并且操作系统传递了一个关于它的信号,该信号必须是SIGFPE。 你能做的最好的事情就是用信号处理程序来捕获SIGFPE(在像Linux这样的POSIX操作系统上是sigaction(2))。奇怪的是,你在谈论一个带有WinMain入口点的程序的SIGFPE,除非Windows SEH(结构化异常处理)也使用相同的SIGFPE名称? 根据POSIX,使用SIG_IGN忽略它是未定义的,并且在实践中,如果忽略信号或使信号处理程序返回而不做任何事情,则会导致无限循环。(与页面错误相同),两者都可以让处理程序看到它是哪条指令,因此,以某种方式修复这种情况(就像#PF处理程序通常所做的那样)将重新运行指令,希望下次尝试时不会出错。 所以你的处理程序应该检查是否si_code == FPE_INTDIV。如果是这样,它可以尝试将程序计数器提前到错误指令的末尾,就像它根本没有执行一样运行,而不修改FLAGS和RDX:RAX。(或者你的信号处理程序可以将它们设置为一些一致的值。) 一个线程的寄存器可以被运行在其中的Linux信号处理程序访问和修改。* Getting fault address that generated a UNIX signal * 也涵盖了ucontext_t的内容,包括GPR,包括RAX和RDX,还有RIP,程序计数器。 x86-64指令是可变长度的。您必须从RIP中的地址解码机器码,跳过前缀,查找idiv或div的操作码字节。如果是这样,请将RIP提前到该指令之后以跳过它。指令长度解码是不平凡的,因为具有可变长度寻址模式的内存源操作数是可能的:你必须对ModRM和可选的SIB进行足够的解码,以计算出总的指令长度。(x86-64的设计使这一点不会太痛苦,所以硬件可以有效地完成它:你不需要记住来自雷克斯前缀的位,如果有的话,同时对前缀后面的部分进行长度解码:* rbp not allowed as SIB base?) 没有FLAGS /控制寄存器/其他硬件设置会使div和idiv在除数= 0或商不适合操作数大小时不会引发#DE除法异常。例如,对于64位idiv rcx,如INT64_MIN / -1,它也会在x86-64上出错。 或者对于有符号除法的(unsigned long)-1 / 1,就像你在有符号idiv之前零扩展到RDX:RAX一样。通常使用cqo/idiv在有符号除法之前将RAX符号扩展到RDX:RAX,或者使用xor edx,edx/div在无符号除法之前零扩展到RDX。 (When and why do we sign extend and use cdq with mul/div?) 您可以屏蔽FP异常,默认的x87和SSE(MXCSR)状态是屏蔽FP异常,因此通常在x86-64系统上唯一可以引发SIGFPE的是整数除法。(或者可能是into指令,如果设置了OF标志,则为trap。) 相关:
1条答案
按热度按时间gpnt7bae1#
TL:DR:你不能用
#DE
异常使硬件不出错,而且在跳过信号处理程序中的出错指令以“修复”这种情况后恢复执行也是很重要的。(您还需要将RDX和RAX设置为一些特殊值,如-1
,0
,或者一个值得注意的值,比如0xcccccccccccccccc
。或者取决于什么可以让你的程序的其余部分安静地做一些有用的事情(?)而不是以不同的方式再次崩溃,也许是+1
。)对于实际的浮点异常,x87 FPU和SSE(通过MXCSR)默认具有异常掩码,因此在目标中无效操作(如被零除)时会得到NaN,没有硬件异常。POSIX要求,如果任何算术运算出现错误,并且操作系统传递了一个关于它的信号,该信号必须是SIGFPE。
你能做的最好的事情就是用信号处理程序来捕获SIGFPE(在像Linux这样的POSIX操作系统上是
sigaction(2)
)。奇怪的是,你在谈论一个带有WinMain
入口点的程序的SIGFPE,除非Windows SEH(结构化异常处理)也使用相同的SIGFPE名称?根据POSIX,使用SIG_IGN忽略它是未定义的,并且在实践中,如果忽略信号或使信号处理程序返回而不做任何事情,则会导致无限循环。(与页面错误相同),两者都可以让处理程序看到它是哪条指令,因此,以某种方式修复这种情况(就像#PF处理程序通常所做的那样)将重新运行指令,希望下次尝试时不会出错。
所以你的处理程序应该检查是否
si_code == FPE_INTDIV
。如果是这样,它可以尝试将程序计数器提前到错误指令的末尾,就像它根本没有执行一样运行,而不修改FLAGS和RDX:RAX。(或者你的信号处理程序可以将它们设置为一些一致的值。)一个线程的寄存器可以被运行在其中的Linux信号处理程序访问和修改。* Getting fault address that generated a UNIX signal * 也涵盖了
ucontext_t
的内容,包括GPR,包括RAX和RDX,还有RIP,程序计数器。x86-64指令是可变长度的。您必须从RIP中的地址解码机器码,跳过前缀,查找
idiv
或div
的操作码字节。如果是这样,请将RIP提前到该指令之后以跳过它。指令长度解码是不平凡的,因为具有可变长度寻址模式的内存源操作数是可能的:你必须对ModRM和可选的SIB进行足够的解码,以计算出总的指令长度。(x86-64的设计使这一点不会太痛苦,所以硬件可以有效地完成它:你不需要记住来自雷克斯前缀的位,如果有的话,同时对前缀后面的部分进行长度解码:* rbp not allowed as SIB base?)没有FLAGS /控制寄存器/其他硬件设置会使
div
和idiv
在除数= 0或商不适合操作数大小时不会引发#DE
除法异常。例如,对于64位idiv rcx
,如INT64_MIN / -1
,它也会在x86-64上出错。或者对于有符号除法的
(unsigned long)-1 / 1
,就像你在有符号idiv
之前零扩展到RDX:RAX一样。通常使用cqo
/idiv
在有符号除法之前将RAX符号扩展到RDX:RAX,或者使用xor edx,edx
/div
在无符号除法之前零扩展到RDX。(When and why do we sign extend and use cdq with mul/div?)
您可以屏蔽FP异常,默认的x87和SSE(MXCSR)状态是屏蔽FP异常,因此通常在x86-64系统上唯一可以引发SIGFPE的是整数除法。(或者可能是
into
指令,如果设置了OF标志,则为trap。)相关:
INT_MIN / x
,其中x=-1
在ARM上不会出错,除以0也不会出错。