LV065-代码怎么运行的
我们分析的时候,以韦东山教程中的文件为例进行分析,我们前边的 Makefile 文件中已经可以生成反汇编文件,我们可以直接查看。
一、文件准备
1. start.s
.text
.global _start
_start:
ldr sp,=0x80200000
bl clean_bss
bl main
halt:
b halt
clean_bss:
/* 清除BSS段 */
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2. 链接文件
SECTIONS {
. = 0x80100000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
3. C 程序文件
3.1 led.c
#include "led.h"
static volatile unsigned int *CCM_CCGR1 ;
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR ;
static volatile unsigned int *GPIO5_DR ;
void led_init(void)
{
unsigned int val;
CCM_CCGR1 = (volatile unsigned int *)(0x20C406C);
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = (volatile unsigned int *)(0x2290014);
GPIO5_GDIR = (volatile unsigned int *)(0x020AC000 + 0x4);
GPIO5_DR = (volatile unsigned int *)(0x020AC000);
/* GPIO5_IO03 */
/* a. 使能 GPIO5
* set CCM to enable GPIO5
* CCM_CCGR1 [CG15] 0x20C406C
* bit [31:30] = 0b11
*/
*CCM_CCGR1 |= (3<<30);
/* b. 设置 GPIO5_IO03 用于 GPIO
* set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
* to configure GPIO5_IO03 as GPIO
* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014
* bit [3:0] = 0b0101 alt5
*/
val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
val &= ~(0xf);
val |= (5);
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
/* c. 设置 GPIO5_IO03 作为 output 引脚
* set GPIO5_GDIR to configure GPIO5_IO03 as output
* GPIO5_GDIR 0x020AC000 + 0x4
* bit [3] = 0b1
*/
*GPIO5_GDIR |= (1<<3);
}
void led_ctl(int on)
{
if (on) /* on: output 0*/
{
/* d. 设置 GPIO5_DR 输出低电平
* set GPIO5_DR to configure GPIO5_IO03 output 0
* GPIO5_DR 0x020AC000 + 0
* bit [3] = 0b0
*/
*GPIO5_DR &= ~(1<<3);
}
else /* off: output 1*/
{
/* e. 设置 GPIO5_IO3 输出高电平
* set GPIO5_DR to configure GPIO5_IO03 output 1
* GPIO5_DR 0x020AC000 + 0
* bit [3] = 0b1
*/
*GPIO5_DR |= (1<<3);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
3.2 led.h
#ifndef __LED_H__
#define __LED_H__
void led_init(void);
void led_ctl(int on);
#endif2
3
4
5
6
7
3.3 main.c
#include "led.h"
void delay(volatile unsigned int d)
{
while(d--);
}
int main()
{
led_init();
while(1)
{
led_ctl(1);
delay(1000000);
led_ctl(0);
delay(1000000);
}
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
4. 工具准备
还有一个生成 DCD 数据的工具,需要去 u-boot 的源码中拷贝,所需文件如下:

5. makefile
PREFIX=arm-linux-gnueabihf-
CC=$(PREFIX)gcc
LD=$(PREFIX)ld
AR=$(PREFIX)ar
OBJCOPY=$(PREFIX)objcopy
OBJDUMP=$(PREFIX)objdump
led.img : start.S led.c main.c
$(CC) -nostdlib -g -c -o start.o start.S
$(CC) -nostdlib -g -c -o led.o led.c
$(CC) -nostdlib -g -c -o main.o main.c
$(LD) -T imx6ull.lds -g start.o led.o main.o -o led.elf
$(OBJCOPY) -O binary -S led.elf led.bin
$(OBJDUMP) -D -m arm led.elf > led.dis
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d led.bin led.imx
dd if=/dev/zero of=1k.bin bs=1024 count=1
cat 1k.bin led.imx > led.img
clean:
rm -f led.dis led.bin led.elf led.imx led.img *.o2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
二、上电后在做啥?
如下图, imx6ull 芯片一上电后,会先执行 bootRom 程序,此程序是芯片出厂时已经固定的程序,除了芯片原厂,我们是无法修改的。

(1)bootRom 会把 EMMC 或 TF 卡的前 4K 数据读入到芯片内部 RAM 运行。
(2)bootRom 根据 DCD 进行初始化 DDR。
(3)bootRom 根据 IVT,从 EMMC 或 TF 卡中将 led.bin 读到 DDR 的 0x80100000 地址
(4)跳转到 DDR 的 0x80100000 地址执行 。
目前 led.bin 程序已经复制到内存中, CPU 开始从内存 0x80100000 地址开始执行机器码,每一条机器码是 32 位/4 字节,此处的机器码就是 led.bin 中的 机器码,我们可以打开 led.bin 看一下机器码是不是上边图中画的:

前面介绍过大/小端模式,从这里就可以看出来啦。此处可以看到机器码 e59fd028(指令: ldr sp,= 0x80200000)的存储形式:
地址 机器码
00000000 28
00000001 d0
00000002 9f
00000003 e52
3
4
5
可以看到 imx6ull 的存储方式是小端模式,其实, ARM 处理器中存储方式一般都是 小端模式。
三、运行过程分析
这一部分主要是分析 led.dis 文件。
1. start.s 的反汇编

(1)CPU 执行的第一条机器码就是内存地址 0x80100000 存储的 e59fd028 机器码对应的指令是“ ldr sp, [pc, #40] ”,相当于 start.s 文件的 “ ldr sp,= 0x80200000 ” 指令。执行完后,寄存器 SP 的值等于 0x80200000。
80100000: e59fd028 ldr sp, [pc, #40] ; 80100030 <clean+0x14>(2)每执行完一条机器码,会自动执行下一个内存地址 0x80100004 存储的 eb000001 机器码对应的指令是“ bl 80100010”,相当于 Start.S 文件的“ bl clean_bss”指令。
80100004: eb000001 bl 80100010 <clean_bss>
@......
80100010 <clean_bss>:
80100010: e59f101c ldr r1, [pc, #28] ; 80100034 <clean+0x18>
80100014: e59f201c ldr r2, [pc, #28] ; 80100038 <clean+0x1c>
80100018: e3a03000 mov r3, #02
3
4
5
6
7
8
(3)跳转到内存地址 0x80100010 执行 e59f101c 机器码,对应的指令是 ldr r1, [pc, #28] 。相当于 start.s 文件的“ ldr r1, =__bss_start”指令。
(4)此处 clean_bss 相当于一个函数, CPU 会逐条执行指令,clean 为循环体,直到执行“ mov pc, lr”指令后,才返回。
(5)最后返回哪里?返回内存地址 0x80100008 处执行 fa000057 机器码,对应的指令是“blx 8010016c”。对应 start.s 文件的“ bl main”指令 。
2. 进入 main 函数
我们简单看一下 main 函数的反汇编文件对应情况:

(1)进入 main()函数后,先将寄存器 R7、 LR 入栈,保存现场/上下文,方便 main()函数执行完毕后返回,并且将当前栈指向的内存地址赋值给寄存器 R7。
(2)调用 led_init()函数,因为没有参数传递,所以直接调用 BL 指令进行跳转,即“bl 8010003c”指令。
(3)调用 led_ctl(1)函数,此处只有一个参数,通过寄存器 R0 进行传递,即“movs r0, #1”指令,然后通过 BL 指令进行跳转,即“bl 801000f8”指令,关于参数传递问题,可以参考前面《10-开发平台/15-ARM 汇编》的笔记。
(4)调用 delay(1000000)函数,此处只有一个参数,通过寄存器 R0 进行传递,然后通过 BL 指令进行跳转。
(5)调用 led_ctl(0)函数,此处只有一个参数,通过寄存器 R0 进行传递,然后通过 BL 指令进行跳转。
(6)调用 delay(1000000)函数,此处只有一个参数,通过寄存器 R0 进行传递,然后通过 BL 指令进行跳转。
(7)while(1)循环体到此已经结束,但是需要循环执行循环体的内容,通过 B 指令进行跳转到循环体开头,即“b.n 80100174”指令,执行内存地址 0x80100174 处的指令,也就是 led_ctl(1)函数对应的汇编指令“movs r0,#1”。