树莓派ARM汇编语言编程十讲(第3讲)

内容简介

树莓派单板机(Raspberry Pi Single Computer)是一种极了不起的产品,用户可以以非常低的成本获得一个Linux环境并带GPIO硬件扩展的迷你计算机系统。新一代树莓派4B还提供了良好的工业物联网和AIoT支持。树莓派单板机拥有完整的生态链,软硬件资源丰富,是嵌入式系统开发和智能硬件产品创新的很好选择。
作为嵌入式系统与嵌入式智能硬件开发基础中的基础,汇编语言是许多从事信息科学和工程领域的技术人员应该掌握的一项基本技能。目前,市场上针对树莓派单板机系统介绍C、Scratch、Python等编程语言与实践方面的资源很多,但鲜有系统针对树莓派单板机ARM汇编语言编程方面的介绍。这里以袁志勇主编的《嵌入式系统原理与应用技术》(北京航空航天大学出版社2019年1月第3版)一书中ARM汇编语言编程知识为基础,采用树莓派单板机及Linux操作系统验证平台,较系统地介绍树莓派ARM汇编语言编程技术与示例。由于准备仓促,不妥之处,还请各位不吝赐教。

第3讲:ARM指令寻址方式及树莓派ARM堆栈寻址汇编程序举例

第3讲目录
·ARM指令分类及格式
·ARM指令寻址方式
·ARM堆栈寻址汇编程序举例
·使用GNU GDB命令调试ARM堆栈寻址汇编程序及观察树莓派内存堆栈存储区

一、ARM指令分类及格式
ARM嵌入式微处理器的指令集是加载、存储型的,即指令集中仅能处理寄存器中的数据,而且处理结果都要写回到寄存器,而对存储器的访问则需要通过专门的加载、存储指令来完成。ARM指令可分为以下六类:
●数据处理指令:数据传输指令,算术指令,逻辑指令,比较指令,乘法指令,前导零计数
●程序状态访问指令:MRS和MSR
●分支指令:B、BL和BX
●访存指令:单数据访存指令,多数据访存指令,数据交换指令
●异常产生指令:SWI和BKPT
●协处理器指令:CDP、LDC、STC、MCR、MRC
这里以ARM数据处理类指令为例,说明ARM指令格式。ARM数据处理类指令编码基本格式如图1所示。
在这里插入图片描述
图1 ARM数据处理类指令编码格式
GNU ARM数据处理指令汇编语句基本格式如下:
{标号: }[Opcode]{cond}{s} [Rd], {Rn,} [Operand2] {@注释}
其中,[]内的项是必选项,{}内的项是可选项。如Opcode是指令助记符,是必须的;而cond是指令执行条件,是可选的,如果不写则使用默认条件AL(无条件执行)。
●cond: 表示指令的执行条件/条件码;
●Opcode: 表示指令助记符/操作码 (有16种编码,对应于16条数据处理指令);
●S: 表示指令操作是否影响CPSR,当指令后没有S后缀时指令操作不更新CPSR中的条件标志位;
●Rn: 表示第1个操作数的寄存器或者其编码;
●Rd: 表示目标寄存器或者其编码;
●Operand2: 表示第2操作数,第2操作数可以是立即数、寄存器、寄存器移位;
●X: X=1表示第2操作数是立即数寻址;X=0表示第2操作数是寄存器寻址。
按照图1所示的编码格式,操作码Opcode (未含乘法指令)所对应的指令助记符及其含义如下:
0000: AND, Rd←Op1 AND Op2
0001: EOR, Rd←Op1 EOR Op2
0010: SUB, Rd←Op1-Op2
0011: RSB, Rd←Op2-Op1
0100: ADD, Rd←Op1+Op2
0101: ADC, Rd←Op1+Op2+C
0110: SBC, Rd←OP1-Op2+C-1
0111: RSC, Rd←Op2-Op1+C-1
1000: TST, 置Op1 AND Op2的条件码
1001: TEQ, 置OP1 EOR Op2的条件码
1010: CMP, 置Op1-Op2的条件码
1011: CMN, 置Op1+Op2的条件码
1100: ORR, Rd←Op1 OR Op2
1101: MOV, Rd←Op2
1110 : BIC, Rd←Op1 AND NOT Op2
1111: MVN, Rd←NOT Op2
条件码的位数和位置:每条ARM指令包含4位条件码域,它占用指令编码的最高4位[31:28]。
条件码的表示:条件编码共 2^4=16 种,其中,15种用于指令的条件码。每种条件码用2个英文缩写字符表示(见表1)。
带条件指令的执行:ARM处理器根据指令的执行条件是否满足,决定当前指令是否执行。只有在CPSR中的条件标志位满足指定的条件时,指令才会被执行。不符合条件的代码依然占用一个时钟周期(相当于一个NOP指令)。
条件码的书写方法:条件码的位置在指令助记符的后面(因此也称为条件后缀)。
例:MOVEQ R0, R1
表1 ARM指令条件码
在这里插入图片描述
二、ARM指令寻址方式
所谓寻址方式就是处理器根据指令中给出的地址信息来寻找操作数物理地址的方式。目前ARM处理器支持几种常见的寻址方式。
1.寄存器寻址
寄存器寻址是指所需要的值在寄存器中,指令中地址码给出的是寄存器编号,即寄存器的内容为操作数。
例1:
ADD R0, R1, R2 @R0←R1+R2
2.立即寻址
立即寻址是一种特殊的寻址方式,指令中在操作码字段后面的地址码部分不是操作数地址,而是操作数本身。
例2:
ADD R3, R3, #10 @R3←R3+10
立即数要以“#”号作前缀,以十进制数10为例,其16进制立即数为#0xa,2进制立即数为#0b1010。
关于立即数的构成,可参考图2。
在这里插入图片描述
图2 立即数构成示意图
从图3.2可知,有效的立即数可表示为:=Immed_8 循环右移2 × rot位
由于4 位rot移位值的取值 (0-15)乘于2,得到一个范围在0~30、步长为 2的移位值。 因此,ARM中的立即数又称为8位位图。我们只需记住一条准则: “最后8位Immed_8移动偶数位”得到立即数。只有通过此构造方法得到的立即数才是合法的。
下面是三条带有立即数的MOV 指令及对应的机器码,请注意机器码中的立即数计算:
MOV R0, #0xF200
@ E3A00CF2, 0xF200 =0xF2循环右移(2×C)
MOV R1, #0x110000
@ E3A01811, 0x110000 =0x11循环右移(2×8)
MOV R4, #0x12800
@ E3A04B4A, 0x12800 =0x4A循环右移(2×B)
又如,0xFF、0x104(其8位图为0x41)、0xFF0、0xFF00是合法的立即数; 0x101、0x102、0xFF1是非法的立即数。
3.寄存器移位寻址
寄存器移位寻址方式是ARM指令集中所特有的,第二个寄存器操作数在与第一个操作数结合之前,选择进行移位操作。在寄存器移位寻址中,移位的位数可以用立即数或寄存器方式表示。
例3:
ADD R3, R2, R1, LSL #3 @ R3←R2+8×R1(即R1中的值向左移3位,与R2中的值相加,结果存入R3)
MOV R0,R1,ROR R2 @ R0←R1循环右移R2位
ARM中有一桶式移位器,途经它的操作数在被使用前能够被移位或循环移位任意位数,这在处理列表、表格和其他复杂数据结构时非常有用。
ARM中常用的几种移位操作指令如下:
(1)算术右移ASR
存储第二操作数的寄存器算术右移。算术移位的操作数是带符号数,完成移位时应该保持操作数的符号不变。因此,当被移位的操作数为正数时,寄存器的高端空出位补0;当被移位的操作数为负数时,寄存器的高端空出位补1。
(2)逻辑左移LSL
存储第二操作数的寄存器逻辑左移。寄存器中的高端送至C标志位,低端空出位补0。
(3)逻辑右移LSR
存储第二操作数的寄存器逻辑右移。寄存器中的高端空出位补0。
(4)循环右移ROR
存储第二操作数的寄存器循环右移。从寄存器低端移出的位填入到寄存器高端的空出位上。
(5)扩展的循环右移RRX
存储第二操作数的寄存器进行带进位位的循环右移。每右移一位,寄存器中高端空出位用原C标志位的值填充。
若移位的位数由5位立即数(取值范围0-31)给出,称为立即数控制移位方式(immediate specified shift);若移位的位数由通用寄存器(不能是R15)的低5位决定,称为寄存器控制移位方式(register specified shift) 。
关于寄存器控制移位方式,需说明以下两点:
●移位的寄存器不能是PC,否则会产生不可预知的结果。
●使用寄存器控制移位方式有额外代价(overhead),需要更多的周期才能完成指令,因为ARM没有能力一次读取3个寄存器。
立即数控制移位方式则没有上述问题。
4.寄存器间接寻址
寄存器间接寻址是指指令中的地址码给出的是某一通用寄存器的编号,在被指定的寄存器中存放操作数的有效地址,而操作数则存放在该地址对应的存储单元中,即寄存器为地址指针。
例4:LDR R0, [R1] @ R0←[R1]
5.基址变址寻址
基址变址寻址(或简称变址寻址)就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数有效地址。变址寻址用于访问基址附近的单元,包括基址加偏移和基址加索引寻址。寄存器间接寻址是偏移量为0的基址加偏移寻址。
基址加偏移寻址中的基址寄存器包含的不是确切的地址。基址需加(或减)最大4KB的偏移来计算访问的地址。
例5:LDR R0, [R1, #4] @ R0←[R1+4]
有三种加偏移量(偏移地址)的变址寻址方式:
(1)前变址方式(pre-indexed)
先将基地址加上偏移量,生成操作数地址,再做指令指定的操作。该方式不修改基址寄存器。如,上面例子即为此方式。
(2)自动变址方式(auto-indexed)
先将基地址加上偏移量,生成操作数地址,再做指令指定的操作;然后再自动修改基址寄存器。
例6:LDR R0, [R1, #4]! @ R0←[R1+4], R1←R1+4
说明:!表示写回或更新基址寄存器。
(3)后变址方式(post-indexed)
先将基址寄存器作为操作数地址;完成指令操作后,再将基地址加上偏移量修改基址寄存器。即先用基地址传数,然后再修改基地址(基址+偏移)。
例7:STR R0, [R1], #12 @ [R1]←R0, R1←R1+12
这里R1是基址寄存器。
6.相对寻址
相对寻址是变址寻址的一种变通,由程序计数器PC提供基地址,指令中的地址码字段作为偏移量,两者相加后得到操作数的有效地址。偏移量指出的是操作数与当前指令之间的相对位置。子程序调用指令BL即是相对寻址指令。
例8:
BL ROUTE_A @ 调用ROUTE_A子程序
BEQ LOOP @ 条件跳转到LOOP标号处

LOOP: MOV R2, #2

ROUTE_A: …
7.多寄存器寻址
多寄存器寻址是指一条指令可以完成多个寄存器值的传送。这种寻址方式允许一条指令完成传送最多16个通用寄存器的值。
●多寄存器寻址是采用多个寄存器传送指令LDM/STM的寻址方式;
●多寄存器传送指令用于把一块数据从存储器的某一位置传送到另一位置;
●多寄存器指令的寻址操作取决于数据是存储在基址寄存器所指的地址之上还是之下、地址是递增还是递减,并与数据的存取操作有关;
●多寄存器寻址操作中的寄存器,可以是R0~R15这16个寄存器的子集,或是所有寄存器。
几种多寄存器指令及其寻址操作说明如下:
LDMIA/STMIA:先传送,后地址加4(Increment After);
LDMIB/STMIB:先地址加4,后传送(Increment Before);
LDMDA/STMDA:先传送,后地址减4 (Decrement After);
LDMDB/STMDB:先地址减4,后传送(Decrement Before)。
例9:
LDMIA R1, {R0, R2, R5} @ R0←[R1], R2←[R1+4], R5←[R1+8]
上面指令的含义是将R1所指向的连续3个存储单元中的内容分别送到寄存器R0,R2,R5中。
由于传送的数据项总是32位的字,基址R1应该字对准。
例10:
STMIA r10, {r0, r1, r4}
STMIB r10, {r0, r1, r4}
STMDA r10, {r0, r1, r4}
STMDB r10, {r0, r1, r4}
上面4条指令执行的操作见图3所示。
在这里插入图片描述
图3 多寄存器寻址示意图
8.堆栈寻址
堆栈是一种按特定顺序进行存取的存储区,这种特定顺序即是“先进后出”或“后进先出”。堆栈寻址是隐含的,它使用一个专门的寄存器(堆栈指针)指向一块存储器区域。栈指针所指定的存储单元就是堆栈的栈顶。堆栈可分为两种:
●向上生长:又称递增(Ascending)堆栈,即地址向高地址方向生长。
●向下生长:又称递减(Descending)堆栈,即地址向低地址方向生长。
若SP指向最后压入的堆栈的有效数据单元,称为满堆栈(Full Stack);若SP指向下一个数据项放入的空单元,称为空堆栈(Empty stack)。
ARM处理器支持上面四种类型的堆栈工作方式:
●满递增堆栈FA(Full Ascending):堆栈指针指向最后压入的数据单元,且由低地址向高地址生成;
●满递减堆栈FD(Full Descending):堆栈指针指向最后压入的数据单元,且由高地址向低地址生成;
●空递增堆栈EA(Empty Ascending):堆栈指针指向下一个将要放入数据的空单元,且由低地址向高地址生成;
●空递减堆栈ED(Empty Descending):堆栈指针指向下一个将要放入数据的空单元,且由高地址向低地址生成;
例11:
STMFD sp!, {r4-r7, lr} @ 将 r4~r7、lr入栈,满递减堆栈
LDMFD sp!, {r4-r7, pc} @ 数据出栈,放入r4~r7、pc寄存器
在这里插入图片描述图4 堆栈操作示意图
图4说明了本例两条指令的入栈和出栈操作过程,图的左端为STMFD入栈操作,图的右端为LDMFD出栈操作。需说明的是,在使用满递减堆栈时,STMFD指令相当于STMDB指令,LDMFD指令相当于LDMIA指令。STMFD/LDMFD是ARM中最常用的堆栈工作方式,堆栈寻址与多寄存器寻址对照见表2所示。
表2 堆栈寻址与多寄存器寻址对照
在这里插入图片描述
三、ARM堆栈寻址汇编程序举例
下面以例11的堆栈操作指令为基础给出一个完整的ARM堆栈寻址汇编程序,ARM源程序清单见如下:
@Filename:stackaddressing.s
.global _start @声明全局标号_start
_start: ldr SP,=0x7EFFF150
mov LR,#0x8034
mov R4,#0x0100
mov R5,#0x00FF
mov R6,#0x1234
mov R7,#0xA0BE
stmfd SP!,{R4-R7,LR}
ldmfd SP!,{R0-R3,R9}
b _start
.end
该程序的功能是开始时,设置SP指针指向树莓派内存的0x7EFFF150地址作为堆栈栈底(因不同的树莓派的系统使用场景不同,建议使用系统默认堆栈,此时程序第一句可省略),然后使用5条MOV传送指令语句序列分别给LR、R4-R7寄存器赋初值。最后两条是将寄存器入栈和出栈,由STMFD/LDMFD指令知,这里汇编程序采用了满递减堆栈进行堆栈操作。
四、使用GNU GDB命令调试ARM堆栈寻址汇编程序及观察树莓派内存堆栈存储区
在树莓派Linux终端,ARM堆栈寻址汇编程序的堆栈操作执行后,同样可用GNU GDB观察树莓派内存堆栈区。本讲ARM堆栈寻址汇编程序示例使用的GNU GDB命令说明如下:
(1)l(ist):显示带行号的ARM汇编源程序
(2)b(reak):设置中断程序执行的ARM汇编源程序行号(即设置断点)
(3)run:运行程序并在下一个断点处暂停
(4)i(nfo):显示所有的ARM寄存器®或断点(b)
(5)s(tep):单步执行下一条指令
(6)x/nx addr:从地址单元“addr”开始,按16进制格式显示n个字单元的内容
(7)q(uit):退出GDB调试命令状态并返回树莓派终端Linux命令提示符
首先,用nano编辑器编辑名为stackaddressing.s的ARM汇编程序(见图5)。
在这里插入图片描述
图5 编辑树莓派堆栈寻址ARM汇编源程序
接着,在树莓派Linux终端命令提示符,输入命令序列对名为stackaddressing.s的源程序进行汇编、链接,执行GDB stackaddressing命令进入GDB命令调试状态(见图6)。
在这里插入图片描述
图6 执行GDB stackaddressing命令,进入GDB命令调试状态
用list命令显示带行号的ARM汇编源程序(见图7)。
在这里插入图片描述
图7 用l命令显示带行号的ARM汇编源程序
用b 9命令设置断点为第9行的stmfd SP!,{R4-R7,LR}语句;用run命令执行程序到第9行的语句处停止,显示断点所在的行及语句;用i r命令显示ARM内部各寄存器内容,用x/10x 0x7efff13c命令显示树莓派RAM堆栈区从0x7efff13c地址单元开始的10个字单元的内容(见图8)。
在这里插入图片描述
图8 显示树莓派的ARM寄存器值及RAM堆栈区的内容
从图8可知,程序执行到第9行断点处时,sp=0x7efff150,r4=0x100,r5=0xff,r6=0x1234,r7=0xa0be。
下面输入s单步命令接着执行第9行的stmfd入栈指令(见图9)。
在这里插入图片描述
图9 显示执行stmfd入栈指令后的树莓派ARM寄存器值及RAM堆栈区的内容
在图9中,再次使用了i r命令显示寄存器值,使用x/10x 0x7efff13c命令显示树莓派RAM堆栈区0x7efff13c单元开始的10个字单元的内容。
入栈指令stmfd SP!, {R4-R7, LR}在树莓派中的执行情况见图10所示。
在这里插入图片描述
图10 入栈指令stmfd SP!, {R4-R7, LR}在树莓派中的执行情况
stmfd SP!, {R4-R7,LR}入栈指令对应的微操作序列如下:
SP←SP-4, [SP]←LR
SP←SP-4, [SP]←R7
SP←SP-4, [SP]←R6
SP←SP-4, [SP]←R5
SP←SP-4, [SP]←R4
从以上入栈指令微操作序列,不难得出图10中Raspberry Pi RAM Stack Area堆栈区5个存储单元0x7efff13c~0x7efff14c显示的结果。
再次输入s单步命令执行第10行的ldmfd出栈指令(见图11)。
在这里插入图片描述
图11 显示执行ldmfd出栈指令后的树莓派ARM寄存器值及RAM堆栈区的内容
在图11中,再次使用了i r命令显示寄存器值,使用x/10x 0x7efff13c命令显示树莓派RAM堆栈区0x7efff13c单元开始的10个字单元的内容。
出栈指令ldmfd SP!, {R0-R3,R9}在树莓派中的执行情况见图12所示。
在这里插入图片描述
图12 出栈指令ldmfd SP!, {R0-R3,R9}在树莓派中的执行情况
ldmfd SP!, {R0-R3,R9}出栈指令对应的微操作序列如下:
[SP]→R0, SP+4→SP
[SP]→R1, SP+4→SP
[SP]→R2, SP+4→SP
[SP]→R3, SP+4→SP
[SP]→R9, SP+4→SP
从以上出栈指令微操作序列,不难得出图12中5个寄存器R0~R3和R9显示的结果。最后,输入q/quit退出GDB调试命令状态并返回树莓派终端Linux命令提示符。
End of This Lecture.
(作者Email联系:yuanzywhu@163.com)
发布时间:2020年3月19日
上一讲链接
下一讲链接

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

袁易学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值