What is the function of the push / pop instructions used on registers in x86 assembly?

AssemblyX86TerminologyStack MemoryStack Pointer

Assembly Problem Overview


When reading about assembler I often come across people writing that they push a certain register of the processor and pop it again later to restore it's previous state.

  • How can you push a register? Where is it pushed on? Why is this needed?
  • Does this boil down to a single processor instruction or is it more complex?

Assembly Solutions


Solution 1 - Assembly

pushing a value (not necessarily stored in a register) means writing it to the stack.

popping means restoring whatever is on top of the stack into a register. Those are basic instructions:

push 0xdeadbeef      ; push a value to the stack
pop eax              ; eax is now 0xdeadbeef

; swap contents of registers
push eax
mov eax, ebx
pop ebx

Solution 2 - Assembly

Here is how you push a register. I assume we are talking about x86.

push ebx
push eax

It is pushed on stack. The value of ESP register is decremented to size of pushed value as stack grows downwards in x86 systems.

It is needed to preserve the values. The general usage is

push eax           ;   preserve the value of eax
call some_method   ;   some method is called which will put return value in eax
mov  edx, eax      ;    move the return value to edx
pop  eax           ;    restore original eax

A push is a single instruction in x86, which does two things internally.

  1. Decrement the ESP register by the size of pushed value.
  2. Store the pushed value at current address of ESP register.

Solution 3 - Assembly

Where is it pushed on?

esp - 4. More precisely:

  • esp gets subtracted by 4
  • the value is pushed to esp

pop reverses this.

The System V ABI tells Linux to make rsp point to a sensible stack location when the program starts running: https://stackoverflow.com/questions/9147455/what-is-default-register-state-when-program-launches-asm-linux/32967009#32967009 which is what you should usually use.

How can you push a register?

Minimal GNU GAS example:

.data
    /* .long takes 4 bytes each. */
    val1:
        /* Store bytes 0x 01 00 00 00 here. */
        .long 1
    val2:
        /* 0x 02 00 00 00 */
        .long 2
.text
    /* Make esp point to the address of val2.
     * Unusual, but totally possible. */
    mov $val2, %esp

    /* eax = 3 */
    mov $3, %ea 

    push %eax
    /*
    Outcome:
    - esp == val1
    - val1 == 3
    esp was changed to point to val1,
    and then val1 was modified.
    */

    pop %ebx
    /*
    Outcome:
    - esp == &val2
    - ebx == 3
    Inverses push: ebx gets the value of val1 (first)
    and then esp is increased back to point to val2.
    */

The above on GitHub with runnable assertions.

Why is this needed?

It is true that those instructions could be easily implemented via mov, add and sub.

They reason they exist, is that those combinations of instructions are so frequent, that Intel decided to provide them for us.

The reason why those combinations are so frequent, is that they make it easy to save and restore the values of registers to memory temporarily so they don't get overwritten.

To understand the problem, try compiling some C code by hand.

A major difficulty, is to decide where each variable will be stored.

Ideally, all variables would fit into registers, which is the fastest memory to access (currently about 100x faster than RAM).

But of course, we can easily have more variables than registers, specially for the arguments of nested functions, so the only solution is to write to memory.

We could write to any memory address, but since the local variables and arguments of function calls and returns fit into a nice stack pattern, which prevents memory fragmentation, that is the best way to deal with it. Compare that with the insanity of writing a heap allocator.

Then we let compilers optimize the register allocation for us, since that is NP complete, and one of the hardest parts of writing a compiler. This problem is called register allocation, and it is isomorphic to graph coloring.

When the compiler's allocator is forced to store things in memory instead of just registers, that is known as a spill.

Does this boil down to a single processor instruction or is it more complex?

All we know for sure is that Intel documents a push and a pop instruction, so they are one instruction in that sense.

Internally, it could be expanded to multiple microcodes, one to modify esp and one to do the memory IO, and take multiple cycles.

But it is also possible that a single push is faster than an equivalent combination of other instructions, since it is more specific.

This is mostly un(der)documented:

Solution 4 - Assembly

Pushing and popping registers are behind the scenes equivalent to this:

push reg   <= same as =>      sub  $8,%rsp        # subtract 8 from rsp
                              mov  reg,(%rsp)     # store, using rsp as the address

pop  reg    <= same as=>      mov  (%rsp),reg     # load, using rsp as the address
                              add  $8,%rsp        # add 8 to the rsp

Note this is x86-64 At&t syntax.

Used as a pair, this lets you save a register on the stack and restore it later. There are other uses, too.

Solution 5 - Assembly

Almost all CPUs use stack. The program stack is LIFO technique with hardware supported manage.

Stack is amount of program (RAM) memory normally allocated at the top of CPU memory heap and grow (at PUSH instruction the stack pointer is decreased) in opposite direction. A standard term for inserting into stack is PUSH and for remove from stack is POP.

Stack is managed via stack intended CPU register, also called stack pointer, so when CPU perform POP or PUSH the stack pointer will load/store a register or constant into stack memory and the stack pointer will be automatic decreased xor increased according number of words pushed or poped into (from) stack.

Via assembler instructions we can store to stack:

  1. CPU registers and also constants.
  2. Return addresses for functions or procedures
  3. Functions/procedures in/out variables
  4. Functions/procedures local variables.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionArs embleView Question on Stackoverflow
Solution 1 - AssemblyLinus KleenView Answer on Stackoverflow
Solution 2 - AssemblyMadhur AhujaView Answer on Stackoverflow
Solution 3 - AssemblyCiro Santilli Путлер Капут 六四事View Answer on Stackoverflow
Solution 4 - AssemblygowrathView Answer on Stackoverflow
Solution 5 - AssemblyGJ.View Answer on Stackoverflow