Build: Y86 Stack and Call, pushl / popl and ret instructions

If I did not copy this incorrectly, the above code was written on the board in the classroom by the student using / with teacher corrections:

int array[100], sum, i; void ini() { for(i = 0; i < 100; i++) array[i] = i; } int main() { ini(); sum = 0; for(i = 0; i < 100; i++) sum += array[i]; } .pos 0 irmovl Stack, %esp rrmovl Stack, %ebp jmp main array: .pos 430 sum: .long 0 i: .long 0 main: call ini // irmovl $0, %eax // %eax = 0 irmovl sum, %esi // %esi = 0xsum rmmovl %eax, 0(%esi) // 0(%esi) = %eax <=> 0(0xsum) = 0 [sum = 0] rmmovl %eax, 4(%esi) // 4(%esi) = %eax <=> 4(0xsum) = 0 [i = 0] compare: irmovl $100, %ebx // %ebx = 100 subl %eax, %ebx // %ebx = %ebx - %eax <=> %ebx = 100 - i jle finish // Jumps to "finish" if SF=1 pr ZF=0 mrmovl 0(%esi), %edx // %edx = 0(%esi) <=> %edx = 0(0xsum) = sum addl %eax, %edx // %edx = %edx + %eax <=> %edx = sum + i => sum rmmovl %edx, 0($esi) // 0(%esi) = %edx <=> 0(0xsum) = sum irmovl $1, %ecx // %ecx = 1 addl %ecx, %eax // %eax = %eax + %ecx <=> %eax = i + 1 => i rmmovl %eax, 4(%esi) // 4($esi) = %eax <=> 4(0xsum) = i jmp compare // Jumps unconditionally to "compare" ini: pushl %ebp // rrmovl %esp, %ebp // pushl %ebx // pushl %eax // irmovl $0, %eax // %eax = 0 rmmovl %eax, -8(%ebp) // ini_compare: irmovl $100, %ecx // %ecx = 100 subl %eax, %ecx // %ecx = %ecx - %eax <=> %ecx = 100 - i jle ini_finish // Jumps to "ini_finish" if SF=1 pr ZF=0 rrmovl %eax, %ebx // %ebx = %eax <=> %ebx = i addl %eax, $ebx // %ebx = %ebx + %eax <=> %ebx = i + i = 2i addl %ebx, %ebx // %ebx = %ebx + %ebx <=> %ecx = 2i + 2i = 4i rmmovl %eax, array(%ebx) // array(%ebx) = %eax <=> array(0x4i) = i irmovl %1, %ecx // %ecx = 1 addl %ecx, %eax // %eax = %eax + %ecx <=> %eax = i + 1 => i rmmovl %eax, -8(%ebp) // jmp ini_compare // Jumps unconditionally to "ini_compare" ini_finish: irmovl $4, %ebx // addl %ebx, %esp // popl %ebx // popl %ebp // ret // .pos 600 Stack .long 0 

As you can see, all instructions have a bunch of comments, and I got (I think) most of them, which confuses me with the call, pushl / popl and ret instructions. I do not quite understand them, and I also do not understand what happens to the stack and where all the entries indicate. In principle, lines with comments (//) that are not written on them.

This is really important, I understand how it all works, I hope some of you can shed light on this whole mess.

Some notes on my comments:

  • 0xsum: This does not mean that the address is a "sum", it would not be possible. This is just a means to understand what I'm talking about without using an exact memory address.
  • [sum = 0]: This means that in our C code, the variable sum will be set to 0 at this point.
  • i + 1 => i: This means that we increment the value of "i" by one, and in the next line, "i" will actually represent this incremental value.
+4
source share
1 answer

Let's look at some of the code:

 main: call ini 

This will push the value of the instruction pointer to the stack (so that you can later return to this position in the code) and go to the address label ini. The ret command uses the value stored on the stack to return from the subroutine.

The following is the initialization routine for the subroutine. It stores the values โ€‹โ€‹of some registers on the stack and sets the stack frame by copying the stack pointer (esp) to the base pointer register (ebp). If the routine has local variables, the stack pointer is reduced to make room for variables on the stack, and the base pointer is used to access local variables in the stack frame. In this example, the only local variable is the (unused) return value.

The push command decreases the stack pointer (esp) with the size of the data that will be pressed, and then saves the value to that address. The pop command does the opposite, first gets a value, then increments the stack pointer. (Note that the stack grows down, so the address of the stack pointer gets lower when the stack grows.)

 ini: pushl %ebp // save ebp on the stack rrmovl %esp, %ebp // ebp = esp (create stack frame) pushl %ebx // save ebx on the stack pushl %eax // push eax on the stack (only to decrement stack pointer) irmovl $0, %eax // eax = 0 rmmovl %eax, -8(%ebp) // store eax at ebp-8 (clear return value) 

The code follows the standard template, so it looks a bit uncomfortable when there are no local variables, and there is an unused return value. If there are local variables, subtraction will be used to reduce the stack pointer instead of pressing eax.

The following is the output sequence of the subroutine. It restores the stack to a position before creating the stack frame, and then returns to the code that calls the subroutine.

 ini_finish: irmovl $4, %ebx // ebx = 4 addl %ebx, %esp // esp += ebx (remove stack frame) popl %ebx // restore ebx from stack popl %ebp // restore ebp from stack ret // get return address from stack and jump there 

In response to your comments:

The ebx register is popped and unloaded to store its value. The compiler, apparently, always puts this code there, probably because the register is used very often, just not in this code. Similarly, a stack frame is always created by copying esp to ebp, even if it is not really needed.

An instruction that pushes eax exists only to reduce the stack pointer. This was done for small decrements, as it was shorter and faster than subtracting the stack pointer. The space that it reserves for the return value again, apparently, always does this, even if the return value is not used.

In your diagram, the esp register sequentially points to four bytes that are too high in memory. Remember that the stack pointer decreases after a value is pressed, so it will indicate the value pressed, not the next value. (The memory addresses are also deleted, something like 0x600, not 0x20, as indicated in the stack label declaration.)

+13
source

Source: https://habr.com/ru/post/1286520/


All Articles