汇编(二)汇编与函数

260 阅读3分钟

SP和FP寄存器

sp寄存器 在任意时刻都会保存栈顶的地址。
fp 寄存器 也称为 x29 寄存器属于通用寄存器,但在某些时刻我们用它保存栈底的地址。

注意:ARM64 开始,取消32位的 LDM、STM、PUSH、POP指令,取而代之的是 ldr/ldp、 str/stp ARM64 里面对栈的操作是 16字节 对齐。

内存读写指令

str(store register)指令
str w0, [sp, #0x1c] 将 w0 中的值,写到 sp向上 偏移16个字节的内存位置。
ldr(load register)指令
ldr w0, [sp, #0x18] 将 sp向上 偏移24个字节的内存位置中的值读到 w0 中。

注意:数据是都是往高地址读/写。 stp 和 ldp 为 str 和 ldr 的变种。

练习

使用32个字节空间作为这段程序的栈空间,然后利用栈将 x0 和 x1 的值进行交换。

sub sp, sp, #0x20 ;拉伸栈空间32个字节
stp x0, x1, [sp, #0x10] ;sp往上加16个字节,存放x0和x1
ldp x1, x0, [sp, #0x10] ;将sp偏移16个字节的值取出来,放入x1和x0

跳转和返回指令

bl
将下一条指令的地址放入 lr(x30)寄存器并跳转到标号处。

ret
默认使用 lr(x30)寄存器的值,通过底层指令提示CPU此处作为下条指令地址。

x30寄存器(lr寄存器)
x30寄存器存放的是函数的返回地址。当 ret 指令执行时刻,会寻找 x30 寄存器保存的地址值。

函数的参数

函数的参数是存放在 X0 到 X7 (W0到W7)这8个寄存器里面的。如果超过8个参数,就会入栈. 函数的返回值是放在 X0 寄存器里面的。

// 通过其他函数的栈传递参数(大于8个参数时)
int test(int a,int b,int c,int d,int e,int f,int g,int h,int i, int j, int k){
    return a+b+c+d+e+f+g+h+i+j+k;
}

// e.g. 11个参数
int main(int argc, char * argv[]) {
    test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ,11);
}

查看 main 函数汇编: image.png

查看 test 函数汇编: image.png

函数的局部变量

函数的局部变量放在栈里面
分析如下函数:

int function(int a, int b) {
    int c = 3;
    return  a + b + c;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    function(10, 20);
}

查看 ViewDidLoad 函数汇编: image.png

查看 function 函数汇编: image.png

函数的全局变量与字符串常量

分析如下函数:

int g = 12;

int func(int a, int b){
    printf("haha");
    int c = a + g;
    return c;
}


int main(int argc, char * argv[]) {
    func(1, 2);
}

查看 func 函数汇编: image.png

查看静态字符串: image.png

查看全局变量: image.png

其中重点分析 adrp x0,1add x0,x0,#0xfa5 两句: adrp指令(address page 按页寻址):

  • 将1的值左移12位,此时的1是二进制
  • 加上pc寄存器的值(先需要将pc的低12位清零)

pageSize的大小是4k即十进制4096,十六进制0x1000。