ARM指令寻址方式及RealView MDK开发环境ARM汇编程序实践

内容简介
本文根据作者在CSDN发表的树莓派ARM汇编语言编程十讲(第3讲) (CSDN链接https://blog.csdn.net/yuanzywhu/article/details/104975191)改编而成,适用于手头无树莓派单板机或开发板又想采用PC机和RealView MDK模拟器实践ARM汇编语言的读者。
目 录
·ARM指令分类及格式
·ARM指令寻址方式
·ARM堆栈寻址汇编程序举例
·采用RealView MDK模拟器调试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]。
条件码的表示:条件编码共 24=16 种,其中,15种用于指令的条件码。每种条件码用2个英文缩写字符表示(见表1)。
带条件指令的执行:ARM处理器根据指令的执行条件是否满足,决定当前指令是否执行。只有在CPSR中的条件标志位满足指定的条件时,指令才会被执行。不符合条件的代码依然占用一个时钟周期(相当于一个NOP指令)。
条件码的书写方法:条件码的位置在指令助记符的后面(因此也称为条件后缀)。
表1 ARM指令条件码
在这里插入图片描述
二、ARM指令寻址方式
所谓寻址方式就是处理器根据指令中给出的地址信息来寻找操作数物理地址的方式。目前ARM处理器支持几种常见的寻址方式。
1.寄存器寻址
寄存器寻址是指所需要的值在寄存器中,指令中地址码给出的是寄存器编号,即寄存器的内容为操作数。
例1:
ADD R0, R1, R2 ;R0←R1+R2
注意,这里将树莓派GNU ARM指令以@开始的注释改成了RealView/ADS ARM指令以分号开始的注释,下同。
2.立即寻址
立即寻址是一种特殊的寻址方式,指令中在操作码字段后面的地址码部分不是操作数地址,而是操作数本身。
例2:
ADD R3, R3, #10 ;R3←R3+10
立即数要以“#”号作前缀,以十进制数10为例,其16进制立即数为#0xa,2进制立即数为#0b1010。
关于立即数的构成,可参考图2。
在这里插入图片描述
图2 立即数构成示意图
从图2可知,有效的立即数可表示为:=Immed_8 循环右移2 × rot位
由于4 位rot移位值的取值 (015)乘于2,得到一个范围在030、步长为 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堆栈寻址汇编程序,RealView交叉编译器环境下ARM源程序清单见如下:
;Filename: stackaddressing.s
area ARMEXP,code,readonly
entry
start ldr SP, =0x31FFF150
ldr LR, =0x8034
ldr R4, =0x0100
ldr R5, =0x00FF
ldr R6, =0x1234
ldr R7, =0xA0BE
stmfd SP!,{R4-R7,LR}
ldmfd SP!,{R0-R3,R9}
b start
end
该程序的功能是开始时,设置SP指针指向RealView MDK模拟器内存的0x30FFF150地址作为堆栈栈底,然后使用5条LDR伪指令分别给LR、R4-R7寄存器赋初值。stmfd/ldmfd两条指令分别是执行寄存器的入栈操作和将存放在堆栈中的数据加载到寄存器的出栈操作,这里堆栈操作采用了满递减堆栈。
四、使用RealView MDK模拟器调试ARM堆栈寻址汇编程序及观察堆栈存储区
这里假设在PC机上已事先安装好RealView MDK4.0交叉编译器 (RealView MDK工具软件获取及安装方法,可查阅KEIL、CSDN等相关技术网站)。如果以前使用过Microsoft Visual C++等编译器软件工具,则比较容易掌握RealView MDK软件工具的使用。
这里以前面的完整ARM堆栈寻址汇编程序stackaddressing.s为例,说明RealView MDK模拟器环境下调试ARM汇编程序的方法步骤。
方法步骤:
(1) 新建工程
运行RealView MDK集成开发环境Keil uVision4,选择【Project】→【New uVision Project…】菜单项,单击该菜单项,弹出Create New Project对话框(见图5),根据图5所示输入相关内容,单击【保存】按钮。
在这里插入图片描述
图5 建立工程
(2)选择CPU型号
建立工程后,要为工程选择一款CPU型号,这里选择三星的S3C2410A(见图6)。
在这里插入图片描述
图6 选择CPU型号
(3) 添加启动代码
在这里插入图片描述
图7 添加启动代码
因为是采用模拟器环境运行独立的ARM汇编程序,而非系统开发,这里不使用Startup启动代码,选择【否】。
(4) 选择ARM编译器开发工具
选择【Project】→【Manage】→【Components, Environment and Books…】菜单项,单击该菜单项,弹出Components, Environment and Books对话框,单击对话框中的[Folders/Extensions]卡片选择编译器开发工具(见图8)。
在这里插入图片描述
图8 选择ARM编译器开发工具
从图8可知,有两种ARM编译器开发工具可供选择,一种是RealView ARM编译器,另一种是GNU ARM编译器,这里选择RealView ARM编译器(RealView Compiler)。
(5) 编辑ARM汇编源程序文件
选择[File]→[New…]菜单项,单击该菜单项,弹出Text1文本对话框,输入ARM汇编源程序并保存名为stackaddressing.s的ARM汇编源程序文件(图9)。
在这里插入图片描述
图9 编辑ARM汇编源程序文件
(6) 建立Scatter分散加载文件和Debug调试脚本文件
选择【File】→【New…】菜单项,单击该菜单项,弹出新的文本对话框,在文本框中输入以下Scatter分散加载文件:
;Filename: arm_experiment.sct
LR_ROM1 0x30000000 0x01000000
{
ER_ROM1 0x30000000 0x01000000
{
*.o (ARMEXP,+First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_RAM1 0x31000000 0x01000000
{
.ANY (+RW +ZI)
}
RW_IRAM1 0x40000000 0x00002000
{
.ANY (+RW +ZI)
}
}
录入完毕保存扩展名为.sct的分散加载文件。
再次选择【File】→【New…】菜单项,单击该菜单项,弹出新的文本对话框,在文本框中输入Debug调试脚本初始化文件:
//Filename: debug_in_ram.ini
FUNC void Setup(void)
{
PC=0x30000000;
}
map 0x00000000,0x00200000 read write exec
map 0x30000000,0x34000000 read write exec
Setup();
//g,main
录入完毕后保存扩展名为.ini的调试脚本文件。
(7) 将ARM汇编源程序文件添加到工程
在工程管理窗口【Target1】处右击鼠标弹出快捷菜单,在快捷菜单中选择【Add Group…】建立名为Source Group的工程源文件组;在【Group Source】处右击鼠标弹出快捷菜单(见图10)。
在这里插入图片描述
图10 建立工程源文件组
在图10中,选择【Add Files to Group ‘Source Group’…】弹出Add Files to Group ‘Source Group’对话框,在该对话框中选择ARM汇编源文件stackaddressing.s,单击【Add】添加该文件到工程组Source Group(见图11)。
在这里插入图片描述
图11 选择ARM汇编源文件添加到工程
(8) 设置编译、链接项
选择【Project】→【Options for Target ‘Target 1’…】菜单项,弹出Options for Target ‘Target 1’对话框(见图12)。
在这里插入图片描述
图12 Target工程目标卡片设置
设置图12的Target工程目标选项卡片,接着设置Linker链接选项卡片(见图13)和Debug调试选项卡片(见图14)。
在这里插入图片描述
图13 Linker链接卡片设置
在这里插入图片描述
图14 Debug调试选项卡片设置
特别说明的是,在图13的Linker链接选项卡片要选定一个散布加载文件(这里指定的文件是arm_experiment.sct),在图14的Debug调试选项卡片要选定一个调试脚本初始化文件(这里指定的文件是debug_in_ram.ini)。
(9) 编译、链接工程
选择【Project】→【Build target】菜单项或者是按【F7】键生成ARM汇编目标程序。若有错误,则根据提示修改程序,直到成功生成目标程序。
(10) 调试工程
选择【Debug】→【Start/Stop Debug Session】菜单项或者是按【Ctrl】+【F5】组合键进入调试状态。若无开发板目标硬件,可在图14 的Debug调试选项卡中选中Use Simulator,这样就能在MDK模拟器环境下对ARM汇编程序进行调试了。
单击【Memory 1】打开存储区窗口,本例ARM汇编程序使用的是满递减堆栈指令STMFD/LDMFD对堆栈进行操作,SP初始指针是0x31FFF150,即SP指向栈底,可以在Address处输入0x31FFF13C地址。单击【Step Over】或者按【F10】键,观察单步执行入栈指令STMFD前后,从0x31FFF13C地址开始的堆栈存储单元中内容的变化(见图15)。
在这里插入图片描述
图15 stmfd入栈指令和ldmfd出栈指令执行后的界面
从图15可以看出,执行stmfd指令后,Memory 1 RAM Stack Area堆栈区0x31FFF13C单元开始的5个字单元的内容为: [0x31FFF13C]=0x00000100,[0x31FFF140]=0x000000FF,[0x31FFF144]=0x00001234,[0x31FFF148]=0x0000A0BE,[0x31FFF14C]=0x00008034。
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
从以上入栈指令微操作序列,不难看出图15中Memory 1 RAM Stack Area堆栈区5个存储单元0x31FFF13C ~0x31FFF14C显示的结果。
再次单步执行ldmfd出栈指令,执行结果见图15所示Register寄存器区的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
从以上出栈指令微操作序列,不难看出图15中5个寄存器R0~R3和R9显示的结果。
(作者Email联系:[email protected])
发布时间:2020年4月1日
参考链接:https://blog.csdn.net/yuanzywhu/article/details/104975191

猜你喜欢

转载自blog.csdn.net/yuanzywhu/article/details/105252395