认识ARM64汇编

[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地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容