1

I compiled a program in C which just sums all integers up to input. For example if input is 4 the output is 4+3+2+1=10.

I am having a bit of a trouble understanding the assembly x86 version of this program.

I wrote all the comments myself, please be so kind to indicate what I got right/wrong and how you would describe what each lines does. Through your comments I will be able to absorb a DEEPER understand of what the cpu exactly does, as for the moment I can't say that I fully understand what is going on here. Anyway, here it is. All comments are welcome.

.LC0:
        .byte 0x25,0x64,0x0 ; 2 digits / integers that our program will output
main:
        pushl %ebp ; we save %ebp for later usage
        movl %esp,%ebp ; we set register %ebp to point to the stack frame
        subl $12,%esp ; subtracts 18 bytes from the stack pointer (esp). This allocates 18 bytes of space on the stack to be used for variables.
        movl $0,-12(%ebp)
        leal -4(%ebp),%eax ; subtracks -4 from the memory address of ebp and stores it at register eax
        pushl %eax ; we store register eax for later usage
        pushl $.LC0
        call __isoc99_scanf ; reads from io port / waiting for key input
        movl $1,-8(%ebp)
        leal 8(%esp),%esp ; adds +8 to stack pointer memory address
.L2:
        movl -4(%ebp),%edx
        cmpl -8(%ebp),%edx ; compares our input number with an incremented number
        jl .L3 ; if incremented number is equal or bigger than input number goto .L3
        movl -8(%ebp),%edx
        addl %edx,-12(%ebp)
        incl -8(%ebp)
        jmp .L2 ; loop / another addition to our input
.L3:
        pushl -12(%ebp)
        pushl $.LC0 ; we push the argument to print function
        call printf ; prints result on screen
        xorl %eax,%eax ; sets %eax to zero
        leave ; leave copies the frame pointer to the stack point and releases the stack space formerly used by a procedure for its local variables. leave pops the old frame pointer into (E)BP, thus restoring the caller's frame.
        ret ; returns to address located on the top of the stack```

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Why do you say “subtracts 18 bytes” when the instruction is `subl $12, %esp`? Was the original code perhaps `subl $0x12, %esp`? – fuz May 19 '22 at 10:10
  • Probably thought `$` meant hex, as is usual on some systems. – Jester May 19 '22 at 11:46
  • No way to tell if OP decides not to respond... – fuz May 19 '22 at 11:49
  • The compiler output would be a bit shorter and easier to understand if you enable some optimization. Then it can keep local vars in registers instead of all that store/reload. See [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116). (Also Matt Godbolt's talk about looking at compiler output, linked in my answer there.) – Peter Cordes May 19 '22 at 15:10
  • 12 in hex is 18. How many bytes is there? $ is not hex ? I got that code from godbolt's online compiler. I have a project and need it in x86 assembly. Don't ask me to use gcc or visual compilers, I won't. I don't even care if this works or not. All I care is understanding each instruction. Please help by reposting with your own comments if possible. At least where I am completely wrong! Thank you – Angelos Mavros May 19 '22 at 16:22
  • @user15458015 Thanks for the response! I can now write an answer to your question. – fuz May 19 '22 at 20:09
  • 1
    *Don't ask me to use gcc or visual compilers* - This already looks like un-optimized GCC output from a C function, except for using `xor %eax,%eax` instead of a less-efficient `mov $0, %eax`. (Normally GCC only enables `-fpeephole2` with `-O2` or higher, but these label names look like GCC's, not clang's.) Or maybe it's disassembly of some other compiler using GCC's label numbering scheme, since GCC would have used `.asciz "%d"` instead of `.byte 0x25,0x64,0x0` for the constant data it passes a pointer to, to scanf. But not clang, I don't see a redundant store of a `0` return value. – Peter Cordes May 19 '22 at 20:40

1 Answers1

2

Your analysis is mostly correct with some small mistakes which I'll try to point out. I've taken the liberty to break your comments so they can be read without scrolling.

Note that if you intend to pass off compiler output as hand written assembly, your teacher is very likely to catch this. Do not attempt to cheat on your assignments like this.

As an initial note: your function seems to have three variables. These are stored at -4(%ebp), -8(%ebp), and -12(%ebp). This is also why the C compiler emitted code to decrease the stack pointer by 12, allocating just enough storage for these variables.

    .byte 0x25,0x64,0x0 ; 2 digits / integers that our program will output

This is the string "%d" that is passed to scanf.

    subl $12,%esp ; subtracts 18 bytes from the stack pointer (esp).
                  ; This allocates 18 bytes of space on the stack to
                  ; be used for variables.

Note that the dollar sign merely indicates an immediate operand. Unlike with other assemblers, it does not indicate a hexadecimal number in AT&T syntax. As you've already seen with the .byte directive, this is instead done with the 0x prefix.

    leal -4(%ebp),%eax ; subtracks -4 from the memory address of ebp
                       ; and stores it at register eax

This explanation is confusing. What happens is that the leal instruction takes a memory operand and stores the effective address of the operand into the register operand. So in this case, it computes the address of -4(%ebp) (which is the content of ebp plus 4) and stores it into eax. So eax = ebp + 4. This is used to obtain the address of a piece of stack memory for use with scanf.

    pushl %eax ; we store register eax for later usage
    pushl $.LC0

This does not save eax for later use. Rather, eax is pushed on the stack as an argument to the following scanf call. Likewise, the following instruction pushes the address of string .LC0 onto the stack, preparing a scanf call like scanf("%d", &x) where x is the variable at -4(%ebp).

    call __isoc99_scanf ; reads from io port / waiting for key input

This is just a call to the scanf function in its C99 standard variant. The glibc has some logic to redirect calls to scanf to different functions depending on which C standard revision you picked, so it picks this weirdly named symbol for -std=c99.

Note that scanf is a libc function. It does not perform any port IO but instead may or may not ask the operating system for additional input. Where this input comes from depends on what is attached to the standard input of your process.

    leal 8(%esp),%esp ; adds +8 to stack pointer memory address

Just like any other general purpose register, the stack pointer does not have a memory address. However, it does have a value, representing an address. So it might be more accurate to say “increases the stack pointer by 8, popping the arguments off the stack.”

.L2:
    movl -4(%ebp),%edx
    cmpl -8(%ebp),%edx ; compares our input number with an incremented number
    jl .L3 ; if incremented number is equal or bigger
           ;than input number goto .L3
    movl -8(%ebp),%edx
    addl %edx,-12(%ebp)
    incl -8(%ebp)
    jmp .L2 ; loop / another addition to our input
.L3:

Try to work out what this loop does. An easy way to do this is to write down each instruction as pseudo code and then refactor until it looks like something reasonable. Though if you got this from C code you wrote, you probably already know what it does.

    pushl -12(%ebp)
    pushl $.LC0 ; we push the argument to print function
    call printf ; prints result on screen

Once again, this does something like printf("%d", z) where z is the variable at -12(%ebp).

    ret ; returns to address located on the top of the stack

It might be more accurate to say “returns from the function.” This is because leave has just cleared the stack frame, so the top of the stack holds the return address. The value returned from your function is what is held in eax as per calling convention. This is also why eax was cleared earlier: to return zero.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • BTW, we can also say this looks like a copy/paste of `gcc -O0` from https://godbolt.org/, with a couple minor modifications. That's it omits a `.section .rodata` directive to put `.LC0:` in its own section. It does work to have it in `.text` along with the code, but compilers don't do that. GCC would have used `.asciz "%d"` or `.string "%d"` instead of numeric `.byte` to emit the same data. This also uses `xor %eax,%eax` instead of a less-efficient `mov $0, %eax`. (Normally GCC only enables `-fpeephole2` with `-O2` or higher, but these label names look like GCC's, not clang's.) – Peter Cordes May 19 '22 at 20:47
  • That's not necessary or even helpful in understand what this asm does, although `.asciz "%d"` might be worth mentioning. – Peter Cordes May 19 '22 at 20:48
  • Thank you so much! I don't know how to thank you! This was great help! I didn't expect that. I learned so much from you. God bless you fuz! – Angelos Mavros May 20 '22 at 04:28