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}
POP loads registers from the stack, with the lowest numbered register using the lowest memory address and the highest numbered register using the highest memory address.
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. The 64-bit registers are the ones like "rax" or "r8", not the 32-bit registers like "eax" or "r8d". "pop" retrieves the last value pushed from the stack.
Push registers onto a full descending stack.
The BX instruction causes a branch to the address contained in Rm and exchanges the instruction set, if required: BX Rm derives the target instruction set from bit[0] of Rm : If bit[0] of Rm is 0, the processor changes to, or remains in, ARM state.
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: https://github.com/cirosantilli/arm-assembly-cheat/blob/f8d78775bd052e9ead579a408c0a2a1651adb9f0/v7/push.S
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:
push
and pop
, compilers often use stp
and ldp
instead. Example at: https://github.com/cirosantilli/arm-assembly-cheat/blob/f8d78775bd052e9ead579a408c0a2a1651adb9f0/v8/common_arch.h#L20
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With