栈回溯基础
指令集区分
thumb指令集
- 长度:thumb指令通常是 16 位。
- 特点:thumb 指令集是为了压缩指令集长度减少程序占用空间。
- 对齐方式:2字节对齐,存放 thumb 指令的地址一般会被+1,设置为奇数,用于表示地址上存放的是 thumb 指令。
thumb-2指令集
- 长度:thumb-2 扩展了thumb指令集,可以是16位或 32位。
- 特点:32 位长的 thumb-2 指令通过特定的编码模式来表示,指令通常具有一些特定前缀,例如:'0b11101' 和 '0b11110' 和 '0b11111'。
- 对齐方式:2字节对齐,存放 thumb 指令的地址一般会被+1,设置为奇数,用于表示地址上存放的是 thumb 指令。
arm 指令集
-
长度:arm指令固定为32位。
-
特点:arm 指令不区分前缀,因为所有arm指令都是32位长,它们是统一的,没有特别的前缀来表示。arm指令集通过处理器的状态决定使用,与thumb指令集通过特定指令(如bx, blx)进行状态切换。
-
对齐方式:4字节对齐。
Thumb 和 arm 的切换
- 切换到 arm 模式:在使用 bx 或 blx 是确保地址的最低位为0。
- 切换到 thumb 模式:在使用 bx 或 blx 是确保地址的最低位为1。
例如:
// 切换到 thumb 模式
add r3, pc, #1
bx r3
当使用bx跳转指令,跳到一个奇数地址时,实际上是跳到这个 奇数地址 - 1 的位置,然后CPSR寄存器的标志位T位会置1,表示切换到Thumb指令集,执行后面的指令。
函数跳转指令
ARM 指令集中的跳转指令可以完成从当前指令向前或向后地址空间的跳转,最大 32MB 跳转范围,包括以下 4 条指令:
- B 跳转指令
- BL 带返回的跳转指令
- BLX 带返回的跳转指令,并根据目标地址的最低位切换处理器状态(ARM 或 Thumb(指令+1为奇数表示))
- BX 不返回跳转指令,并根据目标地址的最低位切换处理器状态(ARM 或 Thumb(指令+1为奇数表示))
B 指令
介绍
B 指令是最简单的跳转指令,跳转后不在返回。存储在跳转指令中的实际值是相对当前 PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。
格式:
T1 有条件跳转指令:B<c> <label>
比特位 | 15~12 | 11~8 | 7~0 |
---|---|---|---|
指令码 | 1101 | cond | imm8 |
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
imm8:为8位的立即数(label 偏移量),最大跳转范围 range(-256 ~ 254)
实际偏移量计算:
imm32 = SignExtend(imm8:'0', 32);
注意:cond 不可为‘1110’ 和‘1111’
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7
T2 无条件跳转指令:B<label>
比特位 | 15~11 | 10~0 |
---|---|---|
指令码 | 11100 | imm11 |
imm11:为11位的立即数(label 偏移量),最大跳转范围 range(-2048 ~ 2046)
实际偏移量计算:
imm32 = SignExtend(imm11:'0', 32);
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7
T3 有条件跳转指令:B<c>.W <label>
比特位 | 15~11 | 10 | 9~6 | 5~0 | 15~14 | 13 | 12 | 11 | 10~0 |
---|---|---|---|---|---|---|---|---|---|
指令码 | 11110 | S | cond | imm6 | 10 | J1 | 0 | J2 | imm11 |
S:符号位
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
J1/J2:计算参数位
imm6/imm11:立即数,最大跳转范围range(-1048576 ~ 1048574)
实际偏移量计算:
imm32 = SignExtend(S:J2:J1:imm6:imm11:'0', 32);
适用架构:ARMv6T2, ARMv7
T4 无条件跳转指令:B.W <label>
比特位 | 15~11 | 10 | 9~0 | 15~14 | 13 | 12 | 11 | 10~0 |
---|---|---|---|---|---|---|---|---|
指令码 | 11110 | S | imm10 | 10 | J1 | 1 | J2 | imm11 |
S:符号位
J1/J2:计算参数位
imm10/imm11:立即数,最大跳转范围 range (-16777216 ~ 16777214)
实际偏移量计算
I1 = NOT(J1 EOR S);
I2 = NOT(J2 EOR S);
imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32);
适用架构:ARMv6T2, ARMv7
A1 arm 有条件跳转指令:B<c><label>
比特位 | 31~28 | 27~24 | 23~0 |
---|---|---|---|
指令码 | cond | 1010 | imm24 |
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
imm24:立即数,最大跳转范围 range (-33554432 ~ 33554428)
实际偏移量计算:
imm32 = SignExtend(imm24:'00', 32);
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7
BX 指令
介绍
BX 指令中跳转到寄存器所指定的目标地址, 目标地址处的指令既可以是ARM 指令,也可以是Thumb指令。跳转后不在返回,并且根据寄存器中地址的最低位是否为 1 决定使用 Thumb模式还是 arm 模式。
格式
T1 无条件跳转指令:BX<Rm>
比特位 | 15~10 | 9~8 | 7 | 6~3 | 2~0 |
---|---|---|---|---|---|
指令码 | 010001 | 11 | 0 | Rm | 000 |
Rm:寄存器R1、LR
实际偏移量计算:
m = Uint(Rm);
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7
A1 arm 有条件跳转指令:BX<c> <Rm>
比特位 | 31~28 | 27~20 | 19~16 | 15~12 | 11~8 | 7~4 | 3~0 |
---|---|---|---|---|---|---|---|
指令码 | cond | 00010010 | 1111 | 1111 | 1111 | 0001 | Rm |
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
Rm:寄存器R1、LR
实际偏移量计算:
m = Uint(Rm);
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7
BL 指令
介绍
BL 是一个带返回的跳转指令,跳转之前,会在寄存器 R14 中保存当前指令的下一条指令地址,因此,可以通过将 R14 的内容重新加载到 PC 中,来返回到跳转指令之前的位置继续执行。
格式
T1 无条件跳转指令:BL <label>
比特位 | 15~11 | 10 | 9~0 | 15~14 | 13 | 12 | 11 | 10~0 |
---|---|---|---|---|---|---|---|---|
指令码 | 11110 | S | imm10 | 11 | J1 | 1 | J2 | imm11 |
S:符号位
imm6/imm11:立即数,最大跳转范围 range(-16777216 ~ 16777214)
实际偏移量计算:
I1 = NOT(J1 EOR S);
I2 = NOT(J2 EOR S);
imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32);
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7 if J1 == J2 == 1 ARMv6T2, ARMv7 otherwise
A1 arm 有条件跳转指令:BL<c> <label>
比特位 | 31~28 | 27~24 | 23~0 |
---|---|---|---|
指令码 | cond | 1011 | imm24 |
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
imm24:立即数,最大跳转范围 range (-33554432 ~ 33554428)
实际偏移量计算:
imm32 = SignExtend(imm24:'0', 32);
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7
BLX 指令
介绍
BLX 指令是一个即带有返回值的跳转指令,同时也能指示在调整之后使用 Thumb 模式还是 arm 模式,在寄存器 R14 中保存当前指令的下一条指令地址,用于返回调用前的位置。
格式
T1 无条件立即数跳转指令:BLX<label>
比特位 | 15~11 | 10 | 9~0 | 15~14 | 13 | 12 | 11 | 10~1 | 0 |
---|---|---|---|---|---|---|---|---|---|
指令码 | 11110 | S | imm10H | 11 | J1 | 0 | J2 | imm10L | H |
S:符号位
imm10H/imm10L:立即数,最大跳转范围 range(-16777216 ~ 16777212)
H:
实际偏移量计算:
if CurrentInstrSet() == InstrSet_ThumbEE || H == ‘1’ then UNDEFINED;
I1 = NOT(J1 EOR S); I2 = NOT(J2 EOR S);
imm32 = SignExtend(S:I1:I2:imm10H:imm10L:’00’, 32);
targetInstrSet = InstrSet_ARM;
适用架构:ARMv5T*, ARMv6*, ARMv7 if J1 == J2 == 1ARMv6T2, ARMv7 otherwise
T2 无条件寄存器跳转指令:BLX<Rm>
比特位 | 15~10 | 9~8 | 7 | 6~3 | 2~0 |
---|---|---|---|---|---|
指令码 | 010001 | 11 | 1 | Rm | 000 |
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
Rm:寄存器R1、LR
实际偏移量计算:
m = Uint(Rm);
适用架构:ARMv5T*, ARMv6*, ARMv7
A1 arm 无条件立即数跳转指令:BLX<label>
比特位 | 31~28 | 27~25 | 24 | 23~0 |
---|---|---|---|---|
指令码 | 1111 | 101 | H | imm24 |
H:
imm24:立即数,最大跳转范围 range (-33554432 ~ 33554428)
实际偏移量计算:
imm32 = SignExtend(imm24:H:'0', 32);
targetInstrSet = InstrSet_Thumb;
适用架构:ARMv5T*, ARMv6*, ARMv7
A2 arm 无条件寄存器跳转指令:BLX<c> <Rm>
比特位 | 31~28 | 27~20 | 19~16 | 15~12 | 11~8 | 7~4 | 3~0 |
---|---|---|---|---|---|---|---|
指令码 | cond | 00010010 | 1111 | 1111 | 1111 | 0011 | Rm |
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
Rm:寄存器R1、LR
实际偏移量计算:
m = Uint(Rm);
适用架构:ARMv5T*, ARMv6*, ARMv7
栈操作指令
PUSH 指令
介绍
PUSH 指令一般在函数开始的地方出现,专门用于将寄存器进行压栈操作。在ARM的指令系统中,满栈递减栈入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的逆操作。对于递增栈,相应的操作则全部取反。
格式
T1 多寄存器压栈指令:PUSH<c> <registers>
比特位 | 15~12 | 11 | 10~9 | 8 | 7~0 |
---|---|---|---|---|---|
指令码 | 1011 | 0 | 10 | M | reg_list |
M:表示LR寄存器是否存在于 reg_list 列表中
reg_list:待入栈寄存器列表,以 bit 位表示,真正的寄存器列表 registers = '0':M:'000000':reg_list
注:在 thumb1 指令下寄存器过多时可能会使用T1压栈指令进行2次压栈。
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7
T2 多寄存器压栈指令:PUSH<c>.W <registers>
比特位 | 15~11 | 10~9 | 8~6 | 5~0 | 15 | 14 | 13 | 12~0 |
---|---|---|---|---|---|---|---|---|
指令码 | 11101 | 00 | 100 | 1 0 1101 | 0 | M | 0 | reg_list |
M:表示LR寄存器
reg_list:待入栈寄存器列表,以 bit 位表示,真正的寄存器列表 registers = '0':M:'0':reg_list
注:reg_list 列表中的寄存器个数小于2个则会出错误。
适用架构:ARMv6T2, ARMv7
T3 单寄存器压栈指令:PUSH<c>.W <register>
比特位 | 15~11 | 10~9 | 8~0 | 15~12 | 11~0 |
---|---|---|---|---|---|
指令码 | 11111 | 00 | 0 0 10 0 1101 | Rt | 1 101 00000100 |
Rt:表示入栈寄存器的值,例如 r3 时 Rt 就是 3
计算方式:
t = UInt(Rt); // 转 Rt 转无符号整数
registers = Zeros(16); // 创建16位寄存器,初始化为0
registers<t> = '1'; // 将16位寄存器的第 t 位设置为1
注:t 的值不能为 13 或 15,即不能压栈SP 和 PC 寄存器
适用架构:ARMv6T2, ARMv7
A1 arm 多寄存器压栈指令:PUSH<c> <registers>
比特位 | 31~28 | 27~20 | 19~16 | 15~0 |
---|---|---|---|---|
指令码 | cond | 100100 1 0 | 1101 | reg_list |
cond:条件码,可以根据条件码决定是否压栈
reg_list:待入栈寄存器列表,以 bit 位表示某个寄存器,registers = reg_list
注:如果压栈小于两个寄存器则使用 STMDB / STMFD 指令
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7
A2 arm 单寄存器压栈指令:PUSH<c> <registers>
比特位 | 31~28 | 27~20 | 19~16 | 15~12 | 11~0 |
---|---|---|---|---|---|
指令码 | cond | 010 1 0 0 1 0 | 1101 | Rt | 000000000100 |
cond:条件码,可以根据条件码决定是否压栈
Rt:需要压栈的寄存器值,r3 就是 3
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7
STMDB (STMFD) 指令
介绍
STMDB 指令将寄存器的值存储到指定的内存地址,这个内存地址可以是堆栈,也可以是其他内存地址,常用于函数入口处的压栈操作,其中 DB 表示 down before 先递减,然后将寄存器值存入,在ARM的指令系统中,满栈递减栈入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的逆操作。
格式
T1 多寄存器存储指令:STMDB<Rn>{!}, <registers>
比特位 | 15~11 | 10~9 | 8~6 | 5 | 4 | 3~0 | 15 | 14 | 13 | 12~0 |
---|---|---|---|---|---|---|---|---|---|---|
指令码 | 11101 | 00 | 100 | W | 0 | Rn | 0 | M | 0 | reg_list |
W:Writeback 写回标志,表示对 Rn 地址的操作是否影响 Rn 寄存器
Rn:Rn 基地址寄存器,需要操作的寄存器,Rn! 是会自动设置W=1,例如 sp 时Rn=13
M:表示LR寄存器
reg_list:待写入的寄存器列表, registers = '0':M:'0':reg_list
注:W=1且Rn ='1101' t是为PUSH指令
适用架构:ARMv6T2, ARMv7
A1 arm 多寄存器存储指令:STMDB<c> <Rn>{!}, <registers>
比特位 | 31~28 | 27~22 | 21 | 20 | 19~16 | 15~0 |
---|---|---|---|---|---|---|
指令码 | cond | 100100 | W | 0 | Rn | reg_list |
cond:条件码,可以根据条件码决定是否压栈
W:Writeback 写回标志,表示对 Rn 地址的操作是否影响 Rn 寄存器
Rn:Rn 基地址寄存器,需要操作的寄存器,Rn! 是会自动设置W=1,例如 sp 时Rn=13
reg_list:reg_list:待写入的寄存器列表, registers = reg_list
注:W=1且Rn ='1101' t是为PUSH指令
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7
栈空间申请操作
SUB 指令
介绍
SUB 指令可以用于栈申请。在ARM汇编语言中,可以使用SUB指令来调整堆栈指针的值,从而为栈的使用提供空间。
格式
T1 栈申请指令:SUB SP,SP,#<imm>
比特位 | 15~12 | 11~8 | 7 | 6~0 |
---|---|---|---|---|
指令码 | 1011 | 0000 | 1 | imm7 |
imm7:需要减去的立即数
实际值:
imm32 = ZeroExtend(imm7:'00', 32);
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7
T2 栈申请指令:SUB{S}.W <Rd>, SP, #<const>
比特位 | 15~11 | 10 | 9~5 | 4 | 3~0 | 15 | 14~12 | 11~8 | 7~0 |
---|---|---|---|---|---|---|---|---|---|
指令码 | 11110 | i | 0 1101 | S | 1101 | 0 | imm3 | Rd | imm8 |
i:参与立即数计算
S:符号位
Rd:省略时默认为SP寄存器
imm3/imm8:需要减去的立即数组合位
实际值计算:
imm32 = ThumbExpandImm(i:imm3:imm8)
适用架构:ARMv6T2, ARMv7
T3 栈申请指令:SUBW<Rd>, SP, #<imm12>
比特位 | 15~11 | 10 | 9~5 | 4 | 3~0 | 15 | 14~12 | 11~8 | 7~0 |
---|---|---|---|---|---|---|---|---|---|
指令码 | 11110 | i | 1 0101 | 0 | 1101 | 0 | imm3 | Rd | imm8 |
i:参与立即数计算
Rd:省略时默认为SP寄存器
imm3/imm8:需要减去的立即数组合位
实际值计算:
imm32 = ZeroExtend(i:imm3:imm8, 32)
适用架构:ARMv6T2, ARMv7
A1 arm 栈申请指令:SUB{S}<c> <Rd>, SP, #<const>
比特位 | 31~28 | 27~21 | 20 | 19~16 | 15~12 | 11~0 |
---|---|---|---|---|---|---|
指令码 | cond | 00 1 0010 | S | 1101 | Rd | imm12 |
cond:条件码,可以根据条件码决定是否压栈
S:符号位
Rd:省略时默认为SP寄存器
imm12:需要减去的立即数组合位
实际值计算:
imm32 = ARMExpandImm(imm12)
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7
cond 条件码
ABI下入栈规范
APCS 规范
APCS 规范在ARM架构上定义了程序函数调用和栈帧定义以及寄存器的使用的规范,中定义了 FP 和IP 寄存器的作用,进行子函数调用时会都会进行 push {fp, ip, lr, pc} 压栈操作。
APCS | Reg | 意义 |
---|---|---|
fp | r11 | 栈帧指针寄存器,指向函数栈开始位置 |
ip | r12 | 临时变量寄存器 |
sp | r13 | 栈指针寄存器 |
lr | r14 | 链接寄存器 |
pc | r15 | 程序位置寄存器 |
AAPCS 规范
AAPCS 属于型的规范,在AAPCS 规范发布之后 APCS 规范基本不在使用,并且在新规范中精简了压栈的寄存器个数。
AAPCS | Reg | 意义 |
---|---|---|
sp | r13 | 栈指针寄存器 |
lr | r14 | 链接寄存器 |
pc | r15 | 程序位置寄存器 |
函数调用和异常发生的LR行为
子函数调用函数时前,CPU会自动更新 LR 的值为当前地址的下一条指令地址,注意:是自动,程序员不用管,相当于跟 BX 自动绑定了。更新的值就是下一步要执行的地址。
如果是中断调用则会把 LR 的值更新为 EXC_RETURN 的值,出现异常时栈中保存的 PC 是发生异常时执行的指令地址。
参考文档
Armv7-M Architecture Reference Manual
🌀路西法 的CSDN博客拥有更多美文等你来读。