首页
社区
课程
招聘
[翻译]]ARM汇编简介(四)载入/存储多个值,入栈和出栈
2018-6-28 16:36 4474

[翻译]]ARM汇编简介(四)载入/存储多个值,入栈和出栈

2018-6-28 16:36
4474

最近心情不错,抓紧时间继续学习并翻译~https://azeria-labs.com/arm-conditional-execution-and-branching-part-5/

LOAD/STORE MULTIPLE 载入/存储多个值

Sometimes it is more efficient to load (or store) multiple values at once. For that purpose we use LDM (load multiple) and STM (store multiple). These instructions have variations which basically differ only by the way the initial address is accessed. This is the code we will use in this section. We will go through each instruction step by step. 

有时,一次性加载(或存储)多个值更有效率。因此,我们需要使用LDM(载入多个值)和STM(存储多个值)。这些指令基于起始地址的不同,有不同的形式。下面是我们将在本节中将会使用的代码。我们将一步一步地完成每一个指令。

.data

array_buff:
 .word 0x00000000             /* array_buff[0] */
 .word 0x00000000             /* array_buff[1] */
 .word 0x00000000             /* array_buff[2]. This element has a relative address of array_buff+8 这里存储了array_buff+8的相对地址*/
 .word 0x00000000             /* array_buff[3] */
 .word 0x00000000             /* array_buff[4] */

.text
.global _start

_start:
 adr r0, words+12             /* address of words[3] -> r0  将words[3]的地址传送给r0 */
 ldr r1, array_buff_bridge    /* address of array_buff[0] -> r1  将 array_buff[0]的地址给r1*/
 ldr r2, array_buff_bridge+4  /* address of array_buff[2] -> r2   将 array_buff[2]的地址给r2*/
 ldm r0, {r4,r5}              /* words[3] -> r4 = 0x03; words[4] -> r5 = 0x04  */
 stm r1, {r4,r5}              /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04 */
 ldmia r0, {r4-r6}            /* words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05; */
 stmia r1, {r4-r6}            /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04; r6 -> array_buff[2] = 0x05 */
 ldmib r0, {r4-r6}            /* words[4] -> r4 = 0x04; words[5] -> r5 = 0x05; words[6] -> r6 = 0x06 */
 stmib r1, {r4-r6}            /* r4 -> array_buff[1] = 0x04; r5 -> array_buff[2] = 0x05; r6 -> array_buff[3] = 0x06 */
 ldmda r0, {r4-r6}            /* words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 */
 ldmdb r0, {r4-r6}            /* words[2] -> r6 = 0x02; words[1] -> r5 = 0x01; words[0] -> r4 = 0x00 */
 stmda r2, {r4-r6}            /* r6 -> array_buff[2] = 0x02; r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00 */
 stmdb r2, {r4-r5}            /* r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00; */
 bx lr

words:
 .word 0x00000000             /* words[0] */
 .word 0x00000001             /* words[1] */
 .word 0x00000002             /* words[2] */
 .word 0x00000003             /* words[3] */
 .word 0x00000004             /* words[4] */
 .word 0x00000005             /* words[5] */
 .word 0x00000006             /* words[6] */

array_buff_bridge:
 .word array_buff             /* address of array_buff, or in other words - array_buff[0] */
 .word array_buff+8           /* address of array_buff[2] */


Before we start, keep in mind that a .word refers to a data (memory) block of 32 bits = 4 BYTES. This is important for understanding the offsetting. So the program consists of .data section where we allocate an empty array (array_buff) having 5 elements. We will use this as a writable memory location to STORE data. The .text section contains our code with the memory operation instructions and a read-only data pool containing two labels: one for an array having 7 elements, another for “bridging” .text and .data sections so that we can access the array_buff residing in the .data section.

开始前,请记住.word指向了一个32位共计4字节的数据(内存)区块,这对于理解代码中的偏移很重要。程序中包含了.data区段,在此我们开辟了一个有5个元素的空数组array_buff。.text段包含了由内存操作指令构成的代码,和一个包含了两个标签的只读数据池,其中一个标签规定了一个具有7个元素的数组,另一个标签起到了“桥接”.text区段和.data区段的作用,我们可以用它访问.data区段的array_buffer。

 
adr r0, words+12             /* address of words[3] -> r0   将word[3]的地址传送给r0*/

We use ADR instruction (lazy approach) to get the address of the 4th(words[3]) element into the R0. We point to the middle of the words array because we will be operating forwards and backwards from there.

我们使用ADR指令(偷懒的办法)将数组第四部分(word[3])的地址传送给R0。我们用R0指向word数组的中间位置,所以我们可以从这开始向前或者向后移动指针。


gef> break _start 
gef> run
gef> nexti

R0 now contains the address of word[3], which in this case is 0x80B8. This means, our array starts at the address of word[0]: 0x80AC (0x80B8 –  0xC).

现在R0里包含了word[3]的地址,这里是0x80B8。这意味着数组首地址,也是word[0]的地址为:0x80AC (0x80B8 –  0xC)


gef> x/7w 0x00080AC
0x80ac <words>: 0x00000000 0x00000001 0x00000002 0x00000003
0x80bc <words+16>: 0x00000004 0x00000005 0x00000006

We prepare R1 and R2 with the addresses of the first (array_buff[0]) and third (array_buff[2]) elements of the array_buff array. Once the addresses are obtained, we can start operating on them. 

我们用 array_buff数组的第一个( (array_buff[0] )和第三个元素( array_buff[2] )的地址来分别填充R1和R2。一旦地址被获取,我们就能操作他们了

ldr r1, array_buff_bridge    /* address of array_buff[0] -> r1 */
ldr r2, array_buff_bridge+4  /* address of array_buff[2] -> r2 */

After executing the two instructions above, R1 and R2 contain the addresses of array_buff[0] and array_buff[2].

执行完上面的两条命令后,R1,R2中分别包含了 array_buff[0]的地址和 array_buff[2]的地址

gef> info register r1 r2   //用于获取寄存器的值
r1      0x100d0     65744
r2      0x100d8     65752

The next instruction uses LDM to load two word values from the memory pointed by R0. So because we made R0 point to words[3] element earlier, the words[3] value goes to R4 and the words[4] value goes to R5.

下面一条指令(ldm r0, {r4,r5})使用LDM指令,将R0指向的内存中,取两个字的数据出来。由于之前我们让R0指向了word[3],因此 word[3]的值会被存入R4, word[4]的会被存入R5

ldm r0, {r4,r5}              /* words[3] -> r4 = 0x03; words[4] -> r5 = 0x04 */

We loaded multiple (2 data blocks) with one command, which set R4 = 0x00000003 and R5 = 0x00000004.

也就是说,使用一条指令加载了多个数据(2个数据块),并将R4设置为0x00000003 ,R5设置为 0x00000004

gef> info registers r4 r5
r4      0x3      3
r5      0x4      4

So far so good. Now let’s perform the STM instruction to store multiple values to memory. The STM instruction in our code takes values (0x3 and 0x4) from registers R4 and R5 and stores these values to a memory location specified by R1. We previously set the R1 to point to the first array_buff element so after this operation the array_buff[0] = 0x00000003 and array_buff[1] = 0x00000004. If not specified otherwise, the LDM and STM opperate on a step of a word (32 bits = 4 byte).

到目前为止还不错。现在,让我们使用STM指令将多个值写入内存。STM指令将R4,R5寄存器的值0x3和0x4存入R1指向的内存空间中。由于之前我们让R1指向 array_buff的第一个元素,所以指令执行后 array_buff[0] = 0x00000003 ,array_buff[1] = 0x00000004。如果没有特别说明,LDM和STM指令操作的基本单位是一个字(32位等于4个字节)

stm r1, {r4,r5}              /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04 */

The values 0x3 and 0x4 should now be stored at the memory address 0x100D0 and 0x100D4. The following instruction inspects two words of memory at the address 0x000100D0.

现在,0x3和0x4分别被存储在0x100D0和0x100D4指向的内存空间。下条指令检查了从0x000100D0开始的两个字长度的内存中存储了什么值。

gef> x/2w 0x000100D0
0x100d0 <array_buff>:  0x3   0x4

As mentioned before, LDM and STM have variations. The type of variation is defined by the suffix of the instruction. Suffixes used in the example are: -IA (increase after), -IB (increase before), -DA (decrease after), -DB (decrease before). These variations differ by the way how they access the memory specified by the first operand (the register storing the source or destination address). In practice, LDM is the same as LDMIA, which means that the address for the next element to be loaded is increased after each load. In this way we get a sequential (forward) data loading from the memory address specified by the first operand (register storing the source address).

之前说过,LDM和STM有多种不同的使用形式。具体是哪一种使用形式由指令的后缀所决定。这个示例列出了后缀的几种形式:-IA (之后增加), -IB (之前增加), -DA (之后减少), -DB (之前减少)。这么多种类型,他们之间是如何区分的呢?由指令的第一个字段:运算指令规定了访问内存的方式(寄存器作为源地址还是目标地址)。实际上,由于LDM和LDMIA作用相同,所以每次载入完成后,地址指针会自己增加,从而指向下一个被载入的元素。用这种方法可以从某内存地址中获取( 前向 )序列化的数据,并载入寄存器,

ldmia r0, {r4-r6} /* words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05;  */ 
stmia r1, {r4-r6} /* r4 -> array_buff[0] = 0x03; r5 -> array_buff[1] = 0x04; r6 -> array_buff[2] = 0x05 */

After executing the two instructions above, the registers R4-R6 and the memory addresses 0x000100D0, 0x000100D4, and 0x000100D8 contain the values 0x3, 0x4, and 0x5.

执行完上面的两条指令后,寄存器R4-R6分别包含了 0x3,0x4,0x5 ,内存地址 0x000100D0, 0x000100D4,和 0x000100D8中也包含了0x3,0x4,0x5

gef> info registers r4 r5 r6
r4     0x3     3
r5     0x4     4
r6     0x5     5
gef> x/3w 0x000100D0
0x100d0 <array_buff>: 0x00000003  0x00000004  0x00000005

The LDMIB instruction first increases the source address by 4 bytes (one word value) and then performs the first load. In this way we still have a sequential (forward) loading of data, but the first element is with a 4 byte offset from the source address. That’s why in our example the first element to be loaded from the memory into the R4 by LDMIB instruction is 0x00000004 (the words[4]) and not the 0x00000003 (words[3]) as pointed by the R0.

LDMIB指令先将源地址增加4字节(一个字),然后开始载入数据。该方法仍能让我们载入一串前向数据,但第一个数据的起始地址相较源地址有4个字节的偏移。这就是为什么我们用LDMIB指令将内存中的第一个元素载入R4后,R4是0x04(word[4]),而不是R0指向的0x3(word[3])

ldmib r0, {r4-r6}            /* words[4] -> r4 = 0x04; words[5] -> r5 = 0x05; words[6] -> r6 = 0x06 */
stmib r1, {r4-r6}            /* r4 -> array_buff[1] = 0x04; r5 -> array_buff[2] = 0x05; r6 -> array_buff[3] = 0x06 */

After executing the two instructions above, the registers R4-R6 and the memory addresses 0x100D4, 0x100D8, and 0x100DC contain the values 0x4, 0x5, and 0x6.

执行完上面两条指令后,寄存器R4-R6存储了0x4,0x5,0x6, 0x100D4, 0x100D8, 和0x100DC也存储了 0x4,0x5,0x6。

gef> x/3w 0x100D4
0x100d4 <array_buff+4>: 0x00000004  0x00000005  0x00000006
gef> info register r4 r5 r6
r4     0x4    4
r5     0x5    5
r6     0x6    6

When we use the LDMDA instruction everything starts to operate backwards. R0 points to words[3]. When loading starts we move backwards and load the words[3], words[2] and words[1] into R6, R5, R4. Yes, registers are also loaded backwards. So after the instruction finishes R6 = 0x00000003, R5 = 0x00000002, R4 = 0x00000001. The logic here is that we move backwards because we Decrement the source address AFTER each load. The backward registry loading happens because with every load we decrement the memory address and thus decrement the registry number to keep up with the logic that higher memory addresses relate to higher registry number. Check out the LDMIA (or LDM) example, we loaded lower registry first because the source address was lower, and then loaded the higher registry because the source address increased.

使用LDMDA向相反方向执行运算(译者注:ldmda r0, {r4-r6}       /* words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 */)。R0指向word[3],载入操作开始后,我们向后将 words[3], words[2]和words[1]载入R6, R5, R4。是的你没有看错,连寄存器都是反着被载入的。所以指令将 R6赋值为0x00000003, R5赋值为0x00000002, R4赋值为0x00000001。这里的逻辑是,每次载入操作完成后向后减少内存地址所以是反向赋值。寄存器之所以也是反向载入的是由于我们减少了内存地址,所以对应的寄存器的编号值也跟着下降,因为它需要遵循更高的内存地址关联了更高的寄存器编号这一逻辑。回头检查一下 LDMIA (或是 LDM)那个例子(译者注:ldmia r0, {r4-r6} /* words[3] -> r4 = 0x03, words[4] -> r5 = 0x04; words[5] -> r6 = 0x05;  */ ),我们先载入编号更低的寄存器,因为源地址更低。然后载入到编号更高的寄存器,因为源地址增加了。


Load multiple, decrement after:

载入值,之后降低地址(译者注:建议不要纠结字面含义,建议对照代码理解)

ldmda r0, {r4-r6} /* words[3] -> r6 = 0x03; words[2] -> r5 = 0x02; words[1] -> r4 = 0x01 */


Registers R4, R5, and R6 after execution:

R4,R5,R6的变化:

gef> info register r4 r5 r6
r4     0x1    1
r5     0x2    2
r6     0x3    3


Load multiple, decrement before:

先降低地址,再载入多个值 (译者注:建议不要纠结字面含义,建议对照代码理解)

ldmdb r0, {r4-r6} /* words[2] -> r6 = 0x02; words[1] -> r5 = 0x01; words[0] -> r4 = 0x00 */


Registers R4, R5, and R6 after execution:

R4,R5,R6的变化

gef> info register r4 r5 r6
r4 0x0 0
r5 0x1 1
r6 0x2 2


Store multiple, decrement after.

存储值,之后降低地址 (译者注:建议不要纠结字面含义,建议对照代码理解)

stmda r2, {r4-r6} /* r6 -> array_buff[2] = 0x02; r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00 */


Memory addresses of array_buff[2], array_buff[1], and array_buff[0] after execution:

观察 array_buff[2], array_buff[1], 和 array_buff[0]执行后的改变

gef> x/3w 0x100D0
0x100d0 <array_buff>: 0x00000000 0x00000001 0x00000002


Store multiple, decrement before:

先给地址指针减4,再一边赋值一边降序

stmdb r2, {r4-r5} /* r5 -> array_buff[1] = 0x01; r4 -> array_buff[0] = 0x00; */


Memory addresses of array_buff[1] and array_buff[0] after execution:

观察一下 array_buff[1]和array_buff[0]执行后的结果

gef> x/2w 0x100D0
0x100d0 <array_buff>: 0x00000000 0x00000001


PUSH AND POP  push和pop


There is a memory location within the process called Stack. The Stack Pointer (SP) is a register which, under normal circumstances, will always point to an address wihin the Stack’s memory region. Applications often use Stack for temporary data storage. And As mentioned before, ARM uses a Load/Store model for memory access, which means that the instructions LDR / STR or their derivatives (LDM.. /STM..) are used for memory operations. In x86, we use PUSH and POP to load and store from and onto the Stack. In ARM, we can use these two instructions too:

进程中有使用一块内存区域叫堆栈。堆栈指针(SP)是一个寄存器,在正常情况下,它总是指向栈内存区域中的一个地址。应用程序通常使用堆栈进行临时数据存储。之前提过,ARM使用加载/存储模型进行内存访问,这意味着指令LDR/STR或它们的派生指令(LDM.../STM...)用于内存操作。在x86中,我们使用PUSH和POP从堆栈中加载和存储。在ARM中,我们也可以使用这两个指令:


When we PUSH something onto the Full Descending (more about Stack differences inPart 7: Stack and Functions) stack the following happens:

当我们把数据压入降序分布的栈区(详见第7部分:栈和函数)后会发生:


1.First, the address in SP gets DECREASED by 4.

首先,SP寄存器里的地址值减4


2.Second, information gets stored to the new address pointed by SP.

第二步,将信息储存在SP指向的新的地址空间中


When we POP something off the stack, the following happens:

当把数据弹出栈时会发生:


1.The value at the current SP address is loaded into a certain register,

当前SP地址所指向的内存里的值内载入到相应寄存器中


2.Address in SP gets INCREASED by 4.

SP里的值加4


In the following example we use both PUSH/POP and LDMIA/STMDB:

下面的例子中,我们既使用了 PUSH/POP,也使用了 LDMIA/STMDB


.text
.global _start

_start:
   mov r0, #3
   mov r1, #4
   push {r0, r1}
   pop {r2, r3}
   stmdb sp!, {r0, r1}
   ldmia sp!, {r4, r5}
   bkpt

Let’s look at the disassembly of this code. 看看汇编代码是什么样的

azeria@labs:~$ as pushpop.s -o pushpop.o
azeria@labs:~$ ld pushpop.o -o pushpop
azeria@labs:~$ objdump -D pushpop
pushpop: file format elf32-littlearm

Disassembly of section .text:

00008054 <_start>:
 8054: e3a00003 mov r0, #3
 8058: e3a01004 mov r1, #4
 805c: e92d0003 push {r0, r1}
 8060: e8bd000c pop {r2, r3}
 8064: e92d0003 push {r0, r1}
 8068: e8bd0030 pop {r4, r5}
 806c: e1200070 bkpt 0x0000

As you can see, our LDMIA and STMDB instuctions got translated to PUSH and POP. That’s because PUSH is a synonym for STMDB sp!, reglist and POP is a synonym for LDMIA sp! reglist (seeARM Manual)

正如你看到的,我们的 LDMIA 和STMDB指令被转换成了 PUSH和POP。这是因为push有同义词stmdb SP!,而pop有同义词 LDMIA sp!(见arm手册)


Let’s run this code in GDB.在GDB里运行一下

gef> break _start
gef> run
gef> nexti 2
[...]
gef> x/w $sp
0xbefff7e0: 0x00000001

After running the first two instructions we quickly checked what memory address and value SP points to. The next PUSH instruction should decrease SP by 8, and store the value of R1 and R0 (in that order) onto the Stack.

运行两条指令后我们快速检查一下SP指向哪,这个地址里面的值是多少。下条PUSH指令(push {r0, r1})应该会把SP减8,并将R0和R1的值压入堆栈

gef> nexti
[...] ----- Stack -----
0xbefff7d8|+0x00: 0x3 <- $sp
0xbefff7dc|+0x04: 0x4
0xbefff7e0|+0x08: 0x1
[...] 
gef> x/w $sp
0xbefff7d8: 0x00000003


Next, these two values (0x3 and 0x4) are poped off the Stack into the registers, so that R2 = 0x3 and R3 = 0x4. SP is increased by 8:

然后,这两个值 (0x3和0x4)会被弹出到相应寄存器里(pop {r2, r3}),所以 R2 = 0x3 ,R3 = 0x4。SP加8

gef> nexti
gef> info register r2 r3
r2     0x3    3
r3     0x4    4
gef> x/w $sp
0xbefff7e0: 0x00000001






[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2018-6-29 10:54 被r0Cat编辑 ,原因:
收藏
点赞1
打赏
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  junkboy   +5.00 2018/06/28
最新回复 (4)
雪    币: 344
活跃值: (314)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hasking 2018-6-28 16:56
2
0
顶个
雪    币: 6011
活跃值: (3237)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
MaYil 2018-6-28 18:29
3
0
感谢分享
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
junkboy 2018-6-28 21:35
4
0
看完啦  谢谢翻译  希望楼主心情一直好  等下一集~
雪    币: 8715
活跃值: (8610)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
r0Cat 7 2018-6-28 23:02
5
0
junkboy 看完啦 谢谢翻译 希望楼主心情一直好 等下一集~
感谢JB大神的鼎力支持,出手果然不同凡响,感谢感谢
游客
登录 | 注册 方可回帖
返回