【问题标题】:ASM x64 scanf printf double, GASASM x64 scanf printf double, GAS
【发布时间】:2016-04-24 08:59:55
【问题描述】:

我不明白为什么这段代码不适合我。我需要对双精度使用 scanf 函数,然后对同一个双精度使用 printf 函数。 使用此代码时结果并不好。我看到的是相当随机的字符。

.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

【问题讨论】:

  • 调试器,单步调试,观察寄存器值。
  • 这可能不是问题,因为main 永远不会返回并且您永远不会读取任何堆栈内存,但是您在每个call 之后都会使用add $16, %rsp 破坏堆栈。 %rsp 是一个保留调用的寄存器,因此它在调用后的值与之前相同。此外,您应该将d1 放在bss 中,而不是.data,因为您不需要在可执行文件中存储它的值。实际上是问题所在。
  • ^^ 大声笑,panto 评论:'哦,不,不是'....'哦,是的'
  • @MartinJames:是的,我很好奇没有参数的.double 到底会组装成什么,所以我组装了这段代码并检查了readelf -a 的输出。原来d1 的地址与format 相同...然后在最后一分钟的评论编辑后找到了 15 分钟的其他内容要输入。
  • Another answer 针对具有不同症状的相同问题。 (.int 带有空列表会导致根本没有创建 BSS,从而导致段错误。)

标签: c assembly printf 64-bit scanf


【解决方案1】:

这就是问题所在:

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

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

所以scanf 会覆盖您用于printf 的格式字符串。这是printf 打印的随机垃圾。

解决方法是实际保留一些空间,最好是在堆栈上。但是,如果您真的想要静态存储,请使用 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 $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

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


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

.data
d1: .double 0.0

或使用堆栈上的暂存空间。还更改了:格式字符串的相对于 RIP 的 LEA,因此这将在 PIE(PIC 可执行文件)中工作。生成 PIE 可执行文件时需要显式的@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   $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"

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

相关:How to print a single-precision float with printf:你不能因为 C 默认转换规则提升为double

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-18
  • 2021-06-05
  • 2019-10-25
  • 2012-12-19
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
相关资源
最近更新 更多