在汇编中调用 printf() 会导致 'floating point exception'

标签 c linux assembly printf nasm

我有以下代码:

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()来电 printaxprintbx给我

$ ./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/

相关文章:

c - OpenCV多线程XInitThreads错误

c - 使用 clock_gettime() 得到不一致的结果

Python - 检测键盘布局

assembly - IA32 组件 : lea instruction

assembly - 从堆栈中弹出而不压入

Linux 使用堆栈上的字符串写入系统调用

c - 减少c中的分数

c - 使用可能重复的键调整单独链接的哈希表的大小

linux - 控制另一个进程的内存映射

linux - 优化查找命令