函数的本质 二

函数的本质 二

测试代码

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);

复制代码

调试test之前的汇编

Demo`-[ViewController viewDidLoad]:
    0x102101ee8 <+0>:   sub    sp, sp, #0x40             ; =0x40 拉伸栈空间
    0x102101eec <+4>:   stp    x29, x30, [sp, #0x30] 将x29 x30 存储到栈空间
    0x102101ef0 <+8>:   add    x29, sp, #0x30            ; =0x30 x29指向栈底
    0x102101ef4 <+12>:  stur   x0, [x29, #-0x8]
    0x102101ef8 <+16>:  stur   x1, [x29, #-0x10]
    0x102101efc <+20>:  ldur   x8, [x29, #-0x8]
    0x102101f00 <+24>:  add    x9, sp, #0x10             ; =0x10 
    0x102101f04 <+28>:  str    x8, [sp, #0x10]
    0x102101f08 <+32>:  adrp   x8, 4
    0x102101f0c <+36>:  add    x8, x8, #0x418            ; =0x418 
    0x102101f10 <+40>:  ldr    x8, [x8]
    0x102101f14 <+44>:  str    x8, [x9, #0x8]
    0x102101f18 <+48>:  adrp   x8, 4
    0x102101f1c <+52>:  add    x8, x8, #0x3e8            ; =0x3e8 
    0x102101f20 <+56>:  ldr    x1, [x8]
    0x102101f24 <+60>:  mov    x0, x9
    0x102101f28 <+64>:  bl     0x10210257c               ; symbol stub for: objc_msgSendSuper2
    0x102101f2c <+68>:  mov    w0, #0x1
    0x102101f30 <+72>:  mov    w1, #0x2
    0x102101f34 <+76>:  mov    w2, #0x3
    0x102101f38 <+80>:  mov    w3, #0x4
    0x102101f3c <+84>:  mov    w4, #0x5
    0x102101f40 <+88>:  mov    w5, #0x6
    0x102101f44 <+92>:  mov    w6, #0x7
    0x102101f48 <+96>:  mov    w7, #0x8
->  0x102101f4c <+100>: mov    x8, sp
    0x102101f50 <+104>: mov    w10, #0x9
    0x102101f54 <+108>: str    w10, [x8]
    0x102101f58 <+112>: bl     0x102101e70               ; test at ViewController.m:30
    0x102101f5c <+116>: ldp    x29, x30, [sp, #0x30]
    0x102101f60 <+120>: add    sp, sp, #0x40             ; =0x40 
    0x102101f64 <+124>: ret    

复制代码

WeChat8a777ddf6316b3e569a8b224ed749822.png

PS: @”%@” 是字符串常量 放在常量区

test函数的汇编代码

Demo`test:
->  0x102101e70 <+0>:   sub    sp, sp, #0x30             ; =0x30 ;拉伸栈空间
    0x102101e74 <+4>:   ldr    w8, [sp, #0x30] ;读取上一个函数的栈区域,保存到w8寄存器 即0x9
    0x102101e78 <+8>:   str    w0, [sp, #0x2c] ;将寄存器w0的数据保存到当前栈空间
    0x102101e7c <+12>:  str    w1, [sp, #0x28] ;将寄存器w1的数据保存到当前栈空间
    0x102101e80 <+16>:  str    w2, [sp, #0x24] ;将寄存器w2的数据保存到当前栈空间
    0x102101e84 <+20>:  str    w3, [sp, #0x20] ;将寄存器w3的数据保存到当前栈空间
    0x102101e88 <+24>:  str    w4, [sp, #0x1c] ;将寄存器w4的数据保存到当前栈空间
    0x102101e8c <+28>:  str    w5, [sp, #0x18] ;将寄存器w5的数据保存到当前栈空间
    0x102101e90 <+32>:  str    w6, [sp, #0x14] ;将寄存器w6的数据保存到当前栈空间
    0x102101e94 <+36>:  str    w7, [sp, #0x10] ;将寄存器w7的数据保存到当前栈空间
    0x102101e98 <+40>:  str    w8, [sp, #0xc] ;将寄存器w8的数据保存到当前栈空间
    0x102101e9c <+44>:  ldr    w8, [sp, #0x2c] ;将栈空间第一个参数保存到w8
    0x102101ea0 <+48>:  ldr    w9, [sp, #0x28] ;将栈空间第二个参数保存到w9
    0x102101ea4 <+52>:  add    w8, w8, w9   ;w8 = w8 + w9
    0x102101ea8 <+56>:  ldr    w9, [sp, #0x24] ;将栈空间第三个参数保存到w9
    0x102101eac <+60>:  add    w8, w8, w9   ;w8 = w8 + w9
    0x102101eb0 <+64>:  ldr    w9, [sp, #0x20] ;将栈空间第四个参数保存到w9
    0x102101eb4 <+68>:  add    w8, w8, w9   ;w8 = w8 + w9
    0x102101eb8 <+72>:  ldr    w9, [sp, #0x1c] ;将栈空间第五个参数保存到w9
    0x102101ebc <+76>:  add    w8, w8, w9   ;w8 = w8 + w9
    0x102101ec0 <+80>:  ldr    w9, [sp, #0x18] ;将栈空间第六个参数保存到w9
    0x102101ec4 <+84>:  add    w8, w8, w9   ;w8 = w8 + w9
    0x102101ec8 <+88>:  ldr    w9, [sp, #0x14] ;将栈空间第七个参数保存到w9
    0x102101ecc <+92>:  add    w8, w8, w9   ;w8 = w8 + w9
    0x102101ed0 <+96>:  ldr    w9, [sp, #0x10] ;将栈空间第八个参数保存到w9
    0x102101ed4 <+100>: add    w8, w8, w9   ;w8 = w8 + w9
    0x102101ed8 <+104>: ldr    w9, [sp, #0xc] ;将栈空间第九个参数保存到w9
    0x102101edc <+108>: add    w0, w8, w9   ;w0 = w8 + w9 将结果保存到w0
    0x102101ee0 <+112>: add    sp, sp, #0x30             ; =0x30 ;恢复栈空间
    0x102101ee4 <+116>: ret    

复制代码

在release模式小 test会被优化掉

WeChat20aba01d2038dfb285cca7d133b5f93c.png

自己实现汇编函数嵌套调用

.text
.global _funA, _sum

_funA:
    sub sp, sp, #0x10
    stp x29, x30, [sp]
    bl _sum;
    ldp x29, x30, [sp]
    add sp, sp, #0x10;
    ret

_sum:
    add x0, x0, x1
    ret

复制代码

简写

.text
.global _funA, _sum

_funA:
    #sub sp, sp, #0x10
    #stp x29, x30, [sp]
    stp x29, x30, [sp,#-0x10]!
    bl _sum;
    #ldp x29, x30, [sp]
    #add sp, sp, #0x10;
    ldp x29, x30, [sp], #0x10
    ret

_sum:
    add x0, x0, x1
    ret

复制代码

函数的返回值

一般情况下,函数的返回值是8个字节的指针

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

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

// 调用
struct str str2 = getStr(1, 2, 3, 4, 5, 6);

复制代码

如上面的代码,返回值是一个结构体,24个字节,一个寄存器放不下

WeChatc0de0cb5ca4b6fa5e0952c56243dcd59.png

x8 比sp偏移0x8个字节 x8 = sp + 0x8

Demo`getStr:
->  0x104201ea4 <+0>:  sub    sp, sp, #0x20             ; =0x20 
    0x104201ea8 <+4>:  str    w0, [sp, #0x1c]
    0x104201eac <+8>:  str    w1, [sp, #0x18]
    0x104201eb0 <+12>: str    w2, [sp, #0x14]
    0x104201eb4 <+16>: str    w3, [sp, #0x10]
    0x104201eb8 <+20>: str    w4, [sp, #0xc]
    0x104201ebc <+24>: str    w5, [sp, #0x8]
    0x104201ec0 <+28>: ldr    w9, [sp, #0x1c]: 读取当前的栈,w9 = 0x01
    0x104201ec4 <+32>: str    w9, [x8]      : 将w9写入上一个栈空间 x8指向的位置 将0x01写入到上一个栈
    0x104201ec8 <+36>: ldr    w9, [sp, #0x18]: 读取当前的栈,w9 = 0x02
    0x104201ecc <+40>: str    w9, [x8, #0x4] : 将w9写入上一个栈空间 x8 + 0x4指向的位置 将0x02写入到上一个栈
    0x104201ed0 <+44>: ldr    w9, [sp, #0x14]: 读取当前的栈,w9 = 0x03
    0x104201ed4 <+48>: str    w9, [x8, #0x8] : 将w9写入上一个栈空间 x8 + 0x8指向的位置 将0x03写入到上一个栈
    0x104201ed8 <+52>: ldr    w9, [sp, #0x10]: 读取当前的栈,w9 = 0x04
    0x104201edc <+56>: str    w9, [x8, #0xc] : 将w9写入上一个栈空间 x8 + 0xc指向的位置 将0x04写入到上一个栈
    0x104201ee0 <+60>: ldr    w9, [sp, #0xc]: 读取当前的栈,w9 = 0x05
    0x104201ee4 <+64>: str    w9, [x8, #0x10] : 将w9写入上一个栈空间 x8 + 0x10指向的位置 将0x05写入到上一个栈
    0x104201ee8 <+68>: ldr    w9, [sp, #0x8]: 读取当前的栈,w9 = 0x06
    0x104201eec <+72>: str    w9, [x8, #0x14]  : 将w9写入上一个栈空间 x8 + 0x14指向的位置 将0x06写入到上一个栈
    0x104201ef0 <+76>: add    sp, sp, #0x20             ; =0x20 
    0x104201ef4 <+80>: ret    

复制代码

以x8寄存器的地址为参照写入数据

PS: 返回值大于8个字节,返回值会存储到栈中

增减参数为9个

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

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

struct str str2 = getStr(1, 2, 3, 4, 5, 6, 7 ,8 ,9);

复制代码

WeChate51df24706ebd07c77d7eeb15ce93bed.png

WeChat70fcab2979603c22903f8b08f8e25a1d.png

局部变量

测试代码如下

int funcB(int a, int b) {
    int c = 6;
    return a + b + c;
}

funcB(10, 20);
复制代码

PS: 局部变量就在栈空间里面

WeChat553a5c9273e2dfb00993e5e8d12ec5ad.png

函数嵌套调用

测试代码如下

int funcB(int a, int b) {
    int c = 6;
    int d = funcSum(a, b, c);
    int e = funcSum(a, b, c);
    return e;
}

int funcSum(int a, int b, int c) {
    int d = a + b + c;
    printf("%d", d);
    return d;
}

funcB(10, 20);
复制代码

WeChat402eba2a5166475b2e72c515bfaf4cf6.png

状态寄存器

CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理器,个数和结果都可能不同)。这种寄存器在ARM中,被称为状态寄存器CPSR(current program status register)寄存器
CPSR和其他的寄存器不一样,其他的寄存器是用来存放数据的,都是这个寄存器具有一个含义。而CPSR寄存器是按位起作用,也就是说,它的每一位都有专门的含义,记录特定的信息

PS: CPSR寄存器是32位
* CPSAR的第8位(包括I F T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位 和控制总线相关
* N、Z、C、V均为条件码标识位。它们的内容可被算数或逻辑运算的结果所改变,并可以决定某条指令是否被执行。意义重大

WeChat48cbe7e8b9060fda535aadc96b5cc16d.png

N(Negative)标志

PS: CPSR的第31位N,符号标志位。它记录相关的指令执行后,其结果是否位负数。如果为负数 N = 1,如果是非负数 N = 0
注意: 在ARM64的指令集中,有的指令的执行时,影响状态寄存器。比如add\sub\or等,它们大都是运算指令(进行逻辑或算数运算)。

Z(Zero)标志

PS: CPSR的第30位N,0符号标志位。它记录相关的指令执行后,其结果是否位零。如果位零 N = 1,如果是非零 N = 0

C (Carry)标志

PS: CPSR的第29位是C,进位标识位。一般情况下,进行无符号数的运算
* 加法运算: 当运算结果产生进位时(无符号数溢出), C = 1,否则 C = 0
* 减法运算(包括CMP): 当运算产生借位是(无符号数溢出), C = 0, 否则 C = 1

对于位数为N的无符号数来数,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在第N位,就是相对于最高有效位的更高位。如图所示

16194521243937.png

PS: 比如 8位的无符号数字 1111 1111, 如果加一, 8位会变为 0000 0000, C标志位变为1 。加分溢出 给C赋值1
PS: CMP 比较是减法运算
PS: 比如 0000 0001 – 0000 0010,需要借位, C 变为 0。 减法 C的1被借走,为0

16194528333464.png

PS: 跳转指令分为两种,有条件的跳转和无条件的跳转

测试代码

void func() {
    int a = 1;
    int b = 2;
    
    if (a == b) {
        printf("a == b");
    } else {
        printf("error");
    }
}

func()

复制代码

WeChat6d3ce86f91aae840c1331d8a537bdfde.png
WeChat64a72e1c79ae685e61c595f87514a590.png

测试代码

void func() {
//    int a = 1;
//    int b = 2;
//
//    if (a == b) {
//        printf("a == b");
//    } else {
//        printf("error");
//    }
    
    asm(
        "mov w0, #0xffffffff\n"
        "add w0, w0, #0x0\n"
        );
}

func();

复制代码

WeChat06a9f1545c2de66f0580661549f84d23.png
WeChatc1d1cf48aaa13a8edffaf133af78246f.png

测试Z标记

void func() {    
    asm(
        "mov w0, #0x0\n"
        "adds w0, w0, #0x0\n"
        );
}

func();
复制代码

WeChatc937f56392d226be659caafad826603f.png

WeChat4c687d0f9265fa32d794debc519c4d4e.png

测试NZ标识

void func() {    
    asm(
        "mov w0, #0x0\n"
        "adds w0, w0, #0x0\n"
        );
}

func();
复制代码

WeChate290388373d7bb489613c7155c71e520.png
WeChat6970757e23352eb136e2c14d43f1cd0c.png

进位

我们知道,当两个数相加的时候,有可能产生从最高位向更高位的进位。比如两个32位的数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位丢失了。起始CPU在运算的时候,并不丢失这个进制位,而是记录在一个个数的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面指令

mov w0, 0xaaaaaaaa  ; 0xa的进制是1010
adds w0, w0, w0     ; 执行后 相当于 1010 << 1, 进位1(无符号溢出),所以标记位1
adds w0, w0, w0     ; 执行后 相当于 0101 << 0, 进位0(无符号没溢出),所以标记位0
adds w0, w0, w0     ; 重复上述操作
adds w0, w0, w0     ;
复制代码
void func() {
    asm(
        "mov w0, 0xaaaaaaaa\n"
        "adds w0, w0, w0\n"
        "adds w0, w0, w0\n"
        "adds w0, w0, w0\n"
        "adds w0, w0, w0\n"
        );
}

func();

复制代码

WeChatd9deaf9d1b2907e088c6f1dc8f50d143.png

WeChat6715b6a5eece33cf28c95220d7db3b66.png

WeChat196464f9ec2b830ec3babfae389863d8.png

错位

当两个数据做减法的时候,有肯能向更高位借位。再比如,两个32位的数据: 0x00000000 – 0x000000ff将产生借位,借位后,相当于计算0x100000000 – 0x000000ff。得到0xffffff01这个值。由于借了一位,所以C位用来标记借位。C = 0。比如下面指令:

mov w0 0x0
subs w0, w0, 0xff
subs w0, w0, 0xff
subs w0, w0, 0xff
复制代码

V(Overflow)溢出标志

PS: CPSR的第28位是V,溢出标志位。在进行有符号运算的时候,如果超过了机器所能标识的范围,称为溢出
* 正数 + 正数 为负数 溢出 0111 + 0111 = 1000
* 负数 + 负数 为正数 溢出 1111 + 1000
* 正数 + 负数 不可能溢出

PS: 在同样宽度的数据操作
PS: V位,只要运算,就当作有符号运算。C位,只要运算,就当作有无符号运算。

总结

  • 函数的本质

    • 函数调用栈
      • ARM64中栈是递减栈,向低地址延伸的栈
      • SP寄存器指向栈顶的位置
      • x29(sp)寄存器指向栈底的位置
    • 函数的返回值
      • 默认情况下函数的返回值放在x0寄存器
      • 如果放不下就会利用内存。写入上一个调用栈的内部。用x8寄存器作为参照。
      • 参数的返回值如果大于8个字节,也会保存到栈空间,并且保存的位置也临近当前栈的空间
    • 函数的参数
      • 默认情况下使用x0到x7这8个寄存器
      • 如果是浮点数就是用浮点寄存器
      • 超出了8个参数,用栈空间传递,参数保存到临近当前栈的空间
    • 函数的局部变量
      • 使用栈保存局部变量
    • 函数嵌套调用:
      • 会将x29 x30寄存器入栈保护 x29 指向栈底 x30保存了回家的路
    • 栈空间开始和结束的标志 现场保护
      • 开始
        • 拉伸栈空间
        • x29 x30 入栈
        • x29 指向栈底
      • 结束
        • x29 x30 出栈
        • 平衡栈空间
  • 状态(标志)寄存器

    • ARM64中cpsr寄存器32位为状态寄存器
    • 最高4位(28, 29, 30, 31)为标志位
    • N标志 (负数标记为)
      • 执行结果负数 N = 1,非负数 N = 0
    • Z标志 (0标记为)
      • 结果为零 Z = 1,结果非零 Z = 0
    • C标记 (无符号数溢出)
      • 加法:进位 C = 1,否则 C = 0
      • 减法: 借位 C = 0,否则 C = 1
    • V标志 (有符号溢出)
      • 正数 + 正数 = 负数 V = 1
      • 负数 + 负数 = 正数 V = 1
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享