Search code examples
assemblyx86-6464-bit

Fibonacci sequence assembly code making nonsense operations


I'm compiling the following code:

#include <stdio.h>
#include <stdint.h>

uint32_t fibonacci(uint32_t pos) {
    long next = 1, current = 0, tmp;
    for (long n = 1; n <= pos; n++) {
        tmp = current;
        current = next;
        next += tmp;
    }
    return current;
}

int main() {
    uint32_t target_pos, result;
    
    scanf("%u", &target_pos);
    result = fibonacci(target_pos);
    printf("%u\n", result);
    return 0;
}

Which results in the following assembly code:

0000000000001189 <fibonacci>:
    1189: endbr64
    118d: push   %rbp
    118e: mov    %rsp,%rbp
    1191: mov    %edi,-0x24(%rbp)
    1194: movq   $0x1,-0x20(%rbp)
    119c: movq   $0x0,-0x18(%rbp)
    11a4: movq   $0x1,-0x10(%rbp)
    11ac: jmp    11cb <fibonacci+0x42>
    11ae: mov    -0x18(%rbp),%rax
    11b2: mov    %rax,-0x8(%rbp)
    11b6: mov    -0x20(%rbp),%rax
    11ba: mov    %rax,-0x18(%rbp)
    11be: mov    -0x8(%rbp),%rax
    11c2: add    %rax,-0x20(%rbp)
    11c6: addq   $0x1,-0x10(%rbp)
    11cb: mov    -0x24(%rbp),%eax
    11ce: cmp    %rax,-0x10(%rbp)
    11d2: jle    11ae <fibonacci+0x25>
    11d4: mov    -0x18(%rbp),%rax
    11d8: pop    %rbp
    11d9: retq

Now, I did some research and this is what I have:

  1. The first instruction (endbr64) is security-related [ref], and in my case it will be a NOP
  2. The instructions 0x118d, 0x118e, 0x11d8 and 0x11d9 are related to the return [ref]
  3. %rbp is the base of the stack

With that information, we have this diagram:

asm flow diagram

I've tried to understand it, but some operations are completely nonsense:

  • %eax (that for what I've found it's used for returning data) gets saved (not loaded) unchanged

  • %edi is loaded at the start, and it doesn't change/get queried from that point

  • The asm performs a "variable++" on -0x10(%rbp); so you expect that in -0x10(%rbp) n, that is the only variable that gets incremented by 1, is stored. But in one instruction %rax gets compared "less than or equal" with -0x10(%rbp) (looking at the original C code, I assume that the asm is doing pos <= n, when it should be the other way)

And like that more and more...

Someone can explain me what the hell is happening? I've compiled the C code with an AMD 3950X without optimizations.


Solution

  • Since this apparently turned out to be the problem, I'll go ahead and post it as an answer so this question can get closed.

    %edi is loaded at the start, and it doesn't change/get queried from that point 
    

    I believe you've got that backwards. In at&t syntax, mov %edi,-0x24(%rbp) means move edi to the memory address 0x24 before rbp. Which would make sense, since in the x64 calling convention (for linux), edi contains the first parameter.

    If you prefer reading intel syntax (and what logical person doesn't?), tell your disassembler that that's the output format you want. If you are using objdump to produce your output, that's done using -M intel.

    While I haven't reviewed the rest of your concerns, it's likely this accounts for all of them.

    As for why intel syntax isn't the default, I'll just say that some people like the at&t syntax better (probably the same people who think tabs should be 2 spaces <sniff>). I expect most linux tools use this format by default. At least now you'll know to keep an eye out for it.