在 x86 汇编中使用
idiv
除以 0 时,出现 SIGFPE 异常。我如何从汇编中禁用它?我需要系统调用还是可以直接在 x86 中完成?
复制:
测试.asm
default rel
global WinMain
section .data
section .text
WinMain:
mov rcx, 0
mov rdx, 0
idiv rcx
命令:
nasm -f win64 test.asm
gcc 测试.obj
gdb
跑
TL:DR:您无法使硬件不会因
#DE
异常而出现故障,并且在跳过信号处理程序中的故障指令以“修复”情况后恢复执行并非易事。 (您还需要将 RDX 和 RAX 设置为一些特殊值,如 -1
、-0
,或值得注意的值,如 0xcccccccccccccccc
。)
对于实际的浮点异常,x87 FPU 和 SSE(通过 MXCSR)默认具有异常掩码,因此您在无效操作(例如除以零)时在目标中得到 NaN,没有硬件异常。 POSIX 要求,如果任何算术运算出现错误并且操作系统传递有关该错误的信号,则该信号必须是 SIGFPE。
您能做的最好的事情就是使用信号处理程序捕获 SIGFPE(在 Linux 等 POSIX 操作系统上为
sigaction(2)
)。奇怪的是,您正在谈论具有 WinMain
入口点的程序的 SIGFPE,除非 Windows SEH(结构化异常处理)也使用相同的 SIGFPE 名称?
根据 POSIX,使用 SIG_IGN 忽略它是未定义的,实际上,如果您忽略该信号或让信号处理程序返回而不执行任何操作,则会导致无限循环。硬件 #DE
错误有一个指向错误指令的返回地址(与页面错误相同),这样处理程序就可以看到它是哪条指令,因此以某种方式修复这种情况(就像 #PF 处理程序通常所做的那样)重新运行指令,希望下次尝试时不会出错。因此,您的处理程序应该检查是否
si_code == FPE_INTDIV
。如果是这样,它可能会尝试将程序计数器推进到超过错误指令的末尾,就像它根本没有执行一样运行,使 FLAGS 和 RDX:RAX 保持不变。 (或者您的信号处理程序可以将它们设置为一些一致的值。)线程的寄存器可由在其中运行的 Linux 信号处理程序访问和修改。
获取生成 UNIX 信号的故障地址还涵盖了 ucontext_t
内容,包含 GPR(包括 RAX 和 RDX)以及 RIP(程序计数器)。x86-64 指令是可变长度的。您必须从 RIP 中的地址解码机器代码,跳过前缀,查找作为
idiv
或
div
操作码的操作码字节。如果是这样,请将 RIP 前进到该指令之后以跳过它。指令长度解码并非易事,因为具有可变长度寻址模式的内存源操作数是可能的:您必须对 ModRM 和可选的 SIB 进行解码,足以计算出总指令长度。 (x86-64 的设计目的是让这不会太痛苦,以便硬件可以有效地完成它:在对前缀后面的部分进行指令长度解码时,您不需要记住 REX 前缀中的位(如果有的话):*rbp不允许作为 SIB 基础?)
div
和
idiv
不会在除数 = 0 或商不适合操作数大小时引发
#DE
除异常。例如进入 64 位
idiv rcx
的 RAX,就像
INT64_MIN / -1
一样,它在 x86-64 上也会出错。或者对于带符号除法的
(unsigned long)-1 / 1
,就像您在签名
idiv
之前通过零扩展到 RDX:RAX 所做的那样。通常使用
cqo
/
idiv
在有符号除法之前将 RAX 符号扩展为 RDX:RAX,或使用
xor edx,edx
/
div
在无符号除法之前将零扩展为 RDX。(
into
指令,如果设置了 OF 标志则捕获。)
INT_MIN / x
,其中
x=-1
在 ARM 上不会出错,除以 0 也不会出错。有关 POSIX 的更多背景信息,请参阅在这种情况下需要有关信号。