Search code examples
assemblyarmstack-memorymicroprocessors

I cannot understand how the ARM stack operations work


What does the ascending and descending mean on the ARM stack ? What does full and empty mean ? If the stack is full, wouldn't the push instruction result in stack overflow ?

ARM System Developers Guide


Solution

  • A hardware stack is common to most CPUs and there are several different ways it is implemented in hardware. Most CPUs use a descending stack, which means that when you PUSH a new value, the stack pointer's value decreases by the amount of data pushed (on ARM this is 4 bytes) and the new value is pushed onto the stack. Here's an example using Z80 Assembly, which uses a full descending stack. (Even though the question is about ARM, I feel it best to use a simpler CPU which behaves similarly to make the explanation a little more clear.)

    ld sp,0xFFFF
    ld bc,0x1234
    ld de,0x5678
    push bc
    push de
    

    At this point in execution if you were to read a hexdump of the stack you'd see this:

    0xFF00: 00 00 00 00 00 00 00 00 00 00 00 |78 56| |34 12| 00
                                              E   D    C  B
    

    The vertical bars and the registers below were added by me to show where those values came from, they're not going to appear in a typical hexdump program. This is just to illustrate how the stack works in general. The ARM is very similar, however it can be configured to be big-endian (which means that the bytes are pushed in the order you would expect rather than sequentially reversed as you see here, which is actually the norm for most CPUs)

    The fact that the Z80 uses a full stack convention is reflected in that the current value of the stack pointer is 0xFFFB - which points directly to the last byte we pushed. A CPU that uses the empty stack convention would have its stack pointer pointing to 0xFFF9 right now.

    A stack overflow is when the stack has reached its "limit" and is starting to overwrite the hardware's normal RAM area (a.k.a "the heap"). I'll use the Game Boy Advance to show this example, which actually uses an ARM CPU. The heap starts at 0x02000000, and we'll say our stack pointer is initialized to 0x03000000. For this example, let's say that our "heap" ends at 0x027FFFFF.

    ; program begins here
    mov sp,#0x03000000
    stmfd sp!,{r0-r12,lr}   ;push 56 bytes onto the stack.
    stmfd sp!,{r0-r12,lr}   ;push 56 bytes onto the stack.
    stmfd sp!,{r0-r12,lr}   ;push 56 bytes onto the stack.
    ...
    

    Eventually if you keep doing this you'll start overwriting the memory in the heap with the pushed values, and this is what a stack overflow is. Given how huge the stack is on most modern hardware this is usually a result of assembly code where the author mismanaged the stack or a recursive function is called too many times. It's not likely you'll ever encounter this if your program consists entirely of non-recursive functions and you've made sure to "balance" the stack correctly.