iOS汇编入门必备(指令篇)

2,510 阅读8分钟

一、汇编中指令和伪指令的区别?

指令:机器码助记符,每条指令会生成机器码,由CPU读取执行。 伪指令(伪操作):没有与之对应的机器码,非可执行指令,需要汇编器来解释。

二、OS X 伪指令:

所有汇编程序伪指令(GUN汇编器通用伪指令)的名称都是以‘.’开头。名称大多数不区分大小写,通常使用小写字母表示。

1、定义数据伪指令(下面数据类型空间大小为arm64下的size)

.byte                      // 定义1个字节大小的变量   
.short                     // 定义2个字节大小的变量 
.int                        // 定义4个字节大小的变量
.long                      // 定义8个字节大小的变量
.quad(大数)         // 定义8个字节大小的变量
.ascii                      // 定义字符串以非零结束,行尾需要添加’\0’
.asciz                      // 定义字符串以零结束

2、汇编控制 (1)条件判断伪指令:

.if                        /* 条件判断开始 */
.else 
.endif                   /* 条件判断结束 */

(2)宏定义伪指令:

.macro 宏名                  /* 宏定义开始 */
.endmacro                   /* 宏定义结束 */

3、定义一个section Mach-O文件中的数据或指令都是存在segment中(可通过MachOView 查看),一个segment由零个或者多个section组成,segment 名称用双下划线开头 +全字母大写(例如:__DATA)表示,section名称用双下划线开头+全字母小写(例如:__data)表示

.section  segname , sectname [[[ , type ] , attribute ] , sizeof_stub ]

示例:
.section __DATA __data                                        // 等价于    .data
.section __TEXT __text                                        // 等价于    .text
.section __TEXT __const                                        // 等价于    .const
.section __TEXT,__cstring, cstring_literals            // 等价于    .cstring

更多内容详见文章末尾参考资料:OS X Assembler Directives

4、对齐align 用fill_expression(指定,则必须是绝对的,未指定,则为零)使当前位置与align_expression边界对齐 。align_expression是介于0到15之间2的幂(例如:.align 3意味着2 ^ 3(8)字节对齐)。

.align align_expression [,1byte_fill_expression [,max_bytes_to_fill]]

示例:
.align 3                          // 相当于2 ^ 3(8)字节对齐

5、填充fill 重复拷贝指定字节,重复repeat_expression次,repeat_expression必须是大于0的绝对表达式,fill_size以字节为单位且必须有值,值可以为1,2,4或者8。fill_expression可以是任何绝对表达式(它会被截断为填充大小)。

.fill repeat_expression,fill_size,fill_expression

示例:
.fill    16, 1, 0

6、私有外部符号private_extern .private_extern可以把symbol_name变成私有外部符号。当链接编辑器将此模块与其他模块组合在一起(且keep_private_externs 命令行选项未指定)时,该符号会将它从全局更改为静态。如果在同一符号上同时使用了.private_extern和.globl汇编程序指令,则和只使用 .private_extern 效果一样。

.private_extern symbol_name

示例
.private_extern _objc_restartableRanges

7、标签 标签是用来标记程序和数据对象的位置的标识符,每个标签由一个标识符和一个终止冒号组成,标识符中的字母区分大小写,格式如下:

identifier: [ identifier: ] ...

示例:
L_method_invoke_small:

8、其他伪指令

.abort               // 使汇编器忽略进一步的输入并退出处理
.text                 // 表示代码段
.data                // 表示数据段       
.globl                // 声明为全局符号
.include             // 引入头文件
.set                  // 把符号定义成一个表达式,相当于 symbol_name=absolute_expression

三、注释风格:

1、特定平台单行注释:x86-64使用#开头,arm使用;开头

add  x10, x10, #0x1        # This a comment line       注释代码:x86-64
add  x10, x10, #0x1        ; This a comment line       注释代码:arm

2、GNU通用语法 (1)多行注释

/*
注释代码
*/

(2)单行注释

MOV R1,#15    // Load R1 with 15

四、寄存器

典型的CPU主要组成部分:运算器、控制器、寄存器。 寄存器是CPU用来暂存指令、数据、地址的电脑存储器。它的存储容量有限,是CPU中最快的可读写存储器。

1、ARM64中常见寄存器 (1)31个通用寄存器R0-R30,每个寄存器可以通过以下方式访问:

  • 通过X0-X30访问的时候,它是一个64位的通用寄存器

  • 通过W0-W30访问的时候,它是一个32位(访问低32位)的通用寄存器 image

    X30通用寄存器被用作过程调用链接寄存器。

(2)SP:64位专用堆栈指针寄存器,使用SP/WSP对堆栈指针寄存器进行访问,WSP表示访问堆栈指针的低32位。

没有X31或W31的寄存器,由指令来决定寄存器31是堆栈指针或者是零寄存器。当用作堆栈指针时,将其称为SP。当用作零寄存器时,在32位环境中将其称为WZR,在64位环境中将其称为XZR。

ZR:零寄存器,当它用作源寄存器时,零寄存器读取为零,当它用作目标寄存器时,则丢弃结果。

(3)PC:64位程序计数器,用来保存当前指令地址。软件不能直接写入程序计数器。
 (4)SIMD&FP:32个向量&浮点寄存器,V0-V31。每个寄存器可以通过以下方式访问:

  • 通过Q0-Q31访问的时候,它是一个128位寄存器
  • 通过D0-D31访问的时候,它是一个64位(访问低64位)寄存器
  • 通过S0-S31访问的时候,它是一个32位(访问低32位)寄存器
  • 通过H0-H31访问的时候,它是一个16位(访问低16位)寄存器
  • 通过B0-B31访问的时候,它是一个8位(访问低8位)寄存器
  • 元素的128位向量 image

2、通用寄存器中的参数 出于函数调用目的,通用寄存器被分为4组:

  • X0-X7:参数寄存器,用于将参数传递给函数并返回结果,返回值通过X0返回。
  • X9-X15:调用者保存的临时寄存器。
  • X19-X29:被调用者保存的寄存器。
  • X8,X16-X18,X29,X30:特殊用途的寄存器
 X8:间接结果寄存器,用于传递间接结果的地址位置,如函数返回了一个大型结构。
 X16、X17:表示IP0、IP1,过程内调用临时寄存器。
 X18是平台寄存器,保留供平台ABI使用
 X29:帧指针寄存器(FP)
 X30:链接寄存器(LR)

image

在lldb中可以通过register read查看各寄存器状态

五、常见计算指令

1、移动指令:MOV,寄存器和寄存器之间传值

mov    x2, x16             ;把x16的值传递给寄存器x2

2、算术运算指令: 加(ADD)、减(SUB)

add x1, x2, x3             ;把x2+x3的值传递给寄存器x1
sub x1, x2, x3             ;把x2-x3的值传递给x1

3、逻辑运算指令:与(AND)、或(ORR)、异或(EOR)

and    x1,x1,#0xf           ;把x1中的值与0xf按位与后传递给x1
orr    x1,x1,#6               ;把x1中的值与6按位与后传递给x1
eor    x1,x1,#0xf           ;把x1中的值与0xf按位异或后传递给x1

4、桶形移位器操作指令:LSL、LSR、ASR、ROR

LSL:逻辑左移 
LSR:逻辑右移
ASR:算术右移
ROR:循环右移

5、存/取数据指令:STR(寄存器加载到内存中)、取数据LDR (把内存中的数据传递给寄存器)

str x1, [sp, #0x4]                  ;把x1寄存器的数据传递给sp+0x4地址值指向的内存空间
ldr x1, [sp, #0x4]                  ;把sp+0x4地址值内的数据传递给寄存器x1

6、栈操作指令:STP(入栈)、LDP(出栈)

stp x0, x1,  [sp, #0x4] 
ldp x0, x1,  [sp, #0x4] 

7、比较指令:CMP,CBZ,CBNZ,TBZ,TBNZ

cmp x0,x1                        ;把x0的内容和x1的内容进行比较,根据结果更新条件标志,并丢弃结果,相当于subs xzr x0, x1
cbz x0, LGetImpMiss1                    ;如果x0等于0就跳转到LGetImpMiss1,不影响条件标志
cbnz x0, LGetImpMiss1               ;如果x0不等于0就跳转到LGetImpMiss1,不影响条件标志

tbz x0, #20, LGetImpMiss1                 ;寄存器中指定位某个值是否为零,如果x0中的第20位(x0[20])等于0就跳转到LGetImpMiss1,不影响条件标志

tbnz x0, #20, LGetImpMiss1                 ;寄存器中指定位比较,如果x0中的第20位(x0[20])不等于0就跳转到LGetImpMiss1,不影响条件标志

8、跳转指令: B:无返回跳转,配合CMP使用 BL:带返回的跳转,会将返回地址存储到寄存器x30,说明这是一个子程序调用

示例1:
b    LLookupExample          ;直接跳转到LLookupExample

示例2:配合cmp使用
cmp x0,#6                    
b.eq LReturnZeroExample     ;如果x0等于6,则跳转LReturnZeroExample

条件代码:
b.eq                ;等于
b.ne                ;不等于
b.le                 ;有符号的小于或等于
b.ge                ;有符号的大于或等于
b.lt                ;有符号小于
b.gt                ;有符号大于

示例3:
bl lookUpFindExample

9、子程序返回指令:RET(返回地址存储在x30)

LTestExample:
    mov x2, #0
    ret

10、寻址指令:ADRP(将PC相对地址形成4KB页面,即:取指定label的基地址,存储到指定寄存器中)

adrp    x1, __example_handle@PAGE       ;获取__example_handle所在页的基地址存储到x1寄存器中

11、无符号位域选取指令:UBFX(提取指定位)

ubfx    x11, x0, #60, #4            ;从源寄存器x0中提取4位,位置从60开始。即将x0中的60-63位复制到目标寄存器x11的最低有效位,并将该x11上的其他高位设置为零

六、参考资料

访问我的Github仓库查看更多精彩分享