1

I want to print a float value with 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 contains the floating value 1.6

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

but the program prints

[username@localhost folder]$ ./programname
0.000000

who can I get the program to print 1.6? what am I doing wrong?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
user2798943
  • 197
  • 2
  • 11
  • The alignment issue is a duplicate of [Printing floating point numbers from x86-64 seems to require %rbp to be saved](https://stackoverflow.com/q/16097173), but the first part (about AL = number of FP args in registers) is not. – Peter Cordes Jun 06 '18 at 07:48

3 Answers3

8

The first problem is your code setting AL (via RAX) to 0 whereas it must be 1 because you pass a floating point argument (see here for details). Basically AL should contain the number of variable arguments passed in xmmN registers.

Fixing that without other changes would result in a segfault inside printf, caused by stack misalignment. The program crashes at a movaps instruction (which expects the memory operand to be aligned on 16-byte boundary):

=> 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)

When entering main (or any other function), the stack is aligned RSP%16 == 8, but you need RSP%16 == 0 before a call. If you fix this the program works fine. Below is my test program (notice the sub rsp, 8 in the beginning):

global main
default rel     ; use a more efficient [rel foo] addressing mode by default
extern printf

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

section .text

main:
    sub  rsp, 8                ; align the stack

    movsd xmm0, qword [rs]     ; load a Scalar Double
    mov  rdi, string           ; 64-bit absolute address of format string
    mov  eax, 1                ; AL=1  number of FP args in XMM regs
    call printf

    add  rsp, 8
    xor  eax, eax           ; return 0
    ret                     ; or  call exit with EDI=0,  not call _exit or syscall
; don't use _exit() after printf, it won't flush stdio buffers.
; which will be non-empty if output stdout is redirected to a file.
;       mov eax, 60         ; __NR_exit from unistd_64.h
;       xor edi, edi
;       syscall

Possible pitfalls and related Q&As:

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
szx
  • 6,433
  • 6
  • 46
  • 67
  • I've changed the code, but now it crash with a seg fault in printf. – user2798943 Dec 15 '13 at 13:38
  • You should `ret` from `main` if you're using stdio functions. `sys_exit` doesn't flush buffers, so you'll lose the output if you pipe this program into something else (so stdio will make stdout full-buffered instead of line buffered). Also, `movsd` is the most idiomatic instruction for loading a scalar `double`. – Peter Cordes Apr 18 '18 at 20:35
  • 1
    0x60 isn't a sys_exit, 0x3c is. I think you meant to use a decimal 60. – Kenton Groombridge Apr 10 '23 at 19:49
1

what am I doing wrong?

First: make sure you are using the right calling convention (stack, registers, left to right, right to left, etc.). If your program indeed prints a floating point number, although it is not the one you required, then at least the format string is being passed correctly (or you are having a lot of luck and printf found the address of the format string at the right place even if you didn't put its address there).

Second: the number you are trying to print... is it a float or a double? rs is defined to hold a quadword value (64 bits), but floats are 32 bits. So, if the first point has been checked and it's ok, I suggest you to use "%lf" as format, instead of "%f".

BTW: why do you put RAX = 0? What does it mean regarding the call to printf?

UPDATE: This may help you. A disassembly of a silly program (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
mcleod_ideafix
  • 11,128
  • 2
  • 24
  • 32
  • rax actually al should hold the number of xmm registers used, I changed it to one but now the program crashes with a seg fault in printf. the floating arguments of printf are stored in the xmm registers (amd64 assembler), the string address is stored in rdi. – user2798943 Dec 15 '13 at 13:31
  • the strange thing is that when i put a 1 into rax printf crashes with a seg fault and when i put 0 into rax it prints 0.000, because rax is the float argument count – user2798943 Dec 15 '13 at 14:32
  • `%f` for printf wants a `double` because of C's type promotion rules. `%lf` is redundant. – Peter Cordes Dec 23 '17 at 16:25
1
; file: pf.asm
; assemble with:  nasm -f elf64 pf.asm -o pf.o
; link with: ld -pie -z noexecstack -e _start -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o pf pf.o -lc

default rel             ; Use PC relative offsets (for PIE executable)

global _start           ; Execute at _start (don't use any provided startup code)

extern printf           ; printf is our only external function

section .rodata         ; The format string in printf should be read only
    string: db "Your number is %f", 0xa, 0

section .data           ; We could make this read only, but if we want to change it
    rs dq 1.6           ; through a scanf or something we can

section .text           ; Program code

_start:
;    Uncomment stack frame stuff if stack space needed, not needed for this example

;    push rbp           ; Setup our stack frame
;    mov rbp, rsp

;    sub rsp, 8         ; Create space on the stack

    movq xmm0, qword [rs]       ; Put float in xmm0
    lea rdi, [string]   ; Load address of string, lea is needed for PC relative
    mov eax, 1          ; Using floating point numbers, 0 extend into rax
    call printf wrt ..plt  ; PC relative function call

;    add rsp, 8         ; Remove allocated space on the stack

;    pop rbp            ; Undo our stack frame 

    mov eax, 60         ; Exit syscall
    xor edi, edi        ; No error, 0 extend into rdi
    syscall
  • This is correct and a good example, except for some of the comments being potentially misleading. You don't need `push rbp` in `_start`; there is no caller with a value to save. You could do a `push 0` to give backtracing an end point. Your code would work with the stack stuff uncommented, but the comments don't explain the need to maintain 16-byte stack alignment (see my edit to szx's answer), or the fact that `_start` is special: it's not a function. At `_start`, RSP%16 == 0, same as you need before a `call`. In functions, like `main`, RSP%16 == 8 on entry. – Peter Cordes Apr 10 '23 at 20:43
  • `mov eax, 1` does zero-extend into RAX, but that's irrelevant: the register that variadic functions look at to figure out if they should dump the XMM regs is just `AL`. `mov eax,1` is sometimes more efficient than `mov al,1`, but takes more code size. For `xor edi,edi`, again zero-extension is irrelevant: the syscall signature is `_exit(int)`, so it only looks at the low 32 bits of the register. (https://man7.org/linux/man-pages/man2/exit.2.html). – Peter Cordes Apr 10 '23 at 20:49
  • Also, don't use a raw `_exit` system call after printf: *[Using printf in assembly leads to empty output when piping, but works on the terminal](https://stackoverflow.com/q/38379553)* - use `call exit` also from libc, which will flush stdio, necessary if you redirect to a file like `./test > number.txt` - your program won't `write` anything when stdout isn't a TTY. (`strace ./text > number.txt`) – Peter Cordes Apr 10 '23 at 20:51
  • `call printf wrt ..plt` isn't a PC-relative call directly to printf. `call foo` is always PC-relative, using `call rel32`. https://www.felixcloutier.com/x86/call. `call printf wrt ..plt` calls through a PLT stub, the Procedure Linking Table. `ld` doesn't do this for you when making a PIE executable, unlike when linking a traditional executable where it will invent a PLT stub and rewrite `call printf` to reference the PLT entry. [Can't call C standard library function on 64-bit Linux](//stackoverflow.com/q/52126328) . So your code is correct, comment isn't; maybe say "PIE function call" – Peter Cordes Apr 10 '23 at 20:55