注:以下学习内容学习于韦东山老师arm裸机第一期教程
一.常用arm汇编指令
1.1 ldr,读地址指令
ldr R0,[R1]
假设R1的值是x,那么这条语句:读地址x上的数据(4字节),保存到R0中
1.2 str,写地址指令
str R0,[R1]
假设R1的值是x,把R0的值写到地址x(4字节)
1.3 mov
mov R0,R1
把R1的值赋给R0,R0 = R1
mov R0,#0x100(#后面表示是一个立即数) R0 = 0x100
1.4 ldr ,伪指令
ldr R0, = 0x12345678
R0 = 0x12345678
这是一条伪指令,它会被拆分为几条真正的ARM指令
因为如果写入 mov R0,#0x12345678 是一条错误指令
因为32位的arm指令,肯定会有一些字节表示mov指令本身,有一些位表示R0,剩下的不足32位,不能够保存任意的值,只能够保存简单值,也被称为立即数。 (0x100就是简单值)
因此引入伪指令ldr R0, = 0x12345678(注意有个等号,与读内存时不同),这条指令会被拆分成真正的arm指令。
例如下面的代码.text .global _start _start: /* 伪指令 */ ldr r0, =0x12345678 ldr r1, =0x100 mov r1, #0x200 halt: b halt反汇编代码如下:
led_on.elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e59f0008 ldr r0, [pc, #8] ; 10 <.text+0x10> 4: e3a01c01 mov r1, #256 ; 0x100 8: e3a01c02 mov r1, #512 ; 0x200 0000000c <halt>: c: eafffffe b c <halt> 10: 12345678 eornes r5, r4, #125829120 ; 0x7800000
1.4.1 从上面代码可以看出,伪指令ldr r0, =0x12345678被转换成为了 ldr r0,[pc,#8],即去PC+8地址的地方读数据
由于我使用的是arm-linux编译器,对于arm架构,PC(R15寄存器的别名)值等于当前地址+8,因此回去8+8=16,对应16进制0x10,
因此回去0x10处读数据,读到12345678
对于PC=当前地址+8的解释:
对于arm架构,CPU是以流水线的方式执行的,即当前执行地址a的指令,已经在对地址a+4的指令进行译码,已经在读取地址a+8的指令
因此 PC = a + 8
1.4.2 对于伪指令ldr r1, =0x100,由于0x100比较简单,是一个立即数,因此不需要去内存读,直接转变为mov r1, #256
1.5 sub
sub r0,r1,#4 <==> r0 = r1 - 4
sub r0,r1,r2 <==> r0 = r1 - r2
1.6 add
add r0,r1,#4 <==> r0 = r1 + 4
add r0,r1,r2 <==> r0 = r1 + r2
1.7 b跳转
1.8 bl 跳转的时候将返回地址保存到lr寄存器中
1.9
1.9.1 stm(把多个寄存器的值写入内存)
stmdb sp!, {fp, ip, lr, pc}
把多个寄存器的值写入sp处
假设sp = 4096,由于是db,先减sp' = sp - 4 = 4092,然后存放到后面4个寄存器中,存放时有一个规则,高编号的寄存器存在高地址
fp->r11 ip->r12 lr->r14 pc->r15
因此 PC存放在4092~4095
然后再次减 lr存放在4088~4091
然后再次减 ip存放在4084~4097
然后再次减 fp存放在4080~4083
!表示sp = 最终被修改的sp值 = 4080
1.9.2 ldm(读内存,写入多个寄存器)
ldmia sp, {fp,sp,pc}
将sp地址的值存放到多个寄存器中,存放时有一个规则,高地址存放在编号高的寄存器中
假设sp = 4080,由于是ia,先读sp' = 4080
因此 fp存放4080~4083的值,等于原来保存的fp
/* sp被修改了 */
然后再次增加 sp存放4084~4087的值,等于原来保存的ip
然后再次增加 pc存放4088~4091的值,等于原来保存的lr
pc = lr,因此程序就会返回
最后再次增加 sp' = sp + 4,由于sp后面无!号,sp修改后的地址并不存入sp中
但是sp在前面被修改了。
ia->过后增加(Increment After)
ib->预先增加(Increment Before)
da->过后减少(Decrement After)
/* 常用db,编译器默认使用db */
db->预先减少(Decrement Before)
二.机器码
2.1 在反汇编的第二列的数据中就是机器码,对应的就是bin文件的数据
2.2 编译器将我们写的程序先编译为反汇编,变为机器码,存放到bin文件中,烧到单板内。
2.3 机器码的格式(以mov指令为例)
mov指令机器码格式如下图:
其中最后12位表示立即数,最后12位拆分为高4位rotate,低8位immed_8
立即数 = immed_8 循环右移 (2 * rotate位)
mov r0, #0x100对应的机器码为e3a01c01,其中低12位为1100 0000 0001,rotate = 12, immed_8 = 1
0x100 = 1 循环右移 24位
修改bin文件来达到将mov r0,#0x100,改为mov r0,#0x400
0x400 = 1 循环右移 22位 ==> rotate = 11, immed_8 = 1
因此对应的二进制就是1011 0000 0001,对应的机器码就为e3a01b01,修改对应的bin文件即可