嵌入式linux编程arm初步接触之启动文件汇编、Makefile、第一个main函数

2019-07-13 08:58发布

新建一个汇编启动文件startup.S,代码如下 .text .global _start _start: ldr r0,=0x53000000 ;看门狗寄存器地址 mov r1,#0x0 str r1,[r0] ;写入0,禁止看门狗,否则CPU会不断重启 ldr sp,=1<<12 ;设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K bl main ;调用C程序中的main函数 halt_loop: ;无限循环 b halt_loop 再新建一个led.c文件,代码如下 #define GPFCON (*(volatile unsigned long *)0x56000050) #define GPFDAT (*(volatile unsigned long *)0x56000054) int main() { GPFCON = 0x00000100; // 设置GPF4为输出口, 位[9:8]=0b01 GPFDAT = 0x00000000; // GPF4输出0,LED1点亮 return 0; } 再新建一个Makefile文件,内容如下 led.bin:startup.S led.c arm-linux-gcc -g -c -o startup.o startup.S arm-linux-gcc -g -c -o led.o led.c arm-linux-ld -Ttext 0x00000000 -g startup.o led.o -o led_elf arm-linux-objcopy -O binary -S led_elf led.bin arm-linux-objdump -D -m arm led_elf > led.dis 最后编译运行,得到的反汇编代码如下 led_elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e3a00453 mov r0, #1392508928 ; 0x53000000 4: e3a01000 mov r1, #0 ; 0x0 8: e5801000 str r1, [r0] c: e3a0da01 mov sp, #4096 ; 0x1000 10: eb000000 bl 18
00000014 : 14: eafffffe b 14 00000018
: 18: e1a0c00d mov ip, sp 1c: e92dd800 stmdb sp!, {fp, ip, lr, pc} 20: e24cb004 sub fp, ip, #4 ; 0x4 24: e3a03456 mov r3, #1442840576 ; 0x56000000 28: e2833050 add r3, r3, #80 ; 0x50 2c: e3a02c01 mov r2, #256 ; 0x100 30: e5832000 str r2, [r3] 34: e3a03456 mov r3, #1442840576 ; 0x56000000 38: e2833054 add r3, r3, #84 ; 0x54 3c: e3a02000 mov r2, #0 ; 0x0 40: e5832000 str r2, [r3] 44: e3a03000 mov r3, #0 ; 0x0 48: e1a00003 mov r0, r3 4c: e89da800 ldmia sp, {fp, sp, pc}         在这里,我很惊奇地发现,我编写的汇编代码ldr r0,=0x53000000        ;看门狗寄存器地址经过编译以后变成了mov    r0, #1392508928    ; 0x53000000
        这是为什么呢,因为arm使用的是定长指令集,使用mov指令传送32位数据的时候,只剩下了可耻的12位,不足32位,但是arm又想表示尽可能大的32位数,这里arm使用了一种常数循环移位算法来使用12位数得到一个32位数,这样的话,肯定有些数不能用mov传送表示。我们来看mov指令对应的二进制数e3a00453,e3a表示操作码,00表示寄存器a0,那么就剩下453了。如果每次编写汇编代码都去手工计算这个数能不能使用mov,这是很恐怖的事情。幸好,编译器用ldr伪指令帮我们解决了这个问题。所以,编写汇编代码使用mov的时候能用ldr就用ldr,除非你能肯定立即数是合法的mov操作。
      我们再看看在调用main函数的时候汇编代码做了什么处理,在退出main函数的时候汇编代码做了什么处理。 调用main函数的时候,编译器使用如下汇编代码处理 00000018
: 18: e1a0c00d mov ip, sp //IP=SP;保存SP 1c: e92dd800 stmdb sp!, {fp, ip, lr, pc} //依次对pc,lr,ip,fp压栈 20: e24cb004 sub fp, ip, #4 ; 0x4 //fp=ip-4;此时fp指向栈里面的“fp” 退出main函数的时候,编译器使用如下汇编代码处理 4c: e89da800 ldmia sp, {fp, sp, pc} //弹栈依次弹出fp、sp、pc 这里有一个问题,压进去四个数,弹出来只有3个数,这是为什么,通常不是成对成对地压入弹出吗,分析下 先看下调用过程bl 18
首先,bl指令执行的时候会把下一条指令的执行地址拷贝到r14即lr链接寄存器中 然后,保存当前sp数据到ip寄存器中,即r13是sp寄存器保存到r12是ip寄存器中 其次,依次压栈pc,lr,ip,fp,即r15,r14,r12,r11(fp寄存器,堆栈指针,用来存放函数的局部变量) 然后,sub fp, ip, #4 ;使得堆栈指针也指向当前栈顶指针fp 然后我们再看退出main函数操作 fp出栈,fp恢复调用之前的值 ip出栈,由于事先使用mov ip,sp,这样sp直接恢复调用前的值 pc出栈,pc弹出的内容来自lr链接寄存器,这个lr来源于bl执行的时候 通过这种操作,成功地恢复了调用main函数前的寄存器状态,并且程序继续正常运行。 压栈四个,出栈3个的汇编代码确实不好理解,这里面主要依靠mov ip,sp以及bl指令操作lr寄存器实现。 当然我们也可以使用正常的压栈四个出栈四个实现,进出栈操作7次,mov操作一次,与进出栈操作8次,无mov操作是一样的。