第5章指令集目录第5章指令集5.1 指令集的背景介绍5.2 理解汇编语言语法5.3 指令后缀的使用ARM 指令后缀全称速查表5.4 指令集1.处理器内传送数据2.存储器访问指令3.算术运算4.逻辑运算5.移位和循环移位执行步骤6.比较指令7.程序流控制指令8.休眠模式9.存储器屏障指令10.其他指令知识点扩展1、伪指令EQU2、伪指令LDR3.文字池的本质隐藏在代码段中的“数据仓库”4、逻辑PC和物理PC5.1 指令集的背景介绍早期的ARM处理器支持名为ARM的32位指令集该指令集功能强大支持多数指令的条件执行并提供良好的性能。随着移动市场的火爆移动端的32位处理器对于功耗和成本等要求变高所以1995年ARM公司在ARM7TDMI处理器上支持了一种新的16位指令集。该16位指令集被称为thumb使用这种指令集代码量减少了30%。Thumb指令集提供了ARM指令集的一个子集在ARM7TDMI处理器上设计了一个指令译码器将thumb指令转换为arm32指令通过双状态模式使得兼容性更好。在2003年ARM公司提出了thumb-2技术thumb-2指令集扩展了thumb指令集并加入了许多32位指令该技术使得处理器可以同时使用16位和32位指令集。注意thumb-2里的32位指令与arm32里的32位指令不同。5.2 理解汇编语言语法汇编指令格式如下label mnemonic operand1,operand1,... ;注释 /* label:表示地址位置可选通过这个label可以得到指令的地址。 mnemonic为助记符即汇编指令名称 operand1操作数通常第一个操作数为操作的目的操作数包含不同的操作类型具体可查询汇编指令使用方法 operand2操作数通常为操作的源。 表示为注释符号和C语言编程中的// */ 举例 MOVS R0#0x123 /*设置R0为0x123*/ MOVS R1, #A /*设置R1为字符A*/汇编代码的一个常见特性为定义常量通过定义常量代码的可读性得到提升。下面给出一个常量定义的例子NVIC_IRQ_SETEN EQU 0xE000E100 /*宏定义*/ NVIC_IRQ0_ENABLE EQU 0x1 /*宏定义*/ ... LDR R0, NVIC_IRQ_SETEN /*使用LDR伪指令将地址信息写入到R0*/ MOVS R1,#NVIC_IRQ0_ENABLE /*将0x1立即数写入到R1*/ STR R1, [R0] /*store register,作用是将寄存器中的数据写入内存 将R1中的数写入地址0xE000E100中 */5.3 指令后缀的使用对于ARM处理器的汇编器有些指令跟着后缀后缀描述如下ARM 指令后缀全称速查表后缀全称作用适用指令示例EQEqual相等时执行 (Z1)所有指令ADDEQ R0, R1, R2NENot Equal不等时执行 (Z0)所有指令MOVNE R0, #1CS/HSCarry Set / Higher or Same无符号 ≥ (C1)所有指令ADDCS R0, R0, R1CC/LOCarry Clear / Lower无符号 (C0)所有指令SUBCC R0, R0, #1MIMinus负数时执行 (N1)所有指令RSBMI R0, R0, #0PLPlus非负时执行 (N0)所有指令MOVPL R0, #0VSOverflow Set溢出时执行 (V1)所有指令ADDVS R0, R0, #1VCOverflow Clear无溢出时执行 (V0)所有指令SUBVC R0, R0, R1HIHigher (Unsigned)无符号 (C1 Z0)分支/所有指令BHI targetLSLower or Same (Unsigned)无符号 ≤ (C0Z1)所有指令GEGreater or Equal (Signed)有符号 ≥ (NV)所有指令MOVGE R1, #1LTLess Than (Signed)有符号 (N≠V)所有指令ADDLT R0, R0, #100GTGreater Than (Signed)有符号 (Z0 NV)分支/所有指令CMPGT R0, R1LELess or Equal (Signed)有符号 ≤ (Z1N≠V)所有指令BByte8位操作LDR/STRLDRB R0, [R1]HHalfword16位操作LDR/STRSTRH R0, [R2]DDoubleword64位操作LDR/STR (ARMv5)LDRD R0, R1, [R2]SSet Flags更新CPSR标志位算术/逻辑指令ADDS R0, R1, R2!Writeback更新基址寄存器内存操作指令LDR R0, [R1, #4]!TThumb State切换到Thumb状态BX/BLXBX R0(R0 LSB1)IAIncrement After操作后地址递增LDM/STMLDMIA R0!, {R1-R3}DBDecrement Before操作前地址递减LDM/STMSTMDB SP!, {R4-R6}所有带条件后缀的 ARM 指令在执行前都会检查执行前的 CPSR 状态并根据该状态决定是否执行本条指令的操作。指令执行后才会更新 CPSR如果指令包含S后缀。5.4 指令集Cortex-M3和Cortex-M4处理器的指令可以按照功能分为如下几类1.处理器内传送数据在处理器之间执行数据传送是微处理器中最基本的操作。可能会出现如下操作场景将数据从一个寄存器送到另一个寄存器MOV R4, R0 /*将R0的数据写入到R4*/ MOVS R4, R0 /*将R0的数据写入到R4,同时更新APSR寄存器*/ MVN R3, R7 /*将R7的数据取反后更新至R3MVNmove not*/在寄存器和特殊寄存器CONTROL,PRIMASK,FAULTMASK,BASEPRI之间进行数据传送MRS R7, PRIMASK /*将PRIMASK(特殊寄存器)的数据写入到R7MRS全称move to register from Special register*/ MSR CONTROL, R0 /*将R0的数据写入到CONTORL特殊寄存器中*/将立即数写入到寄存器中MOV R4, #0x12 /*将0x12的数据写入到R4*/ MOVS R4, #0x12 /*将0x12的数据写入到R4,同时更新APSR寄存器*/ MOVW R0, #0x1234 /*将16位立即数加载到寄存器R0的低16位高16位清零*/ MOVT R0, #0x1234 /*将16位立即数加载到寄存器R0的高16位低16位不变*/针对带浮点单元的CORTEX-M4处理器还支持内核寄存器和浮点单元寄存组浮点寄存器组之间以及浮点系统寄存器与内核寄存器立即数与浮点寄存器之间的数据交换。2.存储器访问指令Cortex-M3/M4支持很多存储器访问指令寻址模式及数据大小和数据传输方向具有很多组合方式。立即数偏移数据传输的存储器地址为寄存器中的数值加上偏移值。指令分为load指令和store指令load指令是将存储器的信息写入到寄存器中store指令是将寄存器的数据写入到存储器中。//Load instruction LDRB Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的字节信息读入到Rd中 LDRBload rgister byte*/ LDRSB Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的字节信息进行有符号扩展后读入到Rd中 LDRH Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的半字节信息读入到Rd中 LDRSH Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的半字节信息进行有符号扩展后读入到Rd中 LDR Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的字信息读入到Rd中 LDRD Rd1,Rd2, [Rn,#offset] /*将地址为Rn加上#offset里面的双字信息读入到Rd1和Rd2中 //Store instruction STRB Rd, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd寄存器中的字节信息 STRBstore rgister byte*/ STRH Rd, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd寄存器中的半字节信息 STRHstore rgister halfbyte*/ STR Rd, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd寄存器中的字信息 STRstore rgister word*/ STRD Rd1,Rd2, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd1Rd2寄存器中的两字信息 STRDstore rgister double word*/ /*注意offset参数可以为正数也可以为负数*/注意可以在上述指令后加一个 如LDR R0, [R1, #offset]!这就可以实现地址寄存器R1的更新即在成功完成内存加载后将计算出的新地址R1 offset写回基址寄存器 R1。这个操作为原子操作不可被打断PC相关寻址存储器访问可以产生相对于当前PC的地址的偏移值。将立即数加载到寄存器中。也被称为文本池访问。LDRB R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据字节信息加载到R1中*/ LDRSB R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据字节信息进行有符号扩展后加载到R1中*/ LDRH R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据半字信息加载到R1中*/ LDRSH R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据半字信息进行有符号扩展后加载到R1中 LDR R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据字信息加载到R1中*/ LDRD R1,R2,[PC,#offset] /*将PC地址offset(可选)地址里的数据字节信息加载到R1,R2中*/ */寄存器偏移寄存器偏移用于所处理的数据数组地址为基地址和从索引值计算出来的偏移得到的情况。LDRB R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容字节数据加载到R0 n的取值范围在0-3之间 */ LDRSB R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容字节数据进行符号扩展加载到R0 n的取值范围在0-3之间 */ LDRH R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容半字节数据加载到R0 n的取值范围在0-3之间 */ LDRSH R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容半字节数据进行符号扩展后加载到R0 n的取值范围在0-3之间 */ LDR R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容字数据加载到R0 n的取值范围在0-3之间 */ STRB R0, [R1, R2, LSL #n] /*将R0寄存器中的字节内容加载到地址R1(基地址)变址寄存器R2n之后的地址中去 n的取值范围在0-3之间 */ STRH R0, [R1, R2, LSL #n] /*将R0寄存器中的半字节内容加载到地址R1(基地址)变址寄存器R2n之后的地址中去 n的取值范围在0-3之间 */ STR R0, [R1, R2, LSL #n] /*将R0寄存器中的字内容加载到地址R1(基地址)变址寄存器R2n之后的地址中去 n的取值范围在0-3之间 */后序具有立即数寻址模式的存储器访问指令也有一个立即数偏移数值不过在访问期间是用不到这个偏移值的在访问之后才将地址偏移值的地址更新到地址寄存器中。注意后序指令中不能使用PC和SP这两个寄存器偏移数可以为正数也可以为负数。LDRB Rd, [R0], #offset /*将R0地址中的字节数据加载到Rd中在加载结束后将R0R0offset*/ LDRSB Rd, [R0], #offset /*将R0地址中的字节数据进行符号位扩展后加载到Rd中在加载结束后将R0R0offset*/ LDRH Rd, [R0], #offset /*将R0地址中的半字节数据加载到Rd中在加载结束后将R0R0offset*/ LDRSH Rd, [R0], #offset /*将R0地址中的半字节数据进行符号位扩展后加载到Rd中在加载结束后将R0R0offset*/ LDR Rd, [R0], #offset /*将R0地址中的字数据加载到Rd中在加载结束后将R0R0offset*/ LDRD Rd1, Rd1, [R0], #offset /*将R0地址中的双字数据加载到Rd1Rd2中在加载结束后将R0R0offset*/ STRB Rd, [R0], #offset /*将Rd中所存储的字节信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/ STRH Rd, [R0], #offset /*将Rd中所存储的半字节信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/ STR Rd, [R0], #offset /*将Rd中所存储的字信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/ STRD Rd1,Rd2, [R0], #offset /*将Rd中所存储的双字信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/多加载和多存储ARM架构中有一个重要的优势就是可以读或者写存储器中多个连续数据LDM和STM指令只支持32位数据它们支持两种前序一种每次读写后增加地址一种是每次读写后减少地址。如果该类指令后面跟!则意味着数据加载结束之后会将基址寄存器的信息进行更新。//LDMIA Rn,reg list /*LDMIA: load data memory increment after*/ LDMIA Rn,R1-R5 /*从Rn指定的存储器位置读取多个字依次将数据存储R1,R2,R3,R4,R5中每次读写后地址增加*/ LDMIA Rn,R0,R7,R2-R5, /*从Rn指定的存储器位置读取多个字依次将数据存储在R0,R2,R3,R4,R5,R7中,注意寄存器加载时与顺序无关加载寄存器按照升序排列进行加载*/ //LDMDB Rn,reg list /*LDMIA: load data memory decrement befor先执行地址减操作然后在加载数据*/ LDMDB Rn,R1-R5 /*从Rn指定的存储器位置-4读取多个字依次将数据存储R5,R4,R3,R2,R1中每次读写后地址减少*/ LDMDB Rn,R0,R7,R2-R5, /*从Rn指定的存储器位置-4读取多个字依次将数据存储在R7,R5,R4,R3,R2,R1中*/ //STMIA Rn,reg list /*STMIA: store data memory increment after*/ STMIA Rn,R1-R5 /*往Rn指定的存储器位置写入多个字写入将数据为R1,R2,R3,R4,R5中的数据每次读写后地址增加*/ STMIA Rn,R0,R2-R5,R7 //STMDB Rn,reg list /*STMDB : store data memory decrement befor*/ STMDB Rn,R1-R5 /*往Rn指定的存储器位置-4写入多个字依次将数据存储R5,R4,R3,R2,R1中每次读写后地址增加*/ STMDB Rn,R0,R2-R5,R7 /*往Rn指定的存储器位置-4写入多个字依次将数据存储在R7,R5,R4,R3,R2,R1中*/压栈和出栈栈的PUSH和POP是另外一种形式的多存储和多加载利用当前选定的栈指针来生成地址当前指针可以是主栈指针(msp)也可以是进程栈指针(psp)。实际选择是通过处理器的当前模式和CONTROL寄存器的数值决定。PUSH {R0,R4-R7,R9} ;PUSH指令将R0,R4,R5,R6,R7,R9压入栈中 POP {R0,R2} ;POP指令将栈中的内容存入R0,R2寄存器中SP相关寻址栈空间除了用于函数或者子例程的寄存器临时存储同时还常用于存储局部变量。访问这些临时变量需要前面提到过的LDR和STR指令。在函数开始处为局部变量分配栈空间同时调整SP指针当调用函数结束后SP指针释放这部分空间。3.算术运算Cortex-M3/M4处理器提供了多个用于算术运算的指令具体如下ADD Rd, Rn, Rm ; RdRnRm ADD Rd, Rn, #immed ; RdRnimmed ADC Rd, Rn, Rm ; RdRnRm进位 ADC Rd, #immed ; RdRdimmed进位 ADDW Rd, Rn, #immed ; RdRnimmed,注意这条指令是thumb-2指令支持完成的12位无符号立即数 SUB Rd, Rn, Rm ; RdRn-Rm SUB Rd, #immed ; RdRd-immed SUB Rd, Rn, #immed ; RdRn-immed SBC Rd, Rn, Rm ; RdRn-Rm-借位 SBC Rd, #immed ; RdRd-immed-借位 SUBW Rd, Rn, #immed ;RdRn-immed 注意这条指令是thumb-2指令支持完成的12位无符号立即数 RSB Rd, Rn, #immed ; Rdimmed-Rn RSB Rd, Rn, Rm ; RdRm-Rn MUL Rd, Rn, Rm ;Rd Rn*Rm UDIV Rd, Rn, Rm ;Rd Rn/Rm ;无符号运算 SDIV Rd, Rn, Rm ;Rd Rn/Rm ;有符号运算4.逻辑运算Cortex-M3/M4处理器提供了多个用于逻辑运算的指令具体如下AND Rd,Rn ;RdRd Rn AND Rd,Rn,#immed ;RdRn immed AND Rd,Rn,Rm ;RdRn Rm ORR Rd,Rn ;RdRd | Rn ORR Rd,Rn,#immed ;RdRn | immed ORR Rd,Rn,Rm ;RdRn | Rm BIC Rd,Rn ; RdRd (~Rn) BIC:bit clear BIC Rd,Rn,#immed ;RdRn(~immed) BIC Rd,Rn,Rm ;RdRn (~Rm) ORN Rd,Rn,#immed ;RdRn|(~immed) ORNlogic or not或非操作 ORN Rd,Rn,Rm ;RdRn |(~Rm) EOR Rd,Rn ; RdRd ^ Rn EOR:Exclusive OR EOR Rd,Rn,#immed ;RdRn ^ immed EOR Rd,Rn,Rm ;RdRn ^ Rm5.移位和循环移位Cortex-M3/M4处理器提供了多个用于移位操作的指令具体如下//算术右移 Arithmetic Shift Right ASR Rd, Rn, #immed ;RdRnimmed ASR Rd, Rn ;RdRdRn ASR Rd, Rn, Rm ;RdRnRm //逻辑右移 Logic Shift Right LSR Rd, Rn, #immed ;RdRnimmed LSR Rd, Rn ;RdRdRn LSR Rd, Rn, Rm ;RdRnRm //逻辑左移 Logic Shift Left LSL Rd, Rn, #immed ;RdRnimmed LSL Rd, Rn ;RdRdRn LSL Rd, Rn, Rm ;RdRnRm //循环右移 ROtate Right ROR Rd, Rn ;Rd右移Rn ROR Rd, Rn, Rm ; Rd等于Rn右移Rm //循环右移并展开 Rotate Right with Extend RRX Rd, Rn ;{C,Rd}{Rn,C}给一个例子说明循环右移并展开RRX指令初始状态R1 0x00000003二进制0000 0000 0000 0000 0000 0000 0000 0011C 1CPSR 进位标志指令RRX R0, R1执行步骤构建 33 位数复制下载textC标志位1 R132位 1_00000000000000000000000000000011循环右移 1 位复制下载text原始33位1_00000000000000000000000000000011 右移1位1_10000000000000000000000000000001 → **最低位1移出**分离结果低 32 位10000000000000000000000000000001→0x80000001存入 R0移出位1存入新 C 标志位移位操作总结操作类型汇编指令移位方向填充规则数学等价关键特性逻辑左移LSL← 左移低位补0数据 × 2ⁿ适用于无符号数乘法逻辑右移LSR→ 右移高位补0数据 ÷ 2ⁿ(无符号)适用于无符号数除法算术右移ASR→ 右移高位补符号位(保留正负性)数据 ÷ 2ⁿ(有符号)保留符号位用于有符号数除法循环右移ROR→ 右移高位补移出的低位无直接数学等价数据完整性保留用于位重组带进位循环右移RRX→ 右移1位高位补原进位标志C无直接数学等价33位操作 (数据C标志)用于链式移位6.比较指令比较指令仅用于更新APSR中的标志不会保存运算结果。APSR更新的标志将用于后续的条件判断或条件执行。CMP R0,R1 ;计算R0-R1,根据计算结果更新APSR寄存器 CMP R0,#immed ;计算R0-immed,根据计算结果更新APSR寄存器 CMN R0,R1 ;计算R0R1,根据计算结果更新APSR寄存器 CMN R0,#immed ;计算R0immed,根据计算结果更新APSR寄存器7.程序流控制指令跳转B lable ;跳转到lable如果条状范围超扩正负2KB则使用下面的跳转指令。 B.W lable BX R0 ;实现间接跳转并且基于Rm第0位设置处理器的执行状态T位。最低位为0为ram状态最低位为1为thumb状态函数调用BL lable ;跳转到lable地址出并返回地址保存到LR中 BLX Rn ;跳转到Rn指定的地址并将返回地址保存到LR中同时更新EPSR中的T位。条件条状//APSR寄存器中的状态位 ; N:负标志 ; Z0 ; C进位标志 ; V溢出标志 Bcond lable ;cond见下图lable为函数标签 Bcond.W lable ;8.休眠模式进入休眠模式有两条指令WFI ;wait for interrupt WFE ;wait for event9.存储器屏障指令DMB ;数据存储器屏障确保在执行新的存储器访问之前所有的存储器访问都已经完成 DSB ;数据同步屏障确保在执行下一条指令前所有的存储器访问都已经完成 ISB ;指令同步屏障清空流水线确保在执行新的指令之间前面的指令都已经完成10.其他指令NOP ;空指令知识点扩展1、伪指令EQUEQU在 ARM 汇编中EQU是最重要的伪指令之一全称为Equate等价。它的核心作用是为常量、地址或表达式赋予一个符号化的名称本质上是一种编译时的文本替换机制。是汇编世界的#define。本质在编译预处理阶段将程序中的所有符号名替换为右侧的表达式不生成机器码与真实指令不同EQU不占用内存空间作用域通常全局有效从定义处到文件结束EQU 与普通赋值的本质区别特性EQUMOV/LDR执行阶段编译时 (预处理)运行时是否占用内存否 (纯符号替换)是 (生成机器码)值是否可变常量 (不可变)变量 (可修改)操作对象符号寄存器/内存典型用途定义常量、地址、结构偏移量动态计算、流程控制EQU 与其他伪指令对比伪指令特点示例EQU定义常量数字/地址不可重定义BAUD EQU 115200SETL类似 EQU但允许重复定义后定义覆盖前值COUNT SETL 10GNU汇编中的等价符号 (功能同 EQU)size 0x100DCB分配内存并初始化数据 (非符号定义)str: DCB Hello,02、伪指令LDRARM汇编中的伪指令LDRLoad Register是一个强大的工具它不是真实的机器指令而是由汇编器如GNU AS或ARM Compiler在编译阶段转换为一条或多条等效的真实机器指令。主要功能加载常量或地址到寄存器加载任意32位立即数当需要将超出MOV/MVN指令范围的立即数如0x12345678加载到寄存器时伪指令LDR会自动拆解为合适的机器指令序列。LDR R0, 0x12345678; 伪指令 → 汇编器自动处理编译后可能转换为LDR R0, [PC, #offset]; 从文字池加载PC相对寻址 ... .ltorg; 文字池中存储0x12345678加载标号函数或变量地址在获取全局变量、函数的地址时避免手动计算偏移量。LDR R0, main; 将main函数的地址加载到R0 LDR R1, global_var; 将全局变量global_var的地址加载到R1重要说明真实指令LDR用于从内存加载数据如LDR R0, [R1]而伪指令LDR用于加载常量或地址带等号。伪指令LDR的底层实现原理汇编器处理伪指令LDR的两种方式优化为MOV/MVN若立即数可用单指令表示LDR R0, 0xFF → MOV R0, #0xFF; Thumb-2下可能用 MOVW存入文字池 (Literal Pool) PC相对加载汇编器在代码段末尾或LTORG声明处创建数据区存储常量。生成真实指令从文字池加载LDR R0, [PC, #offset]; offset为到文字池的偏移量3.文字池的本质隐藏在代码段中的“数据仓库”文字池是汇编器自动创建的数据存储区专门用于存放伪指令LDR Rd, value中的大立即数函数/变量的绝对地址编译时确定的复杂表达式结果文字池的三大核心特性1. 位置依赖性汇编器默认在代码段末尾放置文字池若伪指令距离文字池太远 → 超出PC偏移范围导致错误2. 对齐要求ARM模式文字池需4字节对齐Thumb模式强烈建议4字节对齐某些Cortex-M核要求严格对齐3. 作用域隔离每个汇编文件有独立文字池链接器最终合并所有文字池到代码段。4、逻辑PC和物理PC核心原理流水线中的PC永远指向未来在任何流水线设计中PC始终指向当前正在“取指”Fetch阶段的指令地址这意味着执行Execute单元处理的指令译码Decode单元处理的指令取指Fetch单元处理的指令三者是同时存在的不同指令假设当前CPU状态时钟周期 | 取指(F) | 译码(D) | 执行(E) | PC值 ---------------------------------------- 周期1 | 指令A | - | - | 0x8000 (A地址) 周期2 | 指令B | 指令A | - | 0x8004 (B地址) 周期3 | 指令C | 指令B | 指令A | 0x8008 (C地址) ← 关键点当指令A在周期3执行时PC已指向指令C0x8008超前A的地址0x8008 - 0x8000 8字节公式执行阶段PC 当前指令地址 2×指令长度ARM每条指令4字节 → 2×48字节关键结论PC偏移量的通用公式流水线深度执行阶段PC偏移量示例ARM 4字节指令3级PC 当前地址 2×4 8字节0x8000 → PC0x80085级PC 当前地址 4×4 16字节0x8000 → PC0x8010N级PC 当前地址 (N-1)×4字节通用公式黄金规律执行阶段的PC 当前指令地址 (流水线深度 - 1) × 指令长度对程序员/调试者的重大影响调试器显示的是“取指PC”当您看到调试器显示PC0x8010时真正执行的指令在0x8010 - 16 0x8000五级流水线PC相对寻址计算需注意在汇编中写LDR R0, [PC, #0]; 你以为加载当前PC?实际加载的是PC 流水线偏移量处的数据断点行为的深层原因设置断点于0x8000时CPU其实在五级流水线断在0x8000指令的执行阶段此时PC已指向0x8010流水线时代的现实冲突现代CPU采用流水线后--------------- 周期n | F取 | D译 | E执 | ← 指令A在E阶段执行时 --------------- | | F取 | D译 | ← 指令B在D阶段 --------------- | | | F取 | ← 指令C在F阶段 (PC指向这里!) ---------------关键矛盾点物理事实硬件PC寄存器永远指向正在取指的指令逻辑需求程序员需要知道当前执行指令的下一条地址不同场景下的“PC所指”对比表场景PC硬件值程序员/调试器可见值本质原因非流水线CPU当前执行指令地址 4当前执行指令地址 4顺序执行无重叠ARM模式(三级流水线)当前地址 8当前地址 4 (显示修正)流水线超前两条指令Thumb模式(三级流水线)当前地址 4当前地址 2 (显示修正)2字节指令长度差异Cortex-A77(11级流水线)当前地址 40~44当前地址 4调试器主动虚拟化断点触发时取指地址执行地址 (被中断的指令)调试器逆向计算两种“PC”的辩证关系物理PC (硬件层面)永远指向正在取指的指令值 当前执行指令地址 (流水线深度-1)×指令长度程序员不可见但影响PC相对寻址计算逻辑PC (软件层面)调试器和文档显示的“当前指令地址4”是架构设计的抽象层ARM称为Programmers Model用于保证分支目标计算符合直觉调试器单步执行语义正确反汇编列表可读性注推荐一个网站Compiler Explorer实时查看ram汇编输出。
ARM体系架构学习之指令集
第5章指令集目录第5章指令集5.1 指令集的背景介绍5.2 理解汇编语言语法5.3 指令后缀的使用ARM 指令后缀全称速查表5.4 指令集1.处理器内传送数据2.存储器访问指令3.算术运算4.逻辑运算5.移位和循环移位执行步骤6.比较指令7.程序流控制指令8.休眠模式9.存储器屏障指令10.其他指令知识点扩展1、伪指令EQU2、伪指令LDR3.文字池的本质隐藏在代码段中的“数据仓库”4、逻辑PC和物理PC5.1 指令集的背景介绍早期的ARM处理器支持名为ARM的32位指令集该指令集功能强大支持多数指令的条件执行并提供良好的性能。随着移动市场的火爆移动端的32位处理器对于功耗和成本等要求变高所以1995年ARM公司在ARM7TDMI处理器上支持了一种新的16位指令集。该16位指令集被称为thumb使用这种指令集代码量减少了30%。Thumb指令集提供了ARM指令集的一个子集在ARM7TDMI处理器上设计了一个指令译码器将thumb指令转换为arm32指令通过双状态模式使得兼容性更好。在2003年ARM公司提出了thumb-2技术thumb-2指令集扩展了thumb指令集并加入了许多32位指令该技术使得处理器可以同时使用16位和32位指令集。注意thumb-2里的32位指令与arm32里的32位指令不同。5.2 理解汇编语言语法汇编指令格式如下label mnemonic operand1,operand1,... ;注释 /* label:表示地址位置可选通过这个label可以得到指令的地址。 mnemonic为助记符即汇编指令名称 operand1操作数通常第一个操作数为操作的目的操作数包含不同的操作类型具体可查询汇编指令使用方法 operand2操作数通常为操作的源。 表示为注释符号和C语言编程中的// */ 举例 MOVS R0#0x123 /*设置R0为0x123*/ MOVS R1, #A /*设置R1为字符A*/汇编代码的一个常见特性为定义常量通过定义常量代码的可读性得到提升。下面给出一个常量定义的例子NVIC_IRQ_SETEN EQU 0xE000E100 /*宏定义*/ NVIC_IRQ0_ENABLE EQU 0x1 /*宏定义*/ ... LDR R0, NVIC_IRQ_SETEN /*使用LDR伪指令将地址信息写入到R0*/ MOVS R1,#NVIC_IRQ0_ENABLE /*将0x1立即数写入到R1*/ STR R1, [R0] /*store register,作用是将寄存器中的数据写入内存 将R1中的数写入地址0xE000E100中 */5.3 指令后缀的使用对于ARM处理器的汇编器有些指令跟着后缀后缀描述如下ARM 指令后缀全称速查表后缀全称作用适用指令示例EQEqual相等时执行 (Z1)所有指令ADDEQ R0, R1, R2NENot Equal不等时执行 (Z0)所有指令MOVNE R0, #1CS/HSCarry Set / Higher or Same无符号 ≥ (C1)所有指令ADDCS R0, R0, R1CC/LOCarry Clear / Lower无符号 (C0)所有指令SUBCC R0, R0, #1MIMinus负数时执行 (N1)所有指令RSBMI R0, R0, #0PLPlus非负时执行 (N0)所有指令MOVPL R0, #0VSOverflow Set溢出时执行 (V1)所有指令ADDVS R0, R0, #1VCOverflow Clear无溢出时执行 (V0)所有指令SUBVC R0, R0, R1HIHigher (Unsigned)无符号 (C1 Z0)分支/所有指令BHI targetLSLower or Same (Unsigned)无符号 ≤ (C0Z1)所有指令GEGreater or Equal (Signed)有符号 ≥ (NV)所有指令MOVGE R1, #1LTLess Than (Signed)有符号 (N≠V)所有指令ADDLT R0, R0, #100GTGreater Than (Signed)有符号 (Z0 NV)分支/所有指令CMPGT R0, R1LELess or Equal (Signed)有符号 ≤ (Z1N≠V)所有指令BByte8位操作LDR/STRLDRB R0, [R1]HHalfword16位操作LDR/STRSTRH R0, [R2]DDoubleword64位操作LDR/STR (ARMv5)LDRD R0, R1, [R2]SSet Flags更新CPSR标志位算术/逻辑指令ADDS R0, R1, R2!Writeback更新基址寄存器内存操作指令LDR R0, [R1, #4]!TThumb State切换到Thumb状态BX/BLXBX R0(R0 LSB1)IAIncrement After操作后地址递增LDM/STMLDMIA R0!, {R1-R3}DBDecrement Before操作前地址递减LDM/STMSTMDB SP!, {R4-R6}所有带条件后缀的 ARM 指令在执行前都会检查执行前的 CPSR 状态并根据该状态决定是否执行本条指令的操作。指令执行后才会更新 CPSR如果指令包含S后缀。5.4 指令集Cortex-M3和Cortex-M4处理器的指令可以按照功能分为如下几类1.处理器内传送数据在处理器之间执行数据传送是微处理器中最基本的操作。可能会出现如下操作场景将数据从一个寄存器送到另一个寄存器MOV R4, R0 /*将R0的数据写入到R4*/ MOVS R4, R0 /*将R0的数据写入到R4,同时更新APSR寄存器*/ MVN R3, R7 /*将R7的数据取反后更新至R3MVNmove not*/在寄存器和特殊寄存器CONTROL,PRIMASK,FAULTMASK,BASEPRI之间进行数据传送MRS R7, PRIMASK /*将PRIMASK(特殊寄存器)的数据写入到R7MRS全称move to register from Special register*/ MSR CONTROL, R0 /*将R0的数据写入到CONTORL特殊寄存器中*/将立即数写入到寄存器中MOV R4, #0x12 /*将0x12的数据写入到R4*/ MOVS R4, #0x12 /*将0x12的数据写入到R4,同时更新APSR寄存器*/ MOVW R0, #0x1234 /*将16位立即数加载到寄存器R0的低16位高16位清零*/ MOVT R0, #0x1234 /*将16位立即数加载到寄存器R0的高16位低16位不变*/针对带浮点单元的CORTEX-M4处理器还支持内核寄存器和浮点单元寄存组浮点寄存器组之间以及浮点系统寄存器与内核寄存器立即数与浮点寄存器之间的数据交换。2.存储器访问指令Cortex-M3/M4支持很多存储器访问指令寻址模式及数据大小和数据传输方向具有很多组合方式。立即数偏移数据传输的存储器地址为寄存器中的数值加上偏移值。指令分为load指令和store指令load指令是将存储器的信息写入到寄存器中store指令是将寄存器的数据写入到存储器中。//Load instruction LDRB Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的字节信息读入到Rd中 LDRBload rgister byte*/ LDRSB Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的字节信息进行有符号扩展后读入到Rd中 LDRH Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的半字节信息读入到Rd中 LDRSH Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的半字节信息进行有符号扩展后读入到Rd中 LDR Rd, [Rn,#offset] /*将地址为Rn加上#offset里面的字信息读入到Rd中 LDRD Rd1,Rd2, [Rn,#offset] /*将地址为Rn加上#offset里面的双字信息读入到Rd1和Rd2中 //Store instruction STRB Rd, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd寄存器中的字节信息 STRBstore rgister byte*/ STRH Rd, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd寄存器中的半字节信息 STRHstore rgister halfbyte*/ STR Rd, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd寄存器中的字信息 STRstore rgister word*/ STRD Rd1,Rd2, [Rn,#offset] /*向地址为Rn加上#offset里面写入Rd1Rd2寄存器中的两字信息 STRDstore rgister double word*/ /*注意offset参数可以为正数也可以为负数*/注意可以在上述指令后加一个 如LDR R0, [R1, #offset]!这就可以实现地址寄存器R1的更新即在成功完成内存加载后将计算出的新地址R1 offset写回基址寄存器 R1。这个操作为原子操作不可被打断PC相关寻址存储器访问可以产生相对于当前PC的地址的偏移值。将立即数加载到寄存器中。也被称为文本池访问。LDRB R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据字节信息加载到R1中*/ LDRSB R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据字节信息进行有符号扩展后加载到R1中*/ LDRH R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据半字信息加载到R1中*/ LDRSH R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据半字信息进行有符号扩展后加载到R1中 LDR R1,[PC,#offset] /*将PC地址offset(可选)地址里的数据字信息加载到R1中*/ LDRD R1,R2,[PC,#offset] /*将PC地址offset(可选)地址里的数据字节信息加载到R1,R2中*/ */寄存器偏移寄存器偏移用于所处理的数据数组地址为基地址和从索引值计算出来的偏移得到的情况。LDRB R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容字节数据加载到R0 n的取值范围在0-3之间 */ LDRSB R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容字节数据进行符号扩展加载到R0 n的取值范围在0-3之间 */ LDRH R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容半字节数据加载到R0 n的取值范围在0-3之间 */ LDRSH R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容半字节数据进行符号扩展后加载到R0 n的取值范围在0-3之间 */ LDR R0, [R1, R2, LSL #n] /*将地址R1(基地址)变址寄存器R2n之后的地址内容字数据加载到R0 n的取值范围在0-3之间 */ STRB R0, [R1, R2, LSL #n] /*将R0寄存器中的字节内容加载到地址R1(基地址)变址寄存器R2n之后的地址中去 n的取值范围在0-3之间 */ STRH R0, [R1, R2, LSL #n] /*将R0寄存器中的半字节内容加载到地址R1(基地址)变址寄存器R2n之后的地址中去 n的取值范围在0-3之间 */ STR R0, [R1, R2, LSL #n] /*将R0寄存器中的字内容加载到地址R1(基地址)变址寄存器R2n之后的地址中去 n的取值范围在0-3之间 */后序具有立即数寻址模式的存储器访问指令也有一个立即数偏移数值不过在访问期间是用不到这个偏移值的在访问之后才将地址偏移值的地址更新到地址寄存器中。注意后序指令中不能使用PC和SP这两个寄存器偏移数可以为正数也可以为负数。LDRB Rd, [R0], #offset /*将R0地址中的字节数据加载到Rd中在加载结束后将R0R0offset*/ LDRSB Rd, [R0], #offset /*将R0地址中的字节数据进行符号位扩展后加载到Rd中在加载结束后将R0R0offset*/ LDRH Rd, [R0], #offset /*将R0地址中的半字节数据加载到Rd中在加载结束后将R0R0offset*/ LDRSH Rd, [R0], #offset /*将R0地址中的半字节数据进行符号位扩展后加载到Rd中在加载结束后将R0R0offset*/ LDR Rd, [R0], #offset /*将R0地址中的字数据加载到Rd中在加载结束后将R0R0offset*/ LDRD Rd1, Rd1, [R0], #offset /*将R0地址中的双字数据加载到Rd1Rd2中在加载结束后将R0R0offset*/ STRB Rd, [R0], #offset /*将Rd中所存储的字节信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/ STRH Rd, [R0], #offset /*将Rd中所存储的半字节信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/ STR Rd, [R0], #offset /*将Rd中所存储的字信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/ STRD Rd1,Rd2, [R0], #offset /*将Rd中所存储的双字信息写入到R0地址所在的内存区域操作结束后更新R0中的地址信息*/多加载和多存储ARM架构中有一个重要的优势就是可以读或者写存储器中多个连续数据LDM和STM指令只支持32位数据它们支持两种前序一种每次读写后增加地址一种是每次读写后减少地址。如果该类指令后面跟!则意味着数据加载结束之后会将基址寄存器的信息进行更新。//LDMIA Rn,reg list /*LDMIA: load data memory increment after*/ LDMIA Rn,R1-R5 /*从Rn指定的存储器位置读取多个字依次将数据存储R1,R2,R3,R4,R5中每次读写后地址增加*/ LDMIA Rn,R0,R7,R2-R5, /*从Rn指定的存储器位置读取多个字依次将数据存储在R0,R2,R3,R4,R5,R7中,注意寄存器加载时与顺序无关加载寄存器按照升序排列进行加载*/ //LDMDB Rn,reg list /*LDMIA: load data memory decrement befor先执行地址减操作然后在加载数据*/ LDMDB Rn,R1-R5 /*从Rn指定的存储器位置-4读取多个字依次将数据存储R5,R4,R3,R2,R1中每次读写后地址减少*/ LDMDB Rn,R0,R7,R2-R5, /*从Rn指定的存储器位置-4读取多个字依次将数据存储在R7,R5,R4,R3,R2,R1中*/ //STMIA Rn,reg list /*STMIA: store data memory increment after*/ STMIA Rn,R1-R5 /*往Rn指定的存储器位置写入多个字写入将数据为R1,R2,R3,R4,R5中的数据每次读写后地址增加*/ STMIA Rn,R0,R2-R5,R7 //STMDB Rn,reg list /*STMDB : store data memory decrement befor*/ STMDB Rn,R1-R5 /*往Rn指定的存储器位置-4写入多个字依次将数据存储R5,R4,R3,R2,R1中每次读写后地址增加*/ STMDB Rn,R0,R2-R5,R7 /*往Rn指定的存储器位置-4写入多个字依次将数据存储在R7,R5,R4,R3,R2,R1中*/压栈和出栈栈的PUSH和POP是另外一种形式的多存储和多加载利用当前选定的栈指针来生成地址当前指针可以是主栈指针(msp)也可以是进程栈指针(psp)。实际选择是通过处理器的当前模式和CONTROL寄存器的数值决定。PUSH {R0,R4-R7,R9} ;PUSH指令将R0,R4,R5,R6,R7,R9压入栈中 POP {R0,R2} ;POP指令将栈中的内容存入R0,R2寄存器中SP相关寻址栈空间除了用于函数或者子例程的寄存器临时存储同时还常用于存储局部变量。访问这些临时变量需要前面提到过的LDR和STR指令。在函数开始处为局部变量分配栈空间同时调整SP指针当调用函数结束后SP指针释放这部分空间。3.算术运算Cortex-M3/M4处理器提供了多个用于算术运算的指令具体如下ADD Rd, Rn, Rm ; RdRnRm ADD Rd, Rn, #immed ; RdRnimmed ADC Rd, Rn, Rm ; RdRnRm进位 ADC Rd, #immed ; RdRdimmed进位 ADDW Rd, Rn, #immed ; RdRnimmed,注意这条指令是thumb-2指令支持完成的12位无符号立即数 SUB Rd, Rn, Rm ; RdRn-Rm SUB Rd, #immed ; RdRd-immed SUB Rd, Rn, #immed ; RdRn-immed SBC Rd, Rn, Rm ; RdRn-Rm-借位 SBC Rd, #immed ; RdRd-immed-借位 SUBW Rd, Rn, #immed ;RdRn-immed 注意这条指令是thumb-2指令支持完成的12位无符号立即数 RSB Rd, Rn, #immed ; Rdimmed-Rn RSB Rd, Rn, Rm ; RdRm-Rn MUL Rd, Rn, Rm ;Rd Rn*Rm UDIV Rd, Rn, Rm ;Rd Rn/Rm ;无符号运算 SDIV Rd, Rn, Rm ;Rd Rn/Rm ;有符号运算4.逻辑运算Cortex-M3/M4处理器提供了多个用于逻辑运算的指令具体如下AND Rd,Rn ;RdRd Rn AND Rd,Rn,#immed ;RdRn immed AND Rd,Rn,Rm ;RdRn Rm ORR Rd,Rn ;RdRd | Rn ORR Rd,Rn,#immed ;RdRn | immed ORR Rd,Rn,Rm ;RdRn | Rm BIC Rd,Rn ; RdRd (~Rn) BIC:bit clear BIC Rd,Rn,#immed ;RdRn(~immed) BIC Rd,Rn,Rm ;RdRn (~Rm) ORN Rd,Rn,#immed ;RdRn|(~immed) ORNlogic or not或非操作 ORN Rd,Rn,Rm ;RdRn |(~Rm) EOR Rd,Rn ; RdRd ^ Rn EOR:Exclusive OR EOR Rd,Rn,#immed ;RdRn ^ immed EOR Rd,Rn,Rm ;RdRn ^ Rm5.移位和循环移位Cortex-M3/M4处理器提供了多个用于移位操作的指令具体如下//算术右移 Arithmetic Shift Right ASR Rd, Rn, #immed ;RdRnimmed ASR Rd, Rn ;RdRdRn ASR Rd, Rn, Rm ;RdRnRm //逻辑右移 Logic Shift Right LSR Rd, Rn, #immed ;RdRnimmed LSR Rd, Rn ;RdRdRn LSR Rd, Rn, Rm ;RdRnRm //逻辑左移 Logic Shift Left LSL Rd, Rn, #immed ;RdRnimmed LSL Rd, Rn ;RdRdRn LSL Rd, Rn, Rm ;RdRnRm //循环右移 ROtate Right ROR Rd, Rn ;Rd右移Rn ROR Rd, Rn, Rm ; Rd等于Rn右移Rm //循环右移并展开 Rotate Right with Extend RRX Rd, Rn ;{C,Rd}{Rn,C}给一个例子说明循环右移并展开RRX指令初始状态R1 0x00000003二进制0000 0000 0000 0000 0000 0000 0000 0011C 1CPSR 进位标志指令RRX R0, R1执行步骤构建 33 位数复制下载textC标志位1 R132位 1_00000000000000000000000000000011循环右移 1 位复制下载text原始33位1_00000000000000000000000000000011 右移1位1_10000000000000000000000000000001 → **最低位1移出**分离结果低 32 位10000000000000000000000000000001→0x80000001存入 R0移出位1存入新 C 标志位移位操作总结操作类型汇编指令移位方向填充规则数学等价关键特性逻辑左移LSL← 左移低位补0数据 × 2ⁿ适用于无符号数乘法逻辑右移LSR→ 右移高位补0数据 ÷ 2ⁿ(无符号)适用于无符号数除法算术右移ASR→ 右移高位补符号位(保留正负性)数据 ÷ 2ⁿ(有符号)保留符号位用于有符号数除法循环右移ROR→ 右移高位补移出的低位无直接数学等价数据完整性保留用于位重组带进位循环右移RRX→ 右移1位高位补原进位标志C无直接数学等价33位操作 (数据C标志)用于链式移位6.比较指令比较指令仅用于更新APSR中的标志不会保存运算结果。APSR更新的标志将用于后续的条件判断或条件执行。CMP R0,R1 ;计算R0-R1,根据计算结果更新APSR寄存器 CMP R0,#immed ;计算R0-immed,根据计算结果更新APSR寄存器 CMN R0,R1 ;计算R0R1,根据计算结果更新APSR寄存器 CMN R0,#immed ;计算R0immed,根据计算结果更新APSR寄存器7.程序流控制指令跳转B lable ;跳转到lable如果条状范围超扩正负2KB则使用下面的跳转指令。 B.W lable BX R0 ;实现间接跳转并且基于Rm第0位设置处理器的执行状态T位。最低位为0为ram状态最低位为1为thumb状态函数调用BL lable ;跳转到lable地址出并返回地址保存到LR中 BLX Rn ;跳转到Rn指定的地址并将返回地址保存到LR中同时更新EPSR中的T位。条件条状//APSR寄存器中的状态位 ; N:负标志 ; Z0 ; C进位标志 ; V溢出标志 Bcond lable ;cond见下图lable为函数标签 Bcond.W lable ;8.休眠模式进入休眠模式有两条指令WFI ;wait for interrupt WFE ;wait for event9.存储器屏障指令DMB ;数据存储器屏障确保在执行新的存储器访问之前所有的存储器访问都已经完成 DSB ;数据同步屏障确保在执行下一条指令前所有的存储器访问都已经完成 ISB ;指令同步屏障清空流水线确保在执行新的指令之间前面的指令都已经完成10.其他指令NOP ;空指令知识点扩展1、伪指令EQUEQU在 ARM 汇编中EQU是最重要的伪指令之一全称为Equate等价。它的核心作用是为常量、地址或表达式赋予一个符号化的名称本质上是一种编译时的文本替换机制。是汇编世界的#define。本质在编译预处理阶段将程序中的所有符号名替换为右侧的表达式不生成机器码与真实指令不同EQU不占用内存空间作用域通常全局有效从定义处到文件结束EQU 与普通赋值的本质区别特性EQUMOV/LDR执行阶段编译时 (预处理)运行时是否占用内存否 (纯符号替换)是 (生成机器码)值是否可变常量 (不可变)变量 (可修改)操作对象符号寄存器/内存典型用途定义常量、地址、结构偏移量动态计算、流程控制EQU 与其他伪指令对比伪指令特点示例EQU定义常量数字/地址不可重定义BAUD EQU 115200SETL类似 EQU但允许重复定义后定义覆盖前值COUNT SETL 10GNU汇编中的等价符号 (功能同 EQU)size 0x100DCB分配内存并初始化数据 (非符号定义)str: DCB Hello,02、伪指令LDRARM汇编中的伪指令LDRLoad Register是一个强大的工具它不是真实的机器指令而是由汇编器如GNU AS或ARM Compiler在编译阶段转换为一条或多条等效的真实机器指令。主要功能加载常量或地址到寄存器加载任意32位立即数当需要将超出MOV/MVN指令范围的立即数如0x12345678加载到寄存器时伪指令LDR会自动拆解为合适的机器指令序列。LDR R0, 0x12345678; 伪指令 → 汇编器自动处理编译后可能转换为LDR R0, [PC, #offset]; 从文字池加载PC相对寻址 ... .ltorg; 文字池中存储0x12345678加载标号函数或变量地址在获取全局变量、函数的地址时避免手动计算偏移量。LDR R0, main; 将main函数的地址加载到R0 LDR R1, global_var; 将全局变量global_var的地址加载到R1重要说明真实指令LDR用于从内存加载数据如LDR R0, [R1]而伪指令LDR用于加载常量或地址带等号。伪指令LDR的底层实现原理汇编器处理伪指令LDR的两种方式优化为MOV/MVN若立即数可用单指令表示LDR R0, 0xFF → MOV R0, #0xFF; Thumb-2下可能用 MOVW存入文字池 (Literal Pool) PC相对加载汇编器在代码段末尾或LTORG声明处创建数据区存储常量。生成真实指令从文字池加载LDR R0, [PC, #offset]; offset为到文字池的偏移量3.文字池的本质隐藏在代码段中的“数据仓库”文字池是汇编器自动创建的数据存储区专门用于存放伪指令LDR Rd, value中的大立即数函数/变量的绝对地址编译时确定的复杂表达式结果文字池的三大核心特性1. 位置依赖性汇编器默认在代码段末尾放置文字池若伪指令距离文字池太远 → 超出PC偏移范围导致错误2. 对齐要求ARM模式文字池需4字节对齐Thumb模式强烈建议4字节对齐某些Cortex-M核要求严格对齐3. 作用域隔离每个汇编文件有独立文字池链接器最终合并所有文字池到代码段。4、逻辑PC和物理PC核心原理流水线中的PC永远指向未来在任何流水线设计中PC始终指向当前正在“取指”Fetch阶段的指令地址这意味着执行Execute单元处理的指令译码Decode单元处理的指令取指Fetch单元处理的指令三者是同时存在的不同指令假设当前CPU状态时钟周期 | 取指(F) | 译码(D) | 执行(E) | PC值 ---------------------------------------- 周期1 | 指令A | - | - | 0x8000 (A地址) 周期2 | 指令B | 指令A | - | 0x8004 (B地址) 周期3 | 指令C | 指令B | 指令A | 0x8008 (C地址) ← 关键点当指令A在周期3执行时PC已指向指令C0x8008超前A的地址0x8008 - 0x8000 8字节公式执行阶段PC 当前指令地址 2×指令长度ARM每条指令4字节 → 2×48字节关键结论PC偏移量的通用公式流水线深度执行阶段PC偏移量示例ARM 4字节指令3级PC 当前地址 2×4 8字节0x8000 → PC0x80085级PC 当前地址 4×4 16字节0x8000 → PC0x8010N级PC 当前地址 (N-1)×4字节通用公式黄金规律执行阶段的PC 当前指令地址 (流水线深度 - 1) × 指令长度对程序员/调试者的重大影响调试器显示的是“取指PC”当您看到调试器显示PC0x8010时真正执行的指令在0x8010 - 16 0x8000五级流水线PC相对寻址计算需注意在汇编中写LDR R0, [PC, #0]; 你以为加载当前PC?实际加载的是PC 流水线偏移量处的数据断点行为的深层原因设置断点于0x8000时CPU其实在五级流水线断在0x8000指令的执行阶段此时PC已指向0x8010流水线时代的现实冲突现代CPU采用流水线后--------------- 周期n | F取 | D译 | E执 | ← 指令A在E阶段执行时 --------------- | | F取 | D译 | ← 指令B在D阶段 --------------- | | | F取 | ← 指令C在F阶段 (PC指向这里!) ---------------关键矛盾点物理事实硬件PC寄存器永远指向正在取指的指令逻辑需求程序员需要知道当前执行指令的下一条地址不同场景下的“PC所指”对比表场景PC硬件值程序员/调试器可见值本质原因非流水线CPU当前执行指令地址 4当前执行指令地址 4顺序执行无重叠ARM模式(三级流水线)当前地址 8当前地址 4 (显示修正)流水线超前两条指令Thumb模式(三级流水线)当前地址 4当前地址 2 (显示修正)2字节指令长度差异Cortex-A77(11级流水线)当前地址 40~44当前地址 4调试器主动虚拟化断点触发时取指地址执行地址 (被中断的指令)调试器逆向计算两种“PC”的辩证关系物理PC (硬件层面)永远指向正在取指的指令值 当前执行指令地址 (流水线深度-1)×指令长度程序员不可见但影响PC相对寻址计算逻辑PC (软件层面)调试器和文档显示的“当前指令地址4”是架构设计的抽象层ARM称为Programmers Model用于保证分支目标计算符合直觉调试器单步执行语义正确反汇编列表可读性注推荐一个网站Compiler Explorer实时查看ram汇编输出。