使用点亮LED的简单程序分析其汇编码和机器码,通过直接修改bin文件中的机器码修改功能,点亮其他的LED灯。
首先修改Makefile文件把elf文件反汇编生成dis文件,查看其真正的汇编指令:
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
反汇编之后打开dis文件,真正的汇编指令:
Led_on.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
/*[第1行]*/ 0: e59f1014 ldr r1, [pc, #20] ; 1c <.text+0x1c>
/*[第2行]*/ 4: e3a00c01 mov r0, #256 ; 0x100
/*[第3行]*/ 8: e5810000 str r0, [r1]
/*[第4行]*/ c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20>
/*[第5行]*/ 10: e3a00000 mov r0, #0 ; 0x0
/*[第6行]*/ 14: e5810000 str r0, [r1]
00000018 <halt>:
18: eafffffe b 18 <halt>
1c: 56000050 undefined
20: 56000054 undefined
在汇编指令中,最右边是汇编码,中间是机器码,最左边是地址。
在cpu中有27个寄存器,其中16个可以使用,如上图所示,最右边一列是寄存器的别名。其中pc为Program counter程序计数器,当把一个地址写入此寄存器中时,程序就跳到此地址中去。lr是link register链接寄存器,用来保存返回地址,当程序执行完后调回到这个地址。sp是stack point栈指针寄存器。
程序解析过程说明:
/*[第1行]*/
/*[第2行]*/
/*[第3行]*/ 8: e5810000 str r0, [r1] 在这一行将0x100写入r1对应的内存
/*[第4行]*/ c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20> 在这一行中pc等于当前地址加8,所以r1=pc + 12 = 0xc + 8 + 12 =32=0x20,所以下边已经给出此地址的值为56000054,读取出来赋给r1。
/*[第5行]*/ 10: e3a00000 mov r0, #0 ; 0x0 在这一行中将立即数赋给r0。
/*[第6行]*/
编译器将汇编码转换为机器码,这个机器码就是bin文件的内容。如果想将点亮的第一个LED灯换成第二个,经过查阅手册将GPFCON赋值改为0x400,可以通过修改汇编程序然后编译链接,但是也可以通过修改机器码来实现,就是修改第二行的MOV指令对应的机器码,首先查阅MOV指令机器码存储结构。
e3a00c01 mov r0, #256
e3a00c01的位:
bit15~bit12用来表示r0,bit11~bit0用来表示赋值的数值立即数。其中bit11~bit8表示高四位rotate,剩下的是低8位immed_8。而立即数 = immed_8循环右移(2 * rotate)位。
根据上图rotate是12,所以是1循环右移24位得到0..(23)..100000000也就是立即数0x100
但是如果是立即数是0x400,怎么使用rotate和immed_8表示呢,将0x400展开是0..(21)..100 0000 0000数一下0的个数可知道是1循环右移22位得到,所以ratate=22/2=11,rotate为0b1011,所以12位立即数为1011 0000 0001,所以直接修改机器码e3a00c01为e3a00b01即可解决问题。