我们写在汇编程序中的LDR伪指令并不直接对应具体的机器码,LDR伪指令会由编译器通过一定的技巧(文字池)转化为具体的汇编指令(对应具体的机器码)。
如下以最简单的点亮LED的汇编程序,分析下LDR伪指令对应的汇编指令:
/*
* 点亮LED1: gpf4
*/
.text
.global _start
_start:
/* 配置GPF4为输出引脚
* 把0x100写到地址0x56000050
*/
ldr r1, =0x56000050
ldr r0, =0x100 /* mov r0, #0x100 */
str r0, [r1]
/* 设置GPF4输出高电平
* 把0写到地址0x56000054
*/
ldr r1, =0x56000054
ldr r0, =0 /* mov r0, #0 */
str r0, [r1]
/* 死循环 */
halt:
b halt
简单的makefile文件如下:
all:
arm-linux-gcc -c -o led_on.o led_on.S
arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
arm-linux-objcopy -O binary -S led_on.elf led_on.bin
arm-linux-objdump -D led_on.elf > led_on.dis
clean:
rm *.bin *.o *.elf
我们需要分析反汇编码来得到LDR被处理后的汇编指令,arm-linux-objdump -D led_on.elf > led_on.dis
就是用来得到反汇编码的。
led_on.dis的内容如下:
led_on.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e59f1014 ldr r1, [pc, #20] ; 1c <.text+0x1c>
4: e3a00c01 mov r0, #256 ; 0x100
8: e5810000 str r0, [r1]
c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20>
10: e3a00000 mov r0, #0 ; 0x0
14: e5810000 str r0, [r1]
00000018 <halt>:
18: eafffffe b 18 <halt>
1c: 56000050 undefined
20: 56000054 undefined
可以看到ldr r1, =0x56000050
对应的汇编指令为ldr r1, [pc, #20] ; 1c <.text+0x1c>
,编译器将0x56000050这个值直接放到了代码段中,然后使用ldr指令(注意:这里为ldr指令,而不是伪指令)将对应内存里的值读取到r1中。
这里需要注意下pc
的值:
对于ldr r1, [pc, #20]
,这条指令来说当前执行指令的地址为0,而由于当前ARM7为3级流水线。PC的值等于当前执行指定的地址值加8。所以最终就是去8 + 20 = 0x1c
的内存中去取数据。
ARM3级流水线:假设当前执行地址A的指令,已经在对地址A+4的指令进行译码了,在对地址A+8的指令进行取值了。所以当前PC的值为A+8。
我们发现ldr r0, =0x100
指令直接被替换成了mov r0, #256 ; 0x100
,这是因为0x100是可以用立即数表示的,所以这里直接使用了mov指令。我们可以看到mov r0, #256 ; 0x100
对应的机器码为e3a00c01,那么mov指令的机器码的格式是什么呢?
Rd:为目标寄存器的值。
shifter_operand:这12位代表了立即数A,前4位的值为B,后8的值为C。则A为B循环右移2 * C位。
对于e3a00c01来说,就是0x1循环右移2*0xC = 24位,即最终的结果为0x100。