0

I am trying to make a for loop in assembly.

Here is what I am trying to achieve written in C:

#include <stdio.h>
int main(){
    for(int i = 0; i < 10; i++){
        printf("%d\n", i);
    }
    return 0;
}

This is what my assembly code looks like:

.text
.globl _main


loop0.0:
  movl -4(%rbp), %eax           #eax = i
  cmpl $10, %eax                #compare i with 10
  jg loop0.2                    #if 10 > i goto loop0.2

loop0.1:
  leaq _format(%rip), %rdi      #set arg1 to format
  movl -4(%rbp), %esi           #set arg2 to i
  call _printf                  #printf(format, i)

  movl -4(%rbp), %esi           #set esi to i
  addl $1, %esi                 #esi++
  movl %esi, -4(%rbp)           #i = esi
  jmp loop0.0

_main:                           #int main
  pushq %rbp
  movq %rsp, %rbp

  movl $0, -4(%rbp)              #i = 0
  jmp loop0.0                    #goto  loop0.0
  loop0.2:

  xor %eax, %eax                 #return 0;

  popq %rbp
  retq


.data
  _format:
    .asciz "%d\n"

When I run this code, I get the current output:

0
2
2
2
2
2
2

and so on



Why is it that my code displays 0 first (as it should), and then two for an infinite amount of time? I hope my comments are accurate as this is what I think each line of code does.

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
iProgram
  • 6,057
  • 9
  • 39
  • 80

2 Answers2

3

Main doesn’t allocate space on the stack for i, so the call to printf is overwriting i.

Add the instruction

sub $16, %rsp

right after

mov %rsp, %rbp

and add

add $16, %rsp

right before

pop %rbp

The reason to subtract 16 instead of 4 is to maintain stack alignment.

prl
  • 11,716
  • 2
  • 13
  • 31
  • That works. As I don't yet fully understand how the memory and stack work, is it possible for a stack diagram to go with the answer? I don't know how to write them yet. Or is this another question? If so, it it ok to ask it on StackOverflow? – iProgram Sep 30 '18 at 15:10
  • If you search for stack alignment within this forum you will find many questions and answers on the topic. Hopefully one or more of those will enlighten you. I didn’t find one with a good 64-bit diagram, but there were one or two 32-bit diagrams that weren’t bad. – prl Sep 30 '18 at 15:32
  • Be sure to pay attention to which answers apply to 64 bit and which apply to 32 bit. The main ideas apply to both, but they are not identical, and I wouldn’t want you to get confused. – prl Sep 30 '18 at 15:35
  • Is this why previously you said `sub $8` instead of `sub $16 in your initial answer`? Would it be `sub $8` for 32bit and `sub $16` for 34? If this is the case, if I start writing my own OS which loads up in real mode (16 bit), would I `sub $4`? – iProgram Sep 30 '18 at 15:38
  • 1
    The sub $8 was just an error. (I hoped I had fixed it before anyone saw it.) In 32 bit you would use sub $8 in systems that expect 16 byte alignment and sub $4 in systems that do not. The 8 is because eip and ebp are each 4 bytes. – prl Sep 30 '18 at 15:46
2

Instead of keeping i in the red-zone where it's clobbered by a call (see @prl's answer), keep i in a register.

It looks like you're following the (terrible) style of anti-optimized compiler output that stores everything to memory for debugging.

Save/restore rbx around your function and use ebx for i. (To maintain 16-byte stack alignment before the call _printf, don't make a stack frame because you don't need any stack space for locals.)

BTW, it's not wrong, but it's very unconventional to have some of the function body (your loop) before the function entry point. Your code is super overcomplicated in general. This is something like what you'd get from an optimizing compiler for your C function. (I didn't check on https://godbolt.org/, but I'd recommend having a look.)

_main:                   #int main
    push  %rbx           # save RBX and realign the stack by 16

    xor   %ebx, %ebx
.loop:                    # do {
    mov   %ebx, %esi
    lea   _format(%rip), %rdi
    xor   %eax,%eax          # %al = 0 FP args in XMM registers
    call  _printf            # printf(format, i)

    inc   %ebx               # i++

    cmp   $10, %ebx
    jl  .loop             # }while(i<10)

    xor   %eax, %eax                 #return 0;
    pop   %rbx
    ret

 # Read-only data can go in .rodata
 # only mutable static data needs to go in .data
.section .rodata
  _format:    .asciz "%d\n"
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks for the information. The only problem is it is a do while loop and not a for. This means if I is initialised to say 11, the code you have given will display 11, when I only want it to display the number if it is less then 10. Also why do we need a 16 byte alignment? I am also storing data in memory as I am learning to use local variables. I am making my own language which I wish to use with my own OS one day. I am aware this will take a very long time. – iProgram Sep 30 '18 at 15:27
  • 1
    @iProgram: "for" loops don't exist in assembly, just conditional branches. The initial `i` value and the loop bound are both compile-time constants, so we know the loop body runs at least once and it's a waste of time to check an always-true condition before entering the loop. If it's possible for See [Why are loops always compiled into "do...while" style (tail jump)?](https://stackoverflow.com/q/47783926) for more about how compilers would handle the case of runtime-variable loop trip counts, including if the loop body might need to run zero times. (Usually with a branch to skip a do{}while – Peter Cordes Sep 30 '18 at 15:34
  • 1
    @iProgram: 16 byte alignment because the ABI says so. [Why does System V / AMD64 ABI mandate a 16 byte stack alignment?](https://stackoverflow.com/q/49391001). Printf is allowed to assume that RSP was aligned by 16 before the `call`, including using `movaps` or other fault-on-unaligned instructions. Glibc scanf on modern Linux it will in fact fault if called with a misaligned stack even with AL=0, and so will any variadic function with AL != 0. – Peter Cordes Sep 30 '18 at 15:40