打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
4. 批量加载和存储指令实践

4. 批量加载和存储指令实践

批量加载和存储指令共有16个,其中针对堆栈和通常内存数据传送的指令各8个。下面将使用一个汇编程序作为实例,来查看它们的作用。

4.1. 测试源码

为了分析断点的方便,自动加上了行号:

1  .section .data2  array_s: /* 定义数组,用来测试内存数据传送指令 */3   .int 100, 200, 300, 400, 500, 6004  array_e:5  /* 定义宏指令add1,为了测试修改寄存器,以查看出栈/加载后的变化 */6  .macro add17   add r0, r0, #18   add r1, r1, #19   add r2, r2, #110  add r3, r3, #111 .endm12 /* 代码段,4(2^2)字节对齐 */13 .section .text14 .align 215 .global _start16 _start:   /* 首先将r0-r3的寄存器赋值为10-13 */17  mov r0, #1018  mov r1, #1119  mov r2, #1220  mov r3, #1321 /* 测试 */22  stmfd sp!, {r0-r3}23  add124  ldmfd sp!, {r0-r3}25 26  stmfa sp!, {r0-r3}27  add128  ldmfa sp!, {r0-r3}29 30  stmed sp!, {r0-r3}31  add132  ldmed sp!, {r0-r3}33 34  stmea sp!, {r0-r3}35  add136  ldmea sp!, {r0-r3}37 38  ldr r4, =array_s39  ldmib r4, {r0-r3}40  add141  stmib r4, {r0-r3}42 43  ldmia r4, {r0-r3}44  add145  stmia r4, {r0-r3}46 47  ldr r4, =array_e-448  ldmdb r4, {r0-r3}49  add150  stmdb r4, {r0-r3}51 52  ldmda r4, {r0-r3}53  add154  stmda r4, {r0-r3}55 56  mov r0, #057  mov r7, #158  svc 0x00000000

为了通过gdb进行调试以查看寄存器和堆栈的变化,程序的编译命令如下:

arm-linux-as -o raw.o raw.S -garm-linux-ld -o raw raw.o

4.2. stmfd和ldmfd

stmfd和ldmfd分别是对满递减堆栈进行操作。首先设置断点到源码的22行:

(gdb) list......22       stmfd sp!, {r0-r3}23       add124       ldmfd sp!, {r0-r3}2526       stmfa sp!, {r0-r3}27       add128       ldmfa sp!, {r0-r3}2930       stmed sp!, {r0-r3}(gdb) b 22Breakpoint 1 at 0x8084: file raw.S, line 22.

然后使用run/r指令运行到22行指令前的那条指令,并停留在22行,此时可以看到r0-r3的值分别为10-13。另外注意到堆栈的栈顶地址为0xbe8b2e90(sp/r13)。

(gdb) rStarting program: /tmp/raw Breakpoint 1, _start () at raw.S:2222       stmfd sp!, {r0-r3}(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8084   0x8084 <_start+16>fps            0x1001000        16781312cpsr           0x10     16

接下来使用next/n指令进行单步调试,当执行过stmfd sp!, {r0-r3}这条指令后,可以看到sp的值变为了0xbe8b2e80,显然它比原来的栈顶值0xbe8b2e90小了16个字节,所以是递减的,然后再看此时堆栈中的数据,显然0xbe8b2e80地址处存放的是10,并且先入栈的是r3(值为13)。另外0xbe8b2e90并没有存放当前的入栈数据,而是向下跳了4个字节,所以是满堆栈。stmfd指令据将最后的参数先入栈。

(gdb) n       23       add1(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e80       0xbebe2e80lr             0x0      0pc             0x8088   0x8088 <_start+20>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/4 $sp0xbebe2e80:     10      11      12      13

整个入栈入栈过程如下图所示:

图 12. stmfd满递减入栈


接下来的将执行宏指令add1,此时r0-r3中的值将分别加1,此时sp的值不变。

(gdb) n24       ldmfd sp!, {r0-r3}(gdb) info regr0             0xb      11r1             0xc      12r2             0xd      13r3             0xe      14......sp             0xbebe2e80       0xbebe2e80lr             0x0      0pc             0x8098   0x8098 <_start+36>fps            0x1001000        16781312cpsr           0x10     16

紧接着执行ldmfd sp!, {r0-r3}指令,并查看sp发现值为0xbe8b2e90,也即进行了递增操作,另外出栈的16个字节分别存入r0-r13,此时它们又变回了原来的值。并且先出栈的值10被赋值给r0,ldmfd指令将取出的值根据参数的先后放入寄存器。

(gdb) n_start () at raw.S:2626       stmfa sp!, {r0-r3}(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x809c   0x809c <_start+40>fps            0x1001000        16781312cpsr           0x10     16

图 13. ldmfd满递减出栈


4.3. stmfa和ldmfa

对于满递增堆栈来说,堆栈的增长方向是向高地址方向的。继续单步执行,stmfa指令将从寄存器r0-r3压入堆栈:

(gdb) n _start () at raw.S:2727       add1(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2ea0       0xbebe2ea0lr             0x0      0pc             0x80a0   0x80a0 <_start+44>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/4 $sp-120xbebe2e94:     10      11      12      13

注意到此时栈顶指针sp的值为0xbe8b2ea0,它大于原来的值0xbe8b2e90,说明向高地址增长,也即递增。由于x指令总是向高地址取值,所以为了看到压入堆栈中的低地址值,必须从$sp-12地址0xbe8b2e94查看,可以看到压入的值10-13,并且r0先入栈。由于第一个入栈地址0xbe8b2e94是0xbe8b2e90向上偏移地址,所以是满栈。

图 14. stmfa满递增入栈


相应的出栈指令是ldmfa,不过要以参数顺序逆序的方式将出栈的值放入寄存器。继续单步操作,首先是add1宏指令以便与出栈值进行对照,紧接着就是ldmfa sp!, {r0-r3}指令:

(gdb) n28       ldmfa sp!, {r0-r3}(gdb) info regr0             0xb      11r1             0xc      12r2             0xd      13r3             0xe      14r4             0x0      0......sp             0xbebe2ea0       0xbebe2ea0lr             0x0      0pc             0x80b0   0x80b0 <_start+60>fps            0x1001000        16781312cpsr           0x10     16(gdb) n_start () at raw.S:3030       stmed sp!, {r0-r3}(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x80b4   0x80b4 <_start+64>fps            0x1001000        16781312cpsr           0x10     16

可以看到在执行完ldmfa sp!, {r0-r3}指令后,r0-r3寄存器的值变回了以前。

图 15. ldmfa满递增出栈


4.4. stmed和ldmed

空递减堆栈与满递减堆栈的区别在于对当前栈顶的使用,一个是认为让是空的,可以从该地址存储,一个认为它是满的必须从下一个地址开始,通过实例来查看这种差别:

(gdb) n_start () at raw.S:3131       add1(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e80       0xbebe2e80lr             0x0      0pc             0x80b8   0x80b8 <_start+68>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/5 $sp0xbebe2e80:     10      10      11      120xbebe2e90:     13

在执行过stmed sp!, {r0-r3}指令后,可以清楚地看到此时栈顶地址为0xbebe2e80,也即向低地址方向,也即递减,另一个值得注意的地方是直接使用x/5 $sp来输出5个元素,这样才可以看到0xbebe2e90地址处的值为13,也即寄存器r3的值。

图 16. stmed空递减入栈


接着继续执行add1宏指令和ldmed sp!, {r0-r3}:

(gdb) n32       ldmed sp!, {r0-r3}(gdb) info regr0             0xb      11r1             0xc      12r2             0xd      13r3             0xe      14r4             0x0      0......sp             0xbebe2e80       0xbebe2e80lr             0x0      0pc             0x80c8   0x80c8 <_start+84>fps            0x1001000        16781312cpsr           0x10     16(gdb) n_start () at raw.S:3434       stmea sp!, {r0-r3}(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x80cc   0x80cc <_start+88>fps            0x1001000        16781312cpsr           0x10     16

注意到sp地址的变化,以及ro-r3寄存器值的变化,ldmed是stmed的反操作。

图 17. ldmed空递减出栈


4.5. stmea和ldmea

这里不再详细分析,它们是空递增堆栈。stmea的运行结果如下:

(gdb) n_start () at raw.S:3535       add1(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2ea0       0xbebe2ea0lr             0x0      0pc             0x80d0   0x80d0 <_start+92>fps            0x1001000        16781312cpsr           0x10     16

图 18. stmea空递增出栈


ldmea的运行结果如下:

(gdb) n36       ldmea sp!, {r0-r3}(gdb) info regr0             0xb      11r1             0xc      12r2             0xd      13r3             0xe      14r4             0x0      0......sp             0xbebe2ea0       0xbebe2ea0lr             0x0      0pc             0x80e0   0x80e0 <_start+108>fps            0x1001000        16781312cpsr           0x10     16(gdb) n_start () at raw.S:3838       ldr r4, =array_s(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x0      0......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x80e4   0x80e4 <_start+112>fps            0x1001000        16781312cpsr           0x10     16

图 19. ldmea空递增入栈


4.6. ldmib和stmib

一个既定的事实是目标寄存器可能不是sp,而是其它的寄存器,而其他的寄存器可以被安排地址,而堆栈地址却是系统装载时确定的。可以通过这种方式来与特定的内存地址批量交换信息。为了测试这些指令的功能,首先使用加载指令来将事先定义好的数据装入r0-r3寄存器:

(gdb) n39       ldmib r4, {r0-r3}(gdb) info regr0             0xa      10r1             0xb      11r2             0xc      12r3             0xd      13r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x80e8   0x80e8 <_start+116>fps            0x1001000        16781312cpsr           0x10     16

注意到r4寄存器地址的变化,它被装入了数组的开始地址array_s,也即0x10160。接下来的ldmib r4, {r0-r3}指令将r4的地址增加4,然后从r4为基地址的地方拷贝4字节数据到r0,。执行结果如下:

(gdb) n40       add1(gdb) info reg       r0             0xc8     200r1             0x12c    300r2             0x190    400r3             0x1f4    500r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x80ec   0x80ec <_start+120>fps            0x1001000        16781312cpsr           0x10     16

注意到这里的r0-r3的值是200到500,而不是100到400,这就是地址先增加再拷贝的结果。另外注意到r4的地址并没有自动变化,这是因为指令中没有“!”号。如果指令改为ldmib r4!, {r0-r3},那么r4的值将为增加后的值,也即0x10160 + 4 * 0x04 = 0x10170。

图 20. ldmib先加载入


接下来为了比较寄存器导出前和导出后的值的区别会首先使用宏指令add1对r0-r3分别加1,然后使用指令stmib r4, {r0-r3}将r0-r3的数据写回数组中。

(gdb) n41       stmib r4, {r0-r3}(gdb) info regr0             0xc9     201r1             0x12d    301r2             0x191    401r3             0x1f5    501r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x80fc   0x80fc <_start+136>fps            0x1001000        16781312cpsr           0x10     16(gdb) n43       ldmia r4, {r0-r3}(gdb) info  regr0             0xc9     201r1             0x12d    301r2             0x191    401r3             0x1f5    501r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8100   0x8100 <_start+140>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/6 $r40x10160 <array_s>:      100     201     301     4010x10170 <array_s+16>:   501     600

可以看出此时数组中的200-500已经被分别加1,这是寄存器写回的结果。很显然stmib首先增加r4的地址,使用与参数顺序依次将数据写入寄存器。

图 21. stmib先加导出


4.7. ldmia和stmia

接下来看先增后加的ldmia指令结果:

(gdb) n44       add1(gdb) info regr0             0x64     100r1             0xc9     201r2             0x12d    301r3             0x191    401r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8104   0x8104 <_start+144>fps            0x1001000        16781312cpsr           0x10     16

可以看到r0-r4的值是直接从array_s的开始复制过来的,而不是从array_s+1开始的,另外注意到r4的最终值没有变化,因为没有“!”号。

图 22. ldmia后加递增导入


记下来看stmia的结果,它进行导出的动作:

(gdb) n45       stmia r4, {r0-r3}(gdb) info regr0             0x65     101r1             0xca     202r2             0x12e    302r3             0x192    402r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8114   0x8114 <_start+160>fps            0x1001000        16781312cpsr           0x10     16(gdb) n47       ldr r4, =array_e(gdb) info regr0             0x65     101r1             0xca     202r2             0x12e    302r3             0x192    402r4             0x10160  65888......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8118   0x8118 <_start+164>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/6 $r40x10160 <array_s>:      101     202     302     4020x10170 <array_s+16>:   501     600

首先将r0-r3分别加1,然后将它们导出到数组array_s中,可以看到此时array_s的第一个元素分别变成了r0-r3的值。

图 23. stmia后加递增导出


4.8. ldmdb和stmdb

ldmdb和stmdb与ldmib和stmib是对应的,它们的区别在于方向增长的不同,它们向低地址变化。所以为了测试它们的作用,必须将r4的地址通过指令ldr r4, =array_e-4调整为数组的最后一个元素的地址。

(gdb) n47       ldr r4, =array_e - 4(gdb) n48       ldmdb r4, {r0-r3}(gdb) info regr0             0x65     101r1             0xca     202r2             0x12e    302r3             0x192    402r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x811c   0x811c <_start+168>fps            0x1001000        16781312cpsr           0x10     16

注意到执行ldr r4, =array_e - 4后r4的值为0x10174,也即数组最后一个元素的地址。接着观察ldmdb r4, {r0-r3}执行后的结果,它尝试从数组中取出元素,但是是从高地址向地址开始:

(gdb) n49       add1(gdb) info regr0             0xca     202r1             0x12e    302r2             0x192    402r3             0x1f5    501r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8120   0x8120 <_start+172>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/6 $r40x10160 <array_s>:      101     202     302     4020x10170 <array_s+16>:   501     600

可以惊奇的看到r0-r3的值分别变为了202-501,显然赋值的顺序与参数顺序相反,否则r0应该是501。

图 24. ldmdb先加递减导入


(gdb) n50       stmdb r4, {r0-r3}(gdb) info regr0             0xcb     203r1             0x12f    303r2             0x193    403r3             0x1f6    502r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8130   0x8130 <_start+188>fps            0x1001000        16781312cpsr           0x10     16(gdb) n52       ldmda r4, {r0-r3}(gdb) info regr0             0xcb     203r1             0x12f    303r2             0x193    403r3             0x1f6    502r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8134   0x8134 <_start+192>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/6 $r4-200x10160 <array_s>:      101     203     303     4030x10170 <array_s+16>:   502     600

接下来运行加1宏指令,并调用stmdb r4, {r0-r3}指令,stmdb将r0-r3中的值放入数组中。

图 25. stmdb先加递减导出


4.9. ldmda和stmda

这里直接给出执行的结果,和对应的内存寄存器值的变化图。

(gdb) n53       add1(gdb) info reg  r0             0x12f    303r1             0x193    403r2             0x1f6    502r3             0x258    600r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8138   0x8138 <_start+196>fps            0x1001000        16781312cpsr           0x10     16

ldmda r4, {r0-r3}从0x10174地址开始赋值到寄存器r3-r0,所以是逆序的。

图 26. ldmda后加递减导入


(gdb) n54       stmda r4, {r0-r3}(gdb) info reg                                   r0             0x130    304r1             0x194    404r2             0x1f7    503r3             0x259    601r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x8148   0x8148 <_start+212>fps            0x1001000        16781312cpsr           0x10     16(gdb) n56       mov r0, #0(gdb) info reg                                r0             0x130    304r1             0x194    404r2             0x1f7    503r3             0x259    601r4             0x10174  65908......sp             0xbebe2e90       0xbebe2e90lr             0x0      0pc             0x814c   0x814c <_start+216>fps            0x1001000        16781312cpsr           0x10     16(gdb) x/6 $r4-200x10160 <array_s>:      101     203     304     4040x10170 <array_s+16>:   503     601

图 27. stmda后加递减导出


16个批量转存指令的特性可以总结如下:

表 6. 批量栈处理指令

指令缩写指令名称入栈顺序与参数顺序关系出栈顺序与参数顺序关系
stmfd满递减逆序 
ldmfd满递减 同序
stmfa满递增同序 
ldmfa满递增 逆序
stmed空递减逆序 
ldmed空递减 同序
stmea空递增同序 
ldmea空递增 逆序

表 7. 批量数据处理指令

指令缩写指令名称载入顺序与参数顺序关系存储顺序与参数顺序关系
stmib先加递增同序 
ldmib先加递增 同序
stmia后加递增同序 
ldmia后加递增 同序
stmdb先加递减逆序 
ldmdb先加递减 逆序
stmda后加递减逆序 
ldmda后加递减 逆序

表 8. 英文缩写对照表

指令中英文缩写全写中文释义
stmfd-fFull满:sp指向最后一次入栈后的有效值,再次入栈需调整
stmea-eEmpty空:sp指向最后一次入栈后的有效值后的空内存,再次入栈无需调整
stmfd-dDescending递减:堆栈从高地址向低地址生长
stmea-aAscending递增:堆栈从低地址向高地址生长
stmib-iIncrement加:被存取的寄存器值加4字节
stmdb-dDecrement减:被存取的寄存器值减4字节
stmda-bBefore先:先调整地址,再存取值
stmda-aAfter后:先存取值,再调整地址

从参数顺序与地址变化来看有同序和逆序两种情况,但是一个不变的规律是:指令总是将内存低地址处的值载入到低编号的寄存器中,同理低编号的寄存器中的值也被存储到低地址处的内存中。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ARM汇编 -- 嵌入式学习博客
ARM linux的中断处理过程
ARM程序状态寄存器
浅析arm汇编中^、!、cxsf符号和movs等指令使用学习
5. 从0学ARM
ARM汇编之寄存器
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服