我有以下代码:
extern printf
section .data
A db 1
B db 2
C db 3
D db 4
E db 5
fmt db "%d",10,0
fmt2 db "An overflow has occured",10,0
section .text
global _start
_start:
xor rax, rax
xor rbx, rbx
xor rdx, rdx
xor ax, ax
mov al, [B]
mul ax
jo over
call printax
mov bx, ax
xor ax, ax
mov al, [C]
mul ax
jo over
call printax
xor cx, cx
mov cl, [C]
mul cx
jo over
call printax
xor cx, cx
mov cl, [E]
div cx
jo over
call printax
xor dx, dx
xor al, al
mov rdi, fmt
call printf
jmp exit
over:
mov rdi, fmt2
xor al, al
call printf
exit:
mov rax, 60
mov rdi, 0
syscall
printbx:
xor rsi, rsi
mov si, bx
mov cl, al
xor al, al
mov rdi, fmt
call printf
mov al, cl
xor si, si
ret
printax:
xor rsi, rsi
mov si, ax
xor al, al
mov rdi, fmt
call printf
mov ax, si
xor si, si
ret
我用 nasm -f elf64 1.asm
编译它并使用 ld -dynamic-linker /lib/ld-linux-x86-64.so.2 1.o -o 1 -lc
链接它。当我执行二进制文件时,我得到
$ ./1
4
9
0
Floating point exception (core dumped)
printf()
当我在汇编代码中调用它时并不总是失败。
正在删除printf()
来电 printax
和printbx
给我
$ ./1
0
更新:如果我删除div cx
,异常也会消失线。然后我得到以下输出:
$./1
4
9
0
0
0
但是即使我添加它也不会消失
mov cx, 1
mov ax, 1
之前div cx
。
最佳答案
再看看 x86-64 ABI calling conventions 。具体而言,第 15 页:
Registers %rbp, %rbx and %r12 through %r15 “belong” to the calling function and the called function is required to preserve their values. In other words, a called function must preserve these registers’ values for its caller. Remaining registers “belong” to the called function. If a calling function wants to preserve such a register value across a function call, it must save the value in its local stack frame.
因此,当您调用 printf
时,您必须假设除 rbp、rbx、r12..r15
之外的所有寄存器都已损坏。这对您的程序有两个影响:
您尝试通过将
ax
的值存储在si
寄存器中,然后将其放回对printf
的调用来保存它稍后,但这没有帮助,因为si
可能会被破坏。您的
div cx
指令将dx:ax
的内容除以cx
,但dx
也可能被破坏了。
后者是 SIGFPE 的具体原因(尽管它的名称是在整数除法溢出时引发的),至少在我的测试运行中是这样。 printf 返回后,dx 包含一些巨大的数字,因此 dx:ax
除以 cx
不适合16 位,这是溢出。
(这也解释了为什么当您取出 printf
调用时崩溃就消失了 - 它不再破坏您的寄存器。)
这里的另一个教训是,您无法通过事后执行 jo
来检查 x86 上的除法溢出;此错误主要由异常而不是标志来表示。因此,您确实必须在执行指令之前检查操作数,或者在发生异常时安排处理异常(这更复杂,超出了本答案的范围)。
其他一些注意事项:
在最终调用
printf
之前(就在jmp exit
之前),您不会将任何内容加载到rsi
中,因此打印的值是垃圾。16 位算术在 x86-32 或 x86-64 上通常不是首选,除非有充分的理由。它并不更快,并且使用操作数大小前缀使您的代码变得臃肿。最好使用 32 位算术来完成所有工作。
由于您使用自己的入口点而不是让 C 库调用您的
main
,这意味着 libc 没有机会运行自己的初始化代码。因此,调用任何 libc 函数都不一定安全,特别是 stdio 和分配函数。在这种情况下,printf
似乎工作正常,也许它适合调试,但您不应该计划在生产中以这种方式编写程序。
关于在汇编中调用 printf() 会导致 'floating point exception',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58491727/