The stack

  • Stack: A storage space with special access (Last In first Out, Last In Out Firt, LIFO)

SP and FP registers

  • The SP register holds our address at the top of the stack at any given time.
  • The FP register, also known as the X29 register, is a general purpose register, but at some point (such as nesting calls) we use it to store the address at the bottom of the stack!

Note :ARM64 starts, cancel 32-bit LDM,STM,PUSH,POP instructions! Instead, stack operations in LDR \ LDP STR \ STP ARM64 are 16 bytes aligned!!

Function call stack

Common function calls open and restore stack space

sub    sp, sp, #0x40; The tensile0x40(64STP x29, x30, [sp, #0x30]; X29 \x30 register protection add x29, sp, #0x30; X29 points to the bottom of the stack frame... ldp x29, x30, [sp, #0x30]; Restore x29/x30 register add sp, sp, #0x40; Stack balance retCopy the code

About memory read and write instructions

Note: read/write data is read/write to high address

STR (store register) instructions

To read data out of a register and store it in memory.

LDR (load register) instructions

To read data from memory and store it in a register

This LDR and STR variant LDP and STP can also operate on two registers.

The stack uses 32 bytes of space as the stack space of this program, and then uses the stack to swap the values of x0 and x1.

sub sp, sp, #0x20 ; STP x0, x1, [sp, #0x10]; LDP x1, x0, [sp, #0x10]; X0 add sp, sp, #0x20; The stack balancingCopy the code

[] is an address, the middle comma can be followed by the offset, can get the value of the address or put the value to the address, kind of like swift, UnsafePointer pointee.

We can View the data status of Memory through View Memory (PERSONALLY PREFER X / 8G).

View Memory refreshes data with a Page round trip

Bl and RET instructions

Bl label

  • Place the address of the next instruction into the LR (X30) register
  • Go to the label to execute the instruction

ret

  • The default value of lr(X30) register is used, and the underlying instruction prompts the CPU to use this as the next instruction address!

ARM64 platform feature instructions, it is optimized for hardware processing

X30 register

The X30 register holds the return address of the function. When the RET instruction is executed, the address value saved in the X30 register is searched!

Note: when function calls are nested. Need to talk about x30 push!

.text
.global _A,_B

_A:
    str x30, [sp, #-0x10]! ; Mov x0, #0xaaaa
    bl _B
    mov x0, #0xaaaa
    ldr x30, [sp], #0x10; X30, stack restore, balance RET _B: mov x0, #0xbbbb
    ret
Copy the code

There are two short statements:

str x30, [sp, #-0x10]! The equivalent of

sub sp, sp, #0x10
str x30, [sp]
Copy the code

LDR X30, [SP], #0x10 equivalent

ldr x30, [sp]
add sp, sp, #0x10
Copy the code

It kind of means a++ and ++a

The parameters and return values of the function

In ARM64, function parameters are stored in the eight registers X0 through X7(W0 through W7). If there are more than eight arguments, it will be pushed. It is best to limit the number of OC methods to six, since the default is self and CMD

The return value of the function is placed in the X0 register.

Implement a sum function

// Call before declaration
int sum(int a, int b)

// Assembler code
.text
.global  _sum

_sum:
    add x0, x0, x1
    ret
Copy the code

The local variable of a function

The local variables of the function are on the stack!

Function arguments are demonstrated with more than 8 examples

We can see how the system works by writing a function with more than eight arguments:

int test(int a,int b,int c,int d,int e,int f,int g,int h,int i){
    return a + b + c + d + e + f + g + h + i;
}
test(1.2.3.4.5.6.7.8.9);
Copy the code

Then breakpoint to look at assembly code:

We can obviously see that the eight immediate numbers, 1-8, are placed successively in registers w0-w7. 9 is placed in w10, w10 is placed in the address of x8, and x8 is preceded by sp, so 9 is placed in the address of sp, which is the top of the stack of the current function.

Let’s look at how to write assembly by jumping into the test function:

The test function starts by pushing, sub 0x30, and the sp value goes to the lower address 0x30, but in the second step, w8 is offset by 0x30 from sp to the higher address, so it is the top of the previous stack, which was 9. At this point, w8 has 9 in it.

Then w0- W8 are placed on the stack in turn, and finally the values are added from the stack to return the final result. The addition process is a bit tedious, but this is in Debug mode and the compiler is not optimized.

To sum up, if the function has more than eight parameters, the first eight parameters are passed to the next stack through registers X0-x7, and the remaining parameters are stored in the stack, waiting to be fetched by the next stack.

The function returns a value greater than 8 bytes

We define a structure larger than 8 bytes, and then define a function to return the structure:

struct str {
    int a;
    int b;
    int c;
    int d;
    int f;
    int g;
};

struct str getStr(int a,int b,int c,int d,int f,int g) {
    struct str str1;
    str1.a = a;
    str1.b = b;
    str1.c = c;
    str1.d = d;
    str1.f = f;
    str1.g = g;
    return str1;
}

getStr(1.2.3.4.5.6);
Copy the code

Let’s look at the assembler implementation of getStr:

We can clearly see that the assignment of the parameter is based on the address of x8, so that the address of x8 is the address of the return value structure. In this call stack, there is no way to see what is stored in X8, so look at this call stack:

Before we call the function, the final value of x8 is the address of sp offset 0x8, so the return value of the function call is also stored on the previous stack