先看将要用到的几条汇编代码:
1)LDR (load):读内存 LDR R0, 【R1】
假设R1的值是 X,读取地址 X 上的数据(4字节),保存在R0中
2)STR(store):写内存命令, STR R0, 【R1】
假设 R1的值是 X,把 R0 的值写到地址 X(4字节)
3)B:跳转
4)MOV (move): move R0, R1 把R1的值赋给R0
move R0, #0X100 R0 = 0X100
注意:mov只能处理立即数。
当我们不能确定一个数是否是立即数时,使用LDR伪指令。
关于什么样的数才能算是立即数,可以参考ARM体系结构 P24对立即数的叙述。
简单介绍一下:
每个立即数由一个8位的常数循环右移偶数位得到,循环右移的偶数由一个4位二进制数的两倍表示记作rotate_imm,8位常数记作immed_8,立即数记作;
=immed_8循环右移(2*rotate_imm)
5)LDR R0, = 0X12345678
伪指令,它会被拆分为几条真正的RAM指令
首先看板子的硬件连接图:
可以看到LED 1,2,4连接GPF4,5,6。故,查看芯片手册GPF IO口
知道了板子的硬件连接及查看芯片手册知道对应寄存器地址和对应的功能,就可以开始编程了:
/** 点亮LED1: gpf4*/.text/*文本段*/.global _start/*全局标号_start*/_start:/*配置GPF4为输出引脚
* 把0x100写到地址0x56000050*/ldr r1,=0x56000050/*为什么不使用mov,因为立即数不是一眼就能看出来的*/ldr r0,=0x100 /*mov r0, #0x100*/str r0, [r1]/*设置GPF4输出低电平
* 把0写到地址0x56000054*/ldr r1,=0x56000054ldr r0,=0 /*mov r0, #0*/str r0, [r1]/*死循环 ,因为flash后面的数据不可预知,只能停留在这里*/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
进一步分析反汇编:
第一列表示地址,第二列表示机器码,第三列表示汇编码。
ARM9采用三级流水线工作方式,可以大大提高系统效率。三级流水线,取址,译码,执行。
当cpu在执行一条指令时,已经开始对下一条指令进行译码,对下下一条指令进行取址了。这也就是我们常说的PC值等于当前指令地址加上8的原因。
分析第一条汇编,r1=[pc+20];此时pc=当前指令地址(0)+8+20=28=0x1c
可以看到在地址1c处,存放的是0x56000050,这就相当于把[pc+20]地址的内容赋值给r1,此时就r1=0x56000050。
第二条指令把256即0x100赋值给r0;
第三条指令,把r0的值写在r1的地址内,即把0x100写进地址0x56000050。后面的以此类推。
分析了反汇编之后,我们就可以通过更改bin文件,即直接更改二进制文件达到点亮led灯的效果。
刚才我们点亮了GPF4,现在点亮GPF 5,只用把上面的0x100改成0x400,其他不变(后面说注意事项)。
我们知道上面的第二列表示机器码之后,需要达到直接写一串机器码,实现MOV R0,#0X400的功能,此时我们需要查看
MOV指令机器码:
上面生成的二进制文件,可以使用sublime text 或者Uedit32打开。
先看上面我们生成的MOV RO,#256机器码:
e3a00c01
对照上面的MOV机器码,做几点说明,12-15位表示Rd,此时我们使用的是R0,这四个位全为0,表示寄存器R0,最后0-11位(立即数)就是我们需要修改的了。
关于立即数,上面做了说明:
=immed_8循环右移(2*rotate_imm)
这里的0-11位高4位就是rotate_imm,低八位就是8位常数immed_8。
0x100的表示如上图,
公式:=immed_8循环右移(2*rotate_imm)
高4位12,低八位为1,1循环右移2*12,刚好等于0x100.
那么0x400的呢?
可以想到,0x400,二进制0x1后面10个0,前面就需要22位,所以,我们可以采用1循环右移22位。
注意:
右移本来就是不确定的,左移是确定的,但是,右移的不确定是根据cpu的具体实现来决定的,就是不同的cpu可以有不同的处理,在我们这里,就是特定这个cpu S3C2440,所以我们的右移补位方式这里就是对特定CPU的。这个符合我们的需求,但是在其他硬件平台上,右移如何补位,要查看它的参考资料。
所以我们0x400的立即数就可以表示为:高四位为11,低八位位1,这样就可以切合。有公式完成这个就不再赘述了。
最后得出:
现在我们更改之前的bin文件,把上面MOV r0,#256的机器码改成上图的e3a00b01
烧写进入单板,观察是否点亮GPF 5.
之前的bin文件做一点说明:
可以看到我们的e59f1014在图1和图2上似乎顺序不同,一个是反的,一个正的。这是因为我们采取单板默认的小端模式进行的开发,小端模式,高位在高地址,地位在低地址。
所以只用把图1中第五列的0c改成0b即可,下载进入,观看现象。当然是被点亮了。
最后,再说明一点,这个更改机器码确实麻烦,我们已经过去了那个写机器码的年代,但是知道这些,总会有好处的,
上面的改写0x400是因为我刚好的写入的是一个立即数,要是例如0x123这样的非立即数数据,是不会反汇编mov指令的,下面测试:
反汇编:
通过这些学习,我们可以和cpu的距离变得更近。