1.几条汇编指令
1. LDR :(load)读内存
LDR R0 , [R1] // 假设R1的值是x,读取地址x上的数据,保存到R0中。
2. STR :(store)写内存
STR R0 , [R1] // 假设R1的值是x,把R0的值写到地址x上。
3. B:跳转
halt: b halt // 此例用于实现死循环功能,避免程序跑飞。
4. MOV :(move)(移动) 赋值
LDR R0 , R1 // 把R1的值赋给R0,R0 = R1。
LDR R0 , #0x01 // 把立即数的值赋给R0,R0 = 0x01。
5. LDR :伪指令用法
LDR R0 ,= 0x12345678 // 把 0x12345678 这个值赋给R0,R0 = 0x12345678。
伪指令会被拆分为几条真正的arm指令执行,引入伪指令是因为使用MOV指令赋值时真正值得小于寄存位宽。
2.汇编代码
.text
.global _start
_start:
/*配置GPF4为输出引脚*/
/*把0x100写到地址0x56000050*/
LDR r0,=0x56000050
LDR r1,=0x100 /* mov R0,#0X100 */
str r1,[r0]
/*设置GPF4为输出高电平*/
/*把0写到地址0x56000054*/
LDR r0,=0x56000054
LDR r1,=0x00 /* mov R0,#0X00 */
str r1,[r0]
/* 死循环 */
mainloop:
b mainloop
.text和.global 是arm-gcc编译器的关键词。
.text 段保存代码,是只读和【可执行】的,后面那些指令都属于【.text】
段。
.global 告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】;
在本例中,_start是一个函数的起始地址,也是编译、链接后程序的【起始地址】。由于程序是通过加载器来加载的,必须要找到 _start 名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序【如加载器】寻找到。
3.Makefile
led.bin:led.s
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 -f *.bin *.o *.elf
4.反汇编结果
可见其中的地址、机器码和汇编码。