PUSH and POP in x86-64 Assembly

PUSH and POP are two of the most fundamental instructions in assembly, allowing the program to add or remove items from the stack.

The stack starts at a relatively high memory address and grows down toward lower addresses. PUSH refers to ‘pushing’ an item onto the stack, resulting in the RSP (stack pointer register) being decremented. POP does the opposite; it removes the ‘top’ item from the stack, incrementing RSP.

This tutorial aims to shed light on these commands, demystifying their role in stack management and their interaction with the stack pointer RSP.

Review of the Stack and Stack Pointers

In the realm of x86-64 assembly, the stack is a specific memory region that provides LIFO (last in, first out) functionality for fast retrieval of data. It functions similarly to a stack of dishes; it’s easy to get the top dish, but harder to get dishes from the middle. As with a stack of dishes, the last piece of data to be “pushed” on top is the first one to be “popped” off.

The stack pointer, referred to as RSP, keeps track of the location of the top item in the stack at all times. With each push and pop operation, this dynamic register adapts, adjusting its value to point to the new ‘top’ of the stack.

For more information, check out our tutorial on the stack, RSP, and related topics.

The PUSH Instruction

With each execution of the PUSH instruction, two tasks are simultaneously performed.

Firstly, the stack pointer, or RSP, reduces in value by the size of the data being pushed onto the stack. This operation corresponds with the stack’s downward growth direction that we previously discussed. Following this, the data is transitioned to the memory location where the RSP is currently pointing.

By keeping the recently pushed data on top of the stack, it ensures quick access to the most recent information, honoring the “last in, first out” principle.

The POP Instruction

The POP instruction retrieves data from the stack and relocates it to a specific register or memory location.

The POP instruction decreases the size of the stack, reducing its size by incrementing RSP. As with PUSH, its important to keep in mind that two things are happening: (1) RSP gets incremented, and (2) the data at the ‘top’ of the stack is retrieved and relocated to a specified location.

Let’s see an example of PUSH and POP!

Example of PUSH and POP

Here’s a simple example of pushing and popping a value in x86-64 assembly:

section .text
    global _start   ; Entry point for the program

_start:
    mov rax, 42     ; Moves the value 42 into the rax register
    push rax        ; Pushes the value of rax onto the stack
    pop rbx         ; Pops the top value from the stack into rbx

    ; Exit the program
    mov eax, 60     ; syscall number for exit
    xor edi, edi    ; Exit code 0
    syscall         ; Invoke syscall to exit the program

In this example, we first moved the value 42 into the rax register. Then we pushed the value of rax onto the stack. We then popped the top value from the stack into the rbx register.

Finally, we use the syscall instruction to exit the program.

A Slightly More Practical Example of PUSH and POP in Assembly

One practical example where the push and pop instructions are useful is in function prologues and epilogues.

In x86-64 assembly, when a function is called, it needs to set up a stack frame to store its local variables and other information.

The push instruction can be used to save the base pointer (rbp) and the mov instruction used to set the base pointer to the current stack pointer (rsp). Here’s a simple example:

section .text
    global _start

_start:
    call my_function   ; Call my_function
    mov eax, 60        ; syscall number for exit
    xor edi, edi       ; Exit code 0
    syscall            ; Invoke syscall to exit the program

my_function:
    push rbp           ; Save the base pointer
    mov rbp, rsp       ; Set the base pointer to the current stack pointer

    ; Function body
    mov eax, 42        ; Move the value 42 into the eax register

    pop rbp            ; Restore the base pointer
    ret                ; Return from the function

In this example, my_function is called from _start. The function prologue saves the base pointer (rbp) onto the stack and sets the base pointer to the current stack pointer (rsp). This sets up a stack frame for the function. The function body can then use the stack frame to access its local variables. Finally, the function epilogue restores the base pointer and returns from the function using the ret instruction.

Push and pop instructions are essential for managing the stack and creating stack frames in assembly language, making them crucial for function calls and local variable storage.

Why PUSH and POP are so useful in Assembly programming

The value of PUSH and POP in Assembly programming becomes evident when you dive into the realm of function calls, local variables, and recursive algorithms.

These two instructions lay the groundwork for saving and restoring context during function calls, where PUSH is used to store local variables and function parameters onto the stack, and POP retrieves them.

In recursive algorithms, PUSH and POP maintain different instances of variables for each recursive call, aiding in preserving the state of the system.

Without these instructions, managing data in such scenarios would become a strenuous task. In short, the PUSH and POP x86-64 assembly instructions help shape the functionality and efficiency of our code.