x86 Disassembly

Stack

The Stack

A stack is a data structure that stores data values contiguously in memory.

A stack is also known as a LIFO queue (Last In First Out) since values are popped from the stack in the reverse order that they are pushed onto it. Popped data disappears from the stack.

All x86 architectures use a stack as a temporary storage area in RAM that allows the processor to quickly store and retrieve data in memory.

When a value is popped off the stack, the value remains sitting in memory until overwritten. However, you should never rely on the content of memory addresses below ESP, because other functions may overwrite these values without your knowledge.

Push and Pop

The following lines of ASM code are basically equivalent:

Push
push eax

is equivalent to:

sub esp, 4
mov DWORD PTR SS:[esp], eax
Pop
pop eax

is equivalent to:

mov eax, DWORD PTR SS:[esp]
add esp, 4

In the above assembly code:

The single command actually performs much faster than the alternative. It can be visualized that the stack grows from right to left, and ESP decreases as the stack grows in size. The following figure shows a push operation.

Figure 01 Push to a stack

ESP In Action

The following code discards 3 items that were pushed earlier onto the stack, without saving the values:

pop eax
pop eax
pop eax

A faster method is to simply perform some basic arithmetic on ESP to make the pointer go "above" the data items, so they cannot be read anymore, and can be overwritten with the next round of push commands:

add esp, 12  ; 3 DWORDs (4 bytes * 3)

If we want to reserve room on the stack for an item bigger than a DWORD, we can use a subtraction to artificially move ESP forward. We can then access our reserved memory directly as a memory pointer, or we can access it indirectly as an offset value from ESP itself.

For example, to create an array of 100 byte values on the stack and store the pointer to the base of this array in EDI:

sub esp, 100  ; num of bytes in our array
mov edi, esp  ; copy address of 100 bytes area to edi

To destroy that array, use the following instruction:

add esp, 100

Reading Without Popping

To read values on the stack without popping them off the stack, ESP can be used with an offset. For instance, to read the 3 DWORD values from the top of the stack into EAX, we can use the instructions:

mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]

Since ESP moves downward as the stack grows, data on the stack can be accessed with a positive offset. A negative offset should never be used because data "above" the stack cannot be counted on to stay the way you left it. The operation of reading from the stack without popping is often referred to as "peeking".

Data Allocation

There are two areas in the computer memory where a program can store data:

  1. The stack is a linear LIFO buffer that allows fast allocations and deallocations, but has a limited size.
  2. The heap is typically a non-linear data storage area, typically implemented using linked lists, binary trees, or other more exotic methods.
    • Heaps are slightly more difficult to interface with and to maintain than a stack, and allocations and deallocations are performed more slowly. However, heaps can grow as the data grows, and new heaps can be allocated when data quantities become too large.

Explicitly declared variables are allocated on the stack.

Functions and Stack Frames

Functions are frequently set up with a stack frame to allow access to both function parameters and automatic function variables in the execution environment. The idea behind a stack frame is that each subroutine can act independently of its location on the stack, and each subroutine can act as if it is the top of the stack.


References