U-Boot-1.16 Makefile分析第三篇,按照make all的思路来分析。
1、执行make all
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
all: $(ALL)
u-boot.srec为S-Record格式的image;
u-boot.bin为原始二进制文件的image;
System.map按链接地址由小到大的顺序列出了所有符号,可以称之为系统映射表;
U_BOOT_NAND = u-boot-nand.bin,应该是烧录到nand flash的二进制格式的image;
all: $(ALL):就是编译uboot的目标all了,all目标没有相应命令,所以要实现的就是相关的依赖文件,也就是变量ALL中的内容u-boot.srec 、u-boot.bin、System.map、u-boot-nand.bin。
接下来对这四个依赖单独分析
2、u-boot.srec(依赖在下级标题)
$(obj)u-boot.srec: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
$(OBJCOPY) = $(CROSS_COMPILE)objcopy ,用来把一种目标文件中的内容复制到另一种类型的目标文件中。
${OBJCFLAGS} = DBGFLAGS= -g 。
命令展开就是:
arm-linux-gnueabihf-objcopy -g -O srec u-boot u-boot.srec
最终生成S-record格式文件。objcopy参考链接.
2.1 u-boot(依赖在下级标题)
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
$(OBJDUMP) = $(CROSS_COMPILE)objdump,$(CROSS_COMPILE)就是指定的编译器。objdump命令是反汇编目标文件或者可执行文件的命令,而objdump -x 的作用是显示头文件信息。 objdump参考链接.
$(LIBS) =各种带路径的库。所以,$(OBJDUMP) -x $(LIBS) 就是将库中的头文件信息显示出来。
sed -n -e 's/.*(_u_boot_cmd.*\)/-u\1/p 匹配 "*_u_boot_cmd.*"改为-u__u_boot_cmd_.*,/p参数用来将执行后的结果输出出来。
所以,UNDEF_SYM变量的内容就是,通过编译器的objdump工具将LIBS中的库文件包含的头文件输出,匹配其中带有 "__u_boot_cmd_"的字符串,并将其前缀修改为 “-u__u_boot_cmd_.*”。
$(LNDIR) = 顶层Makefile所在路径。
$(LD) 为编译器的连接工具。
$(LDSCRIPT) = board/smdk2410/u-boot.lds。
$(TEXT_BASE) 在board/smdk2410/config.mk文件中有定义,为:0x33F80000
$(PLATFORM_LDFLAGS) 为空
$(LDFLAGS) = -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)=-Bstatic -T board/smdk2410/u-boot.lds -Ttext0x33F80000 -L xx -lgcc.
$(__OBJS) = cpu/arm920t/start.o。
$(__LIBS) 开发板相关的和通用的库文件。
$(PLATFORM_LIBS) -L (编译器依赖的库路径) -lgcc
u-boot目标的命令展开后如下:
UNDEF_SYM=`arm-linux-gnueabihf-objdump -x lib_generic/libgeneric.a board/smdk2410/libsmdk2410.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/nand/libnand.a drivers/nand_legacy/libnand_legacy.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd /home/xx/linux/u-boot-1.1.6 && arm-linux-gnueabihf-ld -Bstatic -T /home/xx/linux/u-boot-1.1.6/board/smdk2410/u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o \
--start-group lib_generic/libgeneric.a board/smdk2410/libsmdk2410.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/nand/libnand.a drivers/nand_legacy/libnand_legacy.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a --end-group -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4 -lgcc \
-Map u-boot.map -o u-boot
其中,最主要的命令为:
arm-linux-gnueabihf-ld
-Bstatic
-T u-boot-1.1.6/board/smdk2410/u-boot.lds
-Ttext 0x33F80000 cpu/arm920t/start.o
--start-group
xxx.a
--end-group
-L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/…/lib/gcc/arm-linux-gnueabihf/4.9.4 -lgcc
-Map u-boot.map -o u-boot
-Bstatic,默认情况下,编译器优先进行动态库链接,-Bstatic选项后面跟的-l xxx.a会进行静态链接。
--Ttext ADDRESS 代码段链接地址;参考链接.
--start-group xxx.a --end-group:链接时在xxx.a循环搜索相关引用。参考链接.
-Map u-boot.map,生成u-boot.map。这个文件包含了很多映射的内容。
-o u-boot,链接生成u-boot文件,这个就是elf格式的了。
2.1.1 depend
depend dep:
for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
make中的for语法,其中"dir"为SHELL中的变量 (展开时使用$$符号),"SUBDIRS"为Makefile中的变量(展开时使用$符号),在这个例子中dir会在每次循环中获取SUBDIRS变量中的内容,循环的次数由SUBDIRS变量包含的字符串数量决定,循环的内容由do…done包括。
for循环中执行的语句do $(MAKE) -C $$dir _depend,展开后等价于:
cd dir
make _depend
而SUBDIRS变量中的内容在上一篇中有提到,为 “tools examples post post/cpu”。但是在tools的Makefile中没有找到_depend目标,所以_depend目标应该是tools的Makefile中用include包含了其他makefile文件。使用grep查找_depend:
./Makefile:270: for dir in $(SUBDIRS) ; do $(MAKE) -C $$dir _depend ; done
./rules.mk:26:_depend: $(obj).depend
很明显,_depend存在于rules.mk文件中,那么tools目录下的Makefile肯定会包含了rules.mk文件。
果然,在212行找到了相关代码,我看了四个目录底下的Makefile,都使用了include包含rules.mk文件。接下里看一下rules.mk中_depend的代码:
rules.mk文件中的代码只包含了_depend目标,代码如下:
_depend: $(obj).depend
$(obj).depend: $(src)Makefile $(TOPDIR)/config.mk $(SRCS)
@rm -f $@
@for f in $(SRCS); do \
g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \
$(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
done
_depend依赖于.depend,.depend依赖于$(src)Makefile $(TOPDIR)/config.mk $(SRCS),src变量的内容在上一篇有讲到,在make时没有指定目标文件路径的情况下,src为空; $(TOPDIR)/config.mk为顶层Makefile路径下的config.mk文件;SRCS在tools目录下的Makefile有定义:
OBJ_LINKS = environment.o crc32.o
OBJ_FILES = img2srec.o mkimage.o envcrc.o gen_eth_addr.o bmp_logo.o
SRCS := $(addprefix $(obj),$(OBJ_LINKS:.o=.c)) $(OBJ_FILES:.o=.c)
$(OBJ_LINKS:.o=.c)是变量的高级用法,作用是将OBJ_LINKS中以.o结尾的字符串变为.c结尾的。所以SRC的内容就为:environment.c crc32.c img2srec.c mkimage.c envcrc.c gen_eth_addr.c bmp_logo.c。
接着分析.depnd的命令:
@rm -f $@
@for f in $(SRCS); do \
g=`basename $$f | sed -e 's/\(.*\)\.\w/\1.o/'`; \
$(CC) -M $(HOST_CFLAGS) $(CPPFLAGS) -MQ $(obj)$$g $$f >> $@ ; \
done
删除路径下的目标文件,也就是旧的.depend。
随后在for循环中,使用basename命令提取文件名(这个basename命令是shell命令),通过管道输出到sed命令sed -e ‘s/(.*).\w/\1.o/’,sed命令中用正则表达式将后缀替换成.o后缀。表达式参考链接.
.* 表示任意字符;
\. 表示 . 字符;
\w用于匹配字母,数字或下划线字符
\1对应前面 .* 的匹配
$(CC):编译器类型;-M:自动找寻源文件中包含的头文件并将其输出,输出一个用于make的规则,该规则描述了这个main源文件的依赖关系; $(HOST_CFLAGS) : 在tools目录的Makefile中有定义,为 -traditional-cpp -Wall,-traditional-cpp作用是强行编译,-Wall作用是编译后显示所有警告;$(CPPFLAGS) 在tools目录下的Makefile中定义,为:
-idirafter $(SRCTREE)/include \
-idirafter $(OBJTREE)/include2 \
-idirafter $(OBJTREE)/include \
-DTEXT_BASE=$(TEXT_BASE) -DUSE_HOSTCC
-idirafter dir把目录dir添加到第二包含路径中.如果某个头文件在主包含路径(用`-I’添加的路径)中没有找到,预处理器就搜索第二包含路径。
-DTEXT_BASE:在gcc中 -D选项是用来在使用gcc/g++编译的时候定义宏。
-D 后面直接跟宏命,相当于定义这个宏,默认这个宏的内容是1。
-D 后面跟 key=value 表示定义key这个宏,它的内容是value。
所以-DTEXT_BASE=$(TEXT_BASE)就是定义TEXT_BASE宏,$(TEXT_BASE)是要copy到sdram中运行的地址,是链接时就确定的地址。
-DUSE_HOSTCC:定义DUSE_HOSTCC宏
-MQ :将目标设置为与您指定的字符串完全相同,这里指定的目标文件就是sed命令用正则表达式替换后的.o文件。
最后将这次编译过程产生的所有信息都重定向到.depend中,其中主要就是编译environment.o crc32.o img2srec.o mkimage.o envcrc.o gen_eth_addr.o bmp_logo.o这些目标所依赖的头文件。
2.1.2 version
version:
@echo -n "#define U_BOOT_VERSION \"U-Boot " > $(VERSION_FILE); \
echo -n "$(U_BOOT_VERSION)" >> $(VERSION_FILE); \
echo -n $(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion \
$(TOPDIR)) >> $(VERSION_FILE); \
echo "\"" >> $(VERSION_FILE)
version中的命令就是将一些版本信息输出到指定的文件中。
“echo -n” -n参数表示不换行输出。
$(VERSION_FILE) = include/version_autogenerated.h。
$(CONFIG_SHELL) = /bin/sh
第三行使用tools/setlocalversion脚本工具,这是个本地版本号的检查工具。
2.1.3 $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ all
$(SUBDIRS) 为tools examples post post/cpu。所以这里就是切换到相应的目录下执行make all。
2.1.4 $(OBJS)
$(OBJS):
$(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))
$(OBJS) = cpu/arm920t/start.o。
$(CPU)=arm920t。
这里切换到cpu/arm920t目录下执行 :make start.o,将相应的源文件进行编译。
$(OBJS)其实就是代表u-boot所需要的.o文件。
2.1.5 $(LIBS)
$(LIBS):
$(MAKE) -C $(dir $(subst $(obj),,$@))
$(LIBS): = LIBS是很多依赖库。
$(obj)为空。
这里切换到库文件的路径下执行make操作,将相应的源文件进行编译。
$(LIBS)其实就是代表u-boot所需要的.a文件。
2.1.6 $(LDSCRIPT)
顶层Makefile中没有$(LDSCRIPT)定义,其定义在config.mk中,具体定义如下:
ifndef LDSCRIPT
#LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds.debug
ifeq ($(CONFIG_NAND_U_BOOT),y)
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot-nand.lds
else
LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/u-boot.lds
endif
在Makefile中添加$(LDSCRIPT)打印:
test:
@echo "LDSCRIPT = $(LDSCRIPT)"
其值输出如下:
3、u-boot.bin(依赖参考2.1u-boot.srec)
$(obj)u-boot.bin: $(obj)u-boot
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
命令展开就是:
arm-linux-gnueabihf-objcopy -O binary u-boot u-boot.bin
最终生成二进制格式的文件。
4、System.map
$(obj)System.map: $(obj)u-boot
@$(NM) $< | \
grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
sort > $(obj)System.map
nm命令被用于显示u-boot的符号表,使用grep -v进行过滤,使用sort命令进行排序,最后输出到System.map文件中。System.map包含了U-Boot的全局变量和函数的地址信息。
5、u-boot.lds
先贴出用到的连接脚本代码:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}