System: The Stack

The Stack: Push and Pop

CS 301: Assembly Language Programming Lecture, Dr. Lawlor

"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.

  • "push" stores a constant or the value in a 64-bit register out onto the stack.
    • push 3
    • push rax
  • "pop" removes the last value pushed onto the stack, and stores it in a register.
    • pop rax
    • pop rdi

For example, this loads 3 into rax and returns.  It's a kinda roundabout way to return a 3, but between the push and the pop you could use rax for something else until you need it.

push 3
pop rax
ret

(Try this in NetRun now!)

You can push and pop from different registers, but everything you push, you MUST pop again at some point afterwards, or your code will crash when you return, because call and return internally push and pop to remember where return should go back to.

For a more complicated 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:
    23
    17
So the first "pop" takes the value 23 off the stack, and puts it in rax, leaving the stack with one value:
    17
The second "pop" takes 17 off the stack, and 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:

WARNING!   DANGER!

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, "rbx" is a preserved register, so you need to save its value before you can use it:

push rbx ; save old copy of this register

mov rbx,23
mov rax,rbx

pop rbx ; restore main's copy from the stack
ret

(Try this in NetRun now!)

Main might be storing something important in rbx, 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 rbx ; save old copy of this register
push r15

mov rbx,23
mov rax,rbx

pop r15 ; restore main's copy from the stack
pop rbx
ret

(Try this in NetRun now!)

You can also save a scratch register, to keep some other function from messing with it.  You do this by pushing your value before calling a function, then popping it afterwards to bring your copy back:

mov rax,17; say I want to keep this value while calling a function...
push rax; save rax to the stack

mov rdi,3 ; now call the function
extern print_long
call print_long

pop rax; 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!

One big advantage to preserved / 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, can get overwritten by any function you call. 

  Scratch Register (rax, rcx, rdx, rdi, rsi, r8-r11) Preserved / Saved Register (rbx, rbp, r12-r15, etc)
Contract Any function can overwrite it. Must be saved before use (push), and restored after use (pop).
Advantage Can use immediately, no setup needed. Won't get trashed when you call a function.
push Before calling another function At the start of your function
pop After calling another function At the end of your function, just before return

When I'm writing a long function that calls a bunch of stuff, I tend to work mostly in preserved registers, which I push and pop at the start and end of my function to keep main from getting annoyed. 

For a short function where I only call a few other functions, I tend to work in scratch registers, and save the few things I need before calling other functions. 

 

LINK: https://www.cs.uaf.edu/2017/fall/cs301/lecture/09_08_stack.html

原文地址:https://www.cnblogs.com/JasperZhao/p/14127807.html