前言
这学期选了一门叫做”智能嵌入式系统原理与设计”的课, 本以为很有意思, 结果上了课才发现这课居然是教ARM汇编的, 原名叫”ARM嵌入式系统结构与编程”. 不过不管怎么说, 学费不能白交, 习也不能白学, 说不定以后能用上呢(狗头, 所以就把这门课学到的东西记在这里, 也方便以后参考.
但是这门课讲的是ARMv5, 所以我又到网上找了些资料学了一下ARMv7, 鉴于网上资料水平参差不齐且笔者水平有限, 如果发现错误, 还恳请读者批评指正
ARMv7
官方参考手册:
类型
- 字节(Byte): 8位
- 半字(Halfword): 16位
- 字(Word): 32位
指令集
- ARM(32位)
- Thumb(16位): 已弃用
- Thumb-2(16&32位)
寄存器
共有43个寄存器, 包括34个通用寄存器和9个状态寄存器
R13(Stack Pointer, SP): 栈指针寄存器, 用于存放栈顶地址
R14(Link Register, LR): 链接寄存器, 存放子程序的返回地址
R15(Program Counter, PC): 程序计数器, 存放当前正在取指的指令的地址. 由于arm采用三级流水线, 指令的执行需要经过取指、译码、执行三个步骤, 所以当处于arm模式时PC的值是当前指令地址+8(+4+4), 处于thumb模式时PC的值是当前指令地址+4(+2+2)
CPSR(Current Program Status Register): 当前程序状态寄存器
SPSR(Saved Program Status Register): 备份的程序状态寄存器, 在异常发生时保存CPSR的值, 退出异常模式时可用于恢复CPSR
APSR(Application Program Status Register): 用户状态寄存器, 是CPSR的限制版本
程序状态寄存器
31 28 27 26 25 24 23 20 19 16 15 10 9 8 6 5 4 0
|-------|-|-------|-|--------|------|-----------|-|-----|-|---------|
|N Z C V|Q|IT[1:0]|J|Reserved| GE | IT[7:2] |E|A I F|T| mode |
|-----------------|-----------------|---------------|---------------|
- 条件位:
- N: 运算结果是否为负(1-负, 0-非负)
- Z: 运算结果是否为0(1-为0, 0-不为0)
- C: 运算是否进位(1-进位, 0-借位)
- V: 运算是否溢出(1-溢出, 0-没溢出)
- Q: 增强型DSP指令是否溢出
- IT: Thumb-2指令集的if-then执行状态位
- J: 处理器是否处于Jazelle状态
- GE: 大于或等于标志位, 主要用于SIMD指令
- E: 处理器的字节序(1-大端模式, 0-小端模式)
- 中断屏蔽位:
- A: 是否禁止异步中止
- I: 是否禁止IRQ
- F: 是否禁止FIQ
- T: 处理器当前使用的指令集(0-ARM指令集, 1-Thumb指令集)
- mode: 处理器模式位
- 0b10000 usr User 用户模式, 是正常程序执行的模式, 不可以访问系统资源, 无法主动切换模式
- 0b10001 fiq FIQ 快速中断模式, 当高优先级中断产生时进入的模式
- 0b10010 irq IRQ 中断模式, 当低优先级中断产生时进入的模式
- 0b10011 svc Supervisor 管理模式, 是一种操作系统保护模式, 当复位或软中断指令执行时进入此模式
- 0b10110 mon Monitor 监视模式, 针对Security扩展
- 0b10111 abt Abort 中止模式, 存取异常时会进入此模式
- 0b11010 hyp Hyp 虚拟化模式, 针对虚拟化扩展
- 0b11011 und Undefined 未定义模式, 当执行未定义指令时会进入此模式
- 0b11111 sys System 系统模式, 是使用和User模式相同寄存器组的特权模式, 可用于运行特权级的操作系统任务
用户状态寄存器
与CPSR的不同之处:
- bit[26:24]: RAZ/SBZP, 如需在不遵循读、修改、写的顺序下使用msr指令来更新APSR的最高字节(bit[31:24]), 则需要将RAZ设置为0
- bit[15:0]: 写是无效的, 在某些实现中也许可以读, 但不建议这么做
异常处理
异常类型 | 处理器模式 | 优先级 | 向量表偏移 |
---|---|---|---|
复位 | SVC | 1 | 0x00 |
未定义指令 | UND | 6 | 0x04 |
软中断SWI | SVC | 6 | 0x08 |
预取指中止 | ABT | 5 | 0x0c |
数据中止 | ABT | 2 | 0x10 |
保留 | / | / | 0x14 |
IRQ中断 | IRQ | 4 | 0x18 |
FIQ中断 | FIQ | 3 | 0x1c |
ARM汇编
指令: CPU机器指令的助记符, 经过编译之后会得到机器码, CPU通过读取这些机器码然后执行相应的动作
伪指令: 本质上并不是指令, 只是指导编译的过程, 最终也不会生成机器码
两者的差异在于是否是指令, 以及最后编译完成是否会生成机器码
ARM指令集
指令格式
编码格式:
31 27 25 24 21 20 19 16 15 12 11 0
|-------|----|-|-------|-|-------|-------|-----------------------|
| cond |type|I| opcode|S| Rn | Rd | operand2 |
|----------------|---------------|---------------|---------------|
- cond: 指令执行的条件
- type: 指令类型
- I: 第二操作数是否是立即数
- opcode: 指令操作码
- S: 指令的操作结果是否影响CPSR
- Rn: 包含第一个操作数的寄存器
- Rd: 目标寄存器
- operand2: 第二个操作数
语法格式:
<opcode>{<cond>}{S}<Rd>,<Rn>{,<operand2>}
<>必填, {}可选
- opcode: 指令助记符
- cond: 执行条件
- S: 操作结果是否影响CPSR
- Rd: 目标寄存器
- Rn: 包含第一个操作数的寄存器
- operand2: 第二个操作数
执行条件:
- 0000 EQ 相等 Z=1
- 0001 NE 不相等 Z=0
- 0010 CS/HS 无符号大于等于 C=1
- 0011 CC/LO 无等号小于 C=0
- 0100 MI 负数 N=1
- 0101 PL 非负数 N=0
- 0110 VS 上溢出 V=1
- 0111 VC 没有上溢出 V=0
- 1000 HI 无符号数大于 C=1且Z=0
- 1001 LS 无符号小于等于 C=0或Z=1
- 1010 GE 有符号数大于等于 N=1且V=1 或 N=0且V=0
- 1011 LT 有符号数小于 N=1且V=0 或 N=0且V=1
- 1100 GT 有符号数大于 Z=0且N=V
- 1101 LE 有符号数小于/等于 Z=1或N!=V
- 1110 AL 无条件执行
第2操作数寻址方式
-
立即数方式:
每个立即数由一个8位的常数进行32位循环右移偶数位得到, 其中循环右移的位数是一个4位二进制数的2倍
编码格式:
11 8 7 0 |--------|---------------| |rotate_4| immed_8 | ---------|---------------| I(25位)设置为1 <immediate>=immed_8进行32位循环右移(2*rotate_4)位
语法格式:
#<immediate>
-
寄存器方式:
操作数即为寄存器中的数值
编码格式:
11 4 3 0 |----------------|-------| | 0 | Rm | ---------|---------------| I(25位)设置为0
语法格式:
<Rm>
-
寄存器移位方式:
操作数为寄存器中的数值做相应的移位而得到
编码格式:
移位的位数为5位的立即数: 11 7 6 5 4 3 0 |------------|-----|-|-------| |shift_amount|shift|0| Rm | -----------|-----------------| 移位的位数存放在寄存器中: 11 8 7 6 5 4 3 0 |----------|-|-----|-|-------| | Rs |0|shift|1| Rm | -----------|-----------------| 寄存器进行RRX移位: 11 8 7 4 3 0 |----------|---------|-------| | 0 | 0 1 1 0 | Rm | -----------|-----------------| I(25位)均设置为0
语法格式:
移位的位数为5位的立即数: <Rm>,<shift> #<shift_imm> 移位的位数存放在寄存器中: <Rm>,<shift> <Rs> 寄存器进行RRX移位: <Rm>,RRX
移位类型:
- 逻辑左移 LSL
- 逻辑右移 LSR
- 算术左移 ASL
- 算术右移 ASR
- 循环右移 ROR
- 带扩展的循环右移 RRX
加载/存储指令
-
LDR/STR加载/存储指令
编码格式:
字、无符号字节访问指令: 31 28 27 25 20 19 16 15 12 11 0 |-------|---|-|-|-|-|-|-|-------|-------|-----------------------| | cond |0 1|I|P|U|B|W|L| Rn | Rd | addressing_mode | |---------------|---------------|---------------|---------------| 半字、有符号字节访问指令: 31 28 27 25 24 20 19 16 15 12 11 8 7 6 5 4 3 0 |-------|-----|-|-|-|-|-|-------|-------|-------|-|-|-|-|-------| | cond |0 0 0|P|U|I|W|L| Rn | Rd |addr_m |1|S|H|1|addr_m | |---------------|---------------|---------------|---------------|
- I: 0-偏移量为12位立即数, 1-偏移量为寄存器移位形式
- P: 0-后变址操作, 1-前变址操作
- U: 0-内存地址为基址寄存器值减去地址偏移量, 1-内存地址为基址寄存器值加上地址偏移量
- B: 0-加载/存储字, 1-加载/存储字节
- W: 0-不执行基址寄存器回写操作, 1-执行基址寄存器回写操作
- L: 0-执行Store操作, 1-执行Load操作
- S: 0-无符号, 1-有符号
- H: 0-字节, 1-半字
语法格式:
LDR{cond}{T} Rd, addressing 从内存中将一个32位的字数据读取到指令中的目标寄存器中 STR{cond}{T} Rd, addressing 将一个32位寄存器中的字数据写入到指令中指定的内存单元 LDR{cond}B{T} Rd, addressing 从内存中将一个8位的字节数据读取到指令中的目标寄存器低8位中, 高24位清零 STR{cond}B{T} Rd, addressing 将一个寄存器的低8位写入到指令中指定的内存地址字节单元 LDR{cond}H Rd, addressing 从内存中将16位的半字数据读取到指令中的目标寄存器低16位中, 高16位清零 STR{cond}H Rd, addressing 将一个寄存器的低16位写入到指令中指定的内存地址字节单元 LDR{cond}SB Rd, addressing 将内存指定地址上的有符号字节数据读取到指令中的目标寄存器中, 高位用符号位填充 LDR{cond}SH Rd, addressing 将内存指定地址上的有符号半字数据读取到指令中的目标寄存器中, 高位用符号位填充
- T: 当在特权极的处理器模式下使用本指令时, 处理器将该操作当作用户模式下的内存访问操作. 这种指令在用户模式下使用无效, 在特权模式下只能使用前变址形式
寻址方式(addressing):
-
寄存器间接寻址
以寄存器中的值作为操作数的地址
语法格式:
[<Rn>]
-
基址加变址寻址
将基址寄存器的内容与指令中给出的地址偏移量相加, 从而得到一个操作数的有效地址
前变址法: 基地址寄存器中的值和地址偏移量先作加减运算, 生成的操作数作为内存访问的地址
后变址法: 将基地址寄存器中的值直接作为内存访问的地址进行操作, 内存访问完毕后基地址寄存器中的值和地址偏移量作加减运算, 并更新基地址寄存器
语法格式:
偏移量为立即数: 前变址不回写: [<Rn>, #+/-<immed_offset>] 前变址回写: [<Rn>, #+/-<immed_offset>]! 后变址回写: [<Rn>], #+/-<immed_offset> 偏移量为寄存器中的值: 前变址不回写: [<Rn>, +/-<Rm>] 前变址回写: [<Rn>, +/-<Rm>]! 后变址回写: [<Rn>], +/-<Rm> 偏移量通过寄存器移位得到: 前变址不回写: [<Rn>, +/-<Rm>, <shift>#shift_amount] 前变址回写: [<Rn>, +/-<Rm>, <shift>#shift_amount]! 后变址回写: [<Rn>], +/-<Rm>, <shift>#shift_amount
-
LDM/STM批量加载/存储指令
编码格式:
31 28 27 25 24 20 19 16 15 0 |-------|-----|-|-|-|-|-|-------|-------------------------------| | cond |1 0 0|P|U|S|W|L| Rn | register_list | |---------------|---------------|---------------|---------------|
- P: 0-后变址操作, 1-前变址操作
- U: 0-地址向下变化, 1-地址向上变化
- S: 用于恢复CPSR和强制用户位. 当程序计数器PC包含在LDM指令的register_list中且S为1时, 则当前模式的SPSR被拷贝到CPSR中; 如果register_list中不包含程序计数器PC且S为1时, 则加载或存储的是用户模式下的寄存器组
- W: 0-不执行基址寄存器回写操作, 1-执行基址寄存器回写操作
- L: 0-执行Store操作, 1-执行Load操作
- register_list: 要加载或存储的寄存器列表, 每一位的位置代表寄存器的编号
注意: 编号低的寄存器对应内存低地址单元, 编号高的寄存器对应内存高地址单元
语法格式:
LDM{<cond>}<addr_mode> <Rn>{!}, <registers>{^} STM{<cond>}<addr_mode> <Rn>{!}, <registers>{^}
-
addr_mode:
内存操作:
- IA(Increment After): 后增, 每次数据传送后地址加4
- IB(Increment Before): 先增, 每次数据传送前地址加4
- DA(Decrement After): 后减, 每次数据传送后地址减4
- DB(Decrement Before): 先减, 每次数据传送前地址减4
堆栈操作:
- FD(Full Decending Stack): 满递减堆栈, 当堆栈指针指向最后压入堆栈的数据, 堆栈由高地址向低地址生成时
- ED(Empty Decending Stack): 空递减堆栈, 当堆栈指针指向下一个将要放入数据的空位置, 堆栈由高地址向低地址生成时
- FA(Full Ascending Stack): 满递增堆栈, 当堆栈指针指向最后压入堆栈的数据, 堆栈由低地址向高地址生成时
- EA(Empty Ascending Stack): 空递增堆栈, 当堆栈指针指向下一个将要放入数据的空位置, 堆栈由低地址向高地址生成时
-
registers: 寄存器列表, 可包含多于一个寄存器或寄存器范围, 由小到大排列, 使用","分开, 如{R1,R2,R6-R9}
-
!: 是否写回
-
^: 如果是LDM指令且寄存器列表中包含程序计数器PC, 那么除了正常的数据加载外, 还会将SPSR的内容复制到CPSR中, 可用于异常处理返回; 若寄存器列表中不包含PC, 处理器将该操作当作用户模式下的内存访问操作, 所加载/存储的寄存器组为用户模式下的寄存器, 但不允许对基址寄存器进行回写操作
-
PUSH/POP寄存器入栈/出栈指令
实现R0~R7和可选的LR寄存器入栈操作以及R0~R7和可选的PC寄存器出栈操作, 堆栈地址由SP寄存器确定, 堆栈是满递减堆栈
语法格式:
PUSH {reglist[,LR]} 相当于 STMFD SP!,{reglist[,LR]} POP {reglist[,PC]} 相当于 LDMFD SP!,{reglist[,PC]}
- reglist: 寄存器列表, 可以是R0~R7
-
SWP数据交换指令(已弃用)将一个以寄存器Rn的值为地址的内存单元的内容读取到一个寄存器Rd中, 同时将另一个寄存器Rm的内容写入到该内存单元中, 可用于原子操作
语法格式:
SWP{<cond>}{B} <Rd>, <Rm>, [<Rn>]
- B: 若有B则交换字节, 否则交换32位的字
数据处理指令
数据传送指令
-
MOV指令
将第二操作数operand2表示的数值传送到目标寄存器Rd中
MOV{cond}{S} Rd, operand2
-
MVN指令
将第二操作数operand2表示的数值按位取反后传送到目标寄存器Rd中
MVN{cond}{S} Rd, operand2
算术运算指令
-
ADD加法指令
将第二操作数operand2表示的数值与寄存器Rn中的值相加, 并把结果传送到目标寄存器Rd中
ADD{cond}{S} Rd, Rn, operand2
-
ADC带C标志位的加法指令
将第二操作数operand2表示的数值与寄存器Rn中的值相加, 再加上CPSR中的C条件标志位的值, 并把结果传送到目标寄存器Rd中
ADC{cond}{S} Rd, Rn, operand2
-
SUB减法指令
从寄存器Rn中减去第二操作数operand2表示的数值, 并把结果传送到目标寄存器Rd中
SUB{cond}{S} Rd, Rn, operand2
-
SBC带C标志位的减法指令
从寄存器Rn中减去第二操作数operand2表示的数值, 再减去CPSR中C条件标志位的反码, 并把结果传送到目标寄存器Rd中
SBC{cond}{S} Rd, Rn, operand2
-
RSB逆向减法指令
从第二操作数operand2表示的数值中减去寄存器Rn中的值, 并把结果传送到目标寄存器Rd中
RSB{cond}{S} Rd, Rn, operand2
-
RSC带C标志位的逆向减法指令
从第二操作数operand2表示的数值中减去寄存器Rn中的值, 再减去CPSR中C条件标志位的反码, 并把结果传送到目标寄存器Rd中
RSC{cond}{S} Rd, Rn, operand2
-
MUL 32位乘法指令
计算两个32位的数的乘积, 并将结果存放到一个32位的寄存器Rd中
MUL{cond}{S} Rd, Rm, Rs
-
MLA 32位乘法指令
计算两个32位的数的乘积, 再将乘积加上第3个操作数Rn的值, 并将结果存放到一个32位的寄存器Rd中
MLA{cond}{S} Rd, Rm, Rs, Rn
-
UMULL 64位无符号乘法指令
计算两个32位无符号数的乘积, 乘积结果的高32位存放到一个32位的寄存器RdHi中, 乘积结果的低32位存放到另一个32位的寄存器RdLo中
UMULL{cond}{S} RdLo, RdHi, Rm, Rs
-
UMLAL 64位无符号乘法指令
两个32位无符号数的64位乘积结果与由(RdHi:RdLo)表示的64位无符号数相加, 计算结果的高32位存放到一个32位的寄存器RdHi中, 低32位存放到另一个32位的寄存器RdLo中
UMLAL{cond}{S} RdLo, RdHi, Rm, Rs
-
SMULL 64位有符号乘法指令
计算两个32位有符号数的乘积, 乘积结果的高32位存放到一个32位的寄存器RdHi中, 乘积结果的低32位存放到另一个32位的寄存器RdLo中
SMULL{cond}{S} RdLo, RdHi, Rm, Rs
-
SMLAL 64位有符号乘法指令
两个32位有符号数的64位乘积结果与由(RdHi:RdLo)表示的64位无符号数相加, 计算结果的高32位存放到一个32位的寄存器RdHi中, 低32位存放到另一个32位的寄存器RdLo中
SMLAL{cond}{S} RdLo, RdHi, Rm, Rs
逻辑运算指令
-
AND与逻辑运算指令
将第二操作数operand2表示的数值与寄存器Rn的值按位做逻辑与操作, 并把结果保存到目标寄存器Rd中
AND{cond}{S} Rd, Rn, operand2
-
ORR或逻辑运算指令
将第二操作数operand2表示的数值与寄存器Rn的值按位做逻辑或操作, 并把结果保存到目标寄存器Rd中
ORR{cond}{S} Rd, Rn, operand2
-
EOR异或逻辑运算指令
将第二操作数operand2表示的数值与寄存器Rn的值按位做逻辑异或操作, 并把结果保存到目标寄存器Rd中
ORR{cond}{S} Rd, Rn, operand2
-
BIC清除逻辑运算指令
将寄存器Rn的值与第二操作数operand2表示的数值的反码按位做逻辑与操作
BIC{cond}{S} Rd, Rn, operand2
比较指令
比较指令没有目标寄存器, 只更新CPSR的条件标志位, 所以无需S
-
CMP相减比较指令
将寄存器Rn的值减去第二操作数operand2表示的数值
CMP{cond} Rn, operand2
-
CMN负数比较指令
将寄存器Rn的值加上第二操作数operand2表示的数值
CMN{cond} Rn, operand2
-
TST位测试指令
将寄存器Rn的值与第二操作数operand2表示的数值按位做逻辑与操作
TST{cond} Rn, operand2
-
TEQ相等测试指令
将寄存器Rn的值与第二操作数operand2表示的数值按位做逻辑异或操作
TEQ{cond} Rn, operand2
杂类数据处理指令
-
CLZ前导零计数指令
用于计算32位寄存器高位中0的个数, 不影响CPSR的条件标志位
CLZ{cond} Rd, Rm
跳转指令
跳转指令可以完成从当前指令向前或向后的32MB的地址空间的跳转
向程序计数器PC写入跳转地址值, 可以实现在4GB的地址空间中的任意跳转
-
B分支指令
跳转到指定的地址执行程序
B{cond} target_address
-
BL带链接的分支指令
跳转到指定的地址执行程序, 同时将程序计数器PC的值保存到LR寄存器中
BL{cond} target_address
-
BX带状态切换的跳转指令
跳转到指定的地址执行程序, 目标地址处的指令既可以是ARM指令, 也可以是Thumb指令
BX{cond} Rm
-
BLX带链接和状态切换的跳转指令
跳转到指令中所指定的目标地址(目标地址总是Thumb指令), 并将处理器的工作状态由ARM状态切换到Thumb状态, 同时将程序计数器PC的当前内容保存到链接寄存器R14中
BLX target_address 或 BLX{cond} Rm
协处理器指令
-
CDP协处理器数据操作指令
通知ARM协处理器执行特定的操作, 若协处理器不能成功完成特定的操作, 则产生未定义指令异常
指令格式:
CDP{<cond>} <coproc>,<opcode1>,<CRd>,<CRn>,<CRm>,{<opcode2>}
- coproc: 协处理器名称, 标准名为pn(n为0~15)
- opcode1: 协处理器将执行操作的第一操作码
- CRd: 目标寄存器的协处理器寄存器
- CRn: 存放第1个源操作数的协处理器寄存器
- CRm: 存放第2个源操作数的协处理器寄存器
- opcode2: 协处理器将执行操作的第二操作码
-
协处理器加载/存储指令
实现ARM处理器与协处理器之间的数据传输, 如果协处理器不能成功执行该操作, 将产生未定义指令异常
指令格式:
LDC{<cond>}{L} <coproc>,<CRd>,<addressing_mode> STC{<cond>}{L} <coproc>,<CRd>,<addressing_mode>
- L: 传输的数据为长整数
- coproc: 协处理器名称
- CRd: 协处理器的寄存器
- addressing_mode: 指定的内存地址
-
ARM寄存器与协处理器寄存器数据传输指令
实现ARM通用寄存器与协处理器寄存器之间的数据传输, 如果协处理器不能成功执行该操作, 将产生未定义指令异常
指令格式:
MCR{<cond>} <coproc>,<opcode1>,<Rd>,<CRn>,<CRm>{,<opcode2>} MRC{<cond>} <coproc>,<opcode1>,<Rd>,<CRn>,<CRm>{,<opcode2>}
- coproc: 协处理器名称
- opcode1: 协处理器将执行操作的第一操作码
- Rd: ARM处理器的通用寄存器, 它用作源/目标寄存器
- CRn: 存放第1个操作数的协处理器寄存器
- CRm: 存放第2个操作数的协处理器寄存器
- opcode2: 协处理器将执行操作的第二操作码
杂项指令
程序状态寄存器处理指令: 实现通用寄存器与程序状态寄存器之间的数据传输
软中断指令: 用于实现系统调用
断点调试指令: 用于程序的调试
-
MRS读程序状态寄存器指令
将状态寄存器的内容传送到通用寄存器中
语法格式:
MRS{cond} <Rd>, CPSR/SPSR
-
MSR写程序状态寄存器指令
将通用寄存器的内容或立即数传送到程序状态寄存器中
语法格式:
MSR {cond} CPSR/SPSR_<fields>, <operand2>
- fields: 状态寄存器中需要操作的位域
- c: bits[7:0], 控制位域
- x: bits[15:8], 扩展位域
- s: bits[23:16], 状态位域
- f: bits[31:24], 条件标志位域
- fields: 状态寄存器中需要操作的位域
-
SWI软中断指令
用于产生软件中断, 执行后会将处理器置于监控模式(SVC)并跳转到相应的异常处理程序(偏移量0x08)
语法格式:
SWI{<cond>} <immed_24>
- immed_24 24位立即数, 指定用户程序调用系统例程的类型
如何读出指令中的24位立即数: 首先确定引起软中断的SWI指令是ARM指令还是Thumb指令, 这可通过对SPSR访问得到. 然后要取得该SWI指令的地址, 这可通过访问LR寄存器得到. 接着读出指令, 分解出立即数.
示例代码(网上抄的, 不知道对不对):
SWI_Hander STMFD SP!,{R0-R3,R12,LR} ; 保护现场 MRS R0,SPSR ; 读取SPSR STMFD SP!,{R0} ; 保存SPSR TST R0,#0x20 ; 测试T标志位 LDRNEH R0,[LR,#-2] ; 若是Thumb指令, 读取指令码(16位) BICNE R0,R0,#0xFF00 ; 取得Thumb指令的8位立即数 LDREQ R0,[LR,#-4] ; 若是ARM指令, 读取指令码(32位) BICNQ R0,R0,#0xFF00000 ; 取得ARM指令的24位立即数 ... LDMFD SP!,{R0-R3,R12,PC}^ ; SWI异常中断返回
-
BKPT断点调试指令
产生软件断点中断, 可用于程序的调试. 当BKPT指令执行时, 处理器停止执行当前指令并进入相应的BKPT入口程序
语法格式:
BKPT <immed_16>
- immed_16: 16位立即数, 被调试软件用来保存额外的断点信息
Thumb指令集
TODO
伪指令
-
LDR大范围地址读取伪指令
将一个32位的常数或者一个地址值读取到寄存器中
- 如果加载的常数符合MOV或MVN指令立即数的要求, 则用MOV或MVN指令替代LDR伪指令
- 如果加载的常数不符合MOV或MVN指令立即数的要求, 汇编器将常量放入常量池, 并使用一条程序相对偏移的LDR指令从内常量池读出常量
LDR{cond} register, =expression
-
ADRL中等范围地址读取伪指令
将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中
ADRL{cond} register, =expression
-
ADR小范围地址读取伪指令
将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中, 当地址是字节对齐时取值范围为-255~+255
ADR{cond} register, =expression
-
NOP空操作伪指令
在汇编时将会被替代成ARM中的空操作, 可用于延时操作
NOP
ARM汇编与C语言混合编程
常用开发工具:
- ADS/SDT, RealView MDK等ARM公司推出的开发工具
- GNU ARM开发工具
APCS
APCS, 即ARM过程调用标准(ARM Procedure Call Standard), 定义了一种机器级别的、核心寄存器相关的过程调用规范
APCS定义了:
- 对寄存器使用的限制
- 使用栈的惯例
- 在函数调用之间传递/返回参数
- 可以被"回溯"的基于栈的结构的格式, 用来提供从失败点到程序入口的函数(和给予的参数)的列表
寄存器名称
寄存器 | 替代名 | 用途 |
---|---|---|
r0 | a1 | 参数/返回值/暂存寄存器1 |
r1 | a2 | 参数/返回值/暂存寄存器2 |
r2 | a3 | 参数/暂存寄存器3 |
r3 | a4 | 参数/暂存寄存器4 |
r4 | v1 | 变量寄存器1 |
r5 | v2 | 变量寄存器2 |
r6 | v3 | 变量寄存器3 |
r7 | v4 | 变量寄存器4 |
r8 | v5 | 变量寄存器5 |
r9 | v6/sb/tr | 平台相关, 可用于静态基址寄存器或线程寄存器等 |
r10 | sl/v7 | 栈界限寄存器 |
r11 | fp/v8 | 帧指针寄存器 |
r12 | ip | 内部过程调用暂存寄存器 |
r13 | sp | 栈指针寄存器 |
r14 | lr | 链接寄存器 |
r15 | pc | 程序计数器 |
- r0~r15 ARM 处理器的通用寄存器
- f0~f7 浮点数运算加速寄存器
- s0~s31 单精度向量浮点数运算寄存器
- d0~d15 双精度向量浮点数运算寄存器
- p0~p15 协处理器
- c0~c15 协处理器寄存器
其他内容略
Keil (RealView MDK)
汇编语句
{symbol} {instruction|directive|pseudo-instruction} {;comment}
符号命名规则:
- 符号由大小写字母、数字、下划线组成, 且符号区分大小写
- 局部标号可以用数字开头, 其他的符号不能
- 符号在其作用范围内必须是唯一的
- 符号不能与预定义的符号, 指令助记符或者伪操作同名
标号: 代表汇编程序中指令或数据的内存地址, 默认只在定义它的源文件中可见, 除非用EXPORT导出
- PC相关标号 基于PC的标号是位于目标指令前或者程序中数据定义伪操作前的标号, 这种标号在汇编时将被处理成PC值加上(或减去)一个数字常量. 常用于表示跳转指令的目标地址, 或者代码段中所嵌入的少量数据
- 寄存器相关标号 基于寄存器的标号常用MAP和FIELD来定义, 也可以用EQU定义. 这种标号在汇编时将被处理成寄存器的值加上(或减去)一个数据常量. 常用于访问数据段中的数据
- 绝对地址 表示的是内存的绝对地址, 是范围在0到2的32次幂-1之间的数字常量, 使用EQU来定义
局部数字标号: 通过数字而不是名称进行引用, 它们的用法与PC相关标号的用法类似, 但是局部数字标号的范围更窄
-
声明一个局部数字标号
语法格式:
n {routname}
- n 局部标号的数字号, 范围在0到99之间
- routname 是一个表示当前作用域范围的局部范围名称
-
对一个局部数字标号的引用
语法格式:
% {F∣B} {A∣T} n{routname}
- n 局部标号的数字号
- routname 当前局部范围的名称
- F 指示汇编器只向前搜索
- B 指示汇编器只向后搜索
- A 指示汇编器搜索宏的所有嵌套层次
- T 指示汇编器搜索宏的当前层次
字符串表达式操作
-
取字符串的长度LEN
语法格式:
:LEN: A
-
将ASCII值转换为字符CHR
语法格式:
:CHR: A
-
将数字量或逻辑表达式转换为字符串STR
语法格式:
:STR: A
-
返回字符串A左端起B长度的字符串LEFT
语法格式:
:LEFT: A
-
返回字符串A右端起B长度的字符串RIGHT
语法格式:
:RIGHT: A
-
字符串连接CC
语法格式:
A :CC: B
伪操作
符号定义伪操作
-
局部变量定义LCLA、LCLL及LCLS
语法格式:
LCLA variable LCLL variable LCLS variable
-
全局变量定义GCLA、GCLL及GCLS
语法格式:
GCLA variable GCLL variable GCLS variable
-
变量赋值伪操作SETA、SETL及SETS
语法格式:
variable_a SETA expr_a variable_l SETL expr_l variable_s SETS expr_s
-
给通用寄存器列表定义名称RLIST
语法格式:
name RLIST {registers_list}
-
VFP寄存器名称定义DN、SN
语法格式:
name DN expr name SN expr
- expr 要定义的VFP寄存器编号, 双精度寄存器编号范围为0~15, 单精度寄存器编号范围为0~31
-
协处理器名称定义CP
语法格式:
name CP expr
- expr 要定义名称的协处理器编号, 编号范围为0~15
-
协处理器寄存器名称定义CN
语法格式:
name CN expr
数据定义伪操作
-
分配字节存储单元DCB
语法格式:
{label} DCB expr{,expr}...
- label 可选的程序标号
- expr 是-128~255之间的数字或字符串
-
分配半字存储单元DCW及DCWU
语法格式:
{label} DCW expr{,expr}... {label} DCWU expr{,expr}...
- label 可选的程序标号
- expr 是-32768~65535之间的数字表达式
-
分配字存储单元DCD及DCDU
语法格式:
{label} DCD expr{,expr}... {label} DCDU expr{,expr}...
- label 可选的程序标号
- expr 表达式
-
分配单精度浮点数存储单元DCFS及DCFSU
语法格式:
{label} DCFS fpliteral{,fpliteral}... {label} DCFSU fpliteral{,fpliteral}...
- label 可选的程序标号
- fpliteral 单精度浮点表达式, 取值范围: 1.17549435e-38~3.4028234e+38
-
分配双精度浮点数存储单元DCFD及DCFDU
语法格式:
{label} DCFD fpliteral{,fpliteral}... {label} DCFDU fpliteral{,fpliteral}...
- label 可选的程序标号
- fpliteral 双精度浮点表达式, 取值范围: 2.22507385850720138e-308~1.7976931348623157e+308
-
分配双字存储单元DCQ及DCQU
语法格式:
{label} DCQ {-}expr{,{-}expr}... {label} DCQU {-}expr{,{-}expr}...
- label 可选的程序标号
- expr 用于初始化内存的数字或表达式, 其数值必须是整数
-
声明数据缓冲池LTORG 在使用LDR伪指令时, 要在适当的位置加入LTROG声明数据缓冲池, 这样就会把要加载的数据保存到缓存池中, 再使用ARM加载指令读出, 如果没有使用LTROG声明数据缓冲池, 则汇编器会在程序末尾自动声明
语法格式:
LTROG
-
分配存储空间SPACE
语法格式:
{label} SPACE expr
- label 可选的程序标号
- expr 分配的字节数
-
定义结构化内存表首地址MAP
语法格式:
MAP expr{,base_register}
- 结构化内存表的首地址为expr与base_register之和
-
定义结构化内存表数据域FIELD
语法格式:
{label} FIELD expr
- label 可选的程序标号, 当指定这一选项时, label的值为当前内存表的位置计数器的值
- expr FIELD指定的域所占内存单元字节数
-
取相对地址初始化内存单元DCDO
语法格式:
{lable} DCDO expr{,expr}...
- lable 可选的程序标号
- expr 数字表达式或为程序标号, 内存分配的字数是由expr的个数决定的
-
分配代码存储单元DCI
语法格式:
{lable} DCI expr
- lable 可选的程序标号, 是内存块起始地址的标号
- expr 数字表达式(整数)或为程序标号
汇编代码控制伪操作
-
IF条件编译伪操作
语法格式:
IF logical_expression 程序代码段A {ELSE 程序代码段B} ENDIF
-
WHILE条件编译伪操作
语法格式:
WHILE logical_expression 程序代码段 WEND
-
MACRO宏定义伪操作
语法格式:
MACRO {$label} macroname {$parameter{,$parameter}...} 程序代码段 MEND
汇编信息报告控制伪操作
-
错误信息报告ASSERT
语法格式:
ASSERT logical_expression
- logical_expression 用于表示的条件的逻辑表达式
-
诊断信息报告INFO
语法格式:
INFO numeric_expression, string_expression
- numeric_expression 数字表达式. 如果numeric_expression为0, 则在第二遍扫描时, 伪操作打印string_expression的内容; 如果numeric_expression的值不为0, 则在汇编处理中, 第一遍扫描时, 伪操作打印string-expression的内容, 并终止汇编
-
列表选项设置OPT
OPT为编译列表选项设置伪操作, 用于在源程序中设置汇编列表选项
语法格式:
OPT n
- n 是OPT指令设置选项编号
-
插入文件标题伪操作TTL与SUBT
语法格式:
TTL title SUBT subtitle
- title 所插入的列表文件的标题
- subtitle 所插入的列表文件的子标题
指令集类型标识伪操作
用来告诉编译器所处理的是32位的ARM指令还是16的Thumb指令, 实现这一操作的操作符有ARM、CODE32、THUMB、CODE16
- ARM或CODE32: 指示编译器将要处理的是32位的ARM指令
- THUMB或CODE16: 指示编译器将要处理的是16位的Thumb指令
文件包含伪操作
文件包含伪操作包括两类:
- 一类是将一个源文件包含到当前源文件中, 并将被包含的文件在其当前位置进行汇编处理
- 另一类是也将一个源文件包含到当前源文件中, 但被包含文件不进行汇编处理
-
文件包含GET或INCLUDE
语法格式:
GET filename INCLUDE filename
- filename 是要在汇编中包含的文件名称, 汇编程序接受UNIX或MS-DOS格式的路径名
-
文件原样包含INCBIN
语法格式:
INCBIN filename
- filename 是要在汇编中包含的文件名称, 汇编程序接受UNIX或MS-DOS格式的路径名
其他类型伪操作
-
对齐方式设置ALIGN
通过用零或NOP指令进行填充来使当前位置与指定的边界对齐
语法格式:
ALIGN {expr{,offset{,pad{,padsize}}}}
-
段属性定义伪操作AREA
用于定义一个代码段或数据段, 段是不可分的已命名独立代码或数据块, 它们由链接器处理
语法格式:
AREA sectionname{,attr}{,attr}...
-
声明程序的入口点ENTRY
语法格式:
ENTRY
-
源程序结尾标识END
通知汇编程序它已到达源文件的末尾
语法格式:
END
-
定义常量或标号名称EQU
语法格式:
name EQU expr{,type}
- name 要为数值指定的符号名称
- expr 可以是一个寄存器相对的地址、程序相对的地址、绝对地址或32位整型常数
-
声明全局标号EXPORT或GLOBAL
声明一个全局的符号, 可由链接器用于解析不同的对象和库文件中的符号引用, GLOBAL是EXPORT的同义词
语法格式:
EXPORT {symbol} {[WEAK{,attr}]} GLOBAL {symbol} {[WEAK{,attr}]}
-
将符号导出到目标文件EXPORTAS
语法格式:
EXPORTAS symbol1, symbol2
- symbol1 是源文件中的符号名称, symbol1必须已定义, 它可以是任何符号, 包括区域名、标签或常数
- symbol2 是希望在目标文件中出现的符号名称
-
外部符号声明IMPORT和EXTERN
语法格式:
IMPORT symbol {[attr}]} IMPORT symbol [WEAK{,attr}] EXTERN symbol {[attr}]} EXTERN symbol [WEAK{,attr}]
-
保留局部符号KEEP
语法格式:
KEEP {symbol}
- symbol 是要保留的局部符号的名称, 如果未指定 symbol, 则保留除相对寄存器符号外的所有局部符号
-
禁止使用浮点指令NOFP
可确保在软件或目标硬件不支持浮点指令的情况下不使用任何浮点指令
语法格式:
NOFP
-
指定段的相关性REQUIRE
指定各段之间的相关性
语法格式:
REQUIRE label
- label 是所需标签的名称
-
栈八字节对齐REQUIRE8和PRESERVE8
语法格式:
REQUIRE8 {bool} PRESERVE8 {bool}
- bool 是一个可选布尔常数, 取值为{TRUE}或{FALSE}
-
局部变量范围定义ROUT
语法格式:
{name} ROUT
- name 是要分配给作用域的名称
实例
AREA MYDATA, DATA ; 定义数据段
AREA MYCODE, CODE ; 定义代码段
ENTRY ; 标记程序入口
EXPORT __main ; 使__main对链接器可见
__main
B . ; 跳转到当前指令, 即死循环
END ; 标记程序结束
混合编程
C语言内嵌汇编
__asm // 内嵌汇编标识
{
[指令]
[指令] // 注释
...
}
汇编语句中可以直接引用C语言变量
汇编调用C语言函数
使用IMPORT伪操作声明将要调用的C程序
C语言调用汇编子程序
使用EXPORT伪操作声明本程序可以被其他程序调用, 同时在C程序中要用关键字extern声明要调用的汇编语言程序
GNU
汇编语句
[<label>:][<instruction|directive|pseudo-instruction>} @ comment
- # 放在行首表示这一行都是注释而不是代码
- /* … */ 支持C风格多行注释
- 立即数前面要加#或$表示这是个立即数
- \ 在行末表示换行, 即下一行与本行为同一语句
符号命名规则:
与C语言基本一致, 符号名由字母、数字以及’_’和’.’组成, 大小写敏感
伪操作
符号定义伪操作
-
常量定义伪操作.equ或.set
语法格式:
.equ symbol, expr .set symbol, expr
- symbol 要指定的名称, 可以是以前定义过的符号
- expr 表示数字常量或程序中的标号
-
常量定义伪操作.equiv
语法格式:
.equiv symbol, expr
symbol 要指定的名称, 不可以是以前定义过的符号 expr 表示数字常量或程序中的标号
-
声明全局常量伪操作.global或.globl
语法格式:
.global symbol .globl symbol
- symbol 要声明的全局变量名称
-
声明外部常量伪操作.extern
语法格式:
.extern symbol
symbol 要声明的外部变量名称
-
设置寄存器别名伪操作.req
语法格式:
<别名> .req <寄存器名>
-
取消寄存器别名伪操作.unreq
语法格式:
.unreq <寄存器别名>
数据定义伪操作
-
字节定义.byte
语法格式:
.byte expr{, expr}...
- expr 数字表达式或程序中的标号
-
半字定义.hword或.short
语法格式:
.hword expr{, expr}... .short expr{, expr}...
- expr 数字表达式或程序中的标号
-
字定义.word或.int或.long
语法格式:
.word expr{, expr}... .int expr{, expr}... .long expr{, expr}...
- expr 数字表达式或程序中的标号
-
字符串定义.ascii和.asciz或.string
语法格式:
.ascii expr{, expr}... .asciz expr{, expr}... .string expr{, expr}...
- expr 表示字符串
-
双字定义.quad
语法格式:
.quad expr{, expr}...
- expr 数字表达式
-
四字定义.octa
语法格式:
.octa expr{, expr}...
- expr 数字表达式
-
单精度浮点数定义.float或.single
语法格式:
.float expr{, expr}... .single expr{, expr}...
- expr 为32位的 IEEE 单精度浮点数
-
双精度浮点数定义.double
语法格式:
.double expr{, expr}...
- expr 为32位的IEEE双精度浮点数
-
重复内存单元定义.fill
语法格式:
.fill repeat{, size}{, value}
- repeat 重复填充的次数
- size 每次所填充的字节数
- value 所填充的数据
-
重复定义伪操作.rept
语法格式:
.rept 重复次数 [数据定义] .endr
-
零填充字节内存单元定义.zero
语法格式:
.zero size
- size 所分配的0填充字节数
-
固定填充字节内存单元定义.space或.skip
语法格式:
.space size{, value} .skip size{, value}
- size 所分配的字节数
-
声明数据缓冲池.ltorg或.pool
语法格式:
.ltorg .pool
预定义控制伪操作
-
条件编译伪操作.if
语法格式:
.if logical_expression 程序代码段A {.else 程序代码段B } .endif
-
宏定义伪操作.macro
语法格式:
.macro {macroname {parameter{, parameter}...} 程序代码段 .endm
-
文件包含伪操作.include
用于将一个源文件包含到当前的源文件中, 所包含的文件在.include指令的位置处进行汇编处理
语法格式:
.include "file_name"
-
二进制文件包含伪操作.incbin
用于将一个二进制文件原封不动地编译到当前文件中其中
语法格式:
.incbin "file_name"[, skip[, count]]
- skip 从文件开头跳过skip个字节开始读取文件
- count 读取的字数
指令集类型标识伪操作
- .arm或.code 32: 指示编译器将要处理的是32位的ARM指令
- .thumbB或.code 16: 指示编译器将要处理的是16位的Thumb指令
其他伪操作
-
段属性定义伪操作
语法格式:
.section expr
- expr 段属性, 可以是.text, .data和.bss中的一个
-
段起始声明伪操作
语法格式:
.text .data .bss
-
对齐方式设置伪操作.align或.balign
语法格式:
.align {alignment}{, fill} .balign {alignment}{, fill}
- alignment 是一个数值表达式, 用于指定对齐方式, 其取值在0~15范围内
- fill 用来指定进行填充的数据
-
代码位置设置伪操作.org
语法格式:
.org offset{, expr}
- offset 是一个数值表达式, 表示地址偏移量
- expr 用来指定进行填充的数据
-
源文件结束伪操作.end
表明源文件的结束, 如果该伪操作之后还有代码, 则不会被编译到可执行文件中
语法格式:
.end
实例
.section .data
# 已初始化的全局变量
.section .bss
# 未初始化的全局变量
.section .text
.global _start
_start:
b .
.end
混合编程
C语言内嵌汇编
语法格式:
asm [volatile](code : output operand list : input operand list : clobberlist);
- volatile: 写上后禁止汇编器优化
- code: 汇编指令
- output operand list: (可选) 由一对[]和它括着的符号名组成, 它后面跟着限制性字符串, 再后面是圆括号和它括着的C变量. 符号名在汇编中使用%[符号名]引用
- input operand list: (可选) 同上
- clobberlist: (可选) 破坏符列表
asm volatile
(
"[指令;] \
[指令;] \ /* 注释 */
..."
: [result] "=r" (y)
: [value] "r" (x)
: "memory" /* 告诉编译器内存被修改过了 */
);
汇编调用C语言函数
使用.extern伪操作声明将要调用的C程序
C语言调用汇编子程序
使用.global伪操作声明汇编程序为全局的函数, 可被外部函数调用, 同时在C程序中要用关键字extern声明要调用的汇编语言程序