"The Stack" is a frequently-used area of memory designed for functions to use as temporary storage. This is normally where you store values while calling another function: you can't store values in the scratch registers, because the function could change them.
The easiest and most common way to use the stack is with the dedicated "push" and "pop" instructions.
For example, this loads 23 into rax, and then 17 into rcx:
push 17
push 23
pop rax
pop rcx
ret
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, and puts it in rax, leaving the stack with one value:
17
The second "pop" picks up that value, puts it in rcx, leaving the stack clean. If the stack was not clean, everything actually works fine except "ret", which jumps to whatever is on the top of the stack. Let me say that again:
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!
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
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! Without the push and pop, main will be annoyed that you messed with its stuff, which in a real program often means a strange and difficult to debug crash.
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
One big advantage to saved registers: you can call other functions, and know that the registers values won't change (because they'll be saved). 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!
CS 301 Lecture Note, 2014, Dr. Orion Lawlor, UAF Computer Science Department.