美文网首页
iOS ARM64汇编03 -- 指令

iOS ARM64汇编03 -- 指令

作者: YanZi_33 | 来源:发表于2021-02-24 11:13 被阅读0次
    • 在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 -- 指令

          本文链接:https://www.haomeiwen.com/subject/whfjfltx.html