uboot 流程分析

配置 uboot 的 vscode 工程以及流程分析

Posted by Jerry Chen on April 27, 2021

配置 uboot 的 vscode 工程

准备工作

  1. 对 uboot 进行一次编译;
  2. 在 vscode 的 remote-wsl 的状态下打开 u-boot-2020.07 目录;

屏蔽多余目录

  1. 在工程根目录新建 .vscode 目录

  2. 在 .vscode 目录中新建 “settings.json” 文件

    files.exclude 排除左侧目录中的显示;

    search.exclude 排除搜索结果中的显示,和 files.exclude 基本相同;

    ** 表示任意层级目录的通配,* 表示通配,[0-9] 表示单字符在 0-9,[^abc] 表示单字符但不是 abc 中的任意一个;

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    
    {
        "files.exclude": {
            "**/.git": true,
            "**/.svn": true,
            "**/.hg": true,
            "**/CVS": true,
            "**/.DS_Store": true,
       
            "**/.gitignore": true,
            "**/*.o": true,
            "**/*.su": true,
            "**/*.cmd": true,
            "Licenses": true,
            ".git*": true,
            ".stamp*": true,
            "*.yml": true,
            // 排除 arch/* 无关目录得到 arch/arm
            "arch/{arc,m68k,microblaze,mips,nds32,nios2,powerpc,riscv,sandbox,sh,x86,xtensa}": true,
            // 排除 arch/arm/mach* 无关目录得到 arch/arm/mach-sunxi
            "arch/arm/mach-[^s]*": true,
            "arch/arm/mach-s[^u]*": true,
            "arch/arm/mach-su[^n]*": true,
            //"arch/arm/mach*": true,
            // 排除 arch/arm/cpu/* 无关目录得到 arch/arm/cpu/arm926ejs
            "arch/arm/cpu/{arm11,arm720t,arm920t,arm946es,arm1136,arm1176,armv7,armv7m,armv8,pxa,sa1100}": true,
            // 排除 arch/arm/dts/* 无关文件得到 arch/arm/dts/sunxi*
            "arch/arm/dts/[^.iMs]*": true,
            "arch/arm/dts/i[^n]*": true,
            "arch/arm/dts/M[^a]*": true,
            "arch/arm/dts/s[^u]*": true,
            "arch/arm/dts/su[^n]*": true,
            "arch/arm/dts/sun[^i]*": true,
            // 排除 arch/arm/include/asm 无关目录
            "arch/arm/include/asm/{arch-am33xx,arch-armada*,arch-aspeed,arch-[b-r]*,arch-s[^u]*,arch-[t-z]*}": true,
            // 排除 board/* 无关目录得到 board/sunxi
            "board/[^s]*": true,
            "board/s[^u]*": true,
            "board/s[^u][^n]*": true,
            // 排除 configs/* 无关目录得到 configs/sunxi*
            "configs/[^s]*": true,
            "configs/s[^u]*": true,
            "configs/s[^u][^n]*": true
        },
        "search.exclude": {
            "**/node_modules": true,
            "**/bower_components": true,
            "**/*.code-search": true,
       
            "**/.gitignore": true,
            "**/*.o": true,
            "**/*.su": true,
            "**/*.cmd": true,
            "Licenses": true,
            ".git*": true,
            ".stamp*": true,
            "*.yml": true,
            // 排除 arch/* 无关目录得到 arch/arm
            "arch/{arc,m68k,microblaze,mips,nds32,nios2,powerpc,riscv,sandbox,sh,x86,xtensa}": true,
            // 排除 arch/arm/mach* 无关目录得到 arch/arm/mach-sunxi
            "arch/arm/mach-[^s]*": true,
            "arch/arm/mach-s[^u]*": true,
            "arch/arm/mach-su[^n]*": true,
            //"arch/arm/mach*": true,
            // 排除 arch/arm/cpu/* 无关目录得到 arch/arm/cpu/arm926ejs
            "arch/arm/cpu/{arm11,arm720t,arm920t,arm946es,arm1136,arm1176,armv7,armv7m,armv8,pxa,sa1100}": true,
            // 排除 arch/arm/dts/* 无关文件得到 arch/arm/dts/sunxi*
            "arch/arm/dts/[^.iMs]*": true,
            "arch/arm/dts/i[^n]*": true,
            "arch/arm/dts/M[^a]*": true,
            "arch/arm/dts/s[^u]*": true,
            "arch/arm/dts/su[^n]*": true,
            "arch/arm/dts/sun[^i]*": true,
            // 排除 arch/arm/include/asm 无关目录
            "arch/arm/include/asm/{arch-am33xx,arch-armada*,arch-aspeed,arch-[b-r]*,arch-s[^u]*,arch-[t-z]*}": true,
            // 排除 board/* 无关目录得到 board/sunxi
            "board/[^s]*": true,
            "board/s[^u]*": true,
            "board/s[^u][^n]*": true,
            // 排除 configs/* 无关目录得到 configs/sunxi*
            "configs/[^s]*": true,
            "configs/s[^u]*": true,
            "configs/s[^u][^n]*": true
        }
    }
    

uboot 顶层 Makefile 调用流程

初步走读

  • 确定 uboot 编译工具;
  • 确定顶层 Makefile 导入到子 Makefile 中的变量;
  • 确定部分引入的子 Makefile 文件;
  1. 一般情况下,编译 uboot 的指令是:

    调用 make 后,工具会去解析当前目录下的 Makefile;

    1
    
    make ARCH=arm CROSS_COMPILE=arm-linux-
    
  2. 顶层 Makefile 如下:

    • 设置编译器

      HOSTARCH 由 uname -m 的结果经过转换后得到;

      如果目标 ARCH 的值和主机 ARCH 相等会直接使用主机编译器;

      如果 CROSS_COMPILE 没有设置也会使用主机编译器;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
      # set default to nothing for native builds
      ifeq ($(HOSTARCH),$(ARCH))
      CROSS_COMPILE ?=
      endif
           
      # Make variables (CC, etc...)
      AS  = $(CROSS_COMPILE)as
      ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
      LD  = $(CROSS_COMPILE)ld.bfd
      else
      LD  = $(CROSS_COMPILE)ld
      endif
      CC  = $(CROSS_COMPILE)gcc
      CPP  = $(CC) -E
      AR  = $(CROSS_COMPILE)ar
      NM  = $(CROSS_COMPILE)nm
      LDR  = $(CROSS_COMPILE)ldr
      STRIP  = $(CROSS_COMPILE)strip
      OBJCOPY  = $(CROSS_COMPILE)objcopy
      OBJDUMP  = $(CROSS_COMPILE)objdump
      
    • 引入文件

      从 scripts/Kbuild.include 文件引入封装好的定义:

      1
      2
      3
      
      # We need some generic definitions (do not try to remake the file).
      scripts/Kbuild.include: ;
      include scripts/Kbuild.include
      

      引入 config.mk、arch/$(ARCH)/Makefile 文件:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      ifneq ($(wildcard $(KCONFIG_CONFIG)),)
      ifneq ($(wildcard include/config/auto.conf),)
      autoconf_is_old := $(shell find . -path ./$(KCONFIG_CONFIG) -newer \
            include/config/auto.conf)
      ifeq ($(autoconf_is_old),)
      include config.mk
      include arch/$(ARCH)/Makefile
      endif
      endif
      endif
      
    • 从 .config 文件引入环境变量

      .config 文件经 make xxx_defconfig 生成;

      1
      2
      
      KCONFIG_CONFIG ?= .config
      export KCONFIG_CONFIG
      
    • 导出给子 Makefile 使用

      其中,ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR 这些变量没有直接在顶层 Makefile 中定义,而是在工程根目录下的 config.mk 中定义的。

      此外,还有如 KBUILD_KCONFIG 的变量也没有在顶层 Makefile 中定义,可能是需要调用 make 时传入;

      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
      
      export HOST_ARCH
      export LC_COLLATE LC_NUMERIC
      export quiet Q KBUILD_VERBOSE
      export srctree objtree VPATH
      export KCONFIG_CONFIG
      export size_check
      export KBUILD_MODULES KBUILD_BUILTIN
      export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD
      export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
      export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
      export CONFIG_SHELL HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE AS LD CC
      export CPP AR NM LDR STRIP OBJCOPY OBJDUMP KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS
      export MAKE LEX YACC AWK PERL PYTHON PYTHON2 PYTHON3
      export HOSTCXX KBUILD_HOSTCXXFLAGS CHECK CHECKFLAGS DTC DTC_FLAGS
      export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS KBUILD_LDFLAGS
      export KBUILD_CFLAGS KBUILD_AFLAGS
      export CC_VERSION_TEXT
      export MODVERDIR
      export RCS_FIND_IGNORE
      export RCS_TAR_IGNORE
      export KBUILD_DEFCONFIG KBUILD_KCONFIG
      export EFI_LDS  # Filename of EFI link script in arch/$(ARCH)/lib
      export EFI_CRT0  # Filename of EFI CRT0 in arch/$(ARCH)/lib
      export EFI_RELOC # Filename of EFU relocation code in arch/$(ARCH)/lib
      export CFLAGS_EFI # Compiler flags to add when building EFI app
      export CFLAGS_NON_EFI # Compiler flags to remove when building EFI app
      export EFI_TARGET # binutils target if EFI is natively supported
      export PLATFORM_LIBS
      export PLATFORM_LIBGCC
      tools-only: export TOOLS_ONLY=y
      tools-all: export HOST_TOOLS_ALL=y
      cross_tools: export CROSS_BUILD_TOOLS=y
      
    • 包含自动创建的头文件(里面是一些宏定义)

      version_autogenerated.h 保存版本号;

      timestamp_autogenerated.h 保存时间戳;

      1
      2
      3
      4
      
      version_h := include/generated/version_autogenerated.h
      timestamp_h := include/generated/timestamp_autogenerated.h
      defaultenv_h := include/generated/defaultenv_autogenerated.h
      dt_h := include/generated/dt.h
      

      我编译后的 include/generated/version_autogenerated.h 如下:

      1
      2
      3
      4
      
      #define PLAIN_VERSION "2020.07"
      #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
      #define CC_VERSION_STRING "arm-buildroot-linux-gnueabi-gcc.br_real (Buildroot -g1fae138) 8.4.0"
      #define LD_VERSION_STRING "GNU ld (GNU Binutils) 2.32"
      

      我编译后的 include/generated/timestamp_autogenerated.h 如下:

      1
      2
      3
      4
      5
      
      #define U_BOOT_DATE "Apr 22 2021"
      #define U_BOOT_TIME "10:54:49"
      #define U_BOOT_TZ "+0800"
      #define U_BOOT_DMI_DATE "04/22/2021"
      #define U_BOOT_BUILD_DATE 0x20210422
      
  3. 工程根目录下的 config.mk:

    • 该文件中设置了 ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR 这些变量;
    • 形如 CONFIG_SYS_ARCH 的变量来自于工程根目录下的 .config 文件,在顶层 Makefile 中有调用该文件并导出到环境变量的操作;
    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
    
    ARCH := $(CONFIG_SYS_ARCH:"%"=%)
    CPU := $(CONFIG_SYS_CPU:"%"=%)
    ifdef CONFIG_SPL_BUILD
    ifdef CONFIG_ARCH_TEGRA
    CPU := arm720t
    endif
    endif
    BOARD := $(CONFIG_SYS_BOARD:"%"=%)
    ifneq ($(CONFIG_SYS_VENDOR),)
    VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
    endif
    ifneq ($(CONFIG_SYS_SOC),)
    SOC := $(CONFIG_SYS_SOC:"%"=%)
    endif
       
    CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
       
    sinclude $(srctree)/arch/$(ARCH)/config.mk	# include architecture dependend rules
    sinclude $(srctree)/$(CPUDIR)/config.mk		# include  CPU	specific rules
       
    ifdef	SOC
    sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk	# include  SoC	specific rules
    endif
    ifneq ($(BOARD),)
    ifdef	VENDOR
    BOARDDIR = $(VENDOR)/$(BOARD)
    else
    BOARDDIR = $(BOARD)
    endif
    endif
    ifdef	BOARD
    sinclude $(srctree)/board/$(BOARDDIR)/config.mk	# include board specific rules
    endif
    

    引入了如下文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    sinclude $(srctree)/arch/$(ARCH)/config.mk
    sinclude $(srctree)/$(CPUDIR)/config.mk
       
    ifdef SOC
    sinclude $(srctree)/$(CPUDIR)/$(SOC)/config.mk
    endif
       
    ifdef BOARD
    sinclude $(srctree)/board/$(BOARDDIR)/config.mk
    endif
    

    引入文件的翻译如下:

    1
    2
    3
    4
    
    sinclude ./arch/arm/config.mk
    sinclude ./arch/arm/cpu/config.mk
    sinclude ./arch/arm/cpu/sunxi/config.mk
    sinclude ./board/sunxi/config.mk
    

make xxx_defconfig 过程

按照步骤剥析如下:

  1. 调用 make xxx_defconfig 会调用顶层 Makefile

  2. 顶层 Makefile

    Makefile 命令前加 @ 表示该命令执行但不打印;

    $(Q) 在顶层赋值 @ 或空;

    Makefile 中 $(MAKE) 是 make 工具中预置环境变量,存放 make 工具的路径;

    $(srctree) 在顶层赋值 . 或 $(KBUILD_SRC);

    $(build) 变量在 scripts/Kbuild.include 中声明,值为 -f $(srctree)/scripts/Makefile.build obj

    特别注意:$(build)=scripts/kconfig 执行时定义了变量 obj=scripts/kconfig

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    scripts_basic:
    	$(Q)$(MAKE) $(build)=scripts/basic
    	$(Q)rm -f .tmp_quiet_recordmcount
       
    outputmakefile:
    ifneq ($(KBUILD_SRC),)
    	$(Q)ln -fsn $(srctree) source
    	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
    	$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
    endif
       
    %config: scripts_basic outputmakefile FORCE
    	$(Q)$(MAKE) $(build)=scripts/kconfig $@
    

    上面可以翻译为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    scripts_basic:
    	@make -f ./scripts/Makefile.build obj=scripts/basic
    	@rm -f . tmp_quiet_recordmcount
       
    # KBUILD_SRC 通过 make ARCH=arm CROSS_COMPILE=arm-linux- KBUILD_SRC=xxx 传入
    # 如果指定了 KBUILD_SRC,将调用 KBUILD_SRC 目录中的脚本在当前目录中生成一个 Makefile
    # 事实上一般不会指定 KBUILD_SRC
    outputmakefile:
    ifneq ($(KBUILD_SRC),)
    	# 为当前目录建立一个 source 的符号链接(-f 强制,-s 软链接,-n 链接视为目录)
    	@ln -fsn $(KBUILD_SRC) source
    	@/bin/bash $(KBUILD_SRC)/scripts/mkmakefile $(KBUILD_SRC) . 2020 07
    endif
       
    # FORCE 没有构建命令
    %config: scripts_basic outputmakefile FORCE
    	@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
    
  3. scripts/Makefile.build(也是 Makefile 文件)

    • scripts/Makefile.build 中的 include 机制:

      @make -f ./scripts/Makefile.build obj=xxxxx 执行时会给 obj 赋值;

      scripts/Makefile.build 对 obj 进行处理和判断后,最终包含了 obj 目录下的 Makefile 文件;

      $(patsubst $(prefix)/%,%,$(obj)) 的意思是把 obj 中所有满足 $(prefix)/xxx 的字符串全部变为 xxx;

      $(if condition,a,b) 的意思是 condition 为 true 时结果为 a,condition 为 false 时结果为 b;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
      # Modified for U-Boot
      prefix := tpl
      src := $(patsubst $(prefix)/%,%,$(obj))
      ifeq ($(obj),$(src))
      prefix := spl
      src := $(patsubst $(prefix)/%,%,$(obj))
      ifeq ($(obj),$(src))
      prefix := .
      endif
      endif
           
      # The filename Kbuild has precedence over Makefile
      kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
      kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
      include $(kbuild-file)
      
    • 调用 scripts_basic 的过程(@make -f ./scripts/Makefile.build obj=scripts/basic):

      由传参可知 obj 为 scripts/basic,包含代码翻译为:

      1
      2
      3
      4
      5
      6
      7
      8
      
      # scripts/basic 和 /scripts/basic 相等时 kbuild-dir 取 scripts/basic,反之取 ./scripts/basic
      kbuild-dir := $(if $(filter /%,scripts/basic),scripts/basic,./scripts/basic)
           
      # ./scripts/basic/Kbuild 存在时 kbuild-file 取 ./scripts/basic/Kbuild,反之取 ./scripts/basic/Makefile
      kbuild-file := $(if $(wildcard ./scripts/basic/Kbuild),./scripts/basic/Kbuild,./scripts/basic/Makefile)
           
      # 包含 ./scripts/basic/Makefile,等同把目标内容拷贝到这里
      include ./scripts/basic/Makefile
      

      在 ./scripts/basic/Makefile 中会给 always 赋值为 fixdep:

      1
      2
      3
      4
      5
      
      hostprogs-y := fixdep
      always  := $(hostprogs-y)
           
      # fixdep is needed to compile other host programs
      $(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep
      

      再回到 scripts/Makefile.build 中,因为 make 时没有传入 target,故会去执行 ./scripts/Makefile.build 中的第一个 target——__build;

      值得注意的是 ./scripts/Makefile.build 中有两个 __build 的 target,make 处理时会合并 target 的依赖项,并以后面出现的 target 操作为准;

      1
      2
      3
      4
      5
      6
      7
      
      PHONY := __build
      __build:
           
      __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
      	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
      	 $(subdir-ym) $(always)
      	@:
      

      加个打印可知 __build 的 5 个依赖项只有 always 非空,也就是最终生成了 ./scripts/basic/fixdep 文件;

    • 调用 %config 的过程(@make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig):

      由传参可知 obj 为 scripts/kconfig,包含代码翻译为:

      1
      2
      3
      4
      5
      6
      7
      8
      
      # scripts/kconfig 和 /scripts/kconfig 相等时 kbuild-dir 取 scripts/kconfig,反之取 ./scripts/kconfig
      kbuild-dir := $(if $(filter /%,scripts/kconfig),scripts/kconfig,./scripts/kconfig)
           
      # ./scripts/kconfig/Kbuild 存在时 kbuild-file 取 ./scripts/kconfig/Kbuild,反之取 ./scripts/kconfig/Makefile
      kbuild-file := $(if $(wildcard ./scripts/kconfig/Kbuild),./scripts/kconfig/Kbuild,./scripts/kconfig/Makefile)
           
      # 包含 ./scripts/kconfig/Makefile,等同把目标内容拷贝到这里
      include ./scripts/kconfig/Makefile
      

      在 ./scripts/kconfig/Makefile 可以找到目标 target——xxx_defconfig;

  4. scripts/kconfig/Makefile:

    首先是生成 conf 执行程序:

    1
    2
    3
    4
    
    # conf:   Used for defconfig, oldconfig and related targets
    # object files used by all kconfig flavours
    conf-objs := conf.o  zconf.tab.o
    hostprogs-y := conf
    

    接着调用 conf 处理 xxx_defconfig:

    KBUILD_KCONFIG 顶层无定义,故这里值为 Kconfig;

    obj 顶层定义值为 .;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    #  Set SRCARCH to .. fake this Makefile.
    SRCARCH := ..
       
    ifdef KBUILD_KCONFIG
    Kconfig := $(KBUILD_KCONFIG)
    else
    Kconfig := Kconfig
    endif
       
    ifeq ($(quiet),silent_)
    silent := -s
    endif
       
    %_defconfig: $(obj)/conf
    	$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
       
    # Added for U-Boot (backward compatibility)
    %_config: %_defconfig
    	@:
    

    上面的重要处理部分可翻译为:

    也就是先生成 make xxx_defconfig 的依赖项 ./conf(scripts/kconfig/conf);

    然后当前目录下的 conf 解析 xxx_defconfig 和工程根目录下 Kconfig 生成 .config;

    1
    2
    
    %_defconfig: ./conf
    	@./conf -s --defconfig=arch/../configs/xxx_defconfig Kconfig
    

最终,可以得到 make xxx_defconfig 的执行流程图如下:

make menuconfig 过程

  1. 调用 make menuconfig 会调用顶层 Makefile

  2. 顶层 Makefile

    Makefile 命令前加 @ 表示该命令执行但不打印;

    $(Q) 在顶层赋值 @ 或空;

    Makefile 中 $(MAKE) 是 make 工具中预置环境变量,存放 make 工具的路径;

    $(srctree) 在顶层赋值 . 或 $(KBUILD_SRC);

    $(build) 变量在 scripts/Kbuild.include 中声明,值为 -f $(srctree)/scripts/Makefile.build obj

    特别注意:$(build)=scripts/kconfig 执行时定义了变量 obj=scripts/kconfig

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    scripts_basic:
    	$(Q)$(MAKE) $(build)=scripts/basic
    	$(Q)rm -f .tmp_quiet_recordmcount
       
    outputmakefile:
    ifneq ($(KBUILD_SRC),)
    	$(Q)ln -fsn $(srctree) source
    	$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
    	$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
    endif
       	
    %config: scripts_basic outputmakefile FORCE
    	$(Q)$(MAKE) $(build)=scripts/kconfig $@
    

    上面可以翻译为:

    KBUILD_SRC 为空,故 outputmakefile 没有实际执行的内容;

    FORCE 也没有构建命令;

    1
    2
    3
    4
    5
    6
    
    scripts_basic:
    	@make -f ./scripts/Makefile.build obj=scripts/basic
    	@rm -f . tmp_quiet_recordmcount
       
    %config: scripts_basic
    	@make -f ./scripts/Makefile.build obj=scripts/kconfig menuconfig
    
  3. scripts/Makefile.build(也是 Makefile 文件)

    • scripts/Makefile.build 中的 include 机制:

      对该机制的分析在 make xxx_defconfig 过程中有写过,这里不赘述;

      @make -f ./scripts/Makefile.build obj=xxxxx 执行时会给 obj 赋值;

      scripts/Makefile.build 对 obj 进行处理和判断后,最终包含了 obj 目录下的 Makefile 文件;

    • 调用 scripts_basic 最终生成了 ./scripts/basic/fixdep 文件,这里也不赘述;

    • 调用 %config 最终包含了 ./scripts/kconfig/Makefile(实际调用这个文件中的 menuconfig);

  4. scripts/kconfig/Makefile:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    # mconf: Used for the menuconfig target based on lxdialog
    hostprogs-y += mconf
    lxdialog := checklist.o inputbox.o menubox.o textbox.o util.o yesno.o
    mconf-objs := mconf.o zconf.tab.o $(addprefix lxdialog/, $(lxdialog))
       
    HOSTLDLIBS_mconf = $(shell . $(obj)/.mconf-cfg && echo $$libs)
    $(foreach f, mconf.o $(lxdialog), \
    	 $(eval HOSTCFLAGS_$f = $$(shell . $(obj)/.mconf-cfg && echo $$$$cflags)))
       
    $(obj)/mconf.o: $(obj)/.mconf-cfg
    $(addprefix $(obj)/lxdialog/, $(lxdialog)): $(obj)/.mconf-cfg
       
    # check if necessary packages are available, and configure build flags
    define filechk_conf_cfg
    	$(CONFIG_SHELL) $<
    endef
       
    $(obj)/.%conf-cfg: $(src)/%conf-cfg.sh FORCE
    	$(call filechk,conf_cfg)
       
    menuconfig: $(obj)/mconf
    	$< $(silent) $(Kconfig)
    
    • 首先调用了 scripts/kconfig/mconf-cfg.sh
    • 接着在 scripts/kconfig 目录生成了 mconf 工具;
    • 最后执行了 scripts/kconfig/mconf -s Kconfig(Kconfig 来自工程根目录);

分析至此,在存在 mconf 的情况下,手动执行 scripts/kconfig/mconf -s Kconfig 的结果如下:

make 过程

通过编译输出信息进行分析
  1. 输入如下命令进行编译

    uboot 的编译器(GCC)版本不能低于 6.0;

    此处编译使用 buildroot 内部下载的交叉编译器;

    1
    
    make ARCH=arm CROSS_COMPILE=/home/jerry/vscode/mgp_r/buildroot-mangopi-r/output/host/bin/arm-buildroot-linux-gnueabi- V=1 > build.log
    
  2. 编译完成后,在 build.log 中搜索 “-o u-boot”

    可知链接器 ld.bfd 根据 u-boot.lds 上的规则将 *.o 链接生成了 u-boot,其中符号地址等信息存储在了 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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
    arm-buildroot-linux-gnueabi-ld.bfd -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext 0x81700000 \
    -o u-boot -T u-boot.lds \
    arch/arm/cpu/arm926ejs/start.o \
    --start-group \
    arch/arm/cpu/built-in.o \
    arch/arm/cpu/arm926ejs/built-in.o \
    arch/arm/lib/built-in.o \
    arch/arm/mach-sunxi/built-in.o \
    board/sunxi/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 \
    drivers/net/built-in.o \
    drivers/net/phy/built-in.o \
    drivers/power/built-in.o \
    drivers/power/battery/built-in.o \
    drivers/power/domain/built-in.o \
    drivers/power/fuel_gauge/built-in.o \
    drivers/power/mfd/built-in.o \
    drivers/power/pmic/built-in.o \
    drivers/power/regulator/built-in.o \
    drivers/serial/built-in.o \
    drivers/spi/built-in.o \
    drivers/usb/cdns3/built-in.o \
    drivers/usb/common/built-in.o \
    drivers/usb/dwc3/built-in.o \
    drivers/usb/emul/built-in.o \
    drivers/usb/eth/built-in.o \
    drivers/usb/gadget/built-in.o \
    drivers/usb/gadget/udc/built-in.o \
    drivers/usb/host/built-in.o \
    drivers/usb/musb-new/built-in.o \
    drivers/usb/musb/built-in.o \
    drivers/usb/phy/built-in.o \
    drivers/usb/ulpi/built-in.o \
    env/built-in.o \
    fs/built-in.o \
    lib/built-in.o \
    net/built-in.o \
    --end-group \
    arch/arm/lib/eabi_compat.o \
    arch/arm/lib/lib.a \
    -Map u-boot.map
    

    接着使用 objcopy 拷贝 u-boot 中的特定段内容生成了 u-boot-nodtb.bin;

    -j section_name 表示只将由 section_name 指定的 section 拷贝到输出文件;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    arm-buildroot-linux-gnueabi-objcopy --gap-fill=0xff \
    -j .text \
    -j .secure_text \
    -j .secure_data \
    -j .rodata \
    -j .hash \
    -j .data \
    -j .got \
    -j .got.plt \
    -j .u_boot_list \
    -j .rel.dyn \
    -j .binman_sym_table \
    -j .text_rest \
    -j .dtb.init.rodata \
    -O binary u-boot u-boot-nodtb.bin
    
  3. 再看下 dtb 文件的生成

    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
    
    mkdir -p arch/arm/dts/ ; 
       
    # .suniv-f1c100s-generic.dtb.pre.tmp 内容为 suniv-f1c100s-generic.dts + '#include "sunxi-u-boot.dtsi"'(尾行)
    (cat arch/arm/dts/suniv-f1c100s-generic.dts; echo '#include "sunxi-u-boot.dtsi"') > arch/arm/dts/.suniv-f1c100s-generic.dtb.pre.tmp; 
       
    # 生成预处理文件 .suniv-f1c100s-generic.dtb.d.pre.tmp
    arm-buildroot-linux-gnueabi-gcc -E -Wp,-MD,arch/arm/dts/.suniv-f1c100s-generic.dtb.d.pre.tmp \
    -nostdinc \
    -I./arch/arm/dts \
    -I./arch/arm/dts/include \
    -Iinclude \
    -I./include \
    -I./arch/arm/include \
    -include ./include/linux/kconfig.h \
    -D__ASSEMBLY__ \
    -undef -D__DTS__ \
    -x assembler-with-cpp \
    -o arch/arm/dts/.suniv-f1c100s-generic.dtb.dts.tmp arch/arm/dts/.suniv-f1c100s-generic.dtb.pre.tmp ; 
       
    # 生成预处理文件 .suniv-f1c100s-generic.dtb.d.dtc.tmp
    # 生成设备树文件 arch/arm/dts/suniv-f1c100s-generic.dtb
    ./scripts/dtc/dtc -O dtb \
    -o arch/arm/dts/suniv-f1c100s-generic.dtb \
    -b 0 -i arch/arm/dts/ \
    -Wno-unit_address_vs_reg -Wno-unit_address_format -Wno-avoid_unnecessary_addr_size -Wno-alias_paths -Wno-graph_child_address -Wno-graph_port -Wno-unique_unit_address -Wno-simple_bus_reg -Wno-pci_device_reg -Wno-pci_bridge -Wno-pci_device_bus_num   -Wno-unit_address_vs_reg -Wno-unit_address_format -Wno-avoid_unnecessary_addr_size -Wno-alias_paths -Wno-graph_child_address -Wno-graph_port -Wno-unique_unit_address -Wno-simple_bus_reg -Wno-pci_device_reg -Wno-pci_bridge -Wno-pci_device_bus_num \
    -d arch/arm/dts/.suniv-f1c100s-generic.dtb.d.dtc.tmp arch/arm/dts/.suniv-f1c100s-generic.dtb.dts.tmp \
    || \
    (echo "Check arch/arm/dts/.suniv-f1c100s-generic.dtb.pre.tmp for errors" && false) ; 
       
    # 合并生成的两个预处理文件
    cat arch/arm/dts/.suniv-f1c100s-generic.dtb.d.pre.tmp arch/arm/dts/.suniv-f1c100s-generic.dtb.d.dtc.tmp > arch/arm/dts/.suniv-f1c100s-generic.dtb.d ; 
       
    # 替换预处理文件中的关键字
    sed -i "s:arch/arm/dts/.suniv-f1c100s-generic.dtb.pre.tmp:arch/arm/dts/suniv-f1c100s-generic.dts:" arch/arm/dts/.suniv-f1c100s-generic.dtb.d
    
  4. 生成 u-boot.bin

    u-boot.bin 是我们最终需要的二进制文件;

    1
    2
    3
    4
    5
    6
    7
    8
    
    # 读取 suniv-f1c100s-generic.dtb 存放到 dts/dt.dtb
    cat arch/arm/dts/suniv-f1c100s-generic.dtb > dts/dt.dtb
       
    # 将 u-boot-nodtb.bin 和 dts/dt.dtb 拼接成 u-boot-dtb.bin
    cat u-boot-nodtb.bin dts/dt.dtb > u-boot-dtb.bin
       
    # 拷贝 u-boot-dtb.bin 为 u-boot.bin
    cp u-boot-dtb.bin u-boot.bin
    
通过 Makefile 进行分析
  1. make 时没有指定 target,这样会默认执行 Makefile 中第一个 target;

    最终执行了 all 这个 target;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    PHONY := _all
    _all:
          
    # but instead _all depend on modules
    PHONY += all
    ifeq ($(KBUILD_EXTMOD),)
    _all: all
    else
    _all: modules
    endif
    
  2. target 为 all 的 Makefile 代码段

    可以看到编译依赖 ALL-y(生成项);

    中间部分都是判断 .config 中的配置打印警告信息;

    最后调用了一个检查配置的脚本;

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    
    all:		$(ALL-y)
    ifeq ($(CONFIG_DEPRECATED),y)
    	$(warning "You have deprecated configuration options enabled in your .config! Please check your configuration.")
    ifeq ($(CONFIG_SPI),y)
    ifneq ($(CONFIG_DM_SPI)$(CONFIG_OF_CONTROL),yy)
    	$(warning "The relevant config item with associated code will remove in v2019.07 release.")
    endif
    endif
    endif
    ifneq ($(CONFIG_DM),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    ifeq ($(CONFIG_MMC),y)
    ifneq ($(CONFIG_DM_MMC)$(CONFIG_OF_CONTROL)$(CONFIG_BLK),yyy)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifeq ($(CONFIG_USB),y)
    ifneq ($(CONFIG_DM_USB)$(CONFIG_OF_CONTROL)$(CONFIG_BLK),yyy)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifeq ($(CONFIG_MVSATA_IDE),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    ifeq ($(CONFIG_LIBATA),y)
    ifneq ($(CONFIG_AHCI),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifeq ($(CONFIG_PCI),y)
    ifneq ($(CONFIG_DM_PCI),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifneq ($(CONFIG_LCD)$(CONFIG_VIDEO),)
    ifneq ($(CONFIG_DM_VIDEO),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifeq ($(CONFIG_OF_EMBED),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    ifeq ($(CONFIG_SPI_FLASH),y)
    ifneq ($(CONFIG_DM_SPI_FLASH)$(CONFIG_OF_CONTROL),yy)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifneq ($(CONFIG_WATCHDOG)$(CONFIG_HW_WATCHDOG),)
    ifneq ($(CONFIG_WDT),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    ifneq ($(CONFIG_NET),)
    ifneq ($(CONFIG_DM_ETH),y)
    	@echo >&2 "===================== WARNING ======================"
    endif
    endif
    	@# Check that this build does not use CONFIG options that we do not
    	@# know about unless they are in Kconfig. All the existing CONFIG
    	@# options are whitelisted, so new ones should not be added.
    	$(call cmd,cfgcheck,u-boot.cfg)
    
  3. target 为 ALL-y 的 Makefile 代码段

    • 生成主要文件:u-boot.srec、u-boot.bin、u-boot.sym、System.map、binary_size_check;
    • 生成厂商相关文件:u-boot-sunxi-with-spl.bin、u-boot-tegra.bin、u-boot-nodtb-tegra.bin、init_sp_bss_offset_check、u-boot-with-dtb.bin、u-boot-rockchip.bin;
    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
    
    # Always append ALL so that arch config.mk's can add custom ones
    ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check
          
    # Build a combined spl + u-boot image for sunxi
    ifeq ($(CONFIG_ARCH_SUNXI)$(CONFIG_SPL),yy)
    ALL-y += u-boot-sunxi-with-spl.bin
    endif
          
    # enable combined SPL/u-boot/dtb rules for tegra
    ifeq ($(CONFIG_ARCH_TEGRA)$(CONFIG_SPL),yy)
    ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
    endif
          
    # Add optional build target if defined in board/cpu/soc headers
    ifneq ($(CONFIG_BUILD_TARGET),)
    ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
    endif
          
    ifeq ($(CONFIG_INIT_SP_RELATIVE)$(CONFIG_OF_SEPARATE),yy)
    ALL-y += init_sp_bss_offset_check
    endif
          
    ifeq ($(CONFIG_MPC85xx)$(CONFIG_OF_SEPARATE),yy)
    ALL-y += u-boot-with-dtb.bin
    endif
          
    ifeq ($(CONFIG_ARCH_ROCKCHIP)$(CONFIG_SPL),yy)
    ALL-y += u-boot-rockchip.bin
    endif
    
  4. target 为 u-boot.bin 的 Makefile 代码段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    ifeq ($(CONFIG_MULTI_DTB_FIT),y)
    # 存在 dtb 容器(lzo、gz 压缩包)的情况
    u-boot.bin: u-boot-fit-dtb.bin FORCE
    	$(call if_changed,copy)
          
    else ifeq ($(CONFIG_OF_SEPARATE),y)
    # 存在单个 dtb(dts/dt.dtb)的情况
    u-boot.bin: u-boot-dtb.bin FORCE
    	$(call if_changed,copy)
          
    else
    # 没有适用 dtb 的情况
    u-boot.bin: u-boot-nodtb.bin FORCE
    	$(call if_changed,copy)
          
    endif
    

    给上面的三种情况均加上打印,接着再编译一次:

    编译命令:make ARCH=arm CROSS_COMPILE=/home/jerry/vscode/mgp_r/buildroot-mangopi-r/output/host/bin/arm-buildroot-linux-gnueabi-

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    u-boot.bin: u-boot-fit-dtb.bin FORCE
    +	@echo "u-boot.bin: u-boot-fit-dtb.bin FORCE"
    	$(call if_changed,copy)
          
    u-boot.bin: u-boot-dtb.bin FORCE
    +	@echo "u-boot.bin: u-boot-dtb.bin FORCE"
    	$(call if_changed,copy)
          
    u-boot.bin: u-boot-nodtb.bin FORCE
    +	@echo "u-boot.bin: u-boot-nodtb.bin FORCE"
    	$(call if_changed,copy)
    

    查询 .config 文件得知其中存在 “CONFIG_OF_SEPARATE=y”,从编译打印结果上也证实了编译流程属于情况二;

  5. 待补充;