14

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}
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Nick Bishop
  • 907
  • 2
  • 7
  • 6
  • 4
    What it does with the brackets around the register list is assemble correctly; they're part of the proper syntax. I guess you _might_ find an asssembler lenient enough to accept `push r3` without them, but it's certainly nonstandard. – Notlikethat Nov 23 '14 at 22:39

2 Answers2

19

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.

ensc
  • 6,704
  • 14
  • 22
  • Is the block size of a point in the stack always 4 bytes or is this just the case for systems that use 32 bit words? – Nick Bishop Nov 24 '14 at 10:45
  • you can store single bytes on the stack; `sp` is just an alias for the `r13` register and can be used like any other register in memory operations. But most high level languages assume a certain alignment of `sp` when a function is entered. On Linux, the EABI requires e.g. an 8 byte aligned stack pointer. Some CPUs (e.g. Cortex M3) have some magic to ensure this constraint when entering an interrupt handler. – ensc Nov 24 '14 at 22:27
  • 1
    So am I correct in saying this stack grows downwards? – Nick Bishop Nov 25 '14 at 17:52
  • 1
    Normally `push {list}` is `stm` (store-multiple) with decrement-before, not just `str`. An assembler can certainly optimize to `str` when there's only one register, but you should definitely mention `stmdb` / `ldmia` – Peter Cordes Nov 29 '18 at 22:04
3

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:

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985