1

I can't figure out why this code isn't working for me. I need to use scanf function for double and then printf for same double. When using this code results are not good. What I see are pretty random characters.

.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
zderzak678
  • 15
  • 5
  • 2
    Debugger, step through, watch register values. – Martin James Apr 24 '16 at 09:15
  • 1
    This probably isn't the problem, since `main` never returns and you never read any stack memory, but you're breaking the stack with your `add $16, %rsp` after each `call`. `%rsp` is a call-preserved register, so it has the same value after the call as it did before. Also, you should put `d1` in the bss, not `.data`, because you don't need to store a value for it in your executable. Actually it is the problem. – Peter Cordes Apr 24 '16 at 09:15
  • ^^ lol, panto comment: 'Oh no it isn't'....'Oh yes it is' – Martin James Apr 24 '16 at 09:23
  • @MartinJames: yup, I was curious what the hell `.double` with no argument would assemble to, so I assembled this code and checked `readelf -a` output. Turns out `d1` had the same address as `format`... Then found 15 minutes worth of other stuff to type after a last-minute comment edit. – Peter Cordes Apr 24 '16 at 09:38
  • [Another answer](http://stackoverflow.com/questions/39356236/why-does-x86-program-segfault-without-data-section) for the same issue with different symptoms. (`.int` with an empty list leads to no BSS being created at all, leading to a segfault.) – Peter Cordes Sep 06 '16 at 20:58

1 Answers1

1

This is the problem:

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

d1 and format have the same address, since .double with no args assembles to nothing. (".double expects zero or more flonums, separated by commas. It assembles floating point numbers.").

So scanf overwrites the format string you use for printf. This is the random garbage that printf prints.

The fix is to actually reserve some space, preferably on the stack. But if you really want static storage then use the BSS. (This doc explains it well, even though it's about some specific gcc port.)

Instead, use this:

#.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

For more stuff about writing efficient asm code, see the links in the tag wiki.


Also would have worked, but wasted 8 bytes in your executable:

.data
d1: .double 0.0

Or to use scratch space on the stack. Also changed: RIP-relative LEA for the format strings, so this will work in a PIE (PIC executable). The explicit @plt is necessary to generate PLT when making a PIE executable.

.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"

You could even store your format strings as immediates, too, but then you need to reserve more stack space to keep it aligned. (e.g. push $"%lf", except GAS syntax doesn't do multi-character integer constants. In NASM you really could do push '%lf' to get those 3 bytes + 5 zeros of padding.)

Related: How to print a single-precision float with printf: you can't because of C default-conversion rules that promote to double.

Also related: a Q&A about the ABI alignment rules: Printing floating point numbers from x86-64 seems to require %rbp to be saved

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Related [How to print a single-precision float with printf](https://stackoverflow.com/q/37082784): you can't; `printf` requires its args promoted to `double`, unlike `scanf` which can take a `float*`. – Peter Cordes Jun 06 '18 at 07:50