认识ARM64汇编

作者: fanglaoda | 来源:发表于2018-12-18 16:01 被阅读0次

[TOC]

之前说过学习汇编就是学习寄存器和指令,查看代码请连接真机。

寄存器

arm64汇编中寄存器是64bit的,使用X[n]表示,低32位以w[n]表示

15450350776264.jpg

64位架构中有3164位的通用寄存器。

可以通过register read查看

(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000001
        x1 = 0x00000002805d8a20
        x2 = 0x00000002837e7980
        x3 = 0x00000002805d8a20
        x4 = 0x0000000000000001
        x5 = 0x0000000000000001
        x6 = 0x0000000100d54000  
        x7 = 0x0000000000000000
        x8 = 0x0000200000000000
        x9 = 0x000161a1d63f7945 (0x00000001d63f7945) (void *)0x01d63f7cb0000001
       x10 = 0x0000000000000000
       x11 = 0x000000000000006d
       x12 = 0x000000000000006d
       x13 = 0x0000000000000000
       x14 = 0x0000000000000000
       x15 = 0x00000001ca634e6d  "touchesBegan:withEvent:"
       x16 = 0x000000019c4cd47c  libobjc.A.dylib`objc_storeStrong
       x17 = 0x0000000000000000
       x18 = 0x0000000000000000
       x19 = 0x0000000100f17810
       x20 = 0x00000002837e7920
       x21 = 0x00000002805d8a20
       x22 = 0x00000001ca634e6d  "touchesBegan:withEvent:"
       x23 = 0x0000000100e11a30
       x24 = 0x00000002837e7980
       x25 = 0x0000000100e11a30
       x26 = 0x00000002805d8a20
       x27 = 0x00000001ca62d900  "countByEnumeratingWithState:objects:count:"
       x28 = 0x00000002805d8a20
        fp = 0x000000016f47d730
        lr = 0x00000001009866dc  ArmAssembly`-[ViewController touchesBegan:withEvent:] + 84 at ViewController.m:38
        sp = 0x000000016f47d720
        pc = 0x0000000100986720  ArmAssembly`foo1 + 16 at ViewController.m:46
      cpsr = 0x80000000

(lldb) 

x0~x7:一般是函数的参数,大于8个的会通过堆栈传参。

/*
 * ArmAssembly`foo2:
 0x101062760 <+0>:  sub    sp, sp, #0x20             
 0x101062764 <+4>:  str    w0, [sp, #0x1c]
 0x101062768 <+8>:  str    w1, [sp, #0x18]
 0x10106276c <+12>: str    w2, [sp, #0x14]
 0x101062770 <+16>: str    w3, [sp, #0x10]
 0x101062774 <+20>: str    w4, [sp, #0xc]
 0x101062778 <+24>: str    w5, [sp, #0x8]
 0x10106277c <+28>: str    w6, [sp, #0x4]
 ->  0x101062780 <+32>: add    sp, sp, #0x20             
 0x101062784 <+36>: ret

 */
void foo7(int a,int b,int c ,int d ,int e,int f,int g) {
    
}
/*
 *ArmAssembly`foo8:
 0x10024275c <+0>:  sub    sp, sp, #0x20             
 0x100242760 <+4>:  str    w0, [sp, #0x1c]
 0x100242764 <+8>:  str    w1, [sp, #0x18]
 0x100242768 <+12>: str    w2, [sp, #0x14]
 0x10024276c <+16>: str    w3, [sp, #0x10]
 0x100242770 <+20>: str    w4, [sp, #0xc]
 0x100242774 <+24>: str    w5, [sp, #0x8]
 0x100242778 <+28>: str    w6, [sp, #0x4]
 0x10024277c <+32>: str    w7, [sp]
 ->  0x100242780 <+36>: add    sp, sp, #0x20             
 0x100242784 <+40>: ret

 */
void foo8(int a,int b,int c ,int d ,int e,int f,int g,int h) {
    
}


/*
 * ArmAssembly`foo9:
 0x100dc2718 <+0>:  sub    sp, sp, #0x30  
           
 0x100dc271c <+4>:  ldr    w8, [sp, #0x30] ; 大于8个参数的时候 已经是从内存栈中去取数据了
 
 0x100dc2720 <+8>:  str    w0, [sp, #0x2c]
 0x100dc2724 <+12>: str    w1, [sp, #0x28]
 0x100dc2728 <+16>: str    w2, [sp, #0x24]
 0x100dc272c <+20>: str    w3, [sp, #0x20]
 0x100dc2730 <+24>: str    w4, [sp, #0x1c]
 0x100dc2734 <+28>: str    w5, [sp, #0x18]
 0x100dc2738 <+32>: str    w6, [sp, #0x14]
 0x100dc273c <+36>: str    w7, [sp, #0x10]
 0x100dc2740 <+40>: str    w8, [sp, #0xc]
 0x100dc2744 <+44>: add    sp, sp, #0x30             
 0x100dc2748 <+48>: ret

 */
void foo9(int a,int b,int c ,int d ,int e,int f,int g,int h,int i) {
    
}

可以看到当参数的个数大于8个的时候就不会从寄存器中去取参数了。

x0:一般表示返回值

int fooReturnValue() {
    return  10;
}

汇编代码

ArmAssembly`fooReturnValue:
    0x100ece7b4 <+0>: mov    w0, #0xa
->  0x100ece7b8 <+4>: ret    

通过lldb指令

(lldb) register read x0
      x0 = 0x000000000000000a
(lldb) 

确实是10

pc:表示当前执行的指令的地址

这个类似8086汇编里面的ip

15450984590679.jpg

比如我们断点该改代码处,查看pc寄存器的值


(lldb) register read pc
      pc = 0x0000000100b6e7b8  ArmAssembly`fooReturnValue + 4 at ViewController.m:140
(lldb) 

lr:链接寄存器,存放着函数的返回地址

lr也就是x30,这个里面存放着函数的返回地址

比如有下面2个方法,方法fooFp方法内部调用fooFp2

void fooFp() {
    int a = 4;
    int b = 5;
    fooFp2();
}

void fooFp2() {
    int a = 2;
    int b = 3;
}

fooFp的汇编代码

ArmAssembly`fooFp:
 0x100dbe76c <+0>:  sub    sp, sp, #0x20             ; =0x20
 0x100dbe770 <+4>:  stp    x29, x30, [sp, #0x10]
 0x100dbe774 <+8>:  add    x29, sp, #0x10            ; =0x10
 
 0x100dbe778 <+12>: mov    w8, #0x5
 0x100dbe77c <+16>: orr    w9, wzr, #0x4
 0x100dbe780 <+20>: stur   w9, [x29, #-0x4]
 0x100dbe784 <+24>: str    w8, [sp, #0x8]
 0x100dbe788 <+28>: bl     0x100dbe798               ; fooFp2 at ViewController.m:154
 
 0x100dbe78c <+32>: ldp    x29, x30, [sp, #0x10]
 0x100dbe790 <+36>: add    sp, sp, #0x20             ; =0x20
 0x100dbe794 <+40>: ret
 

0x100dbe788 <+28>: bl 0x100dbe798这行就是在调用方法fooFp2,如果调用完fooFp2后函数应该要继续执行,那么肯定是要到0x100dbe788 <+28>: bl 0x100dbe798下一行也就是地址0x100dbe78c处,我们到fooFp2中查看下lr寄存器的值

(lldb) register read lr
      lr = 0x0000000100dbe78c  ArmAssembly`fooFp + 32 at ViewController.m:170
(lldb) 

当然, 本质上还是将lr的值给了pc寄存器了,也就是ret指令做的事情。

栈寄存器

分为sp栈顶寄存器和fp栈底寄存器。(如果熟悉8086汇编的话类似于分别类似于spbp

还是用上面的方法fooFpfooFp2来说明这2个寄存器。

不嫌啰嗦这边还是复制下fooFp的汇编代码


ArmAssembly`fooFp:
 0x100dbe76c <+0>:  sub    sp, sp, #0x20             ; 申请栈空间
 0x100dbe770 <+4>:  stp    x29, x30, [sp, #0x10]     ; 保护寄存器的值
 0x100dbe774 <+8>:  add    x29, sp, #0x10            ; 改变fp寄存器的值,用于执行栈底
 
 0x100dbe778 <+12>: mov    w8, #0x5
 0x100dbe77c <+16>: orr    w9, wzr, #0x4
 0x100dbe780 <+20>: stur   w9, [x29, #-0x4]
 0x100dbe784 <+24>: str    w8, [sp, #0x8]
 0x100dbe788 <+28>: bl     0x100dbe798               ; fooFp2 at ViewController.m:154
 
 0x100dbe78c <+32>: ldp    x29, x30, [sp, #0x10]     ; 恢复之前保存的fp和lr的值
 0x100dbe790 <+36>: add    sp, sp, #0x20             ; 恢复sp指针
 0x100dbe794 <+40>: ret 
 

整个过程如下图

15451047620385.jpg

cpsr:程序状态寄存器

cpsr(Current Program Status Register )

spsr (Saved Program Status Register) 异常状况下使用

xzr:零寄存器

表示zero register,一般用来默认值,里面存储的值都是0。

  1. xzr:64位的
  2. wzr:32位的

指令

寻址方式大概规则说明

ADD R0,R0,#1  // R0 = R0 + #1 表示寄存器R0的值 + 1再赋值给R0

ADD R0,R1,R2  // R0 = R1+R2 

ADD R0,R1,[R2] // R0←R1+[R2]

LDR R0,[R1,#4]  // ;R0←[R1+4]

LDR R0,[R1,#4]!  // R0←[R1+4]、R1←R1+4

LDR R0,[R1] ,#4  // ;R0←[R1]、R1←R1+4

LDR R0,[R1,R2] // R0←[R1+R2]

  1. []一般表示是取值的意思,[R2]表示取出R2所存的内存地址比如是0x10000所对应的值比如是66
  2. LDR R0 [R1,#4] :寄存器 R1 的内容加上4形成操作数的有效地址,从而取得操作数存入寄存器 R0 中。
  3. LDR R0,[R1,#4]!:将寄存器 R1 的内容加上 4 形成操作数的有效地址,从而取得操作数存入寄 存器 R0 中,然后,R1 的内容自增 4 个字节。
  4. LDR R0,[R1] ,#4:以寄存器 R1 的内容作为操作数的有效地址,从而取得操作数存入寄存器 R0 中,然后,R1 的内容自增 4 个字节。
  5. LDR R0,[R1,R2]:将寄存器 R1 的内容加上寄存器 R2 的内容形成操作数的有效地址,从而取得 操作数存入寄存器 R0 中。

计算指令

ADD 加法

ADD R0,R1,R2 // R0 = R1 + R2
ADD R0,R1,#256  // R0 = R1 + 256 

ADD R0,R2,R3,LSL#1 //  R0 = R2 + (R3 << 1)

SUB 减法

SUB R0,R1,R2 // R0 = R1 - R2
SUB R0,R1,#256 //R0 = R1 - 256 
SUB R0,R2,R3,LSL#1 // R0 = R2 - (R3 << 1)

逻辑运算

AND逻辑与、EOR逻辑异或、ORR逻辑或、LSL逻辑左移、LSR逻辑右移

内存指令

一般是ST开头的为存数据,比如说STRSTPSTUR
LD开头的表示取数据,比如说LDRLDPlDUR

str    w8, [sp, #0x8] // 表示 将w8存放到sp+0x8表示的内存中
stur   w9, [x29, #-0x4] // 表示 将w9存放到x29, #-0x4表示的内存中
stp x1, x2, [sp, #-16] // 表示 从sp-0x16对应地址的开始存放 x1、x2的表示的值

ldp    x29, x30, [sp, #0x10] //表示 从sp+0x10对应地址的开始取出值赋值给x29,x30

P:可以理解为pair
u: 表示负数

跳转指令

bbl一般搭配cmp指令使用

b:无条件跳转,一般是什么函数内部的ifswitch条件判断;
bl:带函数返回值的跳转,一般是调用其他的函数;

0x100432624 <+88>:  cmp    w10, #0x1                 ; =0x1 
0x100432628 <+92>:  b.le   0x100432630               ; 
    

<1>. CMP:将寄存器 R1 的值与立即数 0x1 相减,并根据结果设置 CPSR 的标志位

标志位的可能值如下表

条件码 助记符后缀 标志 含义
0000 EQ Z 置位 相等 ==
0001 NE Z 清零 不相等 !=
0010 CS C 置位 无符号数大于或等于
0011 CC C 清零 无符号数小于
0100 MI N 置位 负数
0101 PL N 清零 正数或零
0110 VS V 置位 溢出
0111 VC V 清零 未溢出
1000 HI C 置位 Z 清零 无符号数大于
1001 LS C 清零 Z 置位 无符号数小于或等于
1010 GE N 等于 V 带符号数大于或等于
1011 LT N 不等于 V 带符号数小于
1100 GT Z 清零且(N 等于 V) 带符号数大于
1101 LE Z 置位或(N 不等于 V) 带符号数小于或等于
1110 AL 忽略 无条件执行

<2>. b.le 0x100432630:表示如果w10 <= 0x1那么就执行0x100432630

ret指令

  1. 函数返回
  2. lr(x30)寄存器器的值赋值给pc寄存器器。

了解了这些基本知识读一些汇编代码应该没问题了,没有提到的自己查下资料也差不多了。

ARM-GNU汇编

如果你只会arm汇编去看runtime汇编源码的时候会发现还是有些东西不明白,比如下面的代码

#if SUPPORT_TAGGED_POINTERS
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0
    .globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
    .fill 256, 8, 0
#endif

    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached

[...]

ARM汇编开发指用ARM提供的汇编指令,进行ARM程序的开发。
ARM汇编开发,有两种开发方式,一种是使用ARM汇编,一种是使用ARM GNU汇编。两种汇编开发,使用的汇编指令是完全一样的,区别是宏指令,伪指令,伪操作不一样。其实两种开发方式的区别在于所使用的编译工具不一样。
对于ARM汇编,使用的是ARM公司开发的编译器,而ARM GNU汇编,是使用GNU为ARM指令集开发的编译器,也就是arm-gcc。

2种方式的不同之处就是伪操作的不同,苹果遵循的是GNU汇编规范的。点击这个可以查看各个伪操作的意思,比如:

.global:全局声明;
.macro:定义一个宏;
.align:对齐方式
.text:指定程序在哪个段
...

关于汇编还有很多,比如书写汇编代码,内联汇编,有了目前的基础,相信学起来也是很快的。

感谢

  1. https://zh.wikipedia.org/wiki/ARM%E6%9E%B6%E6%A7%8B#cite_note-v8arch-1
  2. https://www.arm.com/files/downloads/ARMv8_Architecture.pdf
  3. http://www.lujun.org.cn/?p=3943
  4. https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/Assembler/000-Introduction/introduction.html#//apple_ref/doc/uid/TP30000851-CH211-SW1

(完)。

demo地址

相关文章

  • ARM64汇编入门 - 汇编基础

    ARM64汇编入门 - 汇编基础ARM64汇编入门 - 汇编基础

  • ARM64汇编学习笔记一(初始汇编)

    ARM64汇编学习笔记一(初始汇编)

  • IOS arm64常用的汇编

    导论 生成汇编文件 寄存器 arm64 汇编条件域 arm64 汇编指令 总结 1. 生成汇编文件 2. 寄存器 ...

  • 认识ARM64汇编

    [TOC] 之前说过学习汇编就是学习寄存器和指令,查看代码请连接真机。 寄存器 在arm64汇编中寄存器是64bi...

  • ARM 64 常见汇编指令

    ARM64常用的汇编指令 运算指令 程序跳转指令

  • iOS逆向

    ARM64汇编1、汇编初探[https://www.jianshu.com/p/f3ae7cef6659]2、函数...

  • arm64汇编基础

    iOS汇编 真机:arm64汇编 模拟器:x86汇编 将c语言的代码转化成汇编: 1. 寄存器 lldb查看当前a...

  • iOS汇编基础(二)寄存器

    以arm64为例 xcode调试汇编 1. xcode 查看运行时的汇编代码 debug -> debug wor...

  • ARM64 学习

    iOS汇编 真机 arm64汇编,GNU 模拟器 x86, AT&T 汇编 寄存器 指令 堆栈 通用寄存器 64b...

  • ARM 汇编学习记录

    1. 简介 根据 CPU 架构不同,汇编主要可以分为两种:模拟器上的x86 汇编、真机上的 arm64 汇编,主要...

网友评论

    本文标题:认识ARM64汇编

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