@oro-oro
2015-10-10T14:58:01.000000Z
字数 3885
阅读 7292
AndroidARM
ARM处理器是精简指令集计算 Reduced Instruction Set Computing (RISC)的一个实例。
mov r0, #1234
相当于:r0=#1234
。#开头,表示16进制时,以0x开头,如#0x1f。
mov r0, r1
执行后,r0 = r1。
NOP 操作通常为mov r0, r0
,对应的HEX为00 00 a0 e1
寄存器移位寻址支持以下5种移位操作:
mov r0, r1, lsl #2
相当于:r0 = r1<<2 = r1*4
。
ldr r0, [r1] // 取值
相当于:r0 = *r1
。
ldr r0, [r1, #-4]
相当于:r0 = *(r1 - 4)
。
lmdia r0, {r1, r2, r3, r4}
LDM 是数据加载指令,指令的后缀IA表示,每次执行完成加载操作后,R0寄存器的值自增1个字。
R1=[R0], R2=[R0+#4], R3=[R0+#8], R4=[R0+#12]
字表示一个32位的数值。
它需要特定的指令完成:
LMDFA/STMFA, LDMEA/STMEA, LDMFD/SDMFD, LDMED/STMED。
LMD/STM 表示多寄存器寻址,一次可以传送多个寄存器值。
FA/EA/FD/ED ..参考指令集。
stmfd sp!, {r1-r7, lr} @将 r1~r7,lr 压栈。多用于保存子程序现场。
ldmfd sp!, {r1~r7, lr} @将 r1~r7, lr 出栈,放入 r1~r7, lr。多用于恢复子程序现场。
可实现连续地址数据从存储器的某一位置拷贝至另一位置。
LDMIA/STMIA, LDMDA/STMDA, LDMIB/STMIB, LDMDB/STMDB。
LDM/SDM 表示多寄存器寻址,一次可以传送多个寄存器值。
IA, DA, IB, DB ..参考指令集。
ldmia r0!, {r1-r3} @ 从r0指向的区域的值取出来,放到r1-r3中。
stmia r0!, {r1-r3} @ 将r1-r3的值取出来,放入r0指向的区域。
相对寻址以PC的当前值为基址,与偏移值相加,得到最终的地址。
bl .lc0
...
.lc0:
...
bl 直接跳到 .lc0 处。
寄存器详细可参考 AAPCS §5.1.1 Core registers
ARM 汇编有16个寄存器:
- r0-r3 主要用来传递函数调用第1到第4个参数(a0-a3),更多的参数须通过栈来传递。
- r0-r1 也作为结果寄存器,保存函数返回结果;被调用的子程序在返回前无须恢复这些寄存器的内容。
- r4-r9 为被调保存(callee-save)寄存器,一般保存内部局部变量(local variables)。
- r7 大部分情况用来保存系统调用号(syscall number)。
- r9 某些变体可能当作特殊寄存器。
- r10(SL)被调保存寄存器,Stack Limit。
- r11(FP)被调保存寄存器, 帧指针(Flame Pointer)。
- r12(IP)特殊寄存器,栈寄存器(Intra Procedure)。
- r13(SP)特殊寄存器,栈指针,类似x86_64中的RSP。
- r14(LR)特殊寄存器。Link Register.
- r15(PC)特殊寄存器。Program Counter (like RIP in x86_64 & EIP in x86).
被调保存寄存器(callee-save register)是指,如果这个寄存器被调用/使用之前,需要被保存。
r7/r11 : FP 或 Flame Pointer(帧指针)
通常 ARM 模式下 r11 会作为帧指针,THUMB 模式下 r7 则作为帧指针。
但在系统有可能根据自己的需要改变这个约定。
r12: IP 或 Intra-Procedure 栈寄存器(stack register)
该寄存器会被链接器当作擦写寄存器(scratch register)在过程(Procedure)调用之间使用。
可擦除寄存器(Scratch registers) 是指数据寄存器 R0, R1, R2 and R3。
一个过程(procedure)在返回时,不能修改它的值。
这个寄存器不会被Linux gcc 或 glibc 使用,但是另外一个系统可能会。
r14: LR 或 Link Register(链接寄存器)
这个寄存器会保存一个子程序(subroutine/函数)的返回值。
当一个子程序被调用时,LR 会被填入程序计数器(PC)。
r15: PC 或 Program Counter(程序计数器)
该寄存器或保存目前正在执行的内存地址。
PC 和 LR 都是跟代码有关的寄存器,一个是 Where you are,另外一个是 Where you were。
r13: SP 或 Stack Pointer(栈指针)
该寄存器指向栈顶。
该栈是一块用来存储本地函数的内存区域。当函数被返回时, 存储空间会被回收。
在堆栈上分配空间, 需要从栈寄存器(the stack register)减去。
分配一个32位的值, 需要从堆栈指针(the stack pointer)减去4。
ARM堆栈结构是从高向低压栈的。
因为处理器是32位的ARM,所以每压一次栈,SP就会移动4个字节(32位),也就是sp = sp-4。
SP 和 FP 都是跟本地数据相关的寄存器。一个是 "Where local data is",另外一个是 "Where the last local data is"。
栈帧(Stack Frame)就是一个函数所在的栈的一部分,所有函数的栈帧串起来就组成了一个完整的栈。
栈帧的两个边界分别由 FP 和 SP 来限定,它们2个指向的是当前函数的栈帧。
考虑 main 函数调用fun1函数的情形,下图是它们使用栈。
观察 func1 的栈帧,它的 SP 和 FP 之间指向的栈帧就是 main 函数的栈帧。
main 函数产生调用时,PC、LR、SP、FP 会在第一时间压栈。
ARM 不能像单片机那样,想取某个标签地址,就可以 mov r1,#标签。
因为ARM立即数寻址有限制,最大是4096,再大就只能相对寻址,显然所有的指针都会超过限制,只能间接寻址,所以需要用另一种方式直接算出寻址位置的地址和全局变量位置的相对地址。
ARM7和ARM9都是3级流水线,取指,译指,执行时同时执行的:
1. Fetch(从存储器装载一条指令)
2. Decode(识别将要被执行的指令)
3. Execute(处理指令并将结果写回寄存器)
而R15(PC)总是指向“正在取指”指令,而不是指向“正在执行”的指令或正在“译码”的指令,那么CPU正在译指的指令地址是PC-4(当ARM状态时,每条指令为4字节),CPU正在执行的指令地址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8,即:PC实际值=当前程序执行位置+8。
也就是说:
PC, 总是指向当前正在被取指的指令的地址,
PC-4,总是指向当前正在被译指的指令的地址,
PC-8,总是指向当前的那条指令,即一般说的,正在被执行的指令的地址。
指令的Execute执行阶段,如果用到PC的值,那么PC那一时刻,就是PC=PC+8。
其他细节具体可参考 3.4. 为何ARM7中PC=PC+8
当程序需要一些循环、过程(procedures)和函数的时候,会用到分支指令。
实现程序跳转的方法,还可以直接给PC寄存器直接赋值实现跳转。
B
Branch, 分支。
该指令不会影响LR寄存器。这意味着一旦我们跳转到子程序(subroutine),不能回溯(traceback)我们曾经在哪儿。这个类似于x86汇编中的JMP指令。
BNE LABEL
表示不为0时,则跳转到LABEL处执行。
BL
BL Branch with Link,带链接的分支。
该指令可以让子程序调用,通过LR保存的PC-4的地址,从子程序返回,只需简单的从LR还原PC的值:mov pc, lr
。
BX 和 BLX
BX Branch with Exchange,带交换的分支。
BLX Branch with Link and Exchange,带链接和交换的分支。
BX和BLX指令用于THUMB模式中,暂时不关注。
算术
ADD op1+op2
ADC op1+op2+carry
SUB op1-op2+carry-1
syntax : <operation> {<cond>}{S} Rd,Rn,operand
examples :
ADD r0,r1,r2
SUB R1,R2,#1
比较
CMP op1-op2
TST op1 & op2
TEQ op1 ^ op2
Syntax : <operation> {<cond>} Rn,Op
examples :
CMP R0,R1
CMP R0,#2
逻辑运算
AND op1,op2
EOR op1,op2
ORR op1,op2
移动
MOV op1,op2
syntax : <Operation>{<cond>}{S} Rn, Op2
Examples:
MOV r0, r1
其他更加详细信息,可参考:
Whirlwind Tour of ARM Assembly
ARM Architecture Reference Manual