裸机Makefile、链接文件

裸机Makefile

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)


注意细节:

  • -nostdlib:传递给连接器。这个选项常用于编译内核、bootloader等程序,它们不需要启动文件、标准库文件。

C语言程序执行的第一条指令。并不是main函数。生成一个C程序的可执行文件时编译器通常会在我们的代码上加上几个被称为启动文件的代crt1.o,crti.o,crtend.o,crtn.o等,他们是标准库文件。这些代码设置C程序的堆栈等,然后调用main函数。他们依赖于操作系统,在裸板上无法执行,所以我们自己写一个。所以,我们自己写的*.S汇编文件就是一个启动文件,它设置好堆栈后调用main函数。因此,我们不需要系统自带的启动文件。

  • -Wall:打开所有警告
  • -I   :加“-I”的目的是因为 Makefile 语法要求指明头文件目录的时候需要加上“-I”。
  • VPATH :VPATH指明依赖文件的搜索路径。对源文件中的头文件查找无效。对于.h文件,需要使用-I ./header
  • LIBPATH:加入了数学库,第三方库的目录。
  • -L:指定库文件的路径    -I  指定头文件的路径

 

链接文件

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) } 
} 

  其中含义如下:

  • ENTRY(begin)指明程序的入口点为begin标号;
  • 段名前后必须要有空格
  • .=0x00300000指明目标代码的起始地址为0x00300000,这一段地址可以是SDRAM的起始地址;
  • .text : { *(.text) }表示从0x00300000开始放置所有目标文件的代码段;
  • *(.text)中前面的“*”是通配符,表示所有代码段都放在.text中,在uboot中是如下示例所写,是有顺序的。
  • .data: { *(.data) }表示数据段从代码段的末尾开始;
  • 再后是.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段的结束位置
}

通过上面的分析可以看出:

  • 由于在链接脚本中规定了文件start.o(对应于start.S文件)作为整个uboot的起始点,因此启动uboot时会执行首先执行start.S;
  • 一般来说,内存空间可分为代码段、数据段、全局变量段、未初始化变量区等.段由编译器决定.从上面的分析可以看出,从0x00000000地址开始,编译器首先将代码段放在最开始的位置,然后是数据段,然后是bss段(未初始化变量区)。
  • 有些uboot的lds文件有keep关键字,由于连接器可能将某些它认为没用的section过滤掉,加上keep作用是防止被优化
  •  

你可能感兴趣的:(裸机Makefile、链接文件)