ARM64汇编
简介
- 学习汇编主要学习一下几点
- 寄存器
- 指令系统
- 堆栈
- 中断系统(嵌入式才学)
- 注意:模拟器与mac都是A&T汇编,需要运行在真机上调试,才是ARM汇编
- lldb 汇编相关常用指令:
- memory read
- 读取所有寄存器的值
- 用这个指令也可以看RAM64总共有那些寄存器。
- memory write 寄存器 值
- 给某个寄存器写入值
register read r0
读取寄存器r0的值。register read
读取所有寄存器的值register write r0 值
往寄存器r0写入值。- po $x0:打印方法调用者
- x/s $x1:打印方法名
- po $x2:打印参数(以此类推,x3、x4也可能是参数)
- 如果是非arm64,寄存器就是r0、r1、r2
- memory read
寄存器
- 通用寄存器
- 64bit的:x0 ~ x28
- 32bit的:w0 ~ w28(属于x0 ~ x28的低32bit)
- x0 ~ x7通常拿来存放函数的参数,更多的参数使用堆栈来传递
- x0 通常拿来存放函数的返回值。
- 程序计数器
- pc (Program Counter)
- 记录CPU当前指令的是哪一条指令。
- 存储着当前CPU正在执行的指令的地址。
- 类似于8086汇编的IP寄存器
- 堆栈指针
- sp(Stack Pointer)
- fp(Frame Pointer),也就是x29
- 链接寄存器
- lr(Link Register),也就是X30
- 存储着函数的返回地址
- 程序状态寄存器
- cpsr (Current Program Status Register)
- spsr (Saved Program Status Register),异常状态下使用
ARM64常用的指令
- mov
- 格式:
mov 目的寄存器,源操作数
- 作用:指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器
- 这个方式跟8086相同
- 格式:
- ret
- 作用:函数返回
- 将lr(x30)寄存器的值复制给PC寄存器。
- ADD
- 格式为: ADD 目的寄存器,操作数 1,操作数 2
- 作用: ADD 指令用于把两个操作数相加,并将结果存放到目的寄存器中。操作数 1 应是一个寄存器,操 作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。
- 举例:
ADD x0,x1,x2 ; x0 = x1+x2
- SUB 指令
- 格式为: SUB 目的寄存器,操作数 1,操作数 2
- 作用:SUB 指令用于把操作数 1 减去操作数 2,并将结果存放到目的寄存器中。操作数 1 应是一个寄存 器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或无符 号数的减法运算。
- CMP 指令
- 格式为: CMP 操作数 1,操作数 2
- 作用:CMP 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新 CPSR 中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是 操作数 1 与操作数 2 的关系(大、小、相等),例如,当操作数 1 大于操作操作数 2,则此后的有 GT 后缀的指令将可以执行。
- B 指令
- 格式为: B{条件} 目标地址
- 作用:B 指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。
- 可以带条件跳转,一般跟cmp配合使用
- 常用的条件如下:
- EQ:equal,相等
- NE:not equal,不相等
- GT:great than,大于
- GE:greate equal 大于等于
- LT:less than,小于
- LE:less equal,小于等于
-
举例使用:
; b指令带条件 mov x0, #0x5 mov x1, #0x5 cmp x0, x1 ;指令加gt条件,跳转到mycode函数 bgt mycode mov x0, #0x5 ret
- BL 指令
- 格式: BL{条件} 目标地址
- 作用:
- 带返回的跳转指令
- 将下一条指令的地址存储到lr(x30)寄存器中
- 跳转到标记处开始执行代码
- 就是函数的调用
- b与bl区别:
- b跳转到一个地址后,不会返回回来,仅仅是跳转作用,即使你跳转的程序结尾写上ret,也没用,不会跳转回来
- bl就像函数的调用,跳转到指定的标号,执行程序段,执行完毕后,在跳转回来,继续执行下面的程序。
- 因此,bl就是函数调用的汇编(相当于8086的call),b就相当于if…else的汇编。
-
用Xcode编写汇编代码:
//arm.h文件: #ifndef arm_h #define arm_h void test(); int add(int a, int b); int sub(int a, int b); #endif /* arm_h */ ---------------------- //arm.s文件: // 声明一个代码段 .text //声明这些函数是公开的,可以被其他文件访问 .global _test, _add, _sub //对外部公开的函数为啥前面要加下划线? //因为,外部掉一共test函数,实际上内部会寻找以下划线开头的该函数,所以,内部命名函数要以下划线开头,但是.h声明仍然是去掉下划线声明 // 内部\私有函数 mycode: mov x0, #0x1 mov x1, #0x2 add x2, x0, x1 ret //注意:如果使用b指令,mycode函数中的ret是无效的。只有使用bl,mycode函数中的ret才有效。 // test函数的实现 _test: ; bl指令(函数调用) bl mycode mov x3, #0x2 mov x4, #0x1 ret // add函数的实现,x0寄存器通常用于存放函数的返回值! _add: add x0, x0, x1 ret // sub函数的实现,x0寄存器通常用于存放函数的返回值! _sub: sub x0, x0, x1 ret
- 内存操作
- ARM64中,mov仅仅用于操作寄存器的。对于操作内存,有专门的指令,如下:
- load,从内存中读取数据
- ldr
- 格式:LDR 目的寄存器,<存储器地址>存储器地址>
- 作用:存储器到寄存器的数据传输指令
-
举例:
LDR x0,[x1,#4] ; x0<-[x1+4] LDR x0,[x1,#4]! ; x0<-[x1+4],x1<-x1+4 LDR x0,[x1] ,#4 ; x0<-[x1],x1<-x1+4 LDR x0,[x1,x2] ; x0<-[x1+x2] 在第一条指令中,将寄存器 x1 的内容加上 4 形成操作数的有效地址,从而取得操作数存入寄存器R0中。 在第二条指令中,将寄存器 x1 的内容加上 4 形成操作数的有效地址,从而取得操作数存入寄存器 x0 中,然后,x1 的内容自增 4 个字节。 在第三条指令中,以寄存器 x1 的内容作为操作数的有效地址,从而取得操作数存入寄存器 x0中,然后,x1 的内容自增 4 个字节。 在第四条指令中,将寄存器 x1 的内容加上寄存器 x2 的内容形成操作数的有效地址,从而取得操作数存入寄存器 x0 中。
- ldur
- 从内存中读取数据
- 与ldr区别
- 偏移地址为负数时,用ldur
- 比如:
LDUR x0,[x1,#-4]
- ldp(p是pair的简称)
- 从内存中读取数据,放到一对寄存器中
- 举例:
ldp w0, w1, [x2, #0x10]
- ldr
- store,往内存中写数据
- str、stur
- str指令:往内存中写入数据
- 举例:
str x0, [x1]
- stp
- 往内存中写数据,放到一对寄存器中
- 零寄存器,里面存储的值是0
- wzr (32bit,Word Zero Register)
- xzr (64bit)
-
比如:
int a = 0; //a 4个字节 long b = 0; //b 8个字节 //汇编如下; stur wzr,[x29,#-0x14] stur xzr,[x29,#-0x20]
- str、stur
ARM指令系统注意事项
- 立即数都要以#开头
- 比如:
mov x1, #0x2
- 比如:
函数的堆栈
- 函数的类型
- 叶子函数
- 该函数中没有调用其他函数
- 非叶子函数
- 该函数中调用了其他函数
- 叶子函数
- 将c语言文件,转化成汇编文件:
xcrun --sdk iphoneos clang -S -arch arm64 main.c -o main.s
- 叶子函数的汇编
-
叶子函数:
void haha() { int a = 2; int b = 3; }
-
汇编代码:
_haha: ; @haha ;划出一段栈空间,16个, sub sp, sp, #16 ; sp = sp -16 orr w8, wzr, #0x3 ;w8=3 orr w9, wzr, #0x2 str w9, [sp, #12] ; 内存中存入2 str w8, [sp, #8] ;内存中存入3 add sp, sp, #16 ; 恢复栈平衡 ret
-
堆栈图:
-
- 非叶子函数的汇编:
-
非叶子函数:
void hehe() { int a = 4; int b = 5; haha(); }
-
非叶子函数汇编
_hehe: ; @hehe ;划出一段栈空间,32个, sub sp, sp, #32 ; =32 ;将x29,x30寄存器的值放(保护)到内存 [sp, #16]中,x30是函数的返回地址; x29又叫fp指针 ;为何要保护fp的值呢? 因为下面代码会修改fp(x29)的值 ;为何要保护lr的值呢? 因为下面调用(bl)其他函数时,lr(30)会存储为调用函数下一条指令的地址 stp x29, x30, [sp, #16] ; 8-byte Folded Spill ;x29放入[sp, #16] 的值;即fp指向sp+16 add x29, sp, #16 ; =16 mov w8, #5 orr w9, wzr, #0x4 ;[x29, #-4],就是fp-4 stur w9, [x29, #-4] ;int a = 4 str w8, [sp, #8] ; int b = 5 bl _haha ;调用haha函数 ; 恢复栈平衡 ldp x29, x30, [sp, #16] ; 8-byte Folded Reload add sp, sp, #32 ; =32 ret
-
堆栈图:
-