- The runtime stack is a memory array managed directly by the CPU, using the ESP register, known as the stack pointer register.
- The ESP register holds a 32-bit offset into some location on the stack. We rarely manipulate ESP directly; instead, it is indirectly modified by instructions such as CALL, RET, PUSH, and POP.
- ESP always points to the last value to be added to, or pushed on, the top of stack.
To demonstrate, let’s begin with a stack containing one value.
- In Figure 1, the ESP (extended stack pointer) contains hexadecimal 00001000, the offset of the most recently pushed value (00000006).
- In our diagrams, the top of the stack moves downward when the stack pointer decreases in value:
- Each stack location in this figure contains 32 bits, which is the case when a program is running in 32-bit mode. In 16-bit real-address mode, the SP register points to the most recently pushed value and stack entries are typically 16 bits long.
Figure 1
There are several important uses of runtime stacks in programs:
- A stack makes a convenient temporary save area for registers when they are used for more than one purpose. After they are modified, they can be restored to their original values.
- When the CALL instruction executes, the CPU saves the current subroutine’s return address on the stack.
- When calling a subroutine, you pass input values called arguments by pushing them on the stack.
- The stack provides temporary storage for local variables inside subroutines.
PUSH Instructions
The PUSH instruction first decrements ESP and then copies a source operand into the stack.
A 16-bit operand causes ESP to be decremented by 2. A 32-bit operand causes ESP to be decremented by 4.
There are three instruction formats:
PUSH reg/mem16
PUSH reg/mem32
PUSH imm32
POP Instructions
The POP instruction first copies the contents of the stack element pointed to by ESP into a 16- or 32-bit destination operand and then increments ESP. If the operand is 16 bits, ESP is incremented by 2; if the operand is 32 bits, ESP is incremented by 4:
POP reg/mem16
POP reg/mem32
- The PUSHFD instruction pushes the 32-bit EFLAGS register on the stack, and POPFD pops the stack into EFLAGS:
pushfd
popfd
- 16-bit programs use the PUSHF instruction to push the 16-bit FLAGS register on the stack and POPF to pop the stack into FLAGS.
- in 32 bits programming both PUSHF and PUSHFD can be used in order to push the 32-bit EFLAGS on the stack
- The MOV instruction cannot be used to copy the flags to a variable, so PUSHFD may be the best way to save the flags. There are times when it is useful to make a backup copy of the flags so you can restore them to their former values later. Often, we enclose a block of code within PUSHFD and POPFD:
pushfd ; save the flags
;
; any sequence of statements here...
;
popfd ; restore the flags
- When using pushes and pops of this type, be sure the program’s execution path does not skip over the POPFD instruction. When a program is modified over time, it can be tricky to remember where all the pushes and pops are located.
The need for precise documentation is critical!
A less error-prone way to save and restore the flags is to push them on the stack and immediately pop them into a variable:
.data
saveFlags DW 0
.code
pushfd ; push flags on stack
pop saveFlags ; copy into a variable
- The following statements restore the flags from the same variable:
push saveFlags ; push saved flag values
popfd ; copy into the flags
- The PUSHAD instruction pushes all of the 32-bit general-purpose registers on the stack in the following order:
EAX, ECX, EDX, EBX, ESP (value before executing PUSHAD), EBP, ESI, and EDI.
- The POPAD instruction pops the same registers off the stack in reverse order.
- Similarly, in 16 bits programs the PUSHA instruction, introduced with the 80286 processor, pushes the 16-bit generalpurpose registers (AX, CX, DX, BX, SP, BP, SI, DI) on the stack in the order listed.
- The POPA instruction pops the same registers in reverse order.
- in 32 bits programming POPA and POPAD, respectively PUSHA and PUSHAD have the same behavior
If you write a procedure that modifies a number of 32-bit registers, use PUSHAD at the beginning of the procedure and POPAD at the end to save and restore the registers. The following code fragment is an example:
pushad ; save general-purpose registers
.
.
mov eax,...
mov edx,...
mov ecx,...
.
.
popad ; restore general-purpose registers
- An important exception to the foregoing example must be pointed out;
procedures returning results in one or more registers should not use PUSHA and PUSHAD.
- Suppose the following ReadValue procedure returns an integer in EAX; the call to POPAD overwrites the return value from EAX:
ReadValue PROC
pushad ; save general-purpose registers
.
.
mov eax,return_value
.
.
popad ; overwrites EAX!
ret
ReadValue ENDP
- Unlike the ESP register, the base pointer EBP is manipulated only explicitly.
- EBP is used by high-level languages to reference function parameters and local variables on the stack. It should not be used for ordinary arithmetic or data transfer except at an advanced level of programming. It is often called the extended frame pointer register.
- EBP role will be clarified in the last courses of the semester when the integration of Assembly Language with high level programming languages (namely C in our case) will be studied. But, a few words on the role of the EBP and ESP stack registers in the functioning of the run time stack can be said at this moment also, considering only an ASM code to implement a function, concept recognized only by a high-level programming language.
- A new procedure/function that is called will become the currently executing subroutine, so in the run time stack a new stackframe must be built for this subroutine. This new stackframe will be delimited by the EBP (at the basis) and the ESP (at the top) stack pointer registers, so the values from EBP and ESP must be updated for adapting to the new subroutine context.
- But, for being able after the call to return to the caller, the caller’s stackframe must be restored, so the current (”old”) EBP value for the caller has to be saved. This is done as the first thing by the new called subroutine, which saves IN THE STACK using a PUSH EBP the base address of the caller’s stackframe. After that, the EBP value can be updated to point to the beginning of the new stackframe (mov ebp,esp), which will be exactly the location shown by ESP, so the new stackframe will start with the value of the „old” EBP. So, in this point EBP and ESP have both the same value (indicating that the new stackframe is empty and ready to receive the required data to „grow” and start the execution of the current subroutine.
- This mechanism is briefly illustrated below in the case of a procedure called AddTwo which will add the values of the two passed parameters and returns their sum in EAX:
AddTwo:
push ebp; saving the caller’s stackframe base for further being able to restore it
mov ebp,esp ; initialising the base of the new stack frame for the currently executing
; procedure AddTwo (see the picture below which illustrates exactly
; this described situation)
mov eax,[ebp + 12] ; transferring into EAX the value of the second parameter passed
; on to the stack by the caller BEFORE the new stackframe takes
; the run time control
add eax,[ebp + 8] ; adding to EAX the first parameter
pop ebp ; restoring the caller stackframe as being the new currently executing one
ret ; going back immediately to the point of call for continuing the execution
; of the program
Stack Frame after Pushing EBP and ESP value was copied to EBP:
After the next two instructions (mov and add) execute, the following figure shows the contents of the stack frame: a function call such as AddTwo(5, 6) would cause the second parameter to be pushed on the stack, followed by the first parameter:
AddTwo could push additional registers on the stack without altering the offsets of the stack parameters from EBP. ESP would change value, but EBP would not.