3

I have a simple program in x86 asm, which makes z = x/y. The thing is, the code should be written correctly when it comes to division, but nothing is printed. There is no output. I have no idea what's wrong, because I can't debug when the program doesn't use eax, ebx etc.

global main
extern printf

section .text

    main:

    finit
    fild dword[x]
    fild dword[y]
    fdiv
    fist dword[z]

    push dword[z]
    push frm
    call printf
    add esp,8

    mov ebx,0
    mov eax,1
    int 0x80


section .data
x: dd 1.2
y: dd 3.14
z: dd 0.0
frm: dd '%lf',10,0
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
J.G.
  • 103
  • 1
  • 6
  • Try without `mov ebx,0` + you should read about floats in x86 asm. – jakub1998 Dec 31 '17 at 11:05
  • 1
    gdb can show the FP registers (`p $st0`, or `tui reg float`). But your problem is not the contents of FP regs. – Peter Cordes Dec 31 '17 at 11:42
  • Aren't varargs floats promoted to doubles? – Margaret Bloom Dec 31 '17 at 11:43
  • @MargaretBloom: yes. https://stackoverflow.com/questions/37082784/how-to-print-a-single-precision-float-with-printf. Or https://stackoverflow.com/questions/29442155/printing-floats-with-printf-in-x86-nasm-32-bit for x87 `fstp qword [esp]`. But this program isn't printing *anything*, so that's not the only bug. Perhaps its being built as 64-bit code, where `printf` has a different calling convention? Or else `printf` doesn't like it when you call it with the x87 stack not empty (which the ABI requires). What do you see if you run it under `strace`? – Peter Cordes Dec 31 '17 at 11:45
  • The format string does contain a newline, so printf should print even though it exits with `sys_exit` without flushing stdio buffers. Unless your `stdout` isn't a TTY, in which case you also have this problem: https://stackoverflow.com/questions/38379553/using-printf-in-assembly-leads-to-an-empty-ouput. (It would be good practice to just `ret` from `main` when you're using C library functions.) – Peter Cordes Dec 31 '17 at 11:49
  • update: `dd` means that there's a terminating `0` before the newline, see comments below. – Peter Cordes Mar 30 '18 at 18:19

2 Answers2

2

1) The C library - I guess you use the one from GCC - doesn't output the result of printf immediately. Rather, it is stored in a separate memory called cache and outputted by chance. In this case the program will be ended by int 0x80/eax=1 faster than the cache will be flushed. You can insert a manual flush:

...
extern fflush
...
push 0
call fflush
add esp, 4
...

The best solution is to use the C exit function. Replace

mov ebx,0
mov eax,1
int 0x80

by

push 0
call exit

2) printf with format %lf needs a double floating point number (8 bytes = QWORD) as input. So change the code:

...
fstp qword[z]
...
push dword[z+4]
push dword[z]
push frm
call printf
add esp,12
...
z: dq 0.0

3) NASM will interpret and convert 1.2 and 3.14 as floating point number. Defined as dd it will be stored as single floating point number. However, fild expects and loads an integer number. Let it load as single:

fld dword[x]
fld dword[y]

The whole bunch:

global main
extern printf, fflush, exit

section .text

    main:

    finit
    fld dword[x]
    fld dword[y]
    fdiv
    fstp qword[z]

    push dword[z+4]
    push dword[z]
    push frm
    call printf
    add esp,12

    push 0
    call fflush
    add esp, 4

    push 0
    call exit

section .data
x: dd 1.2
y: dd 3.14
z: dq 0.0
frm: dd '%lf',10,0
rkhb
  • 14,159
  • 7
  • 32
  • 60
  • 1
    I think you still leave the x87 stack unbalanced: you should use `fdivp`, or `fdiv dword[y]` instead of the 2nd load. Also, `sub esp,8` / `fstp qword [esp]` would be a lot simpler. And you don't need to call `fflush` if you `call exit` or `ret` from main. You only need `fflush` if you call `sys_exit` manually, or similar bypassing of the normal exit cleanup. (And BTW, shift_left was correct: using `dd '%lf', 10` was a problem: it puts a `0` terminator before the LF, otherwise `printf` would print right away when stdout is line-buffered. It's not a cache, it's an I/O *buffer*.) – Peter Cordes Dec 31 '17 at 21:41
1

FILD Load Integer

Converts the signed-integer source operand into double extended-precision floating-point format

3.14 & 1.2 are not integers.

  • 314 / 12 = 26.1666 then shift decimal left or
  • 314 / 120 = 2.61666 or
  • Use DT and declare as extended precision float.

    dd     3.14                ; equates to 4048F5C3H = 1,078,523,331
    dd     1.2                 ;            3F99999Ah = 1,067,030,938
    

    So the result is going to be 9.8934432601532659843776712884109e-1 and FIST is going to convert that to zero

Strings are declared with DB not DD so

    frm:     db      '%lf', 10, 0 will solve that problem
Shift_Left
  • 1,208
  • 8
  • 17
  • `dd` for the string merely results in extra padding (out to a dword boundary); that doesn't explain why it's not printing anything. Good catch on using `fist` instead of `fstp`, though. (And on using `fild` on binary32 FP data). But that doesn't explain why the OP's program didn't print anything, instead of printing zero (or non-zero with whatever garbage was on the stack above the 32 bits they pushed, to form the upper 32 bits of the `double` that `printf` loads . I wouldn't recommend `DT` for `long double` unless you need it; `dq` or `dd` for double or single is fine most of the time. – Peter Cordes Dec 31 '17 at 14:01
  • 1
    @PeterCordes I would have never guessed that `dd '%lf %2X %3d', 10, 13, 0` would have encoded properly other than the 3 zeros before CR and LF. – Shift_Left Dec 31 '17 at 17:47
  • 1
    Oh right, padding to DWORD happens for each element, at the `,` boundaries. So `dd '%lf', 10, 0` is actually `db '%lf',0, 10,0,0,0, 0,0,0,0`. So as a C implicit-length string, the format string is only `"%lf"`, and a line-buffered stdout won't flush before `sys_exit`. IDK why you added a `13`; nobody uses LF CR as a newline, and Linux definitely only uses LF. And note that the extra zeros are *after* the 10 and the 13, because x86 is little-endian. – Peter Cordes Dec 31 '17 at 21:03