ARM64汇编入门

现在iOS设备几乎已经都是ARM64架构,此外,Mac M1芯片的电脑也是基于ARM64架构,本文对ARM64汇编做一个简单的介绍。本文后面给出了一个汇编案例,通过汇编窥探代码底层的实现逻辑。

寄存器

ARM64汇编中有34个寄存器,其中包含31个通用寄存器(x0-x30),sppccpsr
Xcode可以通过register read指令查看所有寄存器的存储值:

(lldb) register read
General Purpose Registers:
        x0 = 0x0000000000000008
        x1 = 0x000000016fdfdc70
        x2 = 0x000000016fdfdc80
        x3 = 0x000000016fdfdd80
        x4 = 0x0000000000000000
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x0000000000000008
        x9 = 0x0000000000000002
       x10 = 0x0000000000000000
       x11 = 0x0000000000000002
       x12 = 0x0000000000000002
       x13 = 0x0000000000000000
       x14 = 0x0000000000000001
       x15 = 0x0000000000000044
       x16 = 0x000000030006f120
       x17 = 0x6ae100016fdfa280
       x18 = 0x0000000000000000
       x19 = 0x00000001000d4060
       x20 = 0x0000000100002ea0  
       x21 = 0x0000000100080070  
       x22 = 0x0000000000000000
       x23 = 0x0000000000000000
       x24 = 0x0000000000000000
       x25 = 0x0000000000000000
       x26 = 0x0000000000000000
       x27 = 0x0000000000000000
       x28 = 0x0000000000000000
        fp = 0x000000016fdfdaf0
        lr = 0x000000010000315c  
        sp = 0x000000016fdfdae0
        pc = 0x0000000100002f4c
      cpsr = 0x60001000
通用寄存器

通用寄存器x0-x3064bit, 如果用不到64位,可以用低位的32位(w0-w30), 也就是说x0w0本质上是一个寄存器,只是利用的位数不一样而已。

  • 通用寄存器可以用来存放函数参数

说明:有的博客说的是“只有x0-x7用来存放参数,如果函数参数超过了8个,则在压入函数栈中”,我目前测试的情况是至少15个参数都是可以通过寄存器来传值,更多的参数就没有测试了。

  • x0通常用来来存放返回值,如果返回的数据比较复杂,会放在x8的这个执行地址上。
特殊寄存器
  • sp寄存器:Stack Pointer,保存栈顶地址
  • fp寄存器:Frame Pointer,保存栈底地址
  • lr寄存器:Link Register,保存跳转指令下一条指令的地址(eg:bl跳转进入执行函数,lr则会保存函数调用完成后需要执行的指令地址)
  • pc寄存器: 保存当前执行中的指令的下一条指令的地址
cpsr寄存器(状态寄存器)

其他寄存器都是存储数据,是个统一的整体;状态寄存器有点特殊,它的每一位都有特殊的含义,记录特定的信息。

最常见的是NZCV标志位,分别代表运算过程中产生的不同状态,可以决定运算结果或者代码执行逻辑。

  • NNegative Cndition Flag,代表运算结果是负数
  • ZZero Condition Flag, Z 为 1 代表0,否则Z 为 0 代表 1
  • CCarry Condition Flag, 无符号运算有溢出时C 为 1
  • VOverflow Condition Flag, 有符号运算有溢出时C 为 1
xzr(零寄存器)

xzr/wzr分别表示64/32位,其做用就是0,写进去表明丢弃结果,读出来是0。

常用指令

加载/存储指令

加载/存储指令⽤于在寄存器和存储器之间传送数据,加载指令⽤于将存储器中的数据传送到寄存器,存储 指令则完成相反的操作。

  • ldr指令
ldr    x8, [sp]        // 将存储地址为sp的数据读取到x8寄存器中
ldr    x9, [sp, #0x8]  // 将存储地址为sp+0x8的数据读取到x9寄存器中
  • ldrb指令(只操作一个字节)
ldrb   w8, [sp, #0x8] // 将存储器地址为sp+0x8的1个字节数据读⼊寄存器w8,并将w8的⾼24位清零。
  • ldrh指令(只操作两个字节)
ldrh   w8, [sp, #0x8] // 将存储器地址为sp+0x8的2个字节数据读⼊寄存器w8,并将w8的⾼16位清零。
  • ldur指令 (u和负地址运算相关)
ldurb  w0, [x29, #-0x8]  // 将存储地址为x29-0x8的数据读取到w0寄存器中
  • stp指令(str 的变种指令,能够同时操做两个寄存器)
stp x29, x30, [sp, #0x10]  // 将 x29, x30 的值存⼊sp+0x10存储地址
  • str指令
str    x0, [sp]         // 将x0寄存器中的值存储到sp的存储地址
str    x0, [sp, #0x10]  // 将x0寄存器中的值存储到sp+0x10的存储地址
  • strb指令(只操作一个字节)
strb    x0, [sp, #0x10]  // 将x0寄存器中的低8位的字节的数据存储到sp+0x10的存储地址
  • strh指令(只操作两个字节)
strh    x0, [sp, #0x10]  // 将x0寄存器中的低16位的字节的数据存储到sp+0x10的存储地址
  • stur指令 (u和负地址运算相关)
stur    w0, [x29, #-0x8]  // 将x0寄存器中的数据存储到x29-0x8的存储地址
  • ldp指令(ldr 的变种指令,能够同时操做两个寄存器)
ldp    x29, x30, [sp, #0x10]  // 将sp+0x10的值取出来,存⼊寄存器 x29 和寄存器 x30
数据处理指令
  • mov指令(不用于内存地址)
mov    w8, #0x1   //将立即数赋值给w8寄存器
mov    x0, x8     //将给x0寄存器赋值给给x8寄存器
  • add指令 (加)
add    sp, sp, #0x20      // 将寄存器sp的值和立即数0x20相加后保存在寄存器sp中
add    x0, x1, x2         // 将寄存器 x1 和 x2 的值相加后保存到寄存器 x0 中
add    x0, x1, [x2]       // 将寄存器x1的值加上寄存器x2 的值做为地址,再取该内存地址的内容放⼊寄存器x0中
  • sub指令 (减)
sub    sp, sp, #0x20     // 将寄存器sp的值和立即数0x20相减后保存在寄存器sp中
sub    x0, x1, x2        // 将寄存器 x1 和 x2 的值相减后保存到寄存器 x0 中
  • mul指令 (乘)
mul    x0, x1, x2     // 将寄存器 x1 和 x2 的值相乘后结果保存到寄存器 x0 中
  • sdiv指令 (除,无符号除是udiv)
sdiv    x0, x1, x2     //  将寄存器 x1 和 x2 的值相除后结果保存到寄存器 x0 中
  • and指令 (位与)
and    x0, x1, x2     //  将寄存器 x1 和 x2 的值按位与后保存到寄存器 x0 中
  • orr指令 (位或)
orr    x0, x1, x2     //  将寄存器 x1 和 x2 的值按位或后保存到寄存器 x0 中
  • eor指令 (位异或)
eor    x0, x1, x2     //  将寄存器 x1 和 x2 的值按位异或后保存到寄存器 x0 中
  • cbnz指令 (和⾮ 0 ⽐较)
cbnz   x0, 0x100002f70    // 如果非0,跳转到0x100002f70指令执行
  • cbz指令 (和0 ⽐较)
cbz   x0, 0x100002f70    // 如果为0,跳转到0x100002f70指令执行
跳转指令
  • b指令 (直接跳转)
b      0x100002fd0
  • bl指令 (跳转前记录下一条指令地址)
bl     0x100002f54
  • blr指令 (跳转到某寄存器 (的值)指向的地址)
blr     x10
  • ret指令 (函数返回)
ret

汇编窥探代码底层

enum EnumTest: Int {
    case n1, n2, n3, n4
}

var e = EnumTest.init(rawValue: 1)

下面的代码是Swift枚举类型init?(rawValue:)方法的汇编代码,通过汇编就可以窥探该方法的实现逻辑。

0x100002f54 <+0>: sub    sp, sp, #0x20              // sp = sp - 32,将栈顶指针向下移动 32 字节
100002f58 <+4>:   str    x0, [sp, #0x8]             // 将x0参数存储到sp+0x8的存储位置 (将参数保存到局部变量)                  
100002f5c <+8>:   str    xzr, [sp, #0x10]           // 将sp+0x10的存储位置的数据清零  
100002f60 <+12>:  str    x0, [sp, #0x10]            // 将x0的数据又保存到sp+0x10的存储位置
100002f64 <+16>:  cbnz   x0, 0x100002f70            // 判断x0是否是0,如果是0执行0x100002f68指令,否则执行0x100002f70指令(分支1)
100002f68 <+20>:  strb   wzr, [sp, #0x1f]           // 将0保存到sp+0x1f的存储位置
100002f6c <+24>:  b  100002fd0                      // 直接跳转到0x100002fd0处理结果
100002f70 <+28>:  ldr    x9, [sp, #0x8]             // 将保存的局部变量参数读取出来,放在x9寄存器上
100002f74 <+32>:  mov    w8, #0x1                   // 将0x1存储到x8寄存器上
100002f78 <+36>:  subs   x8, x8, x9                 // 用0x1-x9寄存器的值放到x8寄存器上
100002f7c <+40>:  b.ne   0x100002f8c                // 如果等于0执行0x100002f80指令,否则执行0x100002f8c指令(分支2)
100002f80 <+44>:  mov    w8, #0x1                   // 将1赋值给w8寄存器
100002f84 <+48>:  strb   w8, [sp, #0x1f]            // 将w8寄存器中的1保存到sp+0x1f的存储位置
100002f88 <+52>:  b  100002fd0                      // 直接跳转到0x100002fd0处理结果
100002f8c <+56>:  ldr    x9, [sp, #0x8]                 
100002f90 <+60>:  mov    w8, #0x2
100002f94 <+64>:  subs   x8, x8, x9
100002f98 <+68>:  b.ne   0x100002fa8                // (分支3)
100002f9c <+72>:  mov    w8, #0x2
100002fa0 <+76>:  strb   w8, [sp, #0x1f]
100002fa4 <+80>:  b  100002fd0               
100002fa8 <+84>:  ldr    x9, [sp, #0x8]
100002fac <+88>:  mov    w8, #0x3
100002fb0 <+92>:  subs   x8, x8, x9
100002fb4 <+96>:  b.ne   0x100002fc4                // (分支4)
100002fb8 <+100>: mov    w8, #0x3
100002fbc <+104>: strb   w8, [sp, #0x1f]
100002fc0 <+108>: b  100002fd0                      // (分支5)
100002fc4 <+112>: mov    w8, #0x4
100002fc8 <+116>: str    w8, [sp, #0x4]
100002fcc <+120>: b  100002fd8               
100002fd0 <+124>: ldrb   w8, [sp, #0x1f]            // 将结果取出来放在w8寄存器中
100002fd4 <+128>: str    w8, [sp, #0x4]             // 将w8寄存器中的结果又放入sp+0x4的存储地址中
100002fd8 <+132>: ldr    w0, [sp, #0x4]             // sp+0x4的存储地址中的值赋值给w0 (相当于将结果放在了x0寄存器上)
100002fdc <+136>: add    sp, sp, #0x20              // 恢复栈顶位置
100002fe0 <+140>: ret                               // 函数执行完后返回

其实编译器帮我们实现的逻辑代码就是如下所示,因为下面的代码的汇编和上面的汇编代码是一致的:

init?(rawValue: Int) {
    switch rawValue {
        case 0: self = .n1
        case 1: self = .n2
        case 2: self = .n3
        case 3: self = .n4
        default: return nil
    }
}
惊奇的发现

原来原始值的枚举类型EnumTest .n1, .n2, .n3, .n4, 内存真实存储的值是 0,1,2,3, 而且nil的存储值竟然是4

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

推荐阅读更多精彩内容