【u-boot-2018.11】make编译过程分析

【u-boot-2018.11】make编译过程目标依赖分析中分析依赖关系时采用自顶向下的方法,从顶层目标开始到最原始的依赖。

此处采用自下而上的方式,先从最原始的依赖开始,一步一步,执行命令生成目标。

1.prepare系列的目标分析

prepare系列的目标依赖关系如下:

1.1 .config

.config是在执行make pangu_basic_defconfig配置时生成,在scripts/kconfig/Makefile中有规则:

%_defconfig: $(obj)/conf
	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)

展开后为:

pangu_basic_defconfig: scripts/kconfig/conf
    scripts/kconfig/conf  --defconfig=arch/../configs/pangu_basic_defconfig Kconfig

scripts/kconfig/conf会从根目录开始读取Kconfig,输出到根目录下的.config文件中。

注:具体执行过程参考【u-boot-2018.11】make配置过程分析

1.2 目标include/config/auto.conf.cmd的规则

include/config/auto.conf.cmd在根目录下的Makefile中的规则如下:

# To avoid any implicit rule to kick in, define an empty command
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;

目标include/config/auto.conf.cmd没有依赖,命令也为空,所以不执行任何操作。

1.3 目标include/config/auto.conf的规则

目标include/config/auto.conf在根目录下的Makefile的规则如下:

# If .config is newer than include/config/auto.conf, someone tinkered
# with it and forgot to run make oldconfig.
# if auto.conf.cmd is missing then we are probably in a cleaned tree so
# we execute the config step to be sure to catch updated Kconfig files
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
	$(Q)$(MAKE) -f $(srctree)/Makefile syncconfig
	@# If the following part fails, include/config/auto.conf should be
	@# deleted so "make silentoldconfig" will be re-run on the next build.
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
		{ rm -f include/config/auto.conf; false; }
	@# include/config.h has been updated after "make silentoldconfig".
	@# We need to touch include/config/auto.conf so it gets newer
	@# than include/config.h.
	@# Otherwise, 'make silentoldconfig' would be invoked twice.
	$(Q)touch include/config/auto.conf

1.3.1 在上面的规则中包含三条命令,其中第1条命令:

$(Q)$(MAKE) -f $(srctree)/Makefile syncconfig

展开后为:

make -f ./Makefile syncconfig

调用该条命令的最终执行结果为执行scripts/kconfig/Makefile中的规则:

# This has become an internal implementation detail and is now deprecated
# for external use.
syncconfig: $(obj)/conf
	$(Q)mkdir -p include/config include/generated
	$< $(silent) --$@ $(Kconfig)

展开后为:

syncconfig: scripts/kconfig/conf
	mkdir -p include/config include/generated
	scripts/kconfig/conf --syncconfig Kconfig

首先创建include/config和include/generated目录,然后scripts/kconfig/conf会从根目录开始读取Kconfig,同时检查并更新配置阶段生成的.config文件,再把最终结果输出到以下四个文件中:

  • include/generated/autoconf.h
  • include/config/auot.conf.cmd
  • include/config/tristate.conf
  • include/config/auto.conf

1.3.2 第二条命令:

$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || { rm -f include/config/auto.conf; false; }

展开后为:

make -f ./scripts/Makefile.autoconf || { rm -f include/config/auto.conf; false; }

先看scripts/Makefile.autoconf文件的头部:

# SPDX-License-Identifier: GPL-2.0
# This helper makefile is used for creating
#  - symbolic links (arch/$ARCH/include/asm/arch
#  - include/autoconf.mk, {spl,tpl}/include/autoconf.mk
#  - include/config.h
#
# When our migration to Kconfig is done
# (= When we move all CONFIGs from header files to Kconfig)
# this makefile can be deleted.

__all: include/autoconf.mk include/autoconf.mk.dep

ifeq ($(shell grep -q '^CONFIG_SPL=y' include/config/auto.conf 2>/dev/null && echo y),y)
__all: spl/include/autoconf.mk
endif

ifeq ($(shell grep -q '^CONFIG_TPL=y' include/config/auto.conf 2>/dev/null && echo y),y)
__all: tpl/include/autoconf.mk
endif

此处设置了CONFIG_SPL=y,没有设置CONFIG_TPL=y,因此整个Makefile的_all依赖只有:

  • spl/include/autoconf.mk

spl/include/autoconf.mk依赖于spl/u-boot.cfg,而spl/u-boot.cfg又依赖于include/config.h:

spl/u-boot.cfg: include/config.h FORCE
	$(Q)mkdir -p $(dir $@)
	$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD)

tpl/u-boot.cfg: include/config.h FORCE
	$(Q)mkdir -p $(dir $@)
	$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD -DCONFIG_TPL_BUILD)

include/autoconf.mk: u-boot.cfg
	$(call cmd,autoconf)

spl/include/autoconf.mk: spl/u-boot.cfg
	$(Q)mkdir -p $(dir $@)
	$(call cmd,autoconf)

注:实际上所有的autoconf.mk都依赖于include/config.h(pangu_basic_defconfig配置只需要spl/include/autoconf.mk)。

1.3.2.1 include/config.h规则

include/config.h由宏filechk_config_h生成:

# include/config.h
# Prior to Kconfig, it was generated by mkconfig. Now it is created here.
define filechk_config_h
	(echo "/* Automatically generated - do not edit */";		\
	for i in $$(echo $(CONFIG_SYS_EXTRA_OPTIONS) | sed 's/,/ /g'); do \
		echo \#define CONFIG_$$i				\
		| sed '/=/ {s/=/	/;q; } ; { s/$$/	1/; }'; \
	done;								\
	echo \#define CONFIG_BOARDDIR board/$(if $(VENDOR),$(VENDOR)/)$(BOARD);\
	echo \#include \<config_defaults.h\>;				\
	echo \#include \<config_uncmd_spl.h\>;				\
	echo \#include \<configs/$(CONFIG_SYS_CONFIG_NAME).h\>;		\
	echo \#include \<asm/config.h\>;				\
	echo \#include \<linux/kconfig.h\>;				\
	echo \#include \<config_fallbacks.h\>;)
endef

include/config.h: scripts/Makefile.autoconf create_symlink FORCE
	$(call filechk,config_h)

最终的生成结果比较简单,不妨看一下:

/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/i2som/pangu
#include <config_defaults.h>
#include <config_uncmd_spl.h>
#include <configs/pangu.h>
#include <asm/config.h>
#include <linux/kconfig.h>
#include <config_fallbacks.h>

注意、注意:生成include/config.h之前,还要应用create_symlink生成相应的符号链接。

1.3.2.2 create_symlink规则:

# symbolic links
# If arch/$(ARCH)/mach-$(SOC)/include/mach exists,
# make a symbolic link to that directory.
# Otherwise, create a symbolic link to arch/$(ARCH)/include/asm/arch-$(SOC).
PHONY += create_symlink
create_symlink:
ifdef CONFIG_CREATE_ARCH_SYMLINK
ifneq ($(KBUILD_SRC),)
	$(Q)mkdir -p include/asm
	$(Q)if [ -d $(KBUILD_SRC)/arch/$(ARCH)/mach-$(SOC)/include/mach ]; then	\
		dest=arch/$(ARCH)/mach-$(SOC)/include/mach;			\
	else									\
		dest=arch/$(ARCH)/include/asm/arch-$(if $(SOC),$(SOC),$(CPU));	\
	fi;									\
	ln -fsn $(KBUILD_SRC)/$$dest include/asm/arch
else
	$(Q)if [ -d arch/$(ARCH)/mach-$(SOC)/include/mach ]; then	\
		dest=../../mach-$(SOC)/include/mach;			\
	else								\
		dest=arch-$(if $(SOC),$(SOC),$(CPU));			\
	fi;								\
	ln -fsn $$dest arch/$(ARCH)/include/asm/arch
endif
endif

这里的注释已经很好的解释了create_symlink的行为,直接参考它即可:

如果arch/$(ARCH)/mach-$(SOC)/include/mach存在,则生成符号链接:

arch/$(ARCH)/include/asm/arch  -->  arch/$(ARCH)/mach-$(SOC)/include/mach

否则生成符号链接:

arch/$(ARCH)/include/asm/arch --> arch/$(ARCH)/include/asm/arch-$(SOC)

对基于arm v7架构的stm32mp157芯片,arch/arm/mach-stm32mp目录存在,则生成符号链接:

arch/arm/include/asm/arch  -->  arch/arm/mach-stm32mp/include/mach

简单说来,create_symlink就是将芯片指定的arch/$(ARCH)/math-$(SOC)链接到跟芯片名字无关的arch/$(ARCH)/include/asm下。

1.3.2.3 spl/include/autoconf.mk规则和spl/u-boot.cfg规则:

# We are migrating from board headers to Kconfig little by little.
# In the interim, we use both of
#  - include/config/auto.conf (generated by Kconfig)
#  - include/autoconf.mk      (used in the U-Boot conventional configuration)
# The following rule creates autoconf.mk
# include/config/auto.conf is grepped in order to avoid duplication of the
# same CONFIG macros
quiet_cmd_autoconf = GEN     $@
      cmd_autoconf = \
		sed -n -f $(srctree)/tools/scripts/define2mk.sed $< |			\
		while read line; do							\
			if [ -n "${KCONFIG_IGNORE_DUPLICATES}" ] ||			\
			   ! grep -q "$${line%=*}=" include/config/auto.conf; then	\
				echo "$$line";						\
			fi								\
		done > $@

quiet_cmd_u_boot_cfg = CFG     $@
      cmd_u_boot_cfg = \
	$(CPP) $(c_flags) $2 -DDO_DEPS_ONLY -dM $(srctree)/include/common.h > [email protected] && { \
		grep 'define CONFIG_' [email protected] > $@;			\
		rm [email protected];						\
	} || {								\
		rm [email protected]; false;					\
	}

u-boot.cfg: include/config.h FORCE
	$(call cmd,u_boot_cfg)

spl/u-boot.cfg: include/config.h FORCE
	$(Q)mkdir -p $(dir $@)
	$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD)

tpl/u-boot.cfg: include/config.h FORCE
	$(Q)mkdir -p $(dir $@)
	$(call cmd,u_boot_cfg,-DCONFIG_SPL_BUILD -DCONFIG_TPL_BUILD)

include/autoconf.mk: u-boot.cfg
	$(call cmd,autoconf)

spl/include/autoconf.mk: spl/u-boot.cfg
	$(Q)mkdir -p $(dir $@)
	$(call cmd,autoconf)

tpl/include/autoconf.mk: tpl/u-boot.cfg
	$(Q)mkdir -p $(dir $@)
	$(call cmd,autoconf)

spl/u-boot.cfg规则会调用cmd_u_boot_cfg命令;spl/include/autoconf.mk规则会调用cmd_autoconf命令。

从cmd_u_boot_cfg和cmd_autoconf两个命令来看,这里会根据include/common.h的依赖,然后调用tools/scripts/define2mk.sed,并合并之前生成的include/config/auto.conf生成最终的autoconf.mk。

1.3 include/config/uboot.release规则

define filechk_uboot.release
	echo "$(UBOOTVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
endef

# Store (new) UBOOTRELEASE string in include/config/uboot.release
include/config/uboot.release: include/config/auto.conf FORCE
	$(call filechk,uboot.release)

命令$(call filechk,uboot.release)展开后就是调用宏filechk_uboot.release,最终将字符串2018.11写入include/config/uboot.release中。

1.4 version.h规则和timestamp.h规则

version_h := include/generated/version_autogenerated.h
timestamp_h := include/generated/timestamp_autogenerated.h
defaultenv_h := include/generated/defaultenv_autogenerated.h


... ...


# Generate some files
# ---------------------------------------------------------------------------

define filechk_version.h
	(echo \#define PLAIN_VERSION \"$(UBOOTRELEASE)\"; \
	echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; \
	echo \#define CC_VERSION_STRING \"$$(LC_ALL=C $(CC) --version | head -n 1)\"; \
	echo \#define LD_VERSION_STRING \"$$(LC_ALL=C $(LD) --version | head -n 1)\"; )
endef

# The SOURCE_DATE_EPOCH mechanism requires a date that behaves like GNU date.
# The BSD date on the other hand behaves different and would produce errors
# with the misused '-d' switch.  Respect that and search a working date with
# well known pre- and suffixes for the GNU variant of date.
define filechk_timestamp.h
	(if test -n "$${SOURCE_DATE_EPOCH}"; then \
		SOURCE_DATE="@$${SOURCE_DATE_EPOCH}"; \
		DATE=""; \
		for date in gdate date.gnu date; do \
			$${date} -u -d "$${SOURCE_DATE}" >/dev/null 2>&1 && DATE="$${date}"; \
		done; \
		if test -n "$${DATE}"; then \
			LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define U_BOOT_DATE "%b %d %C%y"'; \
			LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define U_BOOT_TIME "%T"'; \
			LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define U_BOOT_TZ "%z"'; \
			LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define U_BOOT_DMI_DATE "%m/%d/%Y"'; \
			LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +'#define U_BOOT_BUILD_DATE 0x%Y%m%d'; \
		else \
			return 42; \
		fi; \
	else \
		LC_ALL=C date +'#define U_BOOT_DATE "%b %d %C%y"'; \
		LC_ALL=C date +'#define U_BOOT_TIME "%T"'; \
		LC_ALL=C date +'#define U_BOOT_TZ "%z"'; \
		LC_ALL=C date +'#define U_BOOT_DMI_DATE "%m/%d/%Y"'; \
		LC_ALL=C date +'#define U_BOOT_BUILD_DATE 0x%Y%m%d'; \
	fi)
endef

define filechk_defaultenv.h
	(grep -v '^#' | \
	 grep -v '^$$' | \
	 tr '\n' '\0' | \
	 sed -e 's/\\\x0/\n/' | \
	 xxd -i ; echo ", 0x00" ; )
endef

$(version_h): include/config/uboot.release FORCE
	$(call filechk,version.h)

$(timestamp_h): $(srctree)/Makefile FORCE
	$(call filechk,timestamp.h)

$(defaultenv_h): $(CONFIG_DEFAULT_ENV_FILE:"%"=%) FORCE
	$(call filechk,defaultenv.h)
  • include/generated/version_autogenerated.h

include/generated/version_autogenerated.h目标依赖于include/config/uboot.release文件,执行命令$(call filechk,version.h),也就是调用filechk_version.h宏生成版本相关字符串文件include/generated/version_autogenerated.h,它的内容如下:

#define PLAIN_VERSION "2018.11-stm32mp-r2.2-g656fae5-dirty"
#define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
#define CC_VERSION_STRING "arm-openstlinux_eglfs-linux-gnueabi-gcc (GCC) 8.2.0"
#define LD_VERSION_STRING "GNU ld (GNU Binutils) 2.31.1.20180818"
  • include/generated/timestamp_autogenerated.h

调用filechk_timestamp.h宏生成编译的时间戳文件,如下:

#define U_BOOT_DATE "Aug 25 2019"
#define U_BOOT_TIME "14:43:07"
#define U_BOOT_TZ "+0800"
#define U_BOOT_DMI_DATE "08/25/2019"
#define U_BOOT_BUILD_DATE 0x20190825

1.5 outputmakefile规则

PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
	$(Q)ln -fsn $(srctree) source
	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
	    $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif

如果编译没有设置O,即输出和代码在同一个目录下,那么outputmakefile规则什么都不做;

如果编译指定了输出目录O,则调用scripts/mkmakefile在O选项指定的目录下生成一个简单的Makefile。

1.6 scripts_basic规则

# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

scripts_basic规则的执行结果就是编译生成scripts/basic/fixdep工具,该工具是u-boot编译系统中最常用的工具,用于在编译过程中修正每一个生成文件的依赖关系。

1.7 prepare3规则

# prepare3 is used to check if we are building in a separate output directory,
# and if so do:
# 1) Check that make has not been executed in the kernel src $(srctree)
prepare3: include/config/uboot.release
ifneq ($(KBUILD_SRC),)
	@$(kecho) '  Using $(srctree) as source for U-Boot'
	$(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \
		echo >&2 "  $(srctree) is not clean, please run 'make mrproper'"; \
		echo >&2 "  in the '$(srctree)' directory.";\
		/bin/false; \
	fi;
endif

prepare3规则就是用来检查当编译指定了输出目录O时,源码目录下有没有执行过make,若执行过,要先执行make mrporper。

1.8 prepare1规则

prepare1: prepare2 $(version_h) $(timestamp_h) \
                   include/config/auto.conf
ifeq ($(wildcard $(LDSCRIPT)),)
	@echo >&2 "  Could not find linker script."
	@/bin/false
endif

prepare1规则就是用来检查有没有指定链接文件。

1.9 prepare0规则

prepare0: archprepare FORCE
	$(Q)$(MAKE) $(build)=.

展开后为:

prepare0: archprepare FORCE
	make -f ./scripts/Makefile.build obj=.

编译时,命令make -f ./scripts/Makefile.build obj=.不会生成任何目标。

1.10 prepare系列目标总结

prepare阶段主要做了一下工作:

(1)配置阶段,scripts/kconfig/conf根据传入的指定配置文件在根目录下生成.config文件;

(2)编译阶段,scripts/kconfig/conf读取配置阶段生成的.config文件并检查最新配置,生成以下文件:

  • include/generated/autoconf.h
  • include/config/auot.conf.cmd
  • include/config/tristate.conf
  • include/config/auto.conf

(3)调用宏filechk_config_h生成include/config.h文件;

(4)调用create_symlink将芯片指定的mach-$(SOC)或arch-$(SOC)链接到跟芯片名字无关的arch/$(ARCH)/include/asm目录下;

(5)调用命令cmd_u_boot_cfg和cmd_autoconf生成autoconf.mk;

(6)调用宏filechk_uboot.release生成include/config/uboot.release文件;

(7)调用宏filechk_version.h生成include/generated/version_autogenerated.h文件;

(8)调用宏filechk_timestamp.h生成include/generated/timestamp_autogenerated.h文件;

(9)scripts_basic规则生成fixdep工具,用于对整个系统中的生成的目标文件的依赖文件进行更新。

2. u-boot系列目标依赖

从上图可知,u-boot除了prepare依赖外,还依赖于$(head-y)、$(libs-y)、$(LDSCRIPT),即依赖于:

  • 启动文件arch/arm/cpu/$(CPU)/start.o
  • 各个目录下的built-in.o
  • 链接脚本文件arch/arm/cpu/uboot.lds

2.1 启动文件start.o

$(head-y)在arch/arm/Makefile被直接指定:

head-y := arch/arm/cpu/$(CPU)/start.o

在顶层的Makefile中被指定给变量u-boot-init:

u-boot-init := $(head-y)

2.2 各目录下的build-in.o

$(libs-y)在顶层的Makefile中被指定为各个子目录下的build-in.o的集合:

libs-y += lib/
libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
libs-$(CONFIG_OF_EMBED) += dts/
libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
libs-y += drivers/dma/
libs-y += drivers/gpio/
libs-y += drivers/i2c/
libs-y += drivers/mtd/
libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/raw/
libs-y += drivers/mtd/onenand/
libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/
libs-y += drivers/mtd/spi/
libs-y += drivers/net/
libs-y += drivers/net/phy/
libs-y += drivers/pci/
libs-y += drivers/power/ \
	drivers/power/domain/ \
	drivers/power/fuel_gauge/ \
	drivers/power/mfd/ \
	drivers/power/pmic/ \
	drivers/power/battery/ \
	drivers/power/regulator/
libs-y += drivers/spi/
libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/
libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
libs-$(CONFIG_SYS_FSL_MMDC) += drivers/ddr/fsl/
libs-$(CONFIG_ALTERA_SDRAM) += drivers/ddr/altera/
libs-y += drivers/serial/
libs-y += drivers/usb/dwc3/
libs-y += drivers/usb/common/
libs-y += drivers/usb/emul/
libs-y += drivers/usb/eth/
libs-y += drivers/usb/gadget/
libs-y += drivers/usb/gadget/udc/
libs-y += drivers/usb/host/
libs-y += drivers/usb/musb/
libs-y += drivers/usb/musb-new/
libs-y += drivers/usb/phy/
libs-y += drivers/usb/ulpi/
libs-y += cmd/
libs-y += common/
libs-y += env/
libs-$(CONFIG_API) += api/
libs-$(CONFIG_HAS_POST) += post/
libs-y += test/
libs-y += test/dm/
libs-$(CONFIG_UT_ENV) += test/env/
libs-$(CONFIG_UT_OVERLAY) += test/overlay/

libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)

libs-y := $(sort $(libs-y))

u-boot-dirs	:= $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples

u-boot-alldirs	:= $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))

libs-y		:= $(patsubst %/, %/built-in.o, $(libs-y))

u-boot-init := $(head-y)
u-boot-main := $(libs-y)

以上脚本中,先将$(libs-y)设置为各子目录的集合,最后调用patsubst函数将设置为这些目录下的build-in.o文件的集合,最后赋值给变量u-boot-main作为链接的主体文件。

  • 各目录下的built-in.o是如何生成的

以drivers/mmc/built-in.o为例,先查看生成的依赖文件drivers/mmc/.built-in.o.cmd(这个文件只有在编译后才能看到):

cmd_drivers/mmc/built-in.o :=  arm-openstlinux_eglfs-linux-gnueabi-ld.bfd     -r -o drivers/mmc/built-in.o drivers/mmc/mmc.o drivers/mmc/mmc-uclass.o drivers/mmc/mmc_write.o drivers/mmc/mmc_boot.o drivers/mmc/stm32_sdmmc2.o 

从生成命令cmd_drivers/mmc/built-in.o可以看到,built-in.o是由目录下各个编译生成的*.o文件通过链接操作ld -r而来。

  • ld的-r选项是什么作用呢

在ld的手册里是这样介绍-r选项的:

-r
--relocatable
    Generate relocatable output—i.e., generate an output file that can in turn serve as input to ld. This is often called partial linking. As a side effect, in environments that support standard Unix magic numbers, this option also sets the output file's magic number to OMAGIC. If this option is not specified, an absolute file is produced. When linking C++ programs, this option will not resolve references to constructors; to do that, use `-Ur'.
 
    When an input file does not have the same format as the output file, partial linking is only supported if that input file does not contain any relocations. Different output formats can have further restrictions; for example some a.out-based formats do not support partial linking with input files in other formats at all.
 
    This option does the same thing as `-i'.

简单来说,ld通过-r选项来产生可重定位的输出,相当于部分链接。

在这里通过ld -r将目录drivers/mmc/下的*.o文件先链接为单一文件built-in.o,但其并不是最终的生成文件,而是一个可进行重定位的文件。在下一阶段的链接中,ld会将各个目录下的built-in.o链接生成最终的u-boot。

  • built-in.o规则

生成built-in.o的规则在scripts/Makefile.build中定义:

#
# Rule to compile a set of .o files into one .o file
#
ifdef builtin-target
quiet_cmd_link_o_target = LD      $@
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
		      $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
		      $(cmd_secanalysis),\
		      rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)

$(builtin-target): $(obj-y) FORCE
	$(call if_changed,link_o_target)

targets += $(builtin-target)
endif # builtin-target

2.3 链接脚本u-boot.lds

链接脚本的规则如下:

quiet_cmd_cpp_lds = LDS     $@
cmd_cpp_lds = $(CPP) -Wp,-MD,$(depfile) $(cpp_flags) $(LDPPFLAGS) \
		-D__ASSEMBLY__ -x assembler-with-cpp -std=c99 -P -o $@ $<

u-boot.lds: $(LDSCRIPT) prepare FORCE
	$(call if_changed_dep,cpp_lds)

2.4 生成u-boot的规则

顶层Makefile中定义了生成u-boot文件的规则:

# Rule to link u-boot
# May be overridden by arch/$(ARCH)/config.mk
quiet_cmd_u-boot__ ?= LD      $@
      cmd_u-boot__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_u-boot) -o $@ \
      -T u-boot.lds $(u-boot-init)                             \
      --start-group $(u-boot-main) --end-group                 \
      $(PLATFORM_LIBS) -Map u-boot.map;                        \
      $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)

quiet_cmd_smap = GEN     common/system_map.o
cmd_smap = \
	smap=`$(call SYSTEM_MAP,u-boot) | \
		awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \
	$(CC) $(c_flags) -DSYSTEM_MAP="\"$${smap}\"" \
		-c $(srctree)/common/system_map.c -o common/system_map.o

u-boot:	$(u-boot-init) $(u-boot-main) u-boot.lds FORCE
	+$(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
	$(call cmd,smap)
	$(call cmd,u-boot__) common/system_map.o
endif

u-boot文件的生成很简单,调用ld命令,将$(u-boot-init)和$(u-boot-main)指定的一系列文件通过链接脚本u-boot.lds链接起来。

u-boot针对pangu_board生成的命令是这样的(命令太长,分成多行):

arm-openstlinux_eglfs-linux-gnueabi-ld.bfd   			\
-pie  --gc-sections -Bstatic  --no-dynamic-linker 		\
-Ttext 0xC0100000 										\
-o u-boot 												\
-T u-boot.lds 											\
arch/arm/cpu/armv7/start.o 								\
--start-group  											\
	arch/arm/cpu/built-in.o  							\
	arch/arm/cpu/armv7/built-in.o  						\
	arch/arm/lib/built-in.o  							\
	arch/arm/mach-stm32mp/built-in.o  					\
	board/i2som/pangu/built-in.o  						\
	cmd/built-in.o  									\
	common/built-in.o  									\
	disk/built-in.o  									\
	drivers/built-in.o  								\
	drivers/dma/built-in.o  							\
	drivers/gpio/built-in.o  							\
	drivers/i2c/built-in.o  							\
	... ...												\
	env/built-in.o  									\
	fs/built-in.o  										\
	lib/built-in.o  									\
	net/built-in.o  									\
	test/built-in.o  									\
	test/dm/built-in.o 									\
--end-group 											\
arch/arm/lib/eabi_compat.o  							\
arch/arm/lib/lib.a 										\
-Map u-boot.map

生成u-boot文件后,后续就是针对u-boot文件的各种处理了。

3.顶层目标依赖

显然,在生成了u-boot的基础上,进一步生成所需要的各种目标文件:

  • u-boot.srec
# Normally we fill empty space with 0xff
quiet_cmd_objcopy = OBJCOPY $@
cmd_objcopy = $(OBJCOPY) --gap-fill=0xff $(OBJCOPYFLAGS) \
	$(OBJCOPYFLAGS_$(@F)) $< $@

... ...

OBJCOPYFLAGS_u-boot.hex := -O ihex

OBJCOPYFLAGS_u-boot.srec := -O srec

u-boot.hex u-boot.srec: u-boot FORCE
	$(call if_changed,objcopy)

调用objcopy命令,通过-O ihex或-O srec指定生成u-boot.hex或u-boot.srec格式文件。

  • u-boot.sym
quiet_cmd_sym ?= SYM     $@
      cmd_sym ?= $(OBJDUMP) -t $< > $@
u-boot.sym: u-boot FORCE
	$(call if_changed,sym)

调用$(OBJDUMP)命令生成符号表文件u-boot.sym。

  • System.map
SYSTEM_MAP = \
		$(NM) $1 | \
		grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \
		LC_ALL=C sort
System.map:	u-boot
		@$(call SYSTEM_MAP,$<) > $@

调用$(NM)命令打印u-boot文件的符号表,并用grep -v处理后得到System.map文件,里面包含了最终使用到的各种符号的位置信息。

  • u-boot.bin
quiet_cmd_cat = CAT     $@
cmd_cat = cat $(filter-out $(PHONY), $^) > $@

append = cat $(filter-out $< $(PHONY), $^) >> $@

... ...

PHONY += dtbs
dtbs: dts/dt.dtb
	@:
dts/dt.dtb: u-boot
	$(Q)$(MAKE) $(build)=dts dtbs

quiet_cmd_copy = COPY    $@
      cmd_copy = cp $< $@

ifeq ($(CONFIG_MULTI_DTB_FIT),y)

fit-dtb.blob: dts/dt.dtb FORCE
	$(call if_changed,mkimage)

MKIMAGEFLAGS_fit-dtb.blob = -f auto -A $(ARCH) -T firmware -C none -O u-boot \
	-a 0 -e 0 -E \
	$(patsubst %,-b arch/$(ARCH)/dts/%.dtb,$(subst ",,$(CONFIG_OF_LIST))) -d /dev/null

u-boot-fit-dtb.bin: u-boot-nodtb.bin fit-dtb.blob
	$(call if_changed,cat)

u-boot.bin: u-boot-fit-dtb.bin FORCE
	$(call if_changed,copy)
else ifeq ($(CONFIG_OF_SEPARATE),y)
u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
	$(call if_changed,cat)

u-boot.bin: u-boot-dtb.bin FORCE
	$(call if_changed,copy)
else
u-boot.bin: u-boot-nodtb.bin FORCE
	$(call if_changed,copy)
endif

由于使用了DEVICE_TREE设置,即CONFIG_OF_SEPARATE=y,因此u-boot.bin与u-boot-dtb.bin是一样的,而u-boot-dtb.bin是由u-boot-nodtb.bin和dts/dt.dtb两个文件直接使用cat命令生成。

dts/dt.dtb的生成规则是:

dts/dt.dtb: u-boot
	$(Q)$(MAKE) $(build)=dts dtbs

它的命令展开后为:

make -f ./scripts/Makefile.build obj=dts dtbs

至于生成u-boot-nodtb.bin的规则:

u-boot-nodtb.bin: u-boot FORCE
	$(call if_changed,objcopy)
	$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
	$(BOARD_SIZE_CHECK)

显然,u-boot-nodtb.bin是u-boot文件通过objcopy得到的。

  • u-boot.cfg​​​​​​​
u-boot.cfg spl/u-boot.cfg tpl/u-boot.cfg: include/config.h FORCE
	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf $(@)

注:u-boot.cfg的生成规则与前面的prepare部分的u-boot.cfg的生成规则是一样的。

u-boot.cfg中包含所有用到的宏定义,因此阅读源码时如果不确定某个宏的值,可以检查u-boot.cfg文件。

自此,生成了所有的目标文件,完成了整个编译过程的分析

猜你喜欢

转载自blog.csdn.net/linuxweiyh/article/details/100061110