u-boot-2016.09 make编译过程分析(二)

u-boot-2016.09 make编译过程分析(二)

综述

u-bootv2014.10版本开始引入KBuild系统,同更改前的编译系统相比,由于Kbuild系统的原因,其Makefile变得更加复杂。

u-boot的编译跟kernel编译一样,分两步执行:

  • 第一步:配置,执行make xxx_defconfig进行各项配置,生成.config文件
  • 第二部:编译,执行make进行编译,生成可执行的二进制文件u-boot.bin或u-boot.elf

Makefile的核心是依赖和命令。对于每个目标,首先会检查依赖,如果依赖存在,则执行命令更新目标;如果依赖不存在,则会以依赖为目标,先生成依赖,待依赖生成后,再执行命令生成目标。

  • 博客《u-boot-2016.09 make配置过程分析》详尽解释了第一步的操作,在这一步中,u-boot执行配置命令make xxx_defconfig时先搜集所有默认的Kconfig配置,然后再用命令行指定的xxx_defconfig配置进行更新并输出到根目录的.config文件中。

  • 配置完成后执行make命令生成二进制文件的过程,由于涉及的依赖和命令很多,也将make编译过程分析分为两部分,目标依赖和命令执行。

    • 博客《u-boot-2016.09 make编译过程分析(一)》中描述了make过程中的依赖关系
    • 本篇主要分析make过程中的通过命令生成各个目标的依赖,从而一步一步更新目标,直至更新并生成顶层目标u-boot.bin

第二部分、执行命令更新目标

将上面的依赖关系并到一起,就得到了一个完整的u-boot目标依赖图:
完整的目标依赖关系
(完整的关系图较大,可以将图片拖到浏览器的其他窗口看大图)

这些依赖有两类:

  • 依赖本身通过执行命令生成,但不存在进一步的依赖;
  • 依赖自身还有进一步的依赖,在生成了进一步依赖的基础上,执行命令生成依赖;

完成目标依赖分析后,剩下的就是基于完整的目标依赖关系图,从最底层的依赖开始,逐层运行命令生成目标,直到生成顶层目标。

《u-boot-2016.09 make编译过程分析(一)》分析依赖关系时采用自顶向下的方法,从顶层目标开始到最原始的依赖结束。
此处采用自下而上的方式,先从最原始的依赖开始,一步一步,执行命令生成目标。

1. prepare系列目标依赖

完整的prepare系列的目标依赖:

依次从最右边的依赖说起:

1.1 scripts/kconfig/conf生成的文件

  • .config

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

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

这里展开后为:

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

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

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

以上4个文件在执行make编译命令的开始会检查%.conf的依赖规则:

include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
    $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
        { rm -f include/config/auto.conf; false; }
    $(Q)touch include/config/auto.conf

调用make -f ./Makefile silentoldconfig的最终结果是执行scripts/kconfig/Makefile中的规则:

silentoldconfig: $(obj)/conf
    $(Q)mkdir -p include/config include/generated
    $< $(silent) --$@ $(Kconfig)

这个规则展开为:

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

scripts/kconfig/conf会从根目录开始读取Kconfig,同时检查并更新配置阶段生成的.config文件,再把最终结果输出到以上的4个文件中。

所生成的4个文件中,include/config/auto.conf依赖于include/config/auto.conf.cmd,但是这里的依赖文件include/config/auto.conf.cmd文件并非由fixdep生成,而是直接由conf工具生成,算是*.cmd文件生成的特例。

scripts/kconfig/conf生成了图中右侧的依赖:include/config/auto.conf$(KCONIFG_CONFIG)/.configinclude/config/auto.conf.cmd

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

在生成include/config/auto.conf的规则中:

# 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 silentoldconfig
    @# 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

除了执行$(MAKE) -f $(srctree)/Makefile silentoldconfig外,还执行$(MAKE) -f $(srctree)/scripts/Makefile.autoconf

scripts/Makefile.autoconf的头部是这样的:

# 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.
#
# SPDX-License-Identifier:  GPL-2.0
#

__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=yCONFIG_TPL=y,所以整个makefile__all的依赖有:

  • include/autoconf.mk
  • include/autoconf.mk.dep

然而include/autoconf.mk还要进一步依赖于config.h

1.2.1 include/config.h的规则

所有的autoconf.mk都依赖于include/config.hrpi_3_32b_defconfig配置只需要include/autoconf.mk):

include/autoconf.mk include/autoconf.mk.dep \
    spl/include/autoconf.mk tpl/include/autoconf.mk: 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 \;               \
    echo \#include \;              \
    echo \#include \;     \
    echo \#include \;                \
    echo \#include \;)
endef

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

最终生成的include/config.h也比较简单,不妨看看:

/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/raspberrypi/rpi
#include 
#include 
#include 
#include 
#include 

生成config.h之前,还要应用create_symlink生成相应的符号链接。

1.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)/math-$(SOC)/include/mach存在,则生成符号链接:arch/$(ARCH)/include/asm/arch --> arch/$(ARCH)/math-$(SOC)
  • 否则生成符号链接arch/$(ARCH)/include/asm/arch --> arch/$(ARCH)

对基于arm v7架构的bcm2837芯片,arch/arm/math-bcm283x文件夹存在,所以生成链接:
arch/arm/include/asm --> arch/arm/mach-bcm283x/include/mach

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

1.2.3 include/autoconf.mk的规则

# 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 = \
    $(CPP) $(c_flags) $2 -DDO_DEPS_ONLY -dM $(srctree)/include/common.h > [email protected] && { \
        sed -n -f $(srctree)/tools/scripts/define2mk.sed [email protected] |       \
        while read line; do                         \
            if [ -n "${KCONFIG_IGNORE_DUPLICATES}" ] ||         \
               ! grep -q "$${line%=*}=" include/config/auto.conf; then  \
                echo "$$line";                      \
            fi                              \
        done > $@;                              \
        rm [email protected];                              \
    } || {                                      \
        rm [email protected]; false;                           \
    }

include/autoconf.mk: FORCE
    $(call cmd,autoconf)

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

1.2.4 include/autoconf.mk.dep的规则
quiet_cmd_autoconf_dep = GEN     $@
      cmd_autoconf_dep = $(CC) -x c -DDO_DEPS_ONLY -M -MP $(c_flags) \
    -MQ include/config/auto.conf $(srctree)/include/common.h > $@ || {  \
        rm $@; false;                           \
    }
include/autoconf.mk.dep: FORCE
    $(call cmd,autoconf_dep)

这个规则比较简单,由于autoconf.mkcommon.hauto.conf而来,因此直接处理这两个文件的依赖并合并到autoconf.mk.dep中。

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,最终将字符串2016.09写入include/config/uboot.release中。

1.4 timestamp.hversion.h的规则

version_h := include/generated/version_autogenerated.h
timestamp_h := include/generated/timestamp_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"'; \
        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"'; \
    fi)
endef

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

$(timestamp_h): $(srctree)/Makefile FORCE
    $(call filechk,timestamp.h)
  • include/generated/version_autogenerated.h

根据include/config/uboot.release文件,规则调用filechk_version.h宏生成版本相关字符串文件include/generated/version_autogenerated.h,如下:

#define PLAIN_VERSION "2016.09"
#define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
#define CC_VERSION_STRING "arm-linux-gnueabi-gcc (Ubuntu/Linaro 4.7.3-12ubuntu1) 4.7.3"
#define LD_VERSION_STRING "GNU ld (GNU Binutils for Ubuntu) 2.24"
  • include/generated/timestamp_autogenerated.h

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

#define U_BOOT_DATE "Oct 02 2016"
#define U_BOOT_TIME "21:54:42"
#define U_BOOT_TZ "+0800"
#define U_BOOT_DMI_DATE "10/02/2016"

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/mkmakefileO选项指定的目录下生成一个简单的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 parepare0的规则

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

展开后为:

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

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

1.8 prepare系列目标总结

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

  • scripts_basic规则生成fixdep工具,用于对整个系统生成目标文件相应依赖文件的更新;
  • 配置阶段,scripts/kconfig/conf根据传入的指定配置文件在根目录下生成.config文件
  • 编译阶段,scripts/kconfig/conf读取配置阶段生成的.config,并检查最新配置生成以下文件:
    • include/generated/autoconf.h
    • include/config/auto.conf.cmd
    • include/config/tristate.conf
    • include/config/auto.conf
  • 调用宏filechk_config_h生成include/config.h文件
  • 调用命令cmd_autoconf_dep生成autoconf.mkautoconf.mk.cmd文件
  • 调用宏filechk_uboot.release生成include/config/uboot.release文件
  • 调用宏filechk_version.h生成include/generated/version_autogenerated.h文件
  • 调用宏filechk_timestamp.h生成include/generated/timestamp_autogenerated.h文件
  • 调用宏create_symlink将芯片指定的arch/ (ARCH)math (SOC)连接到跟芯片名字无关的arch/$(ARCH)/include/asm下

2. u-boot文件系列目标依赖

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

  • 启动文件arch/arm/cpu/$(CPU)/start.o
  • 各个目录下的build-in.o
  • 链接脚本文件arch/arm/cpu/u-boot.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-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
...

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

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

...

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

...
u-boot-main := $(libs-y)

以上脚本中,先将$(libs-y)设置为各子目录的集合,最后调用patsubst函数将$(libs-y)设置为这些目录下的built-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-linux-gnueabi-ld.bfd     -r -o drivers/mmc/built-in.o drivers/mmc/mmc_legacy.o drivers/mmc/bcm2835_sdhci.o drivers/mmc/mmc.o drivers/mmc/sdhci.o drivers/mmc/mmc_write.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文件先链接为单一文件build-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 -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

...

u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
    $(call if_changed,u-boot__)
...

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

u-boot针对raspberry pi 3生成的命令是这样的(由于原命令太长,这里用\分割为多行):

  arm-linux-gnueabi-ld.bfd   -pie  --gc-sections -Bstatic \
  -Ttext 0x00008000 \
  -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-bcm283x/built-in.o  \
                 board/raspberrypi/rpi/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  \
                ...
                 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.hexu-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.binu-boot-nodtb.bin
PHONY += dtbs
dtbs: dts/dt.dtb
    @:
dts/dt.dtb: checkdtc u-boot
    $(Q)$(MAKE) $(build)=dts dtbs

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

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,因此u-boot.binu-boot-nodtb.bin是一样的。

至于生成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.binu-boot文件通过objcopy得到。

  • u-boot.cfg
    u-boot.cfg中包含了所有用到的宏定义,其生成规则如下:
# Create a file containing the configuration options the image was built with
quiet_cmd_cpp_cfg = CFG     $@
cmd_cpp_cfg = $(CPP) -Wp,-MD,$(depfile) $(cpp_flags) $(LDPPFLAGS) -ansi \
    -DDO_DEPS_ONLY -D__ASSEMBLY__ -x assembler-with-cpp -P -dM -E -o $@ $<
...
u-boot.cfg: include/config.h FORCE
    $(call if_changed,cpp_cfg)

因此,阅读源码时如果不确定某个宏的值,可以检查u-boot.cfg文件。

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

你可能感兴趣的:(u-boot)