iOS ARM64汇编03 -- 指令

  • 在Xcode中通过写汇编代码来调试汇编指令,具体步骤如下所示:
    • 首先在xcode工程中创建一个arm.s文件
Snip20210224_56.png
Snip20210224_57.png
  • 其次需创建一个头文件,公开.s文件中的函数供外界调用;
Snip20210224_60.png
Snip20210224_61.png
  • 汇编代码文件是以.s结尾的;

  • .text 表示函数实现在代码段;

  • .global _test 表示开启外界调用test函数的权限,如果不加会报错找不到_test函数;

  • 在arm.h文件中公开声明的是test函数,但在arm.s汇编文件中实现的是_test函数,这是由汇编底层机制决定的,汇编底层最终去获取的是_test;

  • 汇编代码调用如下所示:


    Snip20210224_63.png
  • 当断点停在第20行,点击左下方的向下箭头,进入汇编代码内部;

Snip20210224_64.png
  • 现在我们可以通过LLDB命令来做汇编调试了,在汇编调试之前先介绍几个LLDB命令

  • si(step int):汇编指令单步执行;

  • c(continue):跳过函数;

指令

ret指令
  • 格式:ret
  • 含义:表示函数返回;
  • 本质:将lr寄存器中的值赋值给pc寄存器,ret返回时会执行pc中的值所代表的函数;
mov指令
  • 格式: mov 目标寄存器 , 源操作数;
  • 含义:将源操作数移动(写入)到目标寄存器中;
Snip20210225_79.png
  • 汇编代码如下:
.text
.global _test

_test:
mov x0,#0x8
ret
  • LLDB调试结果:
Snip20210224_68.png
  • 可以看到x0寄存器确实通过mov指令,存储了0x0000000000000008这个值;
  • 井号0x8 我们称之为立即数,立即数的写法前面加上井号;
  • 汇编代码修改后如下:
.text
.global _test

_test:
mov x0,#0x8
mov x1,x0
ret
  • LLDB调试结果:
Snip20210224_69.png
  • mov x0,#0x8 指令执行之后 x0 = 0x0000000000000008;
  • si 汇编代码单步执行,则会执行mov x1,x0指令,将x0中的值,赋值给x1;
  • x1中的值也等于 0x0000000000000008;
add指令
  • 格式:add 目标寄存器, 操作数1,操作数2;
  • 含义:将操作数1与操作数2相加,然后赋值给目标寄存器;
Snip20210225_81.png
  • 汇编代码如下所示:
.text
.global _add

_add:
mov x0,#0x1
mov x1,#0x2
add x3,x0,x1
ret
  • 调用代码为:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    add();
    return YES;
}
  • LLDB调试结果:
Snip20210224_71.png
  • register read x0 读取x0寄存器中的值为0x0000000000000001;
  • si 单步执行 mov x1,#0x2指令 即寄存器x1赋值为0x0000000000000002;
  • si 单步执行 add x3,x0,x1指令 即x3=x0+x1,最终x3中的值为0x0000000000000003;
  • 将汇编代码修改如下所示:
.text
.global _add

_add:
add x0,x0,x1
ret
  • 调用代码为:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int sum = add(3,5);
    NSLog(@" add = %d",sum);
    return YES;
}
  • LLDB调试结果如下:
Snip20210225_76.png
  • 当断点停在第2行时,读取x0与x1寄存器中的值分别为3和5,就是外界传进来的参数值,说明add函数的参数被x0与x1两个寄存器接收了,验证了iOS ARM64汇编02-- 寄存器 中的阐述;
  • si 单步执行完 add x0,x0,x1 指令,此时x0寄存器中的值为8;
  • 放开断点,我们看到控制台中的打印结果为 add = 8; 说明add函数的汇编实现将x0寄存器中的值默认为函数的返回值,返回给外界了,验证了iOS ARM64汇编02-- 寄存器 中的关于x0寄存器的阐述;
sub指令
  • 格式:sub 目标寄存器, 操作数1,操作数2;
  • 含义:将操作数1与操作数2相减然后赋值给目标寄存器;
Snip20210225_80.png
  • 汇编代码如下:
.text
.global _sub

_sub:
sub x0,x0,x1
ret
  • 调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int num = sub(5,2);
    NSLog(@" num = %d",num);
    return YES;
}
  • LLDB调试结果如下:
Snip20210225_77.png
  • 当断点停在第2行时,读取x0与x1寄存器中的值分别为5和2,就是外界传进来的参数值,说明sub函数的参数被x0与x1两个寄存器接收了,验证了iOS ARM64汇编02-- 寄存器 中的阐述;
  • si 单步执行完 sub x0,x0,x1 指令,此时x0寄存器中的值为3;
  • 放开断点,我们看到控制台中的打印结果为 num = 3,说明sub函数的汇编实现将x0寄存器中的值默认为函数的返回值,返回给外界了,验证了iOS ARM64汇编02-- 寄存器 中的关于x0寄存器的阐述;
cmp指令
  • 格式:cmp 操作数1,操作数2;
  • 含义:将操作数1与操作数2进行比较,就是相减,同时更新cpsr寄存器中条件标志位的值;
  • cmp是compare的缩写,顾名思义就是比较的意思即比较指令;
Snip20210225_82.png
  • 汇编代码如下所示:
.text
.global _test

_test:
mov x0,#0x3
mov x1,#0x5
cmp x0,x1

ret
  • 外界调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    test();
    return YES;
}
  • LLDB调试结果如下:
  • 在执行 cmp x0,x1指令之前 通过register read命令可以看到cpsr = 0x60000000
  • 在执行完cmp x0,x1指令之后,cpsr = 0x80000000`;
  • cmp x0,x1 x0-x1 = -2其结果为负数;说明cpsr的N位(负数位)等于1;
Snip20210225_86.png
  • 从这里可以看出 cmp指令的执行结果会影响cpsr中的条件标志位N位上的值;
b指令

格式:b 目标地址;
含义:B指令是最简单的跳转指令,一旦遇到B指令,程序会无条件跳转到B之后所指定的目标地址处执行;

Snip20210225_87.png
  • 汇编代码如下所示:
.text
.global _test

_test:
b mycode
mov x0,#0x5
mycode:
mov x0,#0x8

ret
  • 外界调用代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    test();
    return YES;
}
  • LLDB调试结果如下:
Snip20210225_88.png
Snip20210225_89.png
  • 可以看到执行 b mycode指令后直接跳转到mycode,然后执行mov x0,#0x8指令;而mov x0,#0x5指令没有执行;
  • 上面介绍的是最简单的b指令,b指令还可以携带条件的,携带条件就会涉及到条件域的概念,下面介绍来条件域相关知识:
  • 介绍几种最常见的条件域如下所示:
    • EQ: equal 相等;
    • NE:not equal 不相等;
    • GT:great than 大于;
    • LT:less than 小于;
    • GE:great or equal 大于等于;
    • LE:less equal 小于等于;
Snip20210225_92.png
  • 汇编代码如下所示:
.text
.global _test

_test:
mov x0,#0x3
mov x1,#0x5
cmp x0,x1
beq mycode
mov x0,#0x8
ret  //必须加上ret 否则会继续执行下面mycode的相关指令
mycode:
mov x0,#0x9

ret
  • cmp x0,x1指令,比较x0与x1,将两者相减,其结果会影响cpsr寄存器中条件标志位;
  • beq mycode指令,会根据cpsr寄存器中条件标志位,判断是否满足条件,然后决定执行下一步的汇编指令;
  • 由上面的cmp指令与带条件的b指令两者之间的配合使用,很像C代码中的if-else逻辑结构,现在我们写一段简单的关于if-else的C代码,看看底层的汇编是怎么实现的?- OC代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int a = 5;
    int b = 8;
    if (a == b) {
        printf("a = b");
    }else{
        NSLog(@" a != b");
    }
    return YES;
}
  • 汇编代码分析如下:
Snip20210225_94.png
  • 图中的w8与w9,分别存储的就是a和b的值;
  • if-else的汇编实现就是结合cmp指令与带条件的b指令;
bl指令
  • 格式:bl 目标地址;
  • 含义:BL指令是另一个跳转指令,是带返回的跳转指令,所以在跳转之前,它会先将当前标记位的下一条指令存储在寄存器lr(x30)中,然后跳转到标记处开始执行代码,当遇到ret时,会将lr(x30)中存储的地址重新加载到PC寄存器中,使得程序能返回标记位的下一条指令继续执行;
Snip20210225_95.png
  • 汇编代码如下:
.text
.global _test

//mycode相当于一个内部函数
mycode:
mov x0,#0x1
mov x1,#0x2
add x2,x0,x1
ret

_test:
mov x6,#0x6
bl mycode
mov x4,#0x4
mov x5,#0x5

ret
  • 汇编代码分析如下:
  • 在执行bl mycode指令之前:
Snip20210225_98.png
  • 在执行bl mycode指令时:
Snip20210225_99.png
  • 可以看到bl mycode指令的下一个指令地址是 0x10288d9d4
  • 当执行 bl mycode指令时,首先链接寄存器lr会将当前标记位的下一条指令存储起来,看到lr中的存储值发生了变化,变成了 0x00000001026899d4, 然后跳转到mycode执行;
  • 当mycode执行到ret指令时,再去调用lr寄存器中存储地址,回到0x10288d9d4;
  • bl指令 相当于 函数调用;
ldr指令 -- 加载内存 load
  • 格式:ldr 目标寄存器 , [寄存器]
  • 含义:根据右侧寄存器 --> 内存地址 --> 加载内容数据 按目标寄存器的位数加载写入目标寄存器,即从内存地址中读取数据,存到目标寄存器中,至于怎么读取数据,读多少字节数据与目标寄存器有关;
Snip20210225_100.png
  • 汇编代码如下:
.text
.global _test

_test:

ldr x0,[x1]

ret
  • 外界调用代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int a = 8;
    test();
    return YES;
}
  • LLDB调试如下:
Snip20210225_102.png
  • p &a 获取变量a的地址值 0x000000016d88712c;
  • register write x1 0x000000016d88712c 将a的地址值写入x1寄存器;
  • 执行ldr x0,[x1]指令 即将x1寄存器中的内存地址为首地址读取内存8个字节的内存数据,存入x0寄存器中去;
  • register read x0 所以 x0中存储值为0x0000000000000008;
  • 汇编代码修改如下:
.text
.global _test

_test:

ldr w0,[x1]

ret
  • 唯一差别的地方是:将x1寄存器中的内存地址为首地址读取内存4个字节的内存数据,存入w0寄存器中去,因为w0寄存器是32位-存储4个字节的数据;
  • 关于ldr 加载内存指令的相关写法即含义:
LDR x0, [x1]        ;将存储器地址为x1的字数据读入寄存器x0
LDR x0, [x1, x2]    ;将存储器地址为x1+x2的字数据读入寄存器x0
LDR x0, [x1, #8]    ;将存储器地址为x1+8的字数据读入寄存器x0
LDR x0, [x1, x2]!   ;将存储器地址为x1+x2的字数据读入寄存器x0,并将新地址x1+x2写入x1
LDR x0, [x1, #8]!   ;将存储器地址为x1+8的字数据读入寄存器x0,并将新地址x1+8写入x1
LDR x0, [x1], x2    ;将存储器地址为x1的字数据读入寄存器x0,并将新地址x1+x2写入x1
LDR x0, [x1, x2, LSL#2]!    ;将存储地址为x1+x2*4的字数据写入寄存器x0,并将新地址x1+x2*4写入x1
LDR x0. [x1], x2, LSL#2     ;将存储地址为x1的字数据写入寄存器x0,并将新地址x1+x2*4写入x1
ldur指令 -- 加载内存 load
  • ldur指令与ldr指令用法完全相同,唯一的区别在于指令LDR x0, [x1, #立即数],当ldr指令时,立即数是正数,当ldur指令时,立即数是负数;
  • ldr x0, [x1, #0x8]
  • ldur x0, [x1, #-0x8]
ldp指令 加载内存 load
  • 格式:ldp 寄存器1,寄存器2,[寄存器3];
  • 含义:读取寄存器3 --> 内存地址 --> 内存数据,按字节顺序放入寄存器1和寄存器2中;
  • ldp中p是pair的缩写,pair -- 对,一对;
  • 汇编代码如下:
.text
.global _test

_test:
ldp w0,w1,[x2,#0x5]

ret
  • 调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int a = 8;
    test();
    return YES;
}
  • LLDB调试结果如下:
Snip20210226_103.png
  • p &a 打印变量a的内存地址;
  • register write x2 0x000000016f60f12c 将变量a的内存地址写入x2寄存器;
  • si 单步执行 ldp w0,w1,[x2]
  • x 0x000000016f60f12c 是查看0x000000016f60f12c内存地址中数据内容;
  • 最后看到w0中放入了0x000000016f60f12c内存地址中前4个字节数据,w1中放入了0x000000016f60f12c内存地址中后4个字节数据;
str指令 写入内存 store
  • 格式:str 寄存器1 , [寄存器2];
  • 含义:将寄存器1中存储值,写入寄存器2-->内存地址 中去;
  • 汇编代码如下:
.text
.global _test

_test:
str x0,[x1]

ret
  • 调用代码如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    int a = 8;
    test();
    return YES;
}
  • LLDB调试如下:
Snip20210226_104.png
  • p &a 打印变量a的内存地址;
  • register write x0 3 往寄存器x0中写入值3;
  • register write x1 0x000000016ee0312c 往寄存器x1中写入变量a的内存地址0x000000016ee0312c;
  • si 单步执行str x0,[x1],即将x0=3,写入x1= 0x000000016ee0312c的内存地址中,最终看到 a = 3;
stur指令写入内存 store
  • stur指令与str指令用法完全相同,唯一的区别在于指令str x0, [x1, #立即数],当str指令时,立即数是正数,当stur指令时,立即数是负数;
    str x0, [x1, #0x8]
    stur x0, [x1, #-0x8]
stp指令 写入内存 store
  • 格式:stp 寄存器1,寄存器2,[寄存器3];
  • 含义:将寄存器1与寄存器2中的数据 分别写入寄存器3-->内存地址 中去;

你可能感兴趣的:(iOS ARM64汇编03 -- 指令)