3、uboot的编译、链接过程
分析完了配置的命令make smdk6410_config,接下来分析下make all是如何进行编译和链接的。
首先对整个Makefile进行分析,如下:
24 VERSION = 1 //主版本号
25 PATCHLEVEL = 1 //次版本号
26 SUBLEVEL = 6 //修正版本号
27 EXTRAVERSION = //扩展版本号
28 U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
//设置了这个uboot的版本U_BOOT_VERSION = 1.1.6
29 VERSION_FILE = $(obj)include/version_autogenerated.h
//$(obj)在这边还未定义,所以VERSION_FILE = include/version_autogenerated.h,VERSION_FILE在289有用到,用于记录uboot的版本信息。
30
31 HOSTARCH := $(shell uname -m | \
32 sed -e s/i.86/i386/ \
33 -e s/sun4u/sparc64/
34 -e s/arm.*/arm/ \
35 -e s/sa110/arm/ \
36 -e s/powerpc/ppc/ \
37 -e s/macppc/ppc/)
//首先执行shell命令uname -m得到i686/,通过管道传送给sed命令,然后sed命令执行sed -e s/i.86/i386/,意思就是将i686替换成i386,可以自己查下sed命令的用法,因为uname -m得到的只有i686,所以后面几个-e选项就不用看了。之所以将i686改成i386,是因为i686也属于i386体系。再ubuntu-11.10xia执行uname -p可以查看到处理器类型为i686,用uname -i可以查看到硬件平台类型为i386。最后的结果是HOSTARCH=i386,表示主机体系结构为i386。
38
39 HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
40 sed -e 's/\(cygwin\).*/cygwin/')
//首先执行shell命令uname -s得到Linux,表示linxu系统,然后通过管道传送给tr命令,tr命令利用特殊字符类[:upper:]和[:lower:]将大写字母转化成小写字母,即将Linux转化为linux,后面再执行sed命令,不过uname -s得到只有Linux,没有cygwin之类的东西,所以sed可以不看了,最好的结果是HOSTOS=linux,表示主机操作系统为linux。
41
42 export HOSTARCH HOSTOS
//用export 增加了HOSTARCH和HOSTOS这两个环境变量,供下层的Makefile用
43
44 # Deal with colliding definitions from tcsh etc.
45 VENDOR=
//VENDOR表示芯片厂商,这边设置为空,不过在include/config.mk有设置成samsung,下面的程序会把include/config.mk这个文件包含到这个Makefile文件中include/config.mk是在make smdk6410_config的时候创建并设置变量的。
69 ifdef O
70 ifeq ("$(origin O)", "command line")
71 BUILD_DIR := $(O)
72 endif
73 endif
//Uboot支持将目标文件生成在外部的文件夹中,有两种命令可以实现:一是加O选项,比如make O=/tmp/build all将目标文件放在/tmp/build;二是设置环境变量BUILD_DIR,比如export BUILD_DIR=/tmp/build将目标文件放在/tmp/build。如果以上两种方式都没有定义,那么它将会被存放在源码目录下。
//69-73的意思如果定义可O选项,并且并且O=指定的目录和command line指定的目录一样,BUILD_DIR就为O=定义的目录。origin是Makefile中自带的函数,他并不操作
变量的值,只是告诉你这个变量从哪里来自己可以去查下具体的用法。command line就是我们编译的时候输入的命名,如make O=/tmp/build all。
//实际上我们编译的时候用的是make all,没有指定O选项,所以69-73不执行。
74
75 ifneq ($(BUILD_DIR),)
76 saved-output := $(BUILD_DIR)
77
78 # Attempt to create a output directory.
79 $(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})
80
81 # Verify if it was successful.
82 BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)
83 $(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))
84 endif # ifneq ($(BUILD_DIR),)
//75行 判断BUILD_DIR是否为0,若不为0,则save-output保存BUILD_DIR指定的输出目录
//79行 判断BUILD_DIR这个目录是否存在,如果不存在,就创建这个目录
//82行 先进入这个目录,再调用pwd显示当前路径,在将这个路径值赋给BUILD_DIR
//83行 如果BUILD_DIR还不存在的话,则输出saved-output中的目录does not exist
//实际上我们编译的时候用的是make all,没有设置BUILD_DIR,所以75-84不执行。
85
86 OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
//判断变量BUILD_DIR是否为空,如果不为空,则OBJTREE等于BUILD_DIR的值,如果为空,则OBJTREE等于CURDIR的值,CURDIR是Makefile自带的变量,表示当前目录。实际上这边我们没有设置BUILD_DIR变量,所以OBJTREE=$(CURDIR),即OBJTREE为当前目录。OBJTREE表示目标文件目录。
87 SRCTREE := $(CURDIR)
88 TOPDIR := $(SRCTREE)
89 LNDIR := $(OBJTREE)
//OBJTREE和LNDIR为存放生成文件的目录,TOPDIR与SRCTREE为源码顶层目录,都设置成了当前目录。
90 export TOPDIR SRCTREE OBJTREE
//用export 增加了TOPDIR SRCTREE OBJTREE这三个环境变量,供下层的Makefile用
91
92 MKCONFIG := $(SRCTREE)/mkconfig
93 export MKCONFIG
//定义变量MKCONFIG,$(SRCTREE)为源码顶层目录,所以MKCONFIG为源码顶层目录的mkconfig,这个在配置开发板的时候有用到,上篇分析make smdk6410_config的时候,有详细分析过这个文件。
94
95 ifneq ($(OBJTREE),$(SRCTREE))
96 REMOTE_BUILD := 1
97 export REMOTE_BUILD
98 endif
//如果输出目录与源码目录不等,则设定REMOTE_BUILD项,并导出。
103 ifneq ($(OBJTREE),$(SRCTREE))
104 obj := $(OBJTREE)/
105 src := $(SRCTREE)/
106 else
107 obj :=
108 src :=
109 endif
110 export obj src
//如果输出目录OBJTREE与源码顶层目录SRCTREE不等的话,对obj和src进行赋值,否则则obj,src为空。
//obj src会在主目录中的config.mk定义,但在主makefile包含config.mk之前也需要,譬如unconfig, clean, clobber, distclean
//这边OBJTREE和SRCTREE相等,所以obj src都为空。
114 ifeq ($(OBJTREE)/include/config.mk,$(wildcard $(OBJTREE)/include/config.mk))
115
116 # load ARCH, BOARD, and CPU configuration
117 include $(OBJTREE)/include/config.mk
118 export ARCH CPU BOARD VENDOR SOC
119
120 ifndef CROSS_COMPILE
121 ifeq ($(HOSTARCH),ppc)
122 CROSS_COMPILE =
...
...
155 ifeq ($(ARCH),avr32)
156 CROSS_COMPILE = avr32-
157 endif
158 endif
159 endif
//114行判断config.mk是否存在,config.mk是在执行make smdk6410_config的时候生成的,里面设置了ARCH CPU BOARD VENDOR SOC这几个变量。
//117行include/config.mk中的东西都包含在这个Makefile中了
//118行用export 增加了ARCH CPU BOARD VENDOR SOC这五个环境变量,可以供下层的Makefile用
//120-159行根据体系结构设置CROSS_COMPILE变量,即编译的时候使用的编译器。
160
161 CROSS_COMPILE = /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-
162 export CROSS_COMPILE
//直接修改CROSS_COMPILE变量,设置自己的编译器,这边加这两句的话,120-159的设置就没用了,以后我们要修改编译器的时候,也可以直接在这边改就行了。
165 include $(TOPDIR)/config.mk
//包含顶层目录下的config.mk文件,这个下面会用到,下面会说明。
170 OBJS = cpu/$(CPU)/start.o
171 ifeq ($(CPU),i386)
172 OBJS += cpu/$(CPU)/start16.o
173 OBJS += cpu/$(CPU)/reset.o
174 endif
...
...
187 endif
//170行开始就是设置需要的目标文件了,170行表示需要目标文件start.o
//由于我们CPU为s3c64xx,在前面包含的config.mk中设置的,所以171-187都没用到。
189 OBJS := $(addprefix $(obj),$(OBJS))
//addprefix是Makefile中的一个加前缀函数函数,将$(obj)加到$(OBJS),不过这边$(obj)为空,107行设置的
191 LIBS = lib_generic/libgeneric.a
...
...
215 LIBS += $(BOARDLIBS)
//191-215行设置链接时需要这些文件,这些文件涉及到的目录有lib_generic,board,cpu,lib_arm,fs,net,disk,rtc,dtt,drivers,post,common
217 LIBS := $(addprefix $(obj),$(LIBS))
218 .PHONY : $(LIBS)
//217行和189行的意思一样的,218行用到.PHONY定义了一个伪目标,伪目标挺简单的,可以自己去查下。
221 PLATFORM_LIBS += -L $(shell dirname `$(CC) $(CFLAGS) -print-libgcc-file-name`) -lgcc
//加入GCC的库,CC和CFLAGS都是make的隐含变量,CC表示C编译器,CFLAGS表示执行CC时的命令行参数
225 SUBDIRS = tools \
226 examples \
227 post \
228 post/cpu
229 .PHONY : $(SUBDIRS)
//tools、examples等这个目录可以单独编译一些工具,所以把这些目录都包含在SUBDIRS这个变量中。
230
231 ifeq ($(CONFIG_NAND_U_BOOT),y)
232 NAND_SPL = nand_spl
233 U_BOOT_NAND = $(obj)u-boot-nand.bin
234 endif
//支持nandflash启动,我们之前执行make smdk6410_config进行配置,这样的配置默认状态是不支持nandflash启动的,以后要支持nandflash启动的话要进行修改,具体改的时候再分析把。
235
236 __OBJS := $(subst $(obj),,$(OBJS))
237 __LIBS := $(subst $(obj),,$(LIBS))
//subst是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作的字串,中间两个逗号中间什么也没有,所以这句的意思就是直接去掉$(LIBS)变量中的$(obj)。
242 ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
243
244 all: $(ALL)
//我们在编译的时候用的是make all,所以会找到244行的all,$(ALL)为all的依赖,在242行有定义,因为$(obj)为空,$(U_BOOT_NAND)默认状态未定义,所以ALL=u-boot.srec u-boot.bin System.map,即all的依赖为u-boot.srec u-boot.bin System.map,那么就会先去检测u-boot.srec u-boot.bin System.map这三个文件是否需要重新生成,而这个三个文件取决于其各自的依赖是否发生改变。
u-boot.srec是Motorola的S-Record格式的U-Boot映像,该映像来自于ELF格式的U-boot映像u-boot文件。通过以下命令生成:
249 $(obj)u-boot.srec: $(obj)u-boot
250 $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
OBJCOPY和OBJCFLAGS在顶层config.mk中定义,因此,实际上是调用objcopy命令生成的,arm-linux-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec。objcopy将进行映像格式的转化,其中-O srec说明输出映像的格式为serc的格式。
u-boot.bin表示原始二进制格式的镜像文件,是我们最后需要下载到板子上的二进制文件,该映像也是通过objcopy命令对ELF映像u-boot转化而成,只是转化的参数不同而已,在252行可以找到。
252 $(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
$(OBJDUMP) -d $< > $<.dis
//-O binary即让其生成二进制格式的映像。
System.map是索引文件,是uboot的符号表,即该文件中给出内存地址和相应的符号的映射关系,通过System.map文件可以知道函数,全局变量和静态变量的地址。当然有地址也可以查出对应的符号。和Linux内核中System.map生产一样,System.map是通过nm工具从目标文件u-boot中提取对应的符号表:
320 $(obj)System.map: $(obj)u-boot
321 @$(NM) $< | \
322 grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
323 sort > $(obj)System.map
而实际上调用nm工具从u-boot中提取符号表,然后调用grep打印相匹配的行,而sort则是对这些符号排序后输出到System.map中。
当然,其中这些映像和符号表都取决于ELF映像的u-boot,这是这节的重点。u-boot映像取决于以下语句:
266 $(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
267 UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
268 cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
269 --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
270 -Map u-boot.map -o u-boot
即u-boot依赖于depend,version,$(SUBDIRS),$(OBJS),$(LIBS),$(LDSCRIPT)
其中depend定义了文件的依赖关系,而这些依赖关系最终将被包含进同级的Makefile中,以便编译的时候使用,比如当执行make omap1510inn_config 进行配置后,执行Make 命令将首先生成对应的.depend 文件,比如对于tools目录下的.depend文件如下:
environment.o: environment.c ../include/config.h \
../include/configs/omap1510inn.h ../include/cmd_confdefs.h \
../include/configs/omap1510.h ../include/asm/arch/sizes.h \
../include/environment.h
通过这种方式,把相应的配置结果给用起来了,即把相应的config.h使用起来了。
version主要在设置U-boot的版本
287 version:
288 @echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
289 echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
290 echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
291 $(TOPDIR)) >> $(VERSION_FILE); \
292 echo "\"" >> $(VERSION_FILE)
而宏SUBDIRS的命令如下:
225 SUBDIRS = tools \
226 examples \
227 post \
228 post/cpu
278 $(SUBDIRS):
279 $(MAKE) -C $@ all
即进入到各个列举出来的子目录比如tools,example目录,并执行make all操作。
$(OBJS)的定义如下:
170 OBJS = cpu/$(CPU)/start.o
而CPU 的类型取决于配置的结果,前一节“U-Boot的配置”中提到,最终把ARCH, CPU,
BOARD 的配置写入了config.mk文件,比如对于smdk6410而言,ARM为s3c64xx,因
此OBJS =cpu/s3c64xx/start.o这也是整个u-boot映像的第一个文件。
而$(LIBS)将会生成LIBS 定义的所有文件,具体可参看U-Boot顶层目录的Makefile文件191行,此处不再赘述。
$(LDSCRIPT)的定义在顶层目录下的config.mk中,如下:
143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
因此根据config.mk 的定义,将包含具体目标板所在目录的u-boot.lds链接脚本文件
//268行的LDFLAGS确定了连接方式,LDFLAGS在顶层config.mk中定义,165行有将这个文件包含进来。
//在config.mk中的189行有
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
在board/smdk6410/config.mk中,定义了“TEXT_BASE=0x33F80000”。所以,最终结果如下:LDFLAGS中有
-T board/smdk6410/U-boot.lds -Ttext 0x33F80000的字样,这些字样指定了程序的布局、地址。board/smdk6410/U-boot.lds文件如下
24 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
25 /*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
26 OUTPUT_ARCH(arm)
27 ENTRY(_start)
28 SECTIONS
29 {
//指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成
30 . = 0x00000000;
//从0x0位置开始
31
//代码段
32 . = ALIGN(4);
//代码以4字节对齐
33 .text :
34 {
35 cpu/s3c64xx/start.o (.text)
//代码段的第一个代码部分为start.o
//按顺序依次存储下面的部门
36 cpu/s3c64xx/s3c6410/cpu_init.o (.text)
37 cpu/s3c64xx/onenand_cp.o (.text)
38 cpu/s3c64xx/nand_cp.o (.text)
39 cpu/s3c64xx/movi.o (.text)
40 *(.text)
41 lib_arm/div0.o
42 }
43
//const只读常量数据段
44 . = ALIGN(4);
45 .rodata : { *(.rodata) }
46
//static/global 常量段
47 . = ALIGN(4);
48 .data : { *(.data) }
49
//指定got段, got段式是uboot自定义的一个段, 非标准段
50 . = ALIGN(4);
51 .got : { *(.got) }
52
//命令定义段
//把__u_boot_cmd_start赋值为当前位置, 即起始位置
53 __u_boot_cmd_start = .;
//指定u_boot_cmd段, uboot把所有的uboot命令放在该段
54 .u_boot_cmd : { *(.u_boot_cmd) }
//指定u_boot_cmd段, uboot把所有的uboot命令放在该段
55 __u_boot_cmd_end = .;
56
//mmudata段
57 . = ALIGN(4);
58 .mmudata : { *(.mmudata) }
59
//堆栈段/内部临时变量段-bbs
60 . = ALIGN(4);
//把__bss_start赋值为当前位置,即bss段的开始位置
61 __bss_start = .;
//指定bss段
62 .bss : { *(.bss) }
//把_end赋值为当前位置,即bss段的结束位置
63 _end = .;
64 }
从35行可知,cpu/s3c64xx/start.o被放在程序的最前面,所以U-boot的入口点在cpu/s3c64xx/start.S中。
现在总结下U-boot的编译流程:
1、首先编译cpu/$(CPU)/start.S,对于不同的CPU还可能编译cpu/$(CPU)下的其他文件。
2、然后,对于平台/开发板相关的每个目录,每个通用目录都使用他们各自的Makefile生成相应的库。
3、将1、2步骤生产的.o、.a文件按照board/$(BOARD)/config.mk文件中指定的代码段起始地址、board/$(BOARD)/U-boot.lds连接脚本进行连接。
4、第三步得到的是ELF格式的U-boot,后面Makefile还会将它转化为二进制格式、S-Record格式。
//326行else对应114行的ifeq,因为条件满足,所以这边else后面的语句就不执行了。
#########################################################################
326 else
all $(obj)u-boot.hex $(obj)u-boot.srec $(obj)u-boot.bin \
$(obj)u-boot.img $(obj)u-boot.dis $(obj)u-boot \
$(SUBDIRS) version gdbtools updater env depend \
dep tags ctags etags $(obj)System.map:
@echo "System not configured - see README" >&2
@ exit 1
endif
.PHONY : CHANGELOG
CHANGELOG:
git log --no-merges U-Boot-1_1_5.. | \
unexpand -a | sed -e 's/\s\s*$$//' > $@
#########################################################################