Search code examples
assemblyscanfx86-64nasmcalling-convention

How to Access First Parameter of scanf Call in Assembly?


Say I have the following code written in (AT&T) Assembly:

push qword 0
push qword 0
mov rax, 2                ;Tell rax we receive 2 floats
mov rdi, floatformat      ;floatformat db "%lf %lf",0

mov rsi, rsp              ;I am assuming my logic flaw is in these two lines
mov rdx, rsp

call scanf
pop rax                   ;Clean up the stack
pop rax

movsd xmm0, [rsi]         ;This does not give the value I want

As stated in the comments above, I want xmm0 to hold the first float the user types in when call scanf is performed, but only receive the second float. I am aware that this is most likely due to the mov rdx, rsp operation, but if that is not performed, my program does not operate correctly to read in the user inputs.

How can I get the first float the user types in? I have tried researching scanf calling conventions but have yet to find a clear answer.


Solution

  • %lf is a double, not a single precision float. And no, the 2 args you're passing to scanf are double *, not double, so you should set AL=0 not 2.

    Anyway, your problem is that you passed the same pointer for both output operands. And that scanf destroys its arg-passing registers, like the calling convention allows it to. (What registers are preserved through a linux x86-64 function call)

    The C equivalent is like scanf("%lf %lf", &b, &b) after reserving stack space for double a,b;

        ; assuming stack is 16-byte aligned to start with,
        ; e.g. if your function started with an odd number of pushes
    
        sub   rsp, 16
        mov   rsi, rsp        ; pointer to low slot
        lea   rdx, [rsp+8]    ; pointer to high slot
        lea   rdi, [rel format_string]    ; use RIP-relative LEA for 64-bit static addresses
        xor   eax,eax         ; 0 FP args in regs to a variadic function
        call  scanf           ; scanf(fmt, &tmp0, &tmp1)
    
        movaps xmm0, [rsp]     ; load both doubles
        add    rsp, 16         ; now tmp0 and tmp1 are below RSP, in the red-zone
    
        movsd   xmm1, [rsp-8]  ; or shuffle xmm0 to get tmp1 from the high half.
    

    dummy push/pop is usually only worth it (instead of add rsp, imm8) for one stack slot, and then mostly if you're going to call right away instead of explicitly reference RSP. Doing that will insert a stack-sync uop.