从程序集中捕获/禁用 SIGFPE 异常

问题描述 投票:0回答:1

在 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

assembly x86 x86-64 integer-division
1个回答
5
投票

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。

我们什么时候以及为什么使用 mul/div 来扩展和使用 cdq?

您可以屏蔽 FP 异常,默认的 x87 和 SSE (MXCSR) 状态用于屏蔽 FP 异常,因此通常在 x86-64 系统上唯一可以引发 SIGFPE 的是整数除法。 (或者可能是

into

 指令,如果设置了 OF 标志则捕获。)


相关:

© www.soinside.com 2019 - 2024. All rights reserved.