Introduction

Hello World

Printing variables

User input

Testing printf

Mathematical operations

Functions

Arrays

Loops

The stack

Projects:

Estimating sin(x)

Calculating cube roots

Sieve of Eratosthenes

Quadratic Solver

The stack

The stack is a storage device that is ubiquitous to computing. All architectures use a stack and most (if not all) programming languages either have a stack storage mechanism or allow one to be created.

A stack is a FILO storage device in that the first value stored on the stack will be the last value to be retrieved. Only the top element of the stack is available so if you want a value stored earlier then you have to remove all the values above it before you can access the one you want.

While most other architectures adopt a similar terminology where adding a value to the top of the stack is called pushing the value and retreiving the value from the top is called popping the value, ARM does not do that. Basic stack use, such as will be demonstrated, uses two instructions; STP - STore Pair to push two 64 bit values to the stack, and LDP - LoaD Pair to retrieve two 64 bit values into two registers.

There are two rules for the stack in ARM64:

  1. The stack grows downwards in memory. That is, the stack pointer decreases in value as the contents of the stack increase
  2. The stack is STRICTLY 16 bytes aligned. That means that when using 64 bit values (long int or long float) two values must be stored.

The code that will be used for this demo is shown below. A more detailed observation of the code and how it works will be provided by using the GDB debugger so you don't need to understand the whole code at this point.

  
    // using_stack.s

    .global main

    .data

    .text
    main:
        // Prolog
        stp x29, x30, [sp, -16]!
        mov x29, sp

        // Main code
        // Load four registers with test value
        mov x19, #11
        mov x20, #13
        mov x21, #15
        mov x22, #17

        // Push them to the stack
        stp x19, x20, [sp, -16]!
        stp x21, x22, [sp, -16]!

        // Clear the registers
        mov x19, #0
        mov x20, #0
        mov x21, #0
        mov x22, #0

        // Reload from stack
        // Note reversed order
        ldp x21, x22, [sp], 16
        ldp x19, x20, [sp], 16

        // Cleanup
        mov x0, #0
        ldp x29, x30, [sp], 16
        RET
  

The original incarnation of this code used non-cloberable registers because the registers x19 - x22 were copied to x1 - x4 and printed using printf but this made it hard to watch the registers with GDB so the printf functions were removed. The code could use x0 - x3 instead of x19 - x22, and it would work just as well.

To observe the behaviour of the code it was run with the GDB debugger so it could be stepped with the registers, stack pointer, and the stack itself being observed during execution.
The results are shown below. First is the stack pointer (SP) and the stack before any code is executed.

      
      (gdb) info registers $sp
      sp             0x7ffffff410        0x7ffffff410
      (gdb)
      
  
    
    (gdb) x/16gx $sp
    0x7ffffff410:   0x0000000000000000      0x0000005555550664
    0x7ffffff420:   0x0000000000000000      0x0000000000000000
    0x7ffffff430:   0x0000005555550630      0x0000000000000000
    0x7ffffff440:   0x0000000000000000      0x0000000000000000
    0x7ffffff450:   0x0000000000000000      0x45485300325f6b63
    0x7ffffff460:   0x0000007ff7e689d0      0x0000007ffffff568
    0x7ffffff470:   0x0000000100000000      0x0000005555550748
    0x7ffffff480:   0xffffffffffffffff      0x00000055555507d0
    (gdb)
    

For brevity, I will shorten addresses and values.
The SP is currently pointing to the bottom of the stack at address 0x410. The values currently in the stack are of no relevance to this demonstration.

The first line of code that will be executed is this:

    
    stp x29, x30, [sp, -16]!
    

This will reduce the stack pointer by 16, to 0x400, and store the programme counter and link register. While we have no control over the values, we expect to see two new values at 0x400 and 0x408.

    
    (gdb) info registers $sp
    sp             0x7ffffff400        0x7ffffff400
    (gdb)
    
    
    (gdb) x/16gx $sp
    0x7ffffff400:   0x0000007ffffff410      0x0000007ff7e78dd8
    0x7ffffff410:   0x0000000000000000      0x0000005555550664
    0x7ffffff420:   0x0000000000000000      0x0000000000000000
    0x7ffffff430:   0x0000005555550630      0x0000000000000000
    0x7ffffff440:   0x0000000000000000      0x0000000000000000
    0x7ffffff450:   0x0000000000000000      0x45485300325f6b63
    0x7ffffff460:   0x0000007ff7e689d0      0x0000007ffffff568
    0x7ffffff470:   0x0000000100000000      0x0000005555550748
    (gdb)
    

The next section of code immediately loads values into x19 - x22. Once this is done, the registers can be checked.

    
    // Load four registers with test value
    mov x19, #11
    mov x20, #13
    mov x21, #15
    mov x22, #17
    
    
    (gdb) info registers x19 x20 x21 x22 sp
    x19            0xb                 11
    x20            0xd                 13
    x21            0xf                 15
    x22            0x11                17
    sp             0x7ffffff400        0x7ffffff400
    (gdb)
    

The registers have been loaded and the SP is unchanged at 0x400.

The next lines to be executed are the two STP instructions that will push the registers to the stack. The registers will be pushed in order so we expect to see the SP reduced to 0x3f0 and have 11 (x19) and 13 (x20) stored at 0x3f0 and 0x3f8. The second STP instruction should reduce the SP to 0x3e0 and store 15 (x21) and 17 (x22) in 0x3e0 and 0x3e8.

    
    // Push them to the stack
    stp x19, x20, [sp, -16]!
    stp x21, x22, [sp, -16]!
    
    
    (gdb) info registers $sp
    sp             0x7ffffff3e0        0x7ffffff3e0
    (gdb)
    
    
    (gdb) x/16gx $sp
    0x7ffffff3e0:   0x000000000000000f      0x0000000000000011
    0x7ffffff3f0:   0x000000000000000b      0x000000000000000d
    0x7ffffff400:   0x0000007ffffff410      0x0000007ff7e78dd8
    0x7ffffff410:   0x0000000000000000      0x0000005555550664
    0x7ffffff420:   0x0000000000000000      0x0000000000000000
    0x7ffffff430:   0x0000005555550630      0x0000000000000000
    0x7ffffff440:   0x0000000000000000      0x0000000000000000
    0x7ffffff450:   0x0000000000000000      0x45485300325f6b63
    (gdb)
    

The values of 11 (0x0b) and 13 (0x0d) are at 0x3f0 and 0x3f8, and the values of 15 (0x0f) and 17 (0x11) are at 0x3e0 and 0x3e8.
The SP is now pointing at 0x3e0.

The next section of code clears the values in x19 - x22 which we can then check with GDB.

    
    // Clear the registers
    mov x19, #0
    mov x20, #0
    mov x21, #0
    mov x22, #0
    
    
    (gdb) info registers x19 x20 x21 x22
    x19            0x0                 0
    x20            0x0                 0
    x21            0x0                 0
    x22            0x0                 0
    (gdb)
    

Finally, the values are popped from the stack back into their original registers. Following this we expect the SP to be returned to 0x400 and the values from 0x3e0 to 0x3f8 to be gone.

    
    // Reload from stack
    // Note reversed order
    ldp x21, x22, [sp], 16
    ldp x19, x20, [sp], 16
    
    
    (gdb) info registers x19 x20 x21 x22 sp
    x19            0xb                 11
    x20            0xd                 13
    x21            0xf                 15
    x22            0x11                17
    sp             0x7ffffff400        0x7ffffff400
    (gdb)
    

Note that the SP is back to 0x400

    
    (gdb) x/16gx $sp
    0x7ffffff400:   0x0000007ffffff410      0x0000007ff7e78dd8
    0x7ffffff410:   0x0000000000000000      0x0000005555550664
    0x7ffffff420:   0x0000000000000000      0x0000000000000000
    0x7ffffff430:   0x0000005555550630      0x0000000000000000
    0x7ffffff440:   0x0000000000000000      0x0000000000000000
    0x7ffffff450:   0x0000000000000000      0x45485300325f6b63
    0x7ffffff460:   0x0000007ff7e689d0      0x0000007ffffff568
    0x7ffffff470:   0x0000000100000000      0x0000005555550748
    (gdb)
    

The final LoaD Pair instruction occurs during cleanup and restores the programme counter and link register before terminating.

    
    ldp x29, x30, [sp], 16
    
    
    (gdb) info registers sp
    sp             0x7ffffff410        0x7ffffff410
    (gdb)
    
    
    (gdb) x/16gx $sp
    0x7ffffff410:   0x0000000000000000      0x0000005555550664
    0x7ffffff420:   0x0000000000000000      0x0000000000000000
    0x7ffffff430:   0x0000005555550630      0x0000000000000000
    0x7ffffff440:   0x0000000000000000      0x0000000000000000
    0x7ffffff450:   0x0000000000000000      0x45485300325f6b63
    0x7ffffff460:   0x0000007ff7e689d0      0x0000007ffffff568
    0x7ffffff470:   0x0000000100000000      0x0000005555550748
    0x7ffffff480:   0xffffffffffffffff      0x00000055555507d0
    (gdb)
    

Finally, the stack is returned to the way we found it before the code was executed.