ASM x64 scanf printf double,GAS

时间:2016-04-24 08:59:56

标签: c assembly printf 64-bit scanf

我无法弄清楚为什么这段代码对我不起作用。我需要使用scanf函数用于double,然后printf用于相同的double。 使用此代码时结果不佳。我看到的是相当随机的字符。

.data

d1: .double


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

.text
.globl main

main:

subq $8, %rsp

#scanf 
movq $0, %rax
movq $d1, %rsi
movq $format2, %rdi
call scanf
addq $16, %rsp

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

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

1 个答案:

答案 0 :(得分:1)

这是问题所在:

car

.data d1: .double # declares zero doubles, since you used an empty list format: .asciz "%lf\n" d1具有相同的地址,因为没有args的format汇编为空。 ( “.double expects zero or more flonums, separated by commas. It assembles floating point numbers.”)。

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

修复方法是实际保留一些空间,最好是在堆栈上。但如果您真的想要静态存储,那么请使用BSS。 (This doc explains it well,即使它是关于某个特定的gcc端口。)

相反,请使用:

printf

有关编写高效asm代码的更多内容,请参阅标记wiki中的链接。

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

#.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 $8, %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   $1, %eax
    movsd d1, %xmm0
    mov   $print_format, %edi
    call  printf

    add   $8, %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

或者在堆栈上使用临时空间。也改变了:格式字符串的RIP相对LEA,因此这将在PIE(PIC可执行文件)中工作。在制作PIE可执行文件时,显式.data d1: .double 0.0 是生成PLT所必需的。

@plt

您甚至可以将格式字符串存储为immediates,但是您需要保留更多的堆栈空间以保持对齐。 (例如.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 $1, %eax # 1 FP arg in xmm registers (as opposed to memory) lea print_format(%rip), %rdi pop %rdx # deallocate 8 bytes. add $8, %rsp would work, too jmp printf@plt # tailcall return printf(...) .section .rodata print_format: .asciz "%f\n" scan_format: .asciz "%lf" ,除了GAS语法不执行多字符整数常量。在NASM中,你真的可以push $"%lf"来获得那3个字节+ 5个填充零。)

相关:How to print a single-precision float with printf:您不能因为C默认转化规则升级为push '%lf'

还相关:关于ABI对齐规则的Q& A:Printing floating point numbers from x86-64 seems to require %rbp to be saved