u-boot-2016.09 make編譯過程分析(二)
綜述
u-boot
自v2014.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-2016.09 make編譯過程分析(一)》中描述了
第二部分、執行命令更新目標
將上面的依賴關係併到一起,就得到了一個完整的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)
- 1
- 2
- 1
- 2
這裏展開後爲:
rpi_3_32b_defconfig: scripts/kconfig/conf
scripts/kconfig/conf --defconfig=arch/../configs/rpi_3_32b_defconfig Kconfig
- 1
- 2
- 1
- 2
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
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
調用make -f ./Makefile silentoldconfig
的最終結果是執行scripts/kconfig/Makefile
中的規則:
silentoldconfig: $(obj)/conf
$(Q)mkdir -p include/config include/generated
$< $(silent) --$@ $(Kconfig)
- 1
- 2
- 3
- 1
- 2
- 3
這個規則展開爲:
silentoldconfig: scripts/kconfig/conf
mkdir -p include/config include/generated
scripts/kconfig/conf --silentoldconfig Kconfig
- 1
- 2
- 3
- 1
- 2
- 3
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)/.config
和include/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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
除了執行$(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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
此處沒有設置CONFIG_SPL=y
或CONFIG_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.h
(rpi_3_32b_defconfig
配置只需要include/autoconf.mk
):
include/autoconf.mk include/autoconf.mk.dep \
spl/include/autoconf.mk tpl/include/autoconf.mk: include/config.h
- 1
- 2
- 1
- 2
實際上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 \<config_fallbacks.h\>;)
endef
include/config.h: scripts/Makefile.autoconf create_symlink FORCE
$(call filechk,config_h)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
最終生成的include/config.h
也比較簡單,不妨看看:
/* Automatically generated - do not edit */
#define CONFIG_BOARDDIR board/raspberrypi/rpi
#include <config_defaults.h>
#include <config_uncmd_spl.h>
#include <configs/rpi.h>
#include <asm/config.h>
#include <config_fallbacks.h>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
生成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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
註釋已經很好解釋了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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
從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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這個規則比較簡單,由於autoconf.mk
由common.h
和auto.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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
命令$(call filechk,uboot.release)
展開後就是調用宏filechk_uboot.release
,最終將字符串2016.09
寫入include/config/uboot.release
中。
1.4
timestamp.h
和version.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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 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"
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 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
- 2
- 3
- 4
- 1
- 2
- 3
- 4
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 如果編譯沒有設置
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
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
scripts_basic
的執行結果就是編譯生成scripts/basic/fixdep
工具,該工具是u-boot
編譯系統中最常用的工具,用於在編譯過程中修正每一個生成文件的依賴關係。
1.7
parepare0
的規則
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
- 1
- 2
- 1
- 2
展開後爲:
prepare0: archprepare FORCE
make -f ./scripts/Makefile.build obj=.
- 1
- 2
- 1
- 2
編譯時,命令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.mk
和autoconf.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
- 1
- 1
在頂層makefile
中被指定給變量u-boot-init
:
u-boot-init := $(head-y)
- 1
- 1
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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
以上腳本中,先將$(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
- 1
- 2
- 1
- 2
從生成命令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'.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
簡單說來,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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
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)
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
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__)
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
生成了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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
調用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)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
調用$(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,$<) > $@
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
調用$(NM)
命令打印u-boot
文件的符號表,並用grep -v
處理後得到System.map
文件,裏面包含了最終使用到的各個符號的位置信息。
u-boot.bin
和u-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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
由於這裏沒有使用device tree
設置,即編譯沒有定義CONFIG_OF_SEPARATE
,因此u-boot.bin
和u-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)
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
顯然,u-boot-nodtb.bin
是u-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)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
因此,閱讀源碼時如果不確定某個宏的值,可以檢查u-boot.cfg
文件。
自此,生成了所有的目標文件,完成了整個編譯過程的分析。