Chapter 13 - Stack


13
.1  Hardware Stack

The x86 CPU supports instructions to manipulate a stack of 32-bit values in memory.  You may push and pop from the stack to save and restore values of registers.  This allows you to save/restore your registers when you make function calls.  The stack pointer (ESP) stores the memory address of the top of the stack which is the last 4-byte value that was pushed.  If you remember, the stack grows downwards in memory so the top of the stack will have the smallest memory address.  The program below (stack1.asm) pushes 4 numbers and stores the address in the ESP register after each of the pushes.  You can see how the memory address stored in ESP decreases by 4 bytes after each number is pushed.

     push eax   ;push eax to top of stack: decrement esp by 4 then copy the value in eax to the memory address in esp
     pop eax    ;pop top of stack to eax: copy value (4 bytes) in memory address at esp then increment esp by 4

stack1.asm

format PE console
include 'win32ax.inc'

;=======================================
section '.text' code readable executable
;=======================================
start:
     push 95
     mov [A], esp
     push 73
     mov [B], esp
     push 23
     mov [C], esp
     push 42
     mov [D], esp

     cinvoke printf,"esp after 1st push = %d %c", [A], 10
     cinvoke printf,"esp after 2nd push = %d %c", [B], 10
     cinvoke printf,"esp after 3rd push = %d %c", [C], 10
     cinvoke printf,"esp after 4th push = %d %c", [D], 10
     invoke Sleep,-1

;======================================
section '.data' data readable writeable
;======================================
A    dd 0
B    dd 0
C    dd 0
D    dd 0

;====================================
section '.idata' import data readable
;====================================
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
import msvcrt,printf,'printf'
import kernel32,Sleep,'Sleep'

Output

esp after 1st push = 917376
esp after 2nd push = 917372
esp after 3rd push = 917368
esp after 4th push = 917364



13.2  Calling Convention and the Stack

A calling convention is a standardized method for functions/subroutines to be implemented in programming languages.  This allows a program in one language to call functions in another language.  It involves how to pass variables, how to return a value, and how to save registers to the stack.  Below is a brief explanation of the C Calling Convention and how the stack is used.


Caller Rules

1.  Caller saves (push) registers EAX, ECX, and ECX (if caller needs to save)
2.  Push parameters to stack in reverse order
3.  Use the call instruction which places the return address on top of the stack and branches to the function

Callee Rules
1.  Push EBP onto stack and copy ESP into EBP
2.  Allocate local variables onto the stack
3.  Save (push) registers EBX, EDI, and ESI (if callee needs to use)
Return Rules
1.  Leave the return value in EAX
2.  Restore values of EDI and ESI
3.  Deallocate local variables by popping them from stack (or move EBP to ESP)
4.  Restore caller's EBP
5.  Use the ret instruction

 

Since we are not using the calling convention when calling the printf function, you may have noticed we lose the values in registers EAX, ECX, and EDX.


13.3  Call and Return

The call and return allows your program to call and return from a subroutine.  It is used by the calling convention discussed in the previous section.

     call Label   ;push the return address onto the stack and jump to Label
     ret          ;pop the return address from the stack and jump
 

call.asm

format PE console
include 'win32ax.inc'

;=======================================
section '.text' code readable executable
;=======================================
start:
     call subroutine1
     call subroutine2
     invoke Sleep,-1

subroutine1:
     cinvoke printf,"Inside subroutine1 %c", 10
     ret

subroutine2:
     cinvoke printf,"Inside subroutine2 %c", 10
     ret

;======================================
;section '.data' data readable writeable
;======================================
A    dd 0
B    dd 0
C    dd 0
D    dd 0

;====================================
section '.idata' import data readable
;====================================
library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll'
import msvcrt,printf,'printf'
import kernel32,Sleep,'Sleep'

Output

Inside subroutine1
Inside subroutine2