ASM x64 scanf printf double, GAS

ASM x64 scanf printf double, GAS

我不明白为什么这段代码对我不起作用。我需要对 double 使用 scanf 函数,然后对相同的 double 使用 printf。 使用此代码时效果不好。我看到的是非常随机的字符。

.data

d1: .double


format: .asciz "%lf\n"
format2: .asciz "%lf"

.text
.globl main

main:

subq , %rsp

#scanf 
movq [=10=], %rax
movq $d1, %rsi
movq $format2, %rdi
call scanf
addq , %rsp

#printf 
movq , %rax
movsd d1, %xmm0
movq $format, %rdi
call printf     
addq , %rsp

#exit
movq , %rdi
xorq %rax, %rax
call exit

这是问题所在:

.data
d1: .double     # declares zero doubles, since you used an empty list
format: .asciz "%lf\n"

d1format 具有相同的地址,因为没有参数的 .double 汇编为空。 (".double expects zero or more flonums, separated by commas. It assembles floating point numbers.").

因此 scanf 覆盖了您用于 printf 的格式字符串。这是 printf 打印的随机垃圾。

解决方法是实际保留一些 space,最好是在堆栈上。但是如果你真的想要静态存储然后使用 BSS。 (This doc explains it well,即使它与某些特定的 gcc 端口有关。)

改为使用:

#.bss
# .p2align 3
# d1: .skip 8           ### This is the bugfix.  The rest is just improvements

# or just use .lcomm instead of switching to the .bss and back
.lcomm d1, 8

.section .rodata
print_format: .asciz "%f\n"     # For printf, "%f" is the format for double.   %lf still works to print a double, though.  Only %llf or %Lf is long double.
scan_format:  .asciz "%lf"      # scanf does care about the trailing whitespace in the format string: it won't return until it sees something after the whitespeace :/  Otherwise we could use the same format string for both.

.text
.globl main
main:
    subq , %rsp

    xor  %eax,%eax
    mov  $d1, %esi            # addresses for code and static data are always in the low 2G in the default "small" code model, so we can save insn bytes by avoiding REX prefixes.
    mov  $scan_format, %edi
    call scanf

    mov   , %eax
    movsd d1, %xmm0
    mov   $print_format, %edi
    call  printf

    add   , %rsp
    ret

    #xor  %edi,%edi   # exit(0) means success, but we can just return from main instead.  It's not a varargs function, so you don't need to zero rax
    #call exit

有关编写高效 asm 代码的更多信息,请参阅 标签 wiki 中的链接。


也可以,但在您的可执行文件中浪费了 8 个字节:

.data
d1: .double 0.0

或者在堆栈上使用 scratch space。还更改了:格式字符串的 RIP 相关 LEA,因此这将在 PIE(PIC 可执行文件)中工作。在制作 PIE 可执行文件时,显式 @plt 是生成 PLT 所必需的。

.globl main
main:
    xor  %eax, %eax          # no FP args.  (double* is a pointer, aka integer)
    push %rax                # reserve 8 bytes, and align the stack.  (sub works, push is more compact and usually not slower)

    mov  %rsp, %rsi          # pointer to the 8 bytes
    lea  scan_format(%rip), %rdi
    call scanf@plt
    # %eax will be 1 if scanf successfully converted an arg

    movsd (%rsp), %xmm0
    mov   , %eax           # 1 FP arg in xmm registers (as opposed to memory)
    lea   print_format(%rip), %rdi

    pop   %rdx               # deallocate 8 bytes.  add , %rsp would work, too
    jmp  printf@plt          # tailcall  return printf(...)

.section .rodata
print_format: .asciz "%f\n"
scan_format:  .asciz "%lf"

您甚至可以将格式字符串存储为立即数,但是您需要保留更多堆栈 space 以保持其对齐。 (例如 push $"%lf",除了 GAS 语法不做多字符整数常量。在 NASM 中你真的可以做 push '%lf' 来获得这 3 个字节 + 5 个零填充。)

相关::你不能因为 C 默认转换规则提升到 double

另外相关:关于ABI对齐规则的问答:Printing floating point numbers from x86-64 seems to require %rbp to be saved