Push, Pop, Calling Functions
CS 301 Lecture, Dr. Lawlor
The standard 64-bit "function call interface" is:
- Return value goes in eax (32-bit "int" return value) or rax (64-bit "long" return value).
- The first function parameter goes into rdi (second parameter goes in rsi, then rdx, rcx, r8d, r9d, and then stranger places).
- You can freely overwrite the values in rax, rcx, rdx, rsi, rdi, and r8-r11 (the "scratch" registers).
- You can't change the values in rbx, rbp, rsp, and r12-r15 (the "saved" or "preserved" registers).
This seems somewhat annoying: if you call another function, like
print_long, it might trash everything in rax, rcx, rdx, etc. It
won't mess with the preserved registers rbx,rbp, etc, but you can't mess with them either!
Push and Pop
"The stack" is a frequently-used area
of memory that functions use as temporary storage--usually to store values while
calling another function. The easiest and most common way to use the
stack is with the dedicated "push" and "pop" instructions.
- "push" stores a constant or 64-bit register out onto the stack.
- "pop" retrieves the last value pushed from the stack.
Everything you push, you MUST pop again afterwards, or your code will
crash almost immediately!
For example, this loads 23 into rax, and then 17 into rcx:
push 17
push 23
pop rax
pop rcx
ret
(Try this in NetRun now!)
After the first "push", the stack just has one value:
17
After the second "push", the stack has two values:
17 23
So the first "pop" picks up the 23, leaving the stack with one value:
17
The second "pop" picks up that value, leaving the stack clean. If
the stack is not clean, "ret" stops working. Let me say that
again:
WARNING!
If you do not pop *exactly* the same number of times as you push, your program will crash.
Horribly.
So be careful with your pushes and pops!
Saving Registers with Push and Pop
You can use push and pop to save registers at the start and end of
your function. For example, "rbp" is a preserved register, so you need to save its
value before you can use it:
push rbp ; save old copy of this register
mov rbp,23
mov rax,rbp
pop rbp ; restore main's copy from the stack
ret
(Try this in NetRun now!)
Main might be storing something important in rbp, and will complain if
you just change it, but as long as you put it back exactly how it was
before you return, main is perfectly happy letting you use it!
If you have multiple registers to save and restore, be sure to pop them in the *opposite* order they were pushed:
push rbp ; save old copy of this register
push r15
mov rbp,23
mov rax,rbp
pop r15 ; restore main's copy from the stack
pop rbp
ret
(Try this in NetRun now!)
One big advantage to saved registers: you can call other functions, and
know that they won't change their values. All the scratch
registers, by contrast, are likely to get overwritten by any function you call.
You can save a scratch register by pushing it before calling a function, then popping it afterwards:
mov rax,17; say I want to keep this value while calling a function...
push rax; Just save rax to the stack
mov rdi,3 ; now call the function
extern print_long
call print_long
pop rax; And we can now restore rax afterwards, and safely return 17
ret
(Try this in NetRun now!)
Again, you can save as many registers as you want, but you need to pop
them in the opposite order--otherwise you've flipped their values
around!