首页
社区
课程
招聘
[翻译]ARM汇编简介(三)内存指令-加载和存储 (下)
2018-6-19 16:19 8664

[翻译]ARM汇编简介(三)内存指令-加载和存储 (下)

2018-6-19 16:19
8664
本文是内存指令-加载和存储的下篇,接上文https://bbs.pediy.com/thread-228602.htm继续翻译

2. Offset form:Register as the offset. 偏移形式:寄存器的值用作偏移

STR    Ra, [Rb, Rc]
LDR    Ra, [Rb, Rc]
This offset form uses a register as an offset. An example usage of this offset form is when your code wants to access an array where the index is computed at run-time.
该偏移形式将寄存器的值用作偏移量。该偏移形式的一个示例用法是,你的代码想访问一个数组,此时正在计算数组的下标索引

.data
var1: .word 3
var2: .word 4

.text
.global _start

_start:
    ldr r0, adr_var1  @ load the memory address of var1 via label adr_var1 to    R0 
           透过adr_var1标签,将var1变量的内存地址加载进R0
    ldr r1, adr_var2  @ load the memory address of var2 via label adr_var2 to    R1
           透过adr_var2标签,将var2变量的内存地址加载进R1
    ldr r2, [r0]      @ load the value (0x03) at memory address found in R0 to R2   
           将R0里的值作为内存地址,把地址里的数值(0x03)载入r2
    str r2, [r1, r2]  @ address mode: offset. Store the value found in R2 (0x03) to the memory address found in R1 with the offset R2 (0x03). Base register unmodified.   
           寻址模式:偏移寻址。将R2里的值存入:R1+R2(0x03,偏移)的结果所指向的内存空间中。基址寄存器不更新。
    str r2, [r1, r2]! @ address mode: pre-indexed. Store value found in R2 (0x03) to the memory address found in R1 with the offset R2 (0x03). Base register modified: R1 = R1+R2. 
          寻址模式:先索引模式。将R2的值(0x03)存入:R1+R2(0x03,用作偏移)得出的结果所指向的内存空间中。基址寄存器R1更新为R1 = R1+R2。
    ldr r3, [r1], r2  @ address mode: post-indexed. Load value at memory address found in R1 to register R3. Then modify base register: R1 = R1+R2.
         寻址模式:后索引模式。将R2的值作为地址取出里面的值,并将其存入寄存器R3。基址寄存器R1更新为R1=R1+R2。
    bx lr

adr_var1: .word var1
adr_var2: .word var2

After executing the first STR operation with the offset address mode, the value of R2 (0x00000003) will be stored at memory address 0x0001009c + 0x00000003 = 0x0001009F.
用偏移寻址模式执行完第一个STR( str r2, [r1, r2] )指令后,R2的值(0x00000003)会被存储在  0x0001009c + 0x00000003 = 0x0001009F指向的内存空间
gef> x/w 0x0001009F
 0x1009f <var2+3>: 0x00000003

The second STR operation with the pre-indexed address mode will do the same, with the difference that it will update the base register (R1) with the calculated memory address (R1+R2).
第二个STR使用先索引模式执行( str r2, [r1, r2]! ),该指令做了同样的事,不同的是,第二处str会用计算得出的内存地址(R1+R2)更新基址寄存器(R1)
gef> info register r1
 r1     0x1009f      65695

The last LDR operation uses the post-indexed address modeand loads the value at the memory address found in R1 into the register R2, then updates the base register R1 (R1+R2 = 0x1009f + 0x3 = 0x100a2).
最后一处LDR操作( ldr r3, [r1], r2 )使用后索引寻址模式将r1里的值作为地址取出里面的值并将这个值存入寄存器r2。然后更新基址寄存器R1( R1+R2 = 0x1009f + 0x3 = 0x100a2 )


3. Offset form: Scaled register as the offset 偏移形式:移位寄存器作为偏移

LDR    Ra, [Rb, Rc, <shifter>]
STR    Ra, [Rb, Rc, <shifter>]
The third offset form has a scaled register as the offset. In this case, Rb is the base register and Rc is an immediate offset (or a register containing an immediate value) left/right shifted (<shifter>) to scale the immediate. This means that the barrel shifter is used to scale the offset. An example usage of this offset form would be for loops to iterate over an array. Here is a simple example you can run in GDB:
第三偏移形式使用移位寄存器作为偏移量。在本例中,Rb是基寄存器,Rc里面是立即数偏移量(或是寄存器里的一个立即数),用于左/右移位(<移位寄存器>)。这意味着移位寄存器被用来存放移位的偏移量。这种偏移形式的一个示例用法是循环遍历数组。下面是一个简单的例子,可以在GDB中运行:

.data
var1: .word 3
var2: .word 4

.text
.global _start

_start:
    ldr r0, adr_var1         @ load the memory address of var1 via label adr_var1 to R0
           透过adr_var1标签,将var1变量的内存地址加载进R0
    ldr r1, adr_var2         @ load the memory address of var2 via label adr_var2 to R1
           透过adr_var2标签,将var2变量的内存地址加载进R1
    ldr r2, [r0]             @ load the value (0x03) at memory address found in R0 to R2
           将R0里的值作为内存地址,把里面的值(0x03)载入r2
    str r2, [r1, r2, LSL#2]  @ address mode: offset. Store the value found in R2 (0x03) to the memory address found in R1 with the offset R2 left-shifted by 2. Base register (R1) unmodified.
           寻址模式:偏移寻址。将R2里的值(0x03)存入:R1的值+偏移量(R2左移2位后的值)的结果所指向的内存空间中。基址寄存器(R1)不更新。
    str r2, [r1, r2, LSL#2]! @ address mode: pre-indexed. Store the value found in R2 (0x03) to the memory address found in R1 with the offset R2 left-shifted by 2. Base register modified: R1 = R1 + R2<<2
           寻址模式:先索引寻址。将R2里的值(0x03)存入:R1的值+偏移量(R2左移2位后的值)的结果所指向的内存空间中。基址寄存器(R1)更新为R1 = R1 + R2<<2。
    ldr r3, [r1], r2, LSL#2  @ address mode: post-indexed. Load value at memory address found in R1 to the register R3. Then modifiy base register: R1 = R1 + R2<<2
           寻址模式:后索引寻址。将R1的值作为地址取出里面的值,并将其存入寄存器R3。基址寄存器R1更新为R1=R1+R2<<2。
    bkpt

adr_var1: .word var1
adr_var2: .word var2

The first STR operation uses the offset address mode and stores the value found in R2 at the memory location calculated from [r1, r2, LSL#2], which means that it takes the value in R1 as a base (in this case, R1 contains the memory address of var2), then it takes the value in R2 (0x3), and shifts it left by 2. The picture below is an attempt to visualize how the memory location is calculated with [r1, r2, LSL#2]. 

第一个STR操作(str r2, [r1, r2, LSL#2] )使用偏移寻址模式,并将在R2中找到的值作为地址,取出里面的值存储在:[R1,R2,LSL #2 ]计算出的内存地址指向的内存空间中,这意味着它将R1中的值用作基址(此时R1包含了var2变量的内存地址),然后它取出R2(0x3)中的值向左移动两位,把这个值作为偏移量。下面的图片将[R1,R2,LSL×2 ]计算内存地址的方法可视化了。


The second STR operation uses the pre-indexed address mode. This means, it performs the same action as the previous operation, with the difference that it updates the base register R1 with the calculated memory address afterwards. In other words, it will first store the value found at the memory address R1 (0x1009c) + the offset left shifted by #2 (0x03 LSL#2 = 0xC) = 0x100a8, and update R1 with 0x100a8.

第二个操作 STR (str r2, [r1, r2, LSL#2]!)使用先索引地址模式。这意味着,它执行与先前操作相同的动作,不同之处在于,用随后计算出的内存地址更新了基址寄存器R1。换言之,它首先将存储器地址R1(0x1009C)加上偏移:将R1向左偏移2位(0x03 LSL×2=0xC ,这是偏移量)得到0x100A8,将 0x100A8 作为地址,取出里面的值给R2,以及用0x100A8更新R1。

gef> info register r1
r1      0x100a8      65704


The last LDR operation ( ldr r3, [r1], r2)uses the post-indexed address mode. This means, it loads the value at the memory address found in R1 (0x100a8) into register R3, then updates the base register R1 with the value calculated with r2, LSL#2. In other words, R1 gets updated with the value R1 (0x100a8) + the offset R2 (0x3) left shifted by #2 (0xC) = 0x100b4.

最后一个LDR运算使用后索引寻址模式。意思是说,将R1里的数(0x100a8)作为地址,取出该地址空间里的值并将它载入寄存器R3,然后,用r2, LSL#2计算所得的结果更新基址寄存器R1。换言之,使用R1的值(0x100a8),加上偏移R2(0x3)左移两位得到的值( 0xC ),等于0x100b4,用0x100b4更新R1

gef> info register r1
r1      0x100b4      65716

Summary总结


Remember the three offset modes in LDR/STR:记住 LDR/STR用到的三种偏移模式


1.offset mode uses an immediate as offset 用立即数作为偏移的偏移模式

     ldr   r3, [r1, #4]

2.offset mode uses a register as offset 用寄存器的值作为偏移的偏移模式

     ldr   r3, [r1, r2]

3.offset mode uses a scaled register as offset  用移位寄存器的值作为偏移的偏移模式

     ldr   r3, [r1, r2, LSL#2]


How to remember the different address modes in LDR/STR:如何记住 LDR/STR中的不同寻址模式:


If there is a !, it’s prefix address mode 如果有一个!,就是先寻址模式

ldr   r3, [r1, #4]!

ldr   r3, [r1, r2]!

ldr   r3, [r1, r2, LSL#2]!


If the base register is in brackets by itself, it’s postfix address mode 如果方括号里只有基址寄存器它自己,就是后寻址模式

ldr   r3, [r1], #4

ldr   r3, [r1], r2

ldr   r3, [r1], r2, LSL#2


Anything else is offset address mode. 其他的都是偏移寻址模式

ldr   r3, [r1, #4]

ldr   r3, [r1, r2]

ldr   r3, [r1, r2, LSL#2]


LDR FOR PC-RELATIVE ADDRESSING  LDR用于PC相对寻址


LDR is not only used to load data from memory into a register. Sometimes you will see syntax like this:
LDR是唯一一个用于将内存中的数据载入寄存器的指令。有时你会看到类似下面所示的语法

.section .text
.global _start

_start:
   ldr r0, =jump        /* load the address of the function label jump into R0  将函数标签的地址载入R0 */
   ldr r1, =0x68DB00AD  /* load the value 0x68DB00AD into R1  将0x68DB00AD载入R1 */
jump:
   ldr r2, =511         /* load the value 511 into R2 将511载入R2 */ 
   bkpt

These instructions are technically called pseudo-instructions. We can use this syntax to reference data in the literal pool. The literal pool is a memory area in the same section (because the literal pool is part of the code) to store constants, strings, or offsets. In the example above we use these pseudo-instructions to reference an offset to a function, and to move a 32-bit constant into a register in one instruction. The reason why we sometimes need to use this syntax to move a 32-bit constant into a register in one instruction is because ARM can only load a 8-bit value in one go. What? To understand why, you need to know how immediate values are being handled on ARM.
这些指令
这些指令在技术上被称为伪指令。我们可以使用这个语法来引用文本池中的数据。文字池和代码段位于同一内存区段(因为文字池是代码的一部分),用来存储常量、字符串或偏移量。在上面的例子中,我们使用这些伪指令来引用一个函数的偏移量,并用一个指令将一个32位常量移到一个寄存器中。 为什么我们有时需要使用这种语法来使用一个指令将32位常量移到一个的寄存器中呢,这是因为ARM一次 只能加载一个8位的值。什么?要理解为什么,你需要知道ARM上的立即数是如何处理的。

USING IMMEDIATE VALUES ON ARM ARM立即数的使用


Loading immediate values in a register on ARM is not as straightforward as it is on x86. There are restrictions on which immediate values you can use. What these restrictions are and how to deal with them isn’t the most exciting part of ARM assembly, but bear with me, this is just for your understanding and there are tricks you can use to bypass these restrictions (hint: LDR).

将立即数载入ARM寄存器不像在x86上那样简单。我们能使用的立即数是有限制的。 这些限制是什么,如何处理它们并不是ARM汇编中最令人兴奋的部分,但请耐心听我说,这只是为了让你透彻的理解。另外还有一些窍门可以用来绕过这些限制(提示:LDR)。


We know that each ARM instruction is 32 bit long, and all instructions are conditional. There are 16 condition codes which we can use and one condition code takes up 4 bits of the instruction. Then we need 2 bits for the destination register. 2 bits for the first operand register, and 1 bit for the set-status flag, plus an assorted number of bits for other matters like the actual opcodes. The point here is, that after assigning bits to instruction-type, registers, and other fields, there are only 12 bits left for immediate values, which will only allow for 4096 different values.

我们知道,每一条ARM指令都有32位的长度,所有的指令都是由条件的。我们可以使用16种条件指令,每种条件指令占4位。接着我们需要2位用于表示目标寄存器,再用2位表示第一操作数,1位用于设置状态标志位,再加上其他情况要用到的一个分类数(比如实际的操作码)。这里的关键问题是,给指令类型。寄存器和其他字段分配字节空间后,留给立即数的只剩下12位了,12位只允许表示4096个不同的数值


This means that the ARM instruction is only able to use a limited range of immediate values with MOV directly.  If a number can’t be used directly, it must be split into parts and pieced together from multiple smaller numbers.

这意味在直接调用MOV指令只能使用取值范围受到限制的立即数。如果不直接使用数字,就必须把它分成多个部分,用多个比原数小的数字拼在一起使用。


But there is more. Instead of taking the 12 bits for a single integer, those 12 bits are split into an 8 bit number (n) being able to load any 8-bit value in the range of 0-255, and a 4 bit rotation field (r) being a right rotate in steps of 2 between 0 and 30. This means that the full immediate value v is given by the formula: v = n ror 2*r. In other words, the only valid immediate values are rotated bytes (values that can be reduced to a byte rotated by an even number).

但这还没结束。我们并没有把12位全部用于组成一个单独的整数,而是将12位分成8位数字(n)和4位循环移位字段(r)两部分,8位数字的取值范围是0-255, 而4位用于构成循环移位数(r),第二步的整体的范围是0和30之间。意思是说,一个完整的立即数V由如下公式决定:v=n ror 2*r。换言之,唯一有效的立即数是循环字节数(通过移动偶数个位可以减少到一个字节)

(译者注:这里翻译的不太好,建议配合原文看下面作者给的实际例子认真理解)


Here are some examples of valid and invalid immediate values:下面是几个合规的和不合规的立即数


Valid values:
#256        // 1 ror 24 --> 256        等价于ror 1,24   其中24=2*#12(#12是用那4个字节编码的)
#384        // 6 ror 26 --> 384
#484        // 121 ror 30 --> 484
#16384      // 1 ror 18 --> 16384
#2030043136 // 121 ror 8 --> 2030043136
#0x06000000 // 6 ror 8 --> 100663296 (0x06000000 in hex)

Invalid values:
#370        // 185 ror 31 --> 31 is not in range (0 – 30)      不能循环右移31位,超出了取值范围0-30
#511        // 1 1111 1111 --> bit-pattern can’t fit into one byte   二进制数学模型放不进一个字节里(译者注:全是1,怎么循环右移仍然全是1,而且他需要9位才能表述,所以用那种数学模型无法表述511)
#0x06010000 // 1 1000 0001.. --> bit-pattern can’t fit into one byte  二进制数学模型放不进一个字节里

This has the consequence that it is not possible to load a full 32 bit address in one go. We can bypass this restrictions by using one of the following two options:

这导致无法一次性加载一个完整的32位地址。我们可以通过使用以下两个选项之一来绕过这些限制:


1.Construct a larger value out of smaller parts 用较小的数字组成更大的数字

     1.Instead of using MOV  r0, #511   不使用 MOV  r0, #511

     2.Split 511 into two parts: MOV r0, #256, and ADD r0, #255    将511分成两部分, MOV r0, #256, 以及 ADD r0, #255

2.Use a load construct ‘ldr r1,=value’ which the assembler will happily convert into a MOV, or a PC-relative load if that is not possible.

使用 “ ldr r1,=某数载入 ”,这样汇编语言就能开心地自动将它转换成MOV指令或PC相对寻址方式载入

     1.LDR r1, =511


If you try to load an invalid immediate value the assembler will complain and output an error saying: Error: invalid constant. If you encounter this error, you now know what it means and what to do about it.

如果你试着将一个不合规的立即数载入,汇编语言会解释并输出一段错误声明:错误,不合理的常量。如果你恰好遇到了这个错误,你现在知道这是什么意思 ,并且知道如何解决了。
Let’s say you want to load #511 into R0.咱们打个比方,你想把立即数511载入R0

.section .text
.global _start

_start:
    mov     r0, #511
    bkpt

If you try to assemble this code, the assembler will throw an error:如果你编译这段程序,汇编程序会抛出错误:

azeria@labs:~$ as test.s -o test.o
test.s: Assembler messages:
test.s:5: Error: invalid constant (1ff) after fixup

You need to either split 511 in multiple parts or you use LDR as I described before.要么你把511分成多个部分要么就按照之前那种表述方法

.section .text
.global _start

_start:
 mov r0, #256   /* 1 ror 24 = 256, so it's valid */256这个立即数能表达出
 add r0, #255   /* 255 ror 0 = 255, valid. r0 = 256 + 255 = 511 */255这个立即数也能表达出来
 ldr r1, =511   /* load 511 from the literal pool using LDR */使用lrd将511从文本池载入到r1
 bkpt


If you need to figure out if a certain number can be used as a valid immediate value, you don’t need to calculate it yourself. You can use my little python script called rotator.py which takes your number as an input and tells you if it can be used as a valid immediate number.

如果你想搞清楚一个数字能否被用作立即数,你不需要自己计算,你可使用我的小python脚本叫 rotator.py,当你输入自己的数时他会告诉你它能否用作合规的数

azeria@labs:~$ python rotator.py
Enter the value you want to check: 511

Sorry, 511 cannot be used as an immediate number and has to be split.

azeria@labs:~$ python rotator.py
Enter the value you want to check: 256

The number 256 can be used as a valid immediate number.
1 ror 24 --> 256



原文链接:https://azeria-labs.com/memory-instructions-load-and-store-part-4/


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2018-6-20 09:26 被r0Cat编辑 ,原因:
收藏
点赞2
打赏
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  junkboy   +5.00 2018/06/19
最新回复 (3)
雪    币: 1382
活跃值: (68)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
akinet 2018-6-20 00:34
2
0
谢谢分享!~
雪    币: 209
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lyxhh 2018-12-4 11:40
3
0
寻址模式:后索引模式。将R2的值作为地址取出里面的值,应该是r1把
雪    币: 209
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lyxhh 2018-12-4 11:42
4
0
将r1里的值作为地址取出里面的值并将这个值存入寄存器r2  应该是存入r3把
最后于 2018-12-4 11:43 被lyxhh编辑 ,原因:
游客
登录 | 注册 方可回帖
返回