image.png

我们经常遇到的问题估计是调试bug、该配置适配移植等,也就是说已经有可以编译运行的代码,我们找到其中需要修改的点进行修改就可以了。那什么是别人不太会的,但是又比较有用的知识,遇到一个系统性的问题需要用到的,那可能就是对编译过程的理解。本文就来介绍下这些奇怪的知识。

1. 顶层Makefile

顶层也就是uboot代码根目录下的Makefile。顶层Makefile会再去调用子目录里面的Makefile递归执行,其是一个树状结构,顶层Makefile就是树根。

1.1 版本号:

image.png

那么这个怎么最后运行的时候被打印出来了。是时候展现下真正的技术了,在顶层Makefile中。

VERSION = 2023
PATCHLEVEL = 07
SUBLEVEL = 02

UBOOTVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

ubootrelease:
    @echo "$(UBOOTVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"

# Use sed to remove leading zeros from PATCHLEVEL to avoid using octal numbers
define filechk_version.h
    (echo \#define PLAIN_VERSION \"$(UBOOTRELEASE)\"; \
    echo \#define U_BOOT_VERSION \"U-Boot \" PLAIN_VERSION; \
    echo \#define U_BOOT_VERSION_NUM $(VERSION); \
    echo \#define U_BOOT_VERSION_NUM_PATCH $$(echo $(PATCHLEVEL) | \
        sed -e "s/^0*//"); \
    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

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

version_h := include/generated/version_autogenerated.h

这样就生成了include/generated/version_autogenerated.h,在其中:

#define PLAIN_VERSION "2023.07.02-dirty"
#define U_BOOT_VERSION "U-Boot " PLAIN_VERSION

display_options--》display_options_get_banner--》display_options_get_banner_priv

char *display_options_get_banner_priv(bool newlines, const char *build_tag,
                      char *buf, int size)
{
    int len;

    len = snprintf(buf, size, "%s%s", newlines ? "\n\n" : "",
               version_string);

display_options在启动的时候,启动函数数组序列中执行,common/board_f.c中

static const init_fnc_t init_sequence_f[] = {

    env_init,        /* initialize environment */
    init_baud_rate,        /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,        /* stage 1 init of console */
    display_options,    /* say that we are here */
    display_text_info,    /* show debugging info if required */

启动的流程以后再介绍,这里就是各种宏定义,代码生成,然后在c代码中使用。看似复杂,其实一点也不简单,还是挺有趣的。

1.2 编译命令

help:
    @echo  'Cleaning targets:'
    @echo  '  clean          - Remove most generated files but keep the config'
    @echo  '  mrproper      - Remove all generated files + config + various backup files'
    @echo  '  distclean      - mrproper + remove editor backup and patch files'
    @echo  ''
    @echo  'Configuration targets:'
    @$(MAKE) -f $(srctree)/scripts/kconfig/Makefile help
    @echo  ''
    @echo  'Test targets:'
    @echo  ''
    @echo  '  check           - Run all automated tests that use sandbox'
    @echo  '  pcheck          - Run quick automated tests in parallel'
    @echo  '  qcheck          - Run quick automated tests that use sandbox'
    @echo  '  tcheck          - Run quick automated tests on tools'
    @echo  '  pylint          - Run pylint on all Python files'
    @echo  ''
    @echo  'Other generic targets:'
    @echo  '  all          - Build all necessary images depending on configuration'
    @echo  '  tests          - Build U-Boot for sandbox and run tests'
    @echo  '* u-boot      - Build the bare u-boot'
    @echo  '  dir/            - Build all files in dir and below'
    @echo  '  dir/file.[oisS] - Build specified target only'
    @echo  '  dir/file.lst    - Build specified mixed source/assembly target only'
    @echo  '                    (requires a recent binutils and recent build (System.map))'
    @echo  '  tags/ctags      - Generate ctags file for editors'
    @echo  '  etags          - Generate etags file for editors'
    @echo  '  cscope      - Generate cscope index'
    @echo  '  ubootrelease      - Output the release version string (use with make -s)'
    @echo  '  ubootversion      - Output the version stored in Makefile (use with make -s)'
    @echo  "  cfg          - Don't build, just create the .cfg files"
    @echo  "  envtools      - Build only the target-side environment tools"
    @echo  ''
    @echo  'PyPi / pip targets:'
    @echo  '  pip             - Check building of PyPi packages'
    @echo  '  pip_test        - Build PyPi pakages and upload to test server'
    @echo  '  pip_release     - Build PyPi pakages and upload to release server'
    @echo  ''
    @echo  'Static analysers'
    @echo  '  checkstack      - Generate a list of stack hogs'
    @echo  '  coccicheck      - Execute static code analysis with Coccinelle'
    @echo  ''
    @echo  'Documentation targets:'
    @$(MAKE) -f $(srctree)/doc/Makefile dochelp
    @echo  ''
    @echo  '  make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build'
    @echo  '  make V=2   [targets] 2 => give reason for rebuild of target'
    @echo  '  make O=dir [targets] Locate all output files in "dir", including .config'
    @echo  '  make C=1   [targets] Check all c source with $$CHECK (sparse by default)'
    @echo  '  make C=2   [targets] Force check of all c source with $$CHECK'
    @echo  '  make RECORDMCOUNT_WARN=1 [targets] Warn about ignored mcount sections'
    @echo  '  make W=n   [targets] Enable extra gcc checks, n=1,2,3 where'
    @echo  '        1: warnings which may be relevant and do not occur too often'
    @echo  '        2: warnings which occur quite often but may still be relevant'
    @echo  '        3: more obscure warnings, can most likely be ignored'
    @echo  '        Multiple levels can be combined with W=12 or W=123'
    @echo  ''
    @echo  'Execute "make" or "make all" to build all targets marked with [*] '
    @echo  'For further info see the ./README file'
  • make O:指定编译输出目录
  • make V=0|1:输出编译日志级别
  • make C=2:检查所有的源码文件
  • make M=dir:单独编译某个目录

1.3 编译相关

描述递归执行子目录的makefile进行编译,可以自己用AI解释下这个代码的含义

# Look for make include files relative to root of kernel src
#
# This does not become effective immediately because MAKEFLAGS is re-parsed
# once after the Makefile is read.  It is OK since we are going to invoke
# 'sub-make' below.
MAKEFLAGS += --include-dir=$(CURDIR)

获取Host主机的架构和系统

HOSTARCH := $(shell uname -m | \
    sed -e s/i.86/x86/ \
        -e s/sun4u/sparc64/ \
        -e s/arm.*/arm/ \
        -e s/sa110/arm/ \
        -e s/ppc64/powerpc/ \
        -e s/ppc/powerpc/ \
        -e s/macppc/powerpc/\
        -e s/sh.*/sh/)

HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
        sed -e 's/\(cygwin\).*/cygwin/')

export    HOSTARCH HOSTOS

image.png

scripts/Kbuild.include脚本的使用:

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

这个脚本里面有很多变量,这些变量级别都是使用命令生成的

编译工具的设置:

AS        = $(CROSS_COMPILE)as
# Always use GNU ld
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
LEX        = flex
YACC        = bison

CROSS_COMPILE编译时传入的:make -C /home/song/arm/optee/build/../u-boot CROSS_COMPILE=" /home/song/arm/optee/build/../toolchains/aarch64/bin/aarch64-linux-gnu-"

2. make过程分析

2.1 编译目标

默认编译目标是_all,在根目录Makefile中:

# That's our default target when none is given on the command line
PHONY := _all
_all:

all: $(ALL-y)

# Timestamp file to make sure that binman always runs
.binman_stamp: $(INPUTS-y) FORCE
ifeq ($(CONFIG_BINMAN),y)
    $(call if_changed,binman)
endif
    @touch $@

all: .binman_stamp

# Always append INPUTS so that arch config.mk's can add custom ones
INPUTS-y += u-boot.srec u-boot.bin u-boot.sym System.map binary_size_check
# Add optional build target if defined in board/cpu/soc headers
ifneq ($(CONFIG_BUILD_TARGET),)
INPUTS-y += $(CONFIG_BUILD_TARGET:"%"=%)
endif

ifeq ($(CONFIG_INIT_SP_RELATIVE)$(CONFIG_OF_SEPARATE),yy)
INPUTS-y += init_sp_bss_offset_check
endif

ifeq ($(CONFIG_ARCH_ROCKCHIP)_$(CONFIG_SPL_FRAMEWORK),y_)
INPUTS-y += u-boot.img
endif

all 目标依赖$(INPUTS-y),包含 u-boot.srec、u-boot.bin、u-boot.sym、System.map、u-boot.cfg 和 binary_size_check 这几个文件。

最后编译出来的为u-boot.bin,我们看其编译依赖:

ifneq ($(EXT_DTB),)
u-boot-fit-dtb.bin: u-boot-nodtb.bin $(EXT_DTB)
        $(call if_changed,cat)
else
u-boot-fit-dtb.bin: u-boot-nodtb.bin $(FINAL_DTB_CONTAINER)
    $(call if_changed,cat)
endif

u-boot.bin: u-boot-fit-dtb.bin FORCE
    $(call if_changed,copy)

目标 u-boot.bin 依赖于 u-boot-nodtb.bin,命令为$(call if_changed,copy) , 这 里 调 用 了 if_changed , if_changed 是 一 个 函 数 , 这 个 函 数 在scripts/Kbuild.include 中有定义

if_changed = $(if $(strip $(any-prereq) $(arg-check)),                       \
    @set -e;                                                             \
    $(echo-cmd) $(cmd_$(1));                                             \
    printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)

继续进行分析u-boot-nodtb.bin

u-boot-nodtb.bin: u-boot FORCE
    $(call if_changed,objcopy_uboot)
    $(BOARD_SIZE_CHECK)

u-boot:    $(u-boot-init) $(u-boot-main) $(u-boot-keep-syms-lto) 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-init := $(head-y)
u-boot-main := $(libs-y)

arch/arm/Makefile中定义了$(head-y)

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

libs-y 都是 uboot 各子目录的集合

**libs-y += boot/
libs-y += cmd/
libs-y += common/
libs-$(CONFIG_OF_EMBED) += dts/
libs-y += env/
libs-y += lib/
libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/**
...
libs-y        := $(patsubst %/, %/built-in.o, $(libs-y))

patsubst将 libs-y 中的“/”替换为”/built-in.o”,比如“drivers/dma/”就变为了“drivers/dma/built-in.o”,相当于将 libs-y 改为所有子目录中 built-in.o 文件的集合。那么 uboot-main 就等于所有子目录中 built-in.o 的集合。这个规则就相当于将以 u-boot.lds 为链接脚本,将arch/arm/cpu/armv8/start.o 和各个子目录下的 built-in.o 链接在一起生成 u-boot。

built-in.o 是怎么生成的?

以 drivers/gpio/built-in.o 为例,在drivers/gpio/目录下会有个名为.built-in.o.cmd 的文件,此文件内容如下:

cmd_drivers/gpio/built-in.o :=  rm -f drivers/gpio/built-in.o; /home/song/arm/optee/build/../toolchains/aarch64/bin/aarch64-linux-gnu-ar cDPrsT drivers/gpio/built-in.o

drivers/gpio/built-in.o 这个文件是使用aarch64-linux-gnu-ar命令生成的。

2.2 链接脚本u-boot.lds

在Makefile中定义如下

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

    ifeq ($(wildcard $(LDSCRIPT)),)
        LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds

对应的文件为:arch/arm/cpu/armv8/u-boot.lds lds文件规定了编译后各种程序数据在内存中的布局,例如我们经常用到的堆栈也在其中定义,还有异常向量表的位置。具体不展开说了。

prepare是一系列prepare伪目标和动作的组合,完成编译前的准备工作

image.png

  /home/song/arm/optee/build/../toolchains/aarch64/bin/aarch64-linux-gnu-ld.bfd  -z noexecstack  -pie  --gc-sections -Bstatic  --no-dynamic-linker -z notext  --build-id=none -Ttext 0x00000000 -o u-boot -T u-boot.lds arch/arm/cpu/armv8/start.o --whole-archive  arch/arm/cpu/built-in.o  arch/arm/cpu/armv8/built-in.o  arch/arm/lib/built-in.o  board/emulation/common/built-in.o  board/emulation/qemu-arm/built-in.o  boot/built-in.o  cmd/built-in.o  common/built-in.o  disk/built-in.o  drivers/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/host/built-in.o  drivers/usb/isp1760/built-in.o  drivers/usb/mtu3/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 --no-whole-archive -L /home/song/arm/optee/toolchains/aarch64/bin/../lib/gcc/aarch64-none-linux-gnu/11.3.1 -lgcc -Map u-boot.map;  true

aarch64-linux-gnu-ld.bfd进行了链接。这个也是.u-boot.lds.cmd中的内容。

2.3 u-boot.bin生成

image.png

从目标文件出发看下cmd命令。.u-boot.bin.cmd中

cmd_u-boot.bin := cp u-boot-nodtb.bin u-boot.bin

.u-boot.bin.cmd 里面定义了一个变量:cmd_u-boot.bin,此变量的值为“cp u-boot-nodtb.bin,依赖关系为:

u-boot.bin --> u-boot-dtb.bin --> u-boot-nodtb.bin + dts/dt.dtb

u-boot.bin”,也就是拷贝一份 u-boot-nodtb.bin 文件,并且重命名为 u-boot.bin,这个就是 u-boot.bin的来源,来自于文件 u-boot-nodtb.bin。

cmd_u-boot-nodtb.bin := /home/song/arm/optee/build/../toolchains/aarch64/bin/aarch64-linux-gnu-objcopy --gap-fill=0xff  -j .text -j .secure_text -j .secure_data -j .rodata -j .data -j __u_boot_list -j .rela.dyn -j .got -j .got.plt -j .binman_sym_table -j .text_rest -j .dtb.init.rodata -j .efi_runtime -j .efi_runtime_rel -O binary   u-boot u-boot-nodtb.bin && {  echo '  tools/relocate-rela  '; tools/relocate-rela u-boot-nodtb.bin u-boot; } || { rm -f u-boot-nodtb.bin; false; }

aarch64-linux-gnu-objcopy将 ELF 格式的 u-boot 文件转换为二进制的 u-boot-nodtb.bin 文件.

文件.u-boot.cmd 用于生成 u-boot,使用链接工具arm-linux-gnueabihf-ld.bfd,将各个 builtin.o 文件链接在一起就形成了 u-boot 文件。uboot 在编译的时候会将同一个目录中的所有.c 文件都编译在一起,并命名为 built-in.o,相当于将众多的.c 文件对应的.o 文件集合在一起,这个就是 u-boot 文件的来源。

目标 all 除了 u-boot.bin 以外还有其他的依赖,比如 u-boot.srec 、u-boot.sym 、System.map、u-boot.cfg 和 binary_size_check 等等,这些依赖的生成方法和 u-boot.bin 很类似

编译除了生成u-boot.bin外,还有如下的东西:

image.png

  • u-boot:编译出来的 ELF 格式的 uboot 镜像文件。
  • u-boot.bin:编译出来的二进制格式的 uboot 可执行镜像文件。
  • u-boot.cfg:uboot 的另外一种配置文件。
  • u-boot.imx:u-boot.bin 添加头部信息以后的文件,NXP 的 CPU 专用文件。
  • u-boot.lds:链接脚本。
  • u-boot.map:uboot 映射文件,通过查看此文件可以知道某个函数被链接到了哪个地址上。
  • u-boot.srec:S-Record 格式的镜像文件。
  • u-boot.sym:uboot 符号文件。
  • u-boot-nodtb.bin:和 u-boot.bin 一样,u-boot.bin 就是 u-boot-nodtb.bin 的复制文件。

.config文件是make meuconfig自动生成的。

对于u-boot.img和u-boot.bin的区别:

  • u-boot.bin:是二进制编译的uboot引导加载程序, 很多image 文件的生成都需要依赖于它;
  • u-boot.img:它是给u-boot.bin 加上0x40 Byte 长度的Header。 里面包含加载地址,运行地址,CRC等重要信息, 用来让它的加载程序识别;这个只有在配置CONFIG_SPL_FRAMEWORK时才有效;

芯片烧录选择u-boot.bin还是u-boot.img取决于设备的性质。目前许多SoC/CPU启动ROM都能够加载u-boot.img,读取文件的头部,加载u-boot.bin到内存中并最终执行它。

以上流程总结起来如下图:

image.png

参考:

  1. https://www.cnblogs.com/zyly/p/14841687.html#_label3_5
  2. 正点原子嵌入式学习资料

后记:

编译有点记流水账的感觉,很多都是平时遇到有这么个东西,但是还一头雾水,追本溯源才知道原来这个定义或者初始决策藏在这里。

results matching ""

    No results matching ""