从ARM9裸机程序led程序讲起,一个例子是汇编调用了C函数,这涉及函数的返回以及汇编和C的数据空间的分布情况,比较复杂一些。所以先从一个纯汇编语言例子开始。
软件平台:Fedora9 交叉编译环境4.4.3;开发板为友善之臂的2440。
先看汇编程序:
.text
.global _start
_start:
LDR R0,=0x56000010 @ R0设为GPBCON寄存器。此寄存器
@ 用于选择端口B各引脚的功能:
@ 是输出、是输入、还是其他
MOV R1,#0x00000400
STR R1,[R0] @ 设置GPB5为输出口, 位[10:9]=0b01
LDR R0,=0x56000014 @ R0设为GPBDAT寄存器。此寄存器
@ 用于读/写端口B各引脚的数据
MOV R1,#0x00000000 @ 此值改为0x00000020,
@ 可让LED1熄灭
STR R1,[R0] @ GPB5输出0,LED1点亮
MAIN_LOOP:
B MAIN_LOOP
对于上述的程序的各个寄存器以及对寄存器操作参看注释,重点是对其汇编代码对应的机器码的分析。
使用arm-linux-objdump 命令查看下载进开发板中的二进制代码对应的格式。
arm-linux-objdump -D -b binary -m armled_on.bin
led_on.bin: file format binary
Disassembly of section .data:
00000000 <.data>:
0: e59f0014 ldr r0, [pc, #20] ; 0x1c
4: e3a01b01 mov r1, #1024 ; 0x400
8: e5801000 str r1, [r0]
c: e59f000c ldr r0, [pc, #12] ; 0x20
10: e3a01000 mov r1, #0
14: e5801000 str r1, [r0]
18: eafffffe b 0x18
1c: 56000010 undefined instruction 0x56000010
20: 56000014 undefined instruction 0x56000014
左边的一列0,4,8,…,20是十六进制数,表示的是地址偏移量,可以使用arm-linux-ld命令来改变,在使用SDRAM时,会将程序拷贝进SDRAM运行,但是s3c2440能接SDRAM的只有bank六和七,其地址其实就不是0x0了,这是arm-linux-ld就比较有用了。
arm-linux-ld -Ttext 0x0000000 -g led_on.o-o led_on_elf
e59f0014对应的就是机器码了,但是需要注意的是前面提到代码和数据段的问题,数据段是接着代码进行存放的,所以后面的显示undefined instruction其实不是指令,对地址,细心的话能够发现,56000010其实是GPBCON寄存器,二56000014则是对应的GPBDAT数据寄存器。
18: eafffffe b 0x18 这个意识是跳转到0x18地址处执行,而这句前面的地址就是0x18,所以对应的汇编代码是:
MAIN_LOOP:
B MAIN_LOOP
ARM的跳转作指令编码格式如下:
将eafffffe按上述格式展开:
1110_101_0_111111111111111111111110;
可以发现其27-25确实为101。其24位的L,表示是否使用link(R14)寄存器拷贝pc的内容,这个是为了程序返回时的准备,注意该拷贝过程是由硬件自动完成的,该位置一,表示复制,为0表示不复制,该位一个典型应用是中断返回地址的copy,那么31-28的1110对应的意义为cond,不仅仅跳转指令有cond,数据操作类指令机器码中均存在该位,并且意义是相同的。其每位对应的意义参看如下。其实就是无条件执行的意思。
ARM的数据操作指令编码格式如下:
其它的指令参看上述就能明白,但是 0: e59f0014 ldr r0, [pc, #20] ; 0x1c
可能不太明白,
需要说明的是ldr指令有两种用法,其中一种表示的是伪指令,而不是ARM质量。
ARM指令集中,LDR通常都是作加载指令的,但是它也可以作伪指令。
(1)LDR r0,=name,像这种带等号的是伪指令,而不是ARM指令,LDR 伪指令用于加载立即数或一个地址值到指定寄存器.
*如果name是立即数的话:LDRR0,=0X123;//将0X123存入R0
*如果name是个标识符:LDRR0,=NAME;//将NAME的地址存入R0
在SDRAM中跑程序时,使用的跳转指令的地址涉及的偏移和绝对,偏移指的是相对当前指令而言,绝对指的是绝对地址,如0x30000000,该地址可以使用上述的arm-linux-ld制定。可以使用objdump指令查看。
下面是C版本的,前面也同样期初是汇编,然后使用汇编调用了C函数的。
0: e59f0010 ldr r0, [pc, #16] ; 0x18
4: e3a01000 mov r1, #0
8: e5801000 str r1, [r0]
c: e3a0da01 mov sp, #4096 ; 0x1000
10: eb000001 bl 0x1c
14: eafffffe b 0x14
18: 56000010 undefined instruction 0x56000010
1c: e52db004 push {fp} ; (str fp, [sp, #-4]!)
20: e28db000 add fp, sp, #0
24: e59f3024 ldr r3, [pc, #36] ; 0x50
28: e3a02b01 mov r2, #1024 ; 0x400
2c: e5832000 str r2, [r3]
30: e59f301c ldr r3, [pc, #28] ; 0x54
34: e3a02000 mov r2, #0
38: e5832000 str r2, [r3]
3c: e3a03000 mov r3, #0
40: e1a00003 mov r0, r3
44: e28bd000 add sp, fp, #0
48: e8bd0800 pop {fp}
4c: e12fff1e bx lr
50: 56000010 undefined instruction 0x56000010
54: 56000014 undefined instruction 0x56000014
第一列0-18对应于如下汇编代码,其中18地址处是WATCHDOG的地址,及存的是数据。
.text
.global _start
_start:
ldr r0, =0x56000010 @ WATCHDOG寄存器地址
mov r1, #0x0
str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启
ldr sp, =1024*4 @ 设置堆栈,注意:不能大于4k,因为现在可用的内存只有4K
@ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
bl main @ 调用C程序中的main函数
halt_loop:
b halt_loop
剩下的对应于如下C代码:
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
int main()
{
GPBCON =0x00000400; // 设置GPB5为输出口, 位[11:10]=0b01
GPBDAT =0x00000000; // GPB5输出0,LED1点亮
return 0;
}
在反汇编的中的bl 0x1c,即是跳转到C执行,但是在C真正的代码运行之前和结束时都对相应的寄存器进行了适当的保护和返回。注意观察这里的bl对应机器码的link位的设置。这些是编译程序生成的,体现在机器码中。