Push and Pop in Arm
I Don't Really Understand How Stack Commands or How Stacks in General Work. Say If I Had Push R3 Pop R3 Line 1: Does This Mean That the Content of R3 Would Be...
I don't really understand how stack commands or how stacks in general work.
Say if I had
PUSH R3
POP R3
Line 1 : Does this mean that the content of R3 would be put onto the top of the stack? Would the contents of the stack then change if R3 changed?
Line 2: On the second line, would the contents at the top of the stack be moved from the stack into R3 OR is the contents of R3 that was pushed onto the stack popped off the stack?
Also what does pop/push do when a register is surrounded in brackets like so
POP {LR}
2 Answers
The mentioned stack operations are only shortcuts for memory operations using sp in the address part. E.g.
PUSH {r3}
POP {r3}
are aliases for
str r3, [sp, #-4]!
ldr r3, [sp], #4
First operation says "store content of r3 into [sp - #4] and decrement sp by 4". Last one "load r3 from [sp] and increment sp by 4".
Instead of {r3} you can use any other register or register sets (e.g. {r1,r2,r3,lr}. Register sets are specified in a bitmask in the machine code so you can not influence the order in which the registers are stored/loaded.
Minimal armv7 example
The best way to learn is to write minimal examples, run them on emulators, and observe what is going on all registers with GDB:
/* Save sp before push. */
mov r0, sp
/* Push. */
mov r1, #1
mov r2, #2
push {r1, r2}
/* Save sp after push. */
mov r1, sp
/* Restore. */
mov r3, #0
mov r4, #0
pop {r3, r4}
cmp r3, #1
bne fail
cmp r4, #2
bne fail
/* Check that stack pointer moved down by 8 bytes
* (2 registers x 4 bytes each). */
sub r0, r1
cmp r0, #8
bne fail
Boilerplate to run the example on Ubuntu 18.04:
The brackets are called "register lists" in the arm assembly notation. GNU GAS 2.26.1 does not accept push and pop instructions without the braces, even for single register pushes {} as in push r1.
Also note that:
- according to the armv7 calling convention, the stack must be aligned by multiples of 8, so pushing 2, 4, ... registers at a time is way more common than just 1: ARM: Why do I need to push/pop two registers at function calls?
- armv8 dropped
pushandpop, compilers often usestpandldpinstead. Example at: