CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= printf
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
LIBPATH := -lgcc -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-\
linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4
INCUDIRS := imx6u \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
INCLUDE := $(patsubst %, -I %, $(INCUDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS)$(COBJS)
VPATH := $(SRCDIRS)
.PHONY:clean
$(TARGET).bin : $(OBJS)
$(LD) -Timx6u.lds -o $(TARGET).elf $^ $(LIBPATH)
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).dis $(OBJS)
注意细节:
C语言程序执行的第一条指令。并不是main函数。生成一个C程序的可执行文件时编译器通常会在我们的代码上加上几个被称为启动文件的代crt1.o,crti.o,crtend.o,crtn.o等,他们是标准库文件。这些代码设置C程序的堆栈等,然后调用main函数。他们依赖于操作系统,在裸板上无法执行,所以我们自己写一个。所以,我们自己写的*.S汇编文件就是一个启动文件,它设置好堆栈后调用main函数。因此,我们不需要系统自带的启动文件。
SECTIONS{
. = 0X87800000;
.text :
{
obj/start.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : {*(.data)}
. = ALIGN(4);
__bss_start = . ;
.bss ALIGN(4) : {*(.bss) *(COMMON)}
. = ALIGN(4);
__bss_end = . ;
}
1. 区分运行地址、链接地址、加载地址、存储地址
运行地址<—>链接地址:他们两个是等价的,只是两种不同的说法。
加载地址<—>存储地址:他们两个是等价的,也是两种不同的说法。
运行地址:程序在SRAM、SDRAM中执行运行时的地址。就是执行这条指令时,PC应该等于这个地址,换句话说,PC等于这个地址时,这条指令应该保存在这个地址内。这个我们在链接的时候就已经确定好了
加载地址:程序保存在Nand flash中的地址。就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。
2.程序要运行的话就必须将代码从 SD 卡、 EMMC 或者NAND 中拷贝到其运行地址(链接地址)处。“存储地址”和“运行地址”可以一样,比如STM32 的存储起始地址和运行起始地址都是 0X08000000。
3.单片机代码可以在ram中也可以在内部rom中运行,取决于链接地址是rom还是ram。ram里运行速度更快。
特别注意的是:这个rom必须是类似于单片机内部rom,能够绝对地址访问的,一般就是nor flash,不能从emmc、nand里面直接运行。
4.链接器的任务就是将多个目标文件的.text、.data和.bss等段链接在一起,而链接脚本文件是告诉链接器从什么地址开始放置这些段.简而言之,由于一个工程中有多个.c文件,当它们生成.o文件后如何安排它们在可执行文件中的顺序,这就是链接脚本的作用。
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
这么多参数中,只有secname 和 contents 是必须的,即可简写成:
SECTIONS
{
...
secname :
{
contents
}
...
}
ENTRY(begin)
SECTION
{
.=0x00300000;
.text : { *(.text) }
.data: { *(.data) }
.bss: { *(.bss) }
}
其中含义如下:
示例:
OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm) #指定输出可执行文件是elf格式,32位ARM指令,小端
OUTPUT_ARCH(arm) #指定输出可执行文件的平台为ARM
ENTRY(_start) #指定输出可执行文件的起始代码段为_start.
SECTIONS
{
. = 0x00000000; #定位当前地址为0地址
. = ALIGN(4); #代码以4字节对齐
.text : #指定代码段:必须将start.o文件放在代码段的开始位置,其它文件可任意放
{
cpu/arm920t/start.o (.text) #代码段第一部分,指明start.s是入口程序,被放到代码段开头
*(.text) #其它代码部分.其中的*表示其它任意文件,即所有其它文件的代码段
}
. = ALIGN(4);
.rodata : { *(.rodata) } #指定只读数据段,RO段
. = ALIGN(4);
.data : { *(.data) } #指定读/写数据段,RW段
. = ALIGN(4);
.got : { *(.got) } #指定got段, got段式是uboot自定义的一个段, 非标准段
__u_boot_cmd_start = . #把__u_boot_cmd_start赋值为当前位置, 即起始位置
.u_boot_cmd : { *(.u_boot_cmd) }#指定u_boot_cmd段, uboot把所有的uboot命令放在该段.
__u_boot_cmd_end = . #把__u_boot_cmd_end赋值为当前位置,即结束位置
. = ALIGN(4);
__bss_start = . #把__bss_start赋值为当前位置,即bss段的开始位置
.bss : { *(.bss) } #指定bss段
_end = . #把_end赋值为当前位置,即bss段的结束位置
}
通过上面的分析可以看出: