我们经常遇到的工作估计是调试bug、改配置、适配移植等也就是说已经有可以编译运行的代码我们找到其中需要修改的点进行修改就可以了。那什么是别人不太会的但是又比较有用的知识遇到一个系统性的问题需要用到的那可能就是对编译过程的理解。如上图所示编译就像****织布准备线然后进行编制最后成型运行的过程则更像把布匹做成了衣服进行展示。本文就来介绍下这些奇怪的知识。1. 顶层Makefile顶层也就是uboot代码根目录下的Makefile。顶层Makefile会再去调用子目录里面的Makefile递归执行其是一个树状结构顶层Makefile就是树根。1.1 版本号那么这个版本号怎么最后运行的时候被打印出来了。是时候展现下真正的技术了在顶层Makefile中VERSION2023PATCHLEVEL07 SUBLEVEL02 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 numbersdefine 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-es/^0*//);\echo\#define CC_VERSION_STRING\$$(LC_ALLC$(CC)--version|head-n1)\;\echo\#define LD_VERSION_STRING\$$(LC_ALLC$(LD)--version|head-n1)\;)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_VERSIONdisplay_options–》display_options_get_banner–》display_options_get_banner_privchar *display_options_get_banner_priv(bool newlines, const char *build_tag, char *buf, int size){int len;lensnprintf(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, /* stage1init of console */ display_options, /* say that we are here */ display_text_info, /* show debugging infoifrequired */启动的流程以后再介绍这里就是各种宏定义代码生成然后在c代码中使用。看似复杂其实****一点也不简单还是挺有趣的。1.2 编译命令help: echoCleaning targets:echo clean - Remove most generated files but keep the configecho mrproper - Remove all generated files config various backup filesecho distclean - mrproper remove editor backup and patch filesechoechoConfiguration targets:$(MAKE)-f$(srctree)/scripts/kconfig/MakefilehelpechoechoTest targets:echoecho check - Run all automated tests that use sandboxecho pcheck - Run quick automated tests in parallelecho qcheck - Run quick automated tests that use sandboxecho tcheck - Run quick automated tests on toolsecho pylint - Run pylint on all Python filesechoechoOther generic targets:echo all - Build all necessary images depending on configurationecho tests - Build U-Boot for sandbox and run testsecho* u-boot - Build the bare u-bootecho dir/ - Build all files in dir and belowecho dir/file.[oisS] - Build specified target onlyecho dir/file.lst - Build specified mixed source/assembly target onlyecho (requires a recent binutils and recent build (System.map))echo tags/ctags - Generate ctags file for editorsecho etags - Generate etags file for editorsecho cscope - Generate cscope indexecho ubootrelease - Output the release version string (use with make -s)echo ubootversion - Output the version stored in Makefile (use with make -s)echo cfg - Dont build, just create the .cfg filesecho envtools - Build only the target-side environment toolsechoechoPyPi / pip targets:echo pip - Check building of PyPi packagesecho pip_test - Build PyPi pakages and upload to test serverecho pip_release - Build PyPi pakages and upload to release serverechoechoStatic analysersecho checkstack - Generate a list of stack hogsecho coccicheck - Execute static code analysis with CoccinelleechoechoDocumentation targets:$(MAKE)-f$(srctree)/doc/Makefile dochelp echoecho make V0|1 [targets] 0 quiet build (default), 1 verbose buildecho make V2 [targets] 2 give reason for rebuild of targetecho make Odir [targets] Locate all output files in dir, including .configecho make C1 [targets] Check all c source with $$CHECK (sparse by default)echo make C2 [targets] Force check of all c source with $$CHECKecho make RECORDMCOUNT_WARN1 [targets] Warn about ignored mcount sectionsecho make Wn [targets] Enable extra gcc checks, n1,2,3 whereecho 1: warnings which may be relevant and do not occur too oftenecho 2: warnings which occur quite often but may still be relevantecho 3: more obscure warnings, can most likely be ignoredecho Multiple levels can be combined with W12 or W123echoechoExecute make or make all to build all targets marked with [*] echoFor further info see the ./README filemake O指定编译输出目录make V0|1输出编译日志级别make C2检查所有的源码文件make Mdir单独编译某个目录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 :$(shelluname-m|\sed-es/i.86/x86/\-es/sun4u/sparc64/\-es/arm.*/arm/\-es/sa110/arm/\-es/ppc64/powerpc/\-es/ppc/powerpc/\-es/macppc/powerpc/\-es/sh.*/sh/)HOSTOS :$(shelluname-s|tr[:upper:][:lower:]|\sed-es/\(cygwin\).*/cygwin/)exportHOSTARCH HOSTOSscripts**/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 ldifneq($(shell$(CROSS_COMPILE)ld.bfd-v2/dev/null),)LD$(CROSS_COMPILE)ld.bfdelseLD$(CROSS_COMPILE)ld endif CC$(CROSS_COMPILE)gcc CPP$(CC)-EAR$(CROSS_COMPILE)ar NM$(CROSS_COMPILE)nm LDR$(CROSS_COMPILE)ldr STRIP$(CROSS_COMPILE)strip OBJCOPY$(CROSS_COMPILE)objcopy OBJDUMP$(CROSS_COMPILE)objdump LEXflex YACCbisonCROSS_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中# Thats our default target when none is given on the command linePHONY :_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.mks can add custom onesINPUTS-yu-boot.srec u-boot.bin u-boot.sym System.map binary_size_check# Add optional build target if defined in board/cpu/soc headersifneq($(CONFIG_BUILD_TARGET),)INPUTS-y$(CONFIG_BUILD_TARGET:%%)endif ifeq($(CONFIG_INIT_SP_RELATIVE)$(CONFIG_OF_SEPARATE),yy)INPUTS-yinit_sp_bss_offset_check endif ifeq($(CONFIG_ARCH_ROCKCHIP)_$(CONFIG_SPL_FRAMEWORK),y_)INPUTS-yu-boot.img endifall 目标依赖$(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)elseu-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\ncmd_$ : $(make-cmd)$(dot-target).cmd, :)继续进行分析u-boot-nodtb.binu-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.olibs-y都是 uboot 各子目录的集合**libs-yboot/ libs-ycmd/ libs-ycommon/ libs-$(CONFIG_OF_EMBED)dts/ libs-yenv/ libs-ylib/ libs-yfs/ libs-ynet/ libs-ydisk/ libs-ydrivers/**... 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-fdrivers/gpio/built-in.o;/home/song/arm/optee/build/../toolchains/aarch64/bin/aarch64-linux-gnu-ar cDPrsT drivers/gpio/built-in.odrivers/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伪目标和动作的组合完成编译前的准备工作/home/song/arm/optee/build/../toolchains/aarch64/bin/aarch64-linux-gnu-ld.bfd-znoexecstack-pie--gc-sections-Bstatic--no-dynamic-linker-znotext --build-idnone-Ttext0x00000000-ou-boot-Tu-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-Mapu-boot.map;trueaarch64-linux-gnu-ld.bfd工具进行了链接。这个也是.u-boot.lds.cmd中的内容。2.3 u-boot.bin生成从目标文件出发看下cmd命令。.u-boot.bin.cmd中cmd_u-boot.bin :cpu-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.dtbu-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-fill0xff-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-Obinary u-boot u-boot-nodtb.bin{echo tools/relocate-rela ;tools/relocate-rela u-boot-nodtb.bin u-boot;}||{rm-fu-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外还有如下的东西u-boot编译出来的 ELF 格式的 uboot 镜像文件。u-boot.bin编译出来的二进制格式的 uboot 可执行镜像文件。u-boot.cfguboot 的另外一种配置文件。u-boot.imxu-boot.bin 添加头部信息以后的文件NXP 的 CPU 专用文件。u-boot.lds链接脚本。u-boot.mapuboot 映射文件通过查看此文件可以知道某个函数被链接到了哪个地址上。u-boot.srecS-Record 格式的镜像文件。u-boot.symuboot 符号文件。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到内存中并最终执行它。以上编译流程总结起来如下图参考https://www.cnblogs.com/zyly/p/14841687.html#_label3_5正点原子嵌入式学习资料后记编译有点记流水账的感觉很多都是平时遇到有这么个东西但是还一头雾水追本溯源才知道原来这个定义或者初始决策藏在这里。“啥都懂一点啥都不精通干啥都能干干啥啥不是专业入门劝退堪称程序员杂家”。欢迎各位有自己公众号的留言申请转载纯干货持续更新欢迎分享给朋友、点赞、收藏、在看、划线和评论交流vx“那路谈OS与SoC嵌入式软件”欢迎关注个人文章汇总https://thatway1989.github.io