iOS ARM64 汇编入门

这里主要介绍 iOS 平台上 ARM64 架构的寄存器和指令以及内存堆栈。

堆栈

在 iOS 中,栈空间是向下生长的,也就是从高地址向低地址生长,栈顶在低地址,栈底在高地址,比如:
栈顶地址:0x00
栈底地址:0x1C

寄存器

ARM64 主要有34个寄存器,其中包括 31 个通用寄存器(x0-x30)以及 SP,PC,CPSR,可以通过在调试时输入 register read 命令查看

寄存器是64位的,可以使用W0-W30来访问前32位空间

x0-x7

主要用来存放函数传递的参数,以及临时变量,超过8个参数的存放在栈上,其中 x0 也用来存放函数的返回值

FP(x29)

通常存放栈帧地址(栈底指针),指向当前函数栈的底部

LR(x30)

通常称 x30 为程序链接寄存器,因为这个寄存器会记录着当前方法的调用方地址,也就是当前方法返回后程序继续执行的下一条指令地址

SP

栈顶指针,指向当前函数栈的顶部

PC

程序计数器,指向即将要执行的下一条指令地址,软件无法修改

CPSR

状态寄存器,有些指令会修改 CPSR 的值,比如 CPM,有些指令会根据 CPSR 的值来做跳转操作。
CPSR 的每一位值都有特殊的意义,其中最常见的是 NZCV 标志位

NZCV是状态寄存器的条件标志位,分别代表运算过程中产生的状态,其中:
N, negative condition flag,一般代表运算结果是负数
Z, zero condition flag, 指令结果为0时Z=1,否则Z=0;
C, carry condition flag, 无符号运算有溢出时,C=1。
V, oVerflow condition flag 有符号运算有溢出时,V=1。

常用指令

常用指令可大致分为运算指令,寻址指令,跳转指令等

运算指令

mov x1,x0          ;传送:x1 = x0
add x0,x1,x2       ;加法:x0 = x1 + x2
sub x0,x1,x2       ;减法:x0 = x1 - x2
mul x0,x1,x2.      ;乘法:x0 = x1 * x2
and x0,x0,#0xF     ;位与:x0 = x0 & 0xF
orr x0,x0,#9       ;位或:x0 = x0 | 9
eor x0,x0,#0xF     ;异或:x0 = x0 ^ 0xF;
lsl x0,#1          ;逻辑左移:x0 = x0 << 1
lsr x0,#1          ;逻辑右移:x0 = x0 >> 1,左边统一补0
asr x0,#1          ;算术右移:x0 = x0 >> 1,左边补符号位

寻址指令

分为两种,存和取
L 打头的基本都是取值指令,如 LDR(Load Register)、LDP(Load Pair)
S 打头的基本都是存值指令,如 STR(Store Register)、STP(Store Pair)

ldr    x0,[x1]               ;从 x1 指向的地址里面取出一个64位大小的数存入x0
ldp    x1,x2,[x10, #0x10]    ;从 x10+0x10 指向的地址里面取出2个64位的数,分别存入x1、x2
str    x5,[sp, #24]          ;往内存中写数据(偏移值为正), 把 x5 的值(64位的数值)存到 sp+24 指向的地址内存上
stur   w0,[x29, #0x8]        ;往内存中写数据(偏移值为负),将 w0 的值存储到 x29 - 0x8 这个地址里
stp    x29,x30,[sp, #-16]!   ;把 x29、x30 的值存到 sp-16 的地址上,并且把sp-=16  Note:后面有个感叹号的,然后没有stup这个指令哈
ldp    x29,x30,[sp],#16      ;从 sp 地址取出16 byte数据,分别存入x29、x30,然后 sp+=16

跳转指令

bl/b bl 是有返回的跳转;b 是无返回的跳转,BL的L也可以理解为Lr
跳转指令可以配合条件状态(根据 CPSR 相应位置的值)

B    ;跳转指令,可带条件跳转与cmp配合使用
BL   ;带返回的跳转指令, 返回地址保存到LR(X30)
BLR  ; 带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址(例:blr    x8 ;跳转到x8保存的地址中去执行)
RET  ;子程序返回指令,返回地址默认保存在LR(X30)

比较指令

cmp x1,x0  ;将x1和x0(可以是立即数)的值相减,并根据结果设置 CPSR 标志位
CBZ  ;比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)
CBNZ ;比较,如果结果非零(Non Zero)就转移(只能跳到后面的指令)

入门Demo

以一个简单的 C 程序为例,如下代码:

int sum(int a, int b)
{
    int c = a + b + 1;
    return c;
}
int main() 
{
    int a = 10;
    int b = 20;
    int rs = sum(a,b);
    if (rs > 30) {
        return 0;
    }
    else {
        return 1;
    }    
}

有2个函数,main 函数和 sum 函数,通过 clang 命令成汇编代码

clang -arch arm64 -S testAsm.c

最后生成的汇编代码如下:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 10, 15    sdk_version 10, 15, 4
    .globl  _sum                    ; -- Begin function sum
    .p2align    2
_sum:                                   ; @sum
    .cfi_startproc
; %bb.0:
    sub sp, sp, #16             ; =16
    .cfi_def_cfa_offset 16
    str w0, [sp, #12]
    str w1, [sp, #8]
    ldr w8, [sp, #12]
    ldr w9, [sp, #8]
    add w8, w8, w9
    add w8, w8, #1              ; =1
    str w8, [sp, #4]
    ldr w0, [sp, #4]
    add sp, sp, #16             ; =16
    ret
    .cfi_endproc
                                        ; -- End function
    .globl  _main                   ; -- Begin function main
    .p2align    2
_main:                                  ; @main
    .cfi_startproc
; %bb.0:
    sub sp, sp, #32             ; =32
    stp x29, x30, [sp, #16]     ; 16-byte Folded Spill
    add x29, sp, #16            ; =16
    .cfi_def_cfa w29, 16
    .cfi_offset w30, -8
    .cfi_offset w29, -16
    stur    wzr, [x29, #-4]
    mov w8, #10
    str w8, [sp, #8]
    mov w8, #20
    str w8, [sp, #4]
    ldr w0, [sp, #8]
    ldr w1, [sp, #4]
    bl  _sum
    str w0, [sp]
    ldr w8, [sp]
    cmp w8, #30                 ; =30
    b.le    LBB1_2
; %bb.1:
    stur    wzr, [x29, #-4]
    b   LBB1_3
LBB1_2:
    mov w8, #1
    stur    w8, [x29, #-4]
LBB1_3:
    ldur    w0, [x29, #-4]
    ldp x29, x30, [sp, #16]     ; 16-byte Folded Reload
    add sp, sp, #32             ; =32
    ret
    .cfi_endproc
                                        ; -- End function
.subsections_via_symbols

先看 sum 函数的代码(简单起见,这里去除一些不(看)重(不)要(懂)的代码)

_sum:                    ; sum 函数入口
    sub sp, sp, #16      ; sp = sp-16,栈顶指针向下移16字节,相当于分配了16个字节的栈空间(iOS中,栈是向下(低地址)生长的)
    str w0, [sp, #12]    ; 将 w0(x0的低32位) 存放到 s p12的位置(参数a)
    str w1, [sp, #8]     ; 将 w1(x1的低32位) 存放到 sp+8 的位置(参数b)
    ldr w8, [sp, #12]    ; 将 sp+12 位置的值读到 w8 上,也就是 w8=a
    ldr w9, [sp, #8]     ; 将 sp+8 位置的值读到 w9 上,也就是 w9=b
    add w8, w8, w9       ; 两数相加 w8 = w8 + w9
    add w8, w8, #1       ; w8 = w8 + 1
    str w8, [sp, #4]     ; 将 w8 保存到 sp+4 的位置
    ldr w0, [sp, #4]     ; 将 sp+4 的位置读到 w0 上(前面说过,x0也用来存放函数返回值)
    add sp, sp, #16      ; 将栈顶指针向上移16字节(恢复栈指针,相当于释放堆栈)
    ret                  ; 函数返回

总体上逻辑比较简单,基本上没什么难懂的地方

这里有个不明白的地方就是

str w8, [sp, #4]
ldr w0, [sp, #4]

为什么不直接

mov w0, w8

有知道的大佬还请指教一下,为了寄存器内容可以恢复么?

再来看 main 函数的,同样去除一些不重要的代码

_main:                        ; main函数入口
    sub sp, sp, #32           ; sp = sp - 32,将栈顶指针向下移动 32 字节
    stp x29, x30, [sp, #16]   ; 将 x29,x30 存储到 sp+16 的位置(x29,x30 参考前面介绍,这里相当于做个备份,因为后面要修改这2个寄存器)
    add x29, sp, #16          ; x29 = sp + 16(相当于移动栈底指针,堆栈大小 16 字节)
    stur    wzr, [x29, #-4]   ; 将 wzr(也叫零寄存器,这是一个特殊的寄存器,值为0)存储到 x29 - 4 的位置,相当于把这快内存清0
    mov w8, #10               ; 将 w8 置为 10
    str w8, [sp, #8]          ; 将 w8 存储到 sp+8 的位置
    mov w8, #20               ; 将 w8 置为 20
    str w8, [sp, #4]          ; 将 w8 存储到 sp+4 的位置  
    ldr w0, [sp, #8]          ; 将 sp+8 读到 w0(w0 = 10)
    ldr w1, [sp, #4]          ; 将 sp+4 读到 w1 (w1 = 20,前面说过x0-x7是存放参数的)
    bl  _sum                  ; 跳转到 sum 函数 
    str w0, [sp]              ; 存储函数返回值(w0)到 sp
    ldr w8, [sp]              ; 读取 sp 的值到 w8
    cmp w8, #30               ; 将 w8 的内容和30比较,同时设置 CPSR 寄存器
    b.le    LBB1_2            ; 根据 CPSR 结果来决定是跳转 LBB1_2(其中条件le的意思是小于等于的意思,具体可以参考我下面的链接)
    stur    wzr, [x29, #-4]   ; 将 x29-4 位置内存清0 
    b   LBB1_3                ; 跳转到 LBB1_3
LBB1_2:
    mov w8, #1                ; 将 w8 置 1  
    stur    w8, [x29, #-4]    ; 将 w8 保存到 x29-4 的位置
LBB1_3:
    ldur    w0, [x29, #-4]    ; 读取 x29-4 位置的值到 w0 作为函数返回值
    ldp x29, x30, [sp, #16]   ; 将 sp+16 位置的值读取到 x29,x30(恢复x29,x30)
    add sp, sp, #32           ; sp = sp + 32,恢复栈顶指针,释放堆栈
    ret                       ; 函数返回 

main 函数和 sum 函数的分析基本就到这里,基本逻辑还是比较简单的,这里有个最大的区别就是 main 函数有 x29,x30 的备份&恢复操作,原因是 sum 函数是叶子函数,而 main 函数是非叶子函数,非叶子函数有对其他函数的调用,需要开辟新的堆栈空间,以及其他函数返回后需要继续执行下一条指令,这需要修改 x29,x30的值,所以要先备份起来,而叶子函数就不需要。

参考

ARM64 汇编基础
iOS开发同学的arm64汇编入门
iOS 常用汇编指令集

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

推荐阅读更多精彩内容