汇编(三)

2021-04-10  本文已影响0人  浅墨入画

一. 函数的参数

1.1 多个参数
创建空工程001--Demo,编写代码如下

// ViewController.m文件内容
@implementation ViewController

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;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    test(1, 2, 3, 4, 5, 6, 7, 8, 9);
}
@end

test方法调用处打断点,运行工程断点执行到汇编代码中,如下图

image.png

接下来我们分析viewdidload栈空间存放过程,viewdidload汇编代码如下

001--Demo`-[ViewController viewDidLoad]:
    0x10012a578 <+0>:   sub    sp, sp, #0x40             ; =0x40 
    0x10012a57c <+4>:   stp    x29, x30, [sp, #0x30]
    0x10012a580 <+8>:   add    x29, sp, #0x30            ; =0x30 
    0x10012a584 <+12>:  stur   x0, [x29, #-0x8]
    0x10012a588 <+16>:  stur   x1, [x29, #-0x10]
    0x10012a58c <+20>:  ldur   x8, [x29, #-0x8]
    0x10012a590 <+24>:  add    x9, sp, #0x10             ; =0x10 
    0x10012a594 <+28>:  str    x8, [sp, #0x10]
    0x10012a598 <+32>:  adrp   x8, 2
    0x10012a59c <+36>:  add    x8, x8, #0xd18            ; =0xd18 
    0x10012a5a0 <+40>:  ldr    x8, [x8]
    0x10012a5a4 <+44>:  str    x8, [x9, #0x8]
    0x10012a5a8 <+48>:  adrp   x8, 2
    0x10012a5ac <+52>:  add    x8, x8, #0xd00            ; =0xd00 
    0x10012a5b0 <+56>:  ldr    x1, [x8]
    0x10012a5b4 <+60>:  mov    x0, x9
    0x10012a5b8 <+64>:  bl     0x10012aa00               ; symbol stub for: objc_msgSendSuper2
// adrp 表示 address page,内存是分页的
// adrp   x8, 2   add    x8, x8, #0xd18            ; =0xd18   表示从内存中取数据
// symbol stub for: objc_msgSendSuper2 表示调用[super viewDidLoad];

首先执行sub sp, sp, #0x40 拉伸栈空间

sp寄存器拉伸前指向地址 sp寄存器拉伸后指向地址

接下来执行汇编stp x29, x30, [sp, #0x30],把x29 x30寄存器放入栈中

image.png

执行add x29, sp, #0x30,让FP寄存器指向栈底

image.png

从SP到FP就是拉伸的栈空间
字符串常量
NSLog(@"%@",self); 其中 @"%@" 就是字符串常量
注意⚠️ 开辟空间是以16字节对齐,操作数据可以是8字节也可以是16字节
继续执行上面代码,跳入test汇编代码中

001--Demo`test:
    0x104e364d0 <+0>:   sub    sp, sp, #0x30             ; =0x30 
    0x104e364d4 <+4>:   ldr    w8, [sp, #0x30]   // 从上一个函数栈中取数据,接近函数栈顶的区域,有可能作为函数参数的传递
    0x104e364d8 <+8>:   str    w0, [sp, #0x2c]
    0x104e364dc <+12>:  str    w1, [sp, #0x28]
    0x104e364e0 <+16>:  str    w2, [sp, #0x24]
    0x104e364e4 <+20>:  str    w3, [sp, #0x20]
    0x104e364e8 <+24>:  str    w4, [sp, #0x1c]
    0x104e364ec <+28>:  str    w5, [sp, #0x18]
    0x104e364f0 <+32>:  str    w6, [sp, #0x14]
    0x104e364f4 <+36>:  str    w7, [sp, #0x10]
    0x104e364f8 <+40>:  str    w8, [sp, #0xc]
->  0x104e364fc <+44>:  ldr    w8, [sp, #0x2c]
    0x104e36500 <+48>:  ldr    w9, [sp, #0x28]
    0x104e36504 <+52>:  add    w8, w8, w9
    0x104e36508 <+56>:  ldr    w9, [sp, #0x24]
    0x104e3650c <+60>:  add    w8, w8, w9
    0x104e36510 <+64>:  ldr    w9, [sp, #0x20]
    0x104e36514 <+68>:  add    w8, w8, w9
    0x104e36518 <+72>:  ldr    w9, [sp, #0x1c]
    0x104e3651c <+76>:  add    w8, w8, w9
    0x104e36520 <+80>:  ldr    w9, [sp, #0x18]
    0x104e36524 <+84>:  add    w8, w8, w9
    0x104e36528 <+88>:  ldr    w9, [sp, #0x14]
    0x104e3652c <+92>:  add    w8, w8, w9
    0x104e36530 <+96>:  ldr    w9, [sp, #0x10]
    0x104e36534 <+100>: add    w8, w8, w9
    0x104e36538 <+104>: ldr    w9, [sp, #0xc]
    0x104e3653c <+108>: add    w0, w8, w9   // 最终相加的结果给了wo寄存器
    0x104e36540 <+112>: add    sp, sp, #0x30             ; =0x30 
    0x104e36544 <+116>: ret    
// 参数超过8个,效率就会降低,因为存在函数栈传递参数

现在把工程001--Demo切换到release模式下,运行工程发现并没有调用test汇编,原因是test方法的运行对整个程序并没有作用,编译器会把无用的代码屏蔽掉

image.png

现在修改viewcontroller.m文件内容如下

// ViewController.m文件内容
@implementation ViewController

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;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    printf(@"%d",test(1, 2, 3, 4, 5, 6, 7, 8, 9));
}
@end

运行工程发现并没有调用test汇编,编译器直接算出来结果,release模式下编译器会进行优化

image.png

1.2 汇编实现参数调用

// ViewController.m文件内容
@implementation ViewController

int funcA(int a, int b);

- (void)viewDidLoad {
    [super viewDidLoad];
    int a = funcA(10, 20);
    printf(@"%d", a);
}
@end

// asm.s文件内容
.text
.global _funcA,_sum

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

// _funcA也可以这样简写
_funcA:
    stp x29,x30,[sp,#-0x10]!
    bl _sum
    stp x29,x30,[sp],#0x10
    ret

_sum:
    add x0,x0,x1
    ret
// 成功运行打印30

二. 函数的返回值

// ViewController.m文件内容
@implementation ViewController

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 = d;
    str1.d = d;
    str1.f = f;
    str1.g = g;
    return str1;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
}
@end

下图add x8, sp, #0x8 让x8寄存器指向开辟的结构体空间,这里为什么不用sp寄存器呢?

image.png
// getStr 方法汇编代码如下
001--Demo`getStr:
->  0x104f19d94 <+0>:  sub    sp, sp, #0x20             ; =0x20 
    //  这里把1 ~ 6 分别存入栈中
    0x104f19d98 <+4>:  str    w0, [sp, #0x1c]
    0x104f19d9c <+8>:  str    w1, [sp, #0x18]
    0x104f19da0 <+12>: str    w2, [sp, #0x14]
    0x104f19da4 <+16>: str    w3, [sp, #0x10]
    0x104f19da8 <+20>: str    w4, [sp, #0xc]
    0x104f19dac <+24>: str    w5, [sp, #0x8]
    // 上面是把6个参数放入栈中,是从w0 ~ w6,下面为什么直接跳转到w9寄存器,不用w6寄存器呢?
    因为w0 ~ w7 都是用作参数,参数不够的话,w6 w7 两个寄存器不会用到。w8用作返回值,所以此处用w9寄存器作为临时变量
    // 再从栈中取出放入w9寄存器
    0x104f19db0 <+28>: ldr    w9, [sp, #0x1c]
    // 这里以x8寄存器作为参照,把数据写入上一个栈空间中(viewDidLoad栈空间)
    0x104f19db4 <+32>: str    w9, [x8]
    // 以下都是以w9寄存器作为参照取出数据,再把数据写入上一个栈空间
    0x104f19db8 <+36>: ldr    w9, [sp, #0x18]
    0x104f19dbc <+40>: str    w9, [x8, #0x4]
    0x104f19dc0 <+44>: ldr    w9, [sp, #0x14]
    0x104f19dc4 <+48>: str    w9, [x8, #0x8]
    0x104f19dc8 <+52>: ldr    w9, [sp, #0x10]
    0x104f19dcc <+56>: str    w9, [x8, #0xc]
    0x104f19dd0 <+60>: ldr    w9, [sp, #0xc]
    0x104f19dd4 <+64>: str    w9, [x8, #0x10]
    0x104f19dd8 <+68>: ldr    w9, [sp, #0x8]
    0x104f19ddc <+72>: str    w9, [x8, #0x14]
    // 执行到这里之后,就会把结构体数据全部写入上一个栈空间中
    0x104f19de0 <+76>: add    sp, sp, #0x20             ; =0x20 
    0x104f19de4 <+80>: ret 

// 这里并没有用x0寄存器作为返回值,如果我们的函数参数大于8个,返回值也会用栈空间来做

小结
函数返回值如果一个寄存器能够放的下,就使用x0或wo寄存器,如果放不下的话就会用到栈空间

三. 函数的局部变量

// ViewController.m文件内容 
@implementation ViewController
int funcB(int a,int b){
    int c = 6;
    return a + b + c;
}

- (void)viewDidLoad {
    [super viewDidLoad];
     funcB(10, 20);
}
@end 
image.png
// 查看funB函数的汇编代码
001--Demo`funcB:
->  0x1040d5ed4 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x1040d5ed8 <+4>:  str    w0, [sp, #0xc]
    0x1040d5edc <+8>:  str    w1, [sp, #0x8]
    // 局部变量c  值为6
    0x1040d5ee0 <+12>: mov    w8, #0x6
    // 局部变量6存入栈中,这里是4个字节
    0x1040d5ee4 <+16>: str    w8, [sp, #0x4]
    0x1040d5ee8 <+20>: ldr    w8, [sp, #0xc]
    0x1040d5eec <+24>: ldr    w9, [sp, #0x8]
    0x1040d5ef0 <+28>: add    w8, w8, w9
    // 取出局部变量6,存入w9临时寄存器
    0x1040d5ef4 <+32>: ldr    w9, [sp, #0x4]
    0x1040d5ef8 <+36>: add    w0, w8, w9
    0x1040d5efc <+40>: add    sp, sp, #0x10             ; =0x10 
    0x1040d5f00 <+44>: ret 

局部变量存入栈空间中,参数用的寄存器传递,所有计算都是通过寄存器来完成。一旦执行 add sp, sp, #0x10 栈平衡,所有局部变量就会取不到相当于销毁了,栈平衡销毁局部变量。
如果函数嵌套,参数和局部变量会怎么样呢?下面讨论

四. 函数的嵌套调用

// ViewController.m文件内容 
@implementation ViewController
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;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    funcB(10, 20);
}
@end

// 查看funB函数的汇编代码
001--Demo`funcB:
    // 前面三行汇编类似于函数开始
->  0x1004f9e38 <+0>:  sub    sp, sp, #0x30             ; =0x30 
    // x29/fp寄存器 x30/lr寄存器 入栈
    0x1004f9e3c <+4>:  stp    x29, x30, [sp, #0x20]
    // x29指向栈底
    0x1004f9e40 <+8>:  add    x29, sp, #0x20            ; =0x20 
    // 参数入栈保护w0 w1 寄存器,这里只用到两个参数(funcB的两个参数),如果参数多的话,防止函数调用完改变这两个参数的值,而下面又需要使用这几个参数,就需要把多个参数都保护起来
    0x1004f9e44 <+12>: stur   w0, [x29, #-0x4]
    0x1004f9e48 <+16>: stur   w1, [x29, #-0x8]
    // 局部变量生成
    0x1004f9e4c <+20>: mov    w8, #0x6
    // 局部变量入栈
    0x1004f9e50 <+24>: stur   w8, [x29, #-0xc]
    // 这里与下面都用到了 w0 w1 w2 三个寄存器,funcSum也用到这三个寄存器,有可能会改变这三个寄存器的值
    0x1004f9e54 <+28>: ldur   w0, [x29, #-0x4]
    0x1004f9e58 <+32>: ldur   w1, [x29, #-0x8]
    0x1004f9e5c <+36>: ldur   w2, [x29, #-0xc]
    // 调用新的函数funcSum
    0x1004f9e60 <+40>: bl     0x1004f9e8c               ; funcSum at ViewController.m:24
    // 保存函数funcSum的返回值
    0x1004f9e64 <+44>: str    w0, [sp, #0x10]
    // 这里与上面都用到了 w0 w1 w2 三个寄存器,funcSum也用到这三个寄存器,有可能会改变这三个寄存器的值
    0x1004f9e68 <+48>: ldur   w0, [x29, #-0x4]
    0x1004f9e6c <+52>: ldur   w1, [x29, #-0x8]
    0x1004f9e70 <+56>: ldur   w2, [x29, #-0xc]
    0x1004f9e74 <+60>: bl     0x1004f9e8c               ; funcSum at ViewController.m:24
    // 第二次调用函数funcSum,保存返回值
    0x1004f9e78 <+64>: str    w0, [sp, #0xc]
    0x1004f9e7c <+68>: ldr    w0, [sp, #0xc]
    // 下面三行相当于函数结束
    0x1004f9e80 <+72>: ldp    x29, x30, [sp, #0x20]
    0x1004f9e84 <+76>: add    sp, sp, #0x30             ; =0x30 
    0x1004f9e88 <+80>: ret 

小结
寄存器的保护也叫现场保护,会保护到自己的栈空间中。寄存器保护是保护将来可能会用到这些寄存器。x29 x30 的保护就是这样的

五. 状态寄存器

注:CPSR寄存器是32位的

image.png

5.1 案例一

// ViewController.m文件内容 
@implementation ViewController
void func(){
    int a = 1;
    int b = 2;
    if (a == b) {
        printf("a == b\n");
    }else{
        printf("error\n");
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    func();
}
@end

标志寄存器可以用来调试信息,因为它与程序执行流程有关

// 查看fun函数的汇编代码
001--Demo`func:
->  0x10274de80 <+0>:  sub    sp, sp, #0x20             ; =0x20 
    0x10274de84 <+4>:  stp    x29, x30, [sp, #0x10]
    0x10274de88 <+8>:  add    x29, sp, #0x10            ; =0x10 
    0x10274de8c <+12>: mov    w8, #0x1
    0x10274de90 <+16>: stur   w8, [x29, #-0x4]
    0x10274de94 <+20>: mov    w8, #0x2
    0x10274de98 <+24>: str    w8, [sp, #0x8]
    0x10274de9c <+28>: ldur   w8, [x29, #-0x4]
    0x10274dea0 <+32>: ldr    w9, [sp, #0x8]
    // cmp指令 compare比较
    0x10274dea4 <+36>: cmp    w8, w9
    // b指令跳转 .ne 指的是有条件的跳转,这里的跳转就与标志寄存器有关
    0x10274dea8 <+40>: b.ne   0x10274debc               ; <+60> at ViewController.m
    0x10274deac <+44>: adrp   x0, 1
    0x10274deb0 <+48>: add    x0, x0, #0x62f            ; =0x62f 
    0x10274deb4 <+52>: bl     0x10274e56c               ; symbol stub for: printf
    0x10274deb8 <+56>: b      0x10274dec8               ; <+72> at ViewController.m:77:1
    0x10274debc <+60>: adrp   x0, 1
    0x10274dec0 <+64>: add    x0, x0, #0x637            ; =0x637 
    0x10274dec4 <+68>: bl     0x10274e56c               ; symbol stub for: printf
    0x10274dec8 <+72>: ldp    x29, x30, [sp, #0x10]
    0x10274decc <+76>: add    sp, sp, #0x20             ; =0x20 
    0x10274ded0 <+80>: ret 
image.png image.png

上面的if判断中 a=1 b=2,为什么修改了cpsr寄存器的值会执行 a==b 的逻辑? 我们下节课讨论!!!

5.2 案例二

// ViewController.m文件内容 
@implementation ViewController
void func(){
    asm(
        "mov w0,#0xffffffff\n"
        //adds 可以改变标志位
        "adds w0,w0,#0x0\n"
        );
}

- (void)viewDidLoad {
    [super viewDidLoad];
    func();
}
@end

// 查看fun函数的汇编代码
001--Demo`func:
    // 0xffffffff 就是 -1
->  0x100b6decc <+0>: mov    w0, #-0x1
    0x100b6ded0 <+4>: adds   w0, w0, #0x0              ; =0x0 
    0x100b6ded4 <+8>: ret  
image.png image.png

N(Negative)标志

CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.

上面执行到adds w0, w0, #0x0 标志寄存器是 0110,那么根据N标志的分析,程序执行完成 0110 第一位的0 应该变成 1

截屏2021-04-10 下午3.24.27.png

5.3 案例三

Z(Zero)标志

CPSR的第30位是Z,0标志位。它记录相关指令执行后,其结果是否为0.如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.

根据N Z 标志的分析,如果执行结果为0, 那么前两位N Z标志一定是01,下面进行验证

// ViewController.m文件内容 
@implementation ViewController
void func(){
    asm(
        "mov w0,#0x0\n"
        //adds 可以改变标志位
        "adds w0,w0,#0x0\n"
        );
}

- (void)viewDidLoad {
    [super viewDidLoad];
    func();
}
@end
image.png

5.4 案例四

// ViewController.m文件内容 
@implementation ViewController
void func(){
    asm(
        "mov w0,#0x0\n"
        //adds 可以改变标志位
        "adds w0,w0,#0x1\n"
        );
}

- (void)viewDidLoad {
    [super viewDidLoad];
    func();
}
@end

w0 的值为1 是正书,根据上面分析 N Z标志位都是0,运行得到状态寄存器的值为0x00000000 得到验证

C(Carry)标志

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

image.png

进位

mov w0,#0xaaaaaaaa;0xa 的二进制是 1010
adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
adds w0,w0,w0; 重复上面操作
adds w0,w0,w0

借位

mov w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff

5.5 案例五

// ViewController.m文件内容 
@implementation ViewController
void func(){
    asm(
        // w0 是4个字节,0xaaaaaaaa 相当于 1010 1010 1010 1010 1010 1010 1010 1010
        "mov w0,#0xaaaaaaaa\n"
        // 乘以2 相当于左移1位
        "adds w0,w0,w0\n"
        "adds w0,w0,w0\n"
        "adds w0,w0,w0\n"
        "adds w0,w0,w0\n"
        );
}

- (void)viewDidLoad {
    [super viewDidLoad];
    func();
}
@end
image.png

上图C为1 相当于溢出,这时修改寄存器cpsr为0x00000000,一步步执行,发现C位不断0 1 替换

V(Overflow)溢出标志

注意⚠️这里的计算数都是相同宽度的
上面执行的过程,状态寄存器并不知道两个数是正数还是负数,也不知道是有符号数还是无符号数?
cpsr寄存器的 v位会认为是有符号数运算,c位为无符号数运算,高级语言中有符号数与无符号数的运算,有没有溢出状态寄存器的 V C 位都会给出反馈

六. 面试题

6.1 面试题一
函数A调用函数B,A调用B的时候给B传递了参数,当函数B执行完成后,B函数的参数释放了吗?
这里以上面 函数的返回值 代码举例

- (void)viewDidLoad {
    [super viewDidLoad];
    struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
}

getStr(1, 2, 3, 4, 5, 6)方法中的 1 2 3 4 5 6 对于viewDidLoad方法来说,相当于局部变量,这时候如果参数不是6个而是9个,getStr(1, 2, 3, 4, 5, 6, 7, 8, 9); 其中7 8 9 三个参数是存放在viewDidLoad函数栈中,所以当getStr方法执行完成,这仨参数并没有释放,只有viewDidLoad执行完成才会释放。
6.2 面试题二
getStr函数调用之前开辟空间没?如果开辟了,开辟的是栈空间还是堆空间?

- (void)viewDidLoad {
    [super viewDidLoad];
    struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
}

这里开辟的是栈空间,开辟了栈空间之后,整个结构体都在栈中,占用了24字节内存,getStr执行完成之后,str2结构体依然在栈中。

如果viewDidLoad又返回值,并且返回的是 return &str2; 外界能否访问str2的地址来操纵结构体?
不能,因为str2整个结构体在栈空间中,viewDidLoad执行完成,str2结构体就会释放掉。一般来说不会直接返回结构体,而是返回结构体的指针,要想返回结构体指针,结构体需要开辟在堆空间,开辟完成之后再给结构体赋值,最后再返回结构体指针。

上一篇下一篇

猜你喜欢

热点阅读