一、汇编中指令和伪指令的区别?
指令:机器码助记符,每条指令会生成机器码,由CPU读取执行。
伪指令(伪操作):没有与之对应的机器码,非可执行指令,需要汇编器来解释。
二、OS X 伪指令:
所有汇编程序伪指令(GUN汇编器通用伪指令)的名称都是以'.'开头。名称大多数不区分大小写,通常使用小写字母表示。
1、定义数据伪指令(下面数据类型空间大小为arm64下的size)
.byte // 定义1个字节大小的变量
.short // 定义2个字节大小的变量
.int // 定义4个字节大小的变量
.long // 定义8个字节大小的变量
.quad(大数) // 定义8个字节大小的变量
.ascii // 定义字符串以非零结束,行尾需要添加’’
.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位)的通用寄存器
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位向量
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)
在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页面,即:取指定标签的基地址,存储到指定寄存器中)
adrp x1, __example_handle@PAGE ;获取__example_handle所在页的基地址存储到x1寄存器中
11、无符号位域选取指令:UBFX(提取指定位)
ubfx x11, x0, #60, #4 ;从源寄存器x0中提取4位,位置从60开始。即将x0中的60-63位复制到目标寄存器x11的最低有效位,并将该x11上的其他高位设置为零