linux main函数汇编分析,嵌入式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 :

0:e3a00453 movr0, #1392508928; 0x53000000

4:e3a01000 movr1, #0; 0x0

8:e5801000 strr1, [r0]

c:e3a0da01 movsp, #4096; 0x1000

10:eb000000 bl18 00000014 :

14:eafffffe b14 00000018 :

18:e1a0c00d movip, sp

1c:e92dd800 stmdbsp!, {fp, ip, lr, pc}

20:e24cb004 subfp, ip, #4; 0x4

24:e3a03456 movr3, #1442840576; 0x56000000

28:e2833050 addr3, r3, #80; 0x50

2c:e3a02c01 movr2, #256; 0x100

30:e5832000 strr2, [r3]

34:e3a03456 movr3, #1442840576; 0x56000000

38:e2833054 addr3, r3, #84; 0x54

3c:e3a02000 movr2, #0; 0x0

40:e5832000 strr2, [r3]

44:e3a03000 movr3, #0; 0x0

48:e1a00003 movr0, r3

4c:e89da800 ldmiasp, {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 movip, sp //IP=SP;保存SP

1c:e92dd800 stmdbsp!, {fp, ip, lr, pc} //依次对pc,lr,ip,fp压栈

20:e24cb004 subfp, ip, #4; 0x4 //fp=ip-4;此时fp指向栈里面的“fp”

退出main函数的时候,编译器使用如下汇编代码处理

4c:e89da800 ldmiasp, {fp, sp, pc} //弹栈依次弹出fp、sp、pc

这里有一个问题,压进去四个数,弹出来只有3个数,这是为什么,通常不是成对成对地压入弹出吗,分析下

先看下调用过程bl18 首先,bl指令执行的时候会把下一条指令的执行地址拷贝到r14即lr链接寄存器中

然后,保存当前sp数据到ip寄存器中,即r13是sp寄存器保存到r12是ip寄存器中

其次,依次压栈pc,lr,ip,fp,即r15,r14,r12,r11(fp寄存器,堆栈指针,用来存放函数的局部变量)

然后,subfp, 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操作是一样的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值