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

新建一个汇编启动文件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 <main>

00000014 <halt_loop>:
  14:	eafffffe 	b	14 <halt_loop>

00000018 <main>:
  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 <main>:
  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 <main>
首先,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操作是一样的。


        
 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值