【问题标题】:printf float in nasm assembly 64-bitnasm 程序集 64 位中的 printf 浮点数
【发布时间】:2024-01-15 09:47:01
【问题描述】:

我想用 printf 打印一个浮点值

global main
extern printf

section .data
   string: db `%f\n`, 0

section .bss
   rs: resq 1

[...]

   movq xmm0, [rs]
   mov rdi, string
   mov rax, 0
   call printf

rs 包含浮点值 1.6

(gdb) x/fg &rs
0x600ad8 <rs>:  1.6000000000000001

但程序会打印

[username@localhost folder]$ ./programname
0.000000

谁可以让程序打印 1.6?我做错了什么?

【问题讨论】:

标签: assembly printf nasm x86-64 calling-convention


【解决方案1】:

我怀疑这个问题与您的代码设置有关 rax0 而它必须是 1 因为您传递了一个浮点参数(有关详细信息,请参阅 here)。基本上rax 应该包含在xmmN 寄存器中传递的变量参数的数量。

编辑:

printf 中的崩溃似乎是由堆栈错位引起的,因为程序在 movaps 指令处崩溃(它期望内存操作数在 16 字节边界上对齐):

=> 0x7ffff7a65f84 <__printf+36>:    movaps %xmm0,0x50(%rsp)
   0x7ffff7a65f89 <__printf+41>:    movaps %xmm1,0x60(%rsp)
   0x7ffff7a65f8e <__printf+46>:    movaps %xmm2,0x70(%rsp)
   0x7ffff7a65f93 <__printf+51>:    movaps %xmm3,0x80(%rsp)
   0x7ffff7a65f9b <__printf+59>:    movaps %xmm4,0x90(%rsp)
   0x7ffff7a65fa3 <__printf+67>:    movaps %xmm5,0xa0(%rsp)
   0x7ffff7a65fab <__printf+75>:    movaps %xmm6,0xb0(%rsp)
   0x7ffff7a65fb3 <__printf+83>:    movaps %xmm7,0xc0(%rsp)

当输入main 时,堆栈不是 16 字节对齐的,但如果你解决了这个问题,程序就可以正常工作。下面是我的测试程序(注意开头的sub rsp, 8):

global main
extern printf

section .data
    string db `%f\n`, 0
    rs dq 1.6

section .text

main:
    sub rsp, 8
    movq xmm0, qword [rs]
    mov rdi, string
    mov rax, 1
    call printf
    add rsp, 8
    mov eax, 0x60
    xor edi, edi
    syscall

【讨论】:

  • 我已更改代码,但现在它因 printf 中的 seg 错误而崩溃。
  • 如果你正在使用 stdio 函数,你应该从 main retsys_exit 不会刷新缓冲区,因此如果您将此程序通过管道传输到其他内容中,您将丢失输出(因此 stdio 将使 stdout 成为全缓冲而不是行缓冲)。此外,movsd 是加载标量 double 的最惯用的指令。
【解决方案2】:

我做错了什么?

首先:确保使用正确的调用约定(堆栈、寄存器、从左到右、从右到左等)。如果您的程序确实打印了一个浮点数,尽管它不是您需要的,那么至少格式字符串被正确传递(或者您很幸运,printf 找到了格式字符串的地址正确的地方,即使你没有把它的地址放在那里)。

Second:您要打印的数字...是浮点数还是双精度数? rs 被定义为保存四字值(64 位),但浮点数是 32 位。所以,如果第一点已经勾选,没问题的话,我建议你使用"%lf"作为格式,而不是"%f"

顺便说一句:你为​​什么放RAX = 0?拨打printf是什么意思?

更新:这可能会对您有所帮助。一个傻程序的反汇编(f.c):

#include <stdio.h>

main()
{
  float x;

  x = 1.6;
  printf ("%f\n", x);
}

$ gcc -c -S f.c

$ less f.s

        .file   "f.c"
        .section        .rodata
.LC1:
        .string "%f\n"
        .text
.globl main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        subq    $16, %rsp
        movl    $0x3fcccccd, %eax
        movl    %eax, -4(%rbp)
        movss   -4(%rbp), %xmm0
        cvtps2pd        %xmm0, %xmm0
        movl    $.LC1, %eax
        movq    %rax, %rdi
        movl    $1, %eax
        call    printf
        leave

【讨论】:

  • rax 实际上应该保存使用的 xmm 寄存器的数量,我将其更改为一个,但现在程序因 printf 中的 seg 错误而崩溃。 printf 的浮动参数存储在 xmm 寄存器(amd64 汇编器)中,字符串地址存储在 rdi 中。
  • 奇怪的是,当我将 1 放入 rax 时 printf 会因 seg 错误而崩溃,而当我将 0 放入 rax 时,它会打印 0.000,因为 rax 是浮点参数计数
  • %f for printf 由于 C 的类型提升规则,需要 double%lf 是多余的。
最近更新 更多