函数的本质 二
测试代码
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
复制代码
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会被优化掉
自己实现汇编函数嵌套调用
.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个字节,一个寄存器放不下
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);
复制代码
局部变量
测试代码如下
int funcB(int a, int b) {
int c = 6;
return a + b + c;
}
funcB(10, 20);
复制代码
PS: 局部变量就在栈空间里面
函数嵌套调用
测试代码如下
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);
复制代码
状态寄存器
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均为条件码标识位。它们的内容可被算数或逻辑运算的结果所改变,并可以决定某条指令是否被执行。意义重大
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位,就是相对于最高有效位的更高位。如图所示
PS: 比如 8位的无符号数字 1111 1111, 如果加一, 8位会变为 0000 0000, C标志位变为1 。加分溢出 给C赋值1
PS: CMP 比较是减法运算
PS: 比如 0000 0001 – 0000 0010,需要借位, C 变为 0。 减法 C的1被借走,为0
PS: 跳转指令分为两种,有条件的跳转和无条件的跳转
测试代码
void func() {
int a = 1;
int b = 2;
if (a == b) {
printf("a == b");
} else {
printf("error");
}
}
func()
复制代码
测试代码
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();
复制代码
测试Z标记
void func() {
asm(
"mov w0, #0x0\n"
"adds w0, w0, #0x0\n"
);
}
func();
复制代码
测试NZ标识
void func() {
asm(
"mov w0, #0x0\n"
"adds w0, w0, #0x0\n"
);
}
func();
复制代码
进位
我们知道,当两个数相加的时候,有可能产生从最高位向更高位的进位。比如两个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();
复制代码
错位
当两个数据做减法的时候,有肯能向更高位借位。再比如,两个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