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
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
整个入栈入栈过程如下图所示:
接下来的将执行宏指令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
对于满递增堆栈来说,堆栈的增长方向是向高地址方向的。继续单步执行,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向上偏移地址,所以是满栈。
相应的出栈指令是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寄存器的值变回了以前。
空递减堆栈与满递减堆栈的区别在于对当前栈顶的使用,一个是认为让是空的,可以从该地址存储,一个认为它是满的必须从下一个地址开始,通过实例来查看这种差别:
(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的值。
接着继续执行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的反操作。
这里不再详细分析,它们是空递增堆栈。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
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
一个既定的事实是目标寄存器可能不是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。
接下来为了比较寄存器导出前和导出后的值的区别会首先使用宏指令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的地址,使用与参数顺序依次将数据写入寄存器。
接下来看先增后加的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的最终值没有变化,因为没有“!”号。
记下来看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的值。
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。
(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中的值放入数组中。
这里直接给出执行的结果,和对应的内存寄存器值的变化图。
(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,所以是逆序的。
(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
表 6. 批量栈处理指令
指令缩写 | 指令名称 | 入栈顺序与参数顺序关系 | 出栈顺序与参数顺序关系 |
---|---|---|---|
stmfd | 满递减 | 逆序 | |
ldmfd | 满递减 | 同序 | |
stmfa | 满递增 | 同序 | |
ldmfa | 满递增 | 逆序 | |
stmed | 空递减 | 逆序 | |
ldmed | 空递减 | 同序 | |
stmea | 空递增 | 同序 | |
ldmea | 空递增 | 逆序 |
表 7. 批量数据处理指令
指令缩写 | 指令名称 | 载入顺序与参数顺序关系 | 存储顺序与参数顺序关系 |
---|---|---|---|
stmib | 先加递增 | 同序 | |
ldmib | 先加递增 | 同序 | |
stmia | 后加递增 | 同序 | |
ldmia | 后加递增 | 同序 | |
stmdb | 先加递减 | 逆序 | |
ldmdb | 先加递减 | 逆序 | |
stmda | 后加递减 | 逆序 | |
ldmda | 后加递减 | 逆序 |
表 8. 英文缩写对照表
指令中英文缩写 | 全写 | 中文释义 |
---|---|---|
stmfd-f | Full | 满:sp指向最后一次入栈后的有效值,再次入栈需调整 |
stmea-e | Empty | 空:sp指向最后一次入栈后的有效值后的空内存,再次入栈无需调整 |
stmfd-d | Descending | 递减:堆栈从高地址向低地址生长 |
stmea-a | Ascending | 递增:堆栈从低地址向高地址生长 |
stmib-i | Increment | 加:被存取的寄存器值加4字节 |
stmdb-d | Decrement | 减:被存取的寄存器值减4字节 |
stmda-b | Before | 先:先调整地址,再存取值 |
stmda-a | After | 后:先存取值,再调整地址 |
联系客服