printf float in nasm assembly 64-bit
Asked Answered
W

3

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?

Waiver answered 15/12, 2013 at 12:59 Comment(1)
The alignment issue is a duplicate of Printing floating point numbers from x86-64 seems to require %rbp to be saved, but the first part (about AL = number of FP args in registers) is not.Aerification
B
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:

Babettebabeuf answered 15/12, 2013 at 13:37 Comment(3)
I've changed the code, but now it crash with a seg fault in printf.Waiver
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.Aerification
0x60 isn't a sys_exit, 0x3c is. I think you meant to use a decimal 60.Tilghman
W
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
Wellesley answered 15/12, 2013 at 13:7 Comment(3)
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.Waiver
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 countWaiver
%f for printf wants a double because of C's type promotion rules. %lf is redundant.Aerification
T
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
Tilghman answered 10/4, 2023 at 19:21 Comment(4)
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.Aerification
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. (man7.org/linux/man-pages/man2/exit.2.html).Aerification
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 - 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)Aerification
call printf wrt ..plt isn't a PC-relative call directly to printf. call foo is always PC-relative, using call rel32. 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 . So your code is correct, comment isn't; maybe say "PIE function call"Aerification

© 2022 - 2024 — McMap. All rights reserved.