
1. 链接脚本生成1.1 指定链接脚本模板文件在顶层Makefile中会根据设置指定链接脚本的模板文件# If board code explicitly specified LDSCRIPT or CONFIG_SYS_LDSCRIPT, use # that (or fail if absent). Otherwise, search for a linker script in a # standard location. ifndef LDSCRIPT #LDSCRIPT : $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug ifdef CONFIG_SYS_LDSCRIPT # need to strip off double quotes LDSCRIPT : $(srctree)/$(CONFIG_SYS_LDSCRIPT:%%) endif endif # If there is no specified link script, we look in a number of places for it ifndef LDSCRIPT ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT : $(srctree)/board/$(BOARDDIR)/u-boot.lds endif ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT : $(srctree)/$(CPUDIR)/u-boot.lds endif ifeq ($(wildcard $(LDSCRIPT)),) LDSCRIPT : $(srctree)/arch/$(ARCH)/cpu/u-boot.lds endif endif对于盘古开发板来说生成链接脚本使用的模板文件为arch/arm/cpu/u-boot.lds。1.2 链接脚本生成规则u-boot链接最终所使用的链接脚本u-boot.lds位于根目录下通过编译准确的说是预处理才能生成。生成u-boot.lds的规则quiet_cmd_cpp_lds LDS $ cmd_cpp_lds $(CPP) -Wp,-MD,$(depfile) $(cpp_flags) $(LDPPFLAGS) \ -D__ASSEMBLY__ -x assembler-with-cpp -stdc99 -P -o $ $ u-boot.lds: $(LDSCRIPT) prepare FORCE $(call if_changed_dep,cpp_lds)生成u-boot.lds的命令从生成的依赖文件反向.u-boot.lds.cmd反向来看u-boot.lds的生成命令cmd_u-boot.lds : arm-openstlinux_eglfs-linux-gnueabi-gcc -E -Wp,-MD,./.u-boot.lds.d -D__KERNEL__ -D__UBOOT__ -D__ARM__ -Wa,-mimplicit-italways -mthumb -mthumb-interwork -mabiaapcs-linux -mword-relocations -fno-pic -mno-unaligned-access -ffunction-sections -fdata-sections -fno-common -ffixed-r9 -msoft-float -pipe -marcharmv7-a -D__LINUX_ARM_ARCH__7 -mtunegeneric-armv7-a -I./arch/arm/mach-stm32mp/include -Iinclude -I./arch/arm/include -include ./include/linux/kconfig.h -nostdinc -isystem /opt/stm32mp/qt-snapshot/sysroots/x86_64-openstlinux_eglfs_sdk-linux/usr/lib/arm-openstlinux_eglfs-linux-gnueabi/gcc/arm-openstlinux_eglfs-linux-gnueabi/8.2.0/include -ansi -include ./include/u-boot/u-boot.lds.h -DCPUDIRarch/arm/cpu/armv7 -D__ASSEMBLY__ -x assembler-with-cpp -stdc99 -P -o u-boot.lds arch/arm/cpu/u-boot.lds编译命令中-E和-P选项比较特别-E 指示gcc只进行编译不进行链接-P 指示gcc在输出文件中不输出linemarkers适合对u-boot.lds这样的非C代码进行预处理。模板文件arch/arm/cpu/u-boot.lds中包含了C语言的头文件和一些宏定义而链接脚本是不支持包含这类头文件和宏定义的。从命令看gcc通过对模板文件进行预处理得到位于根目录的u-boot.lds这也是最终使用的u-boot.lds。在这个生成文件中原有的头文件和宏都被处理好了。所以关于链接只需要查看这个生成的u-boot.lds文件。2. 链接脚本分析OUTPUT_FORMAT(elf32-littlearm, elf32-littlearm, elf32-littlearm) OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { /DISCARD/ : { *(.rel._secure*) } . 0x00000000; . ALIGN(4); .text : { *(.__image_copy_start) *(.vectors) arch/arm/cpu/armv7/start.o (.text*) } .__efi_runtime_start : { *(.__efi_runtime_start) } .efi_runtime : { *(.text.efi_runtime*) *(.rodata.efi_runtime*) *(.data.efi_runtime*) } .__efi_runtime_stop : { *(.__efi_runtime_stop) } .text_rest : { *(.text*) } .__secure_start : { KEEP(*(.__secure_start)) } .secure_text 0x2FFC0000 : AT(ADDR(.__secure_start) SIZEOF(.__secure_start)) { *(._secure.text) } .secure_data : AT(LOADADDR(.secure_text) SIZEOF(.secure_text)) { *(._secure.data) } .secure_stack ALIGN(ADDR(.secure_data) SIZEOF(.secure_data), CONSTANT(COMMONPAGESIZE)) (NOLOAD) : AT(LOADADDR(.secure_data) SIZEOF(.secure_data)) { KEEP(*(.__secure_stack_start)) . . 4 * (1 10); . ALIGN(CONSTANT(COMMONPAGESIZE)); KEEP(*(.__secure_stack_end)) ASSERT((. - ADDR(.secure_text)) 0x00040000, Error: secure section exceeds secure memory size); } . LOADADDR(.secure_stack); .__secure_end : AT(ADDR(.__secure_end)) { *(.__secure_end) LONG(0x1d1071c); } . ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . ALIGN(4); .data : { *(.data*) } . ALIGN(4); . .; . ALIGN(4); .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } . ALIGN(4); .efi_runtime_rel_start : { *(.__efi_runtime_rel_start) } .efi_runtime_rel : { *(.rel*.efi_runtime) *(.rel*.efi_runtime.*) } .efi_runtime_rel_stop : { *(.__efi_runtime_rel_stop) } . ALIGN(4); .image_copy_end : { (.__image_copy_end) } .rel_dyn_start : { *(.__rel_dyn_start) } .rel.dyn : { *(.rel*) } .rel_dyn_end : { *(.__rel_dyn_end) } .end : { *(.__end) } _image_binary_end .; . ALIGN(4096); .mmutable : { *(.mmutable) } .bss_start __rel_dyn_start (OVERLAY) : { KEEP(*(.__bss_start)); __bss_base .; } .bss __bss_base (OVERLAY) : { *(.bss*) . ALIGN(4); __bss_limit .; } .bss_end __bss_limit (OVERLAY) : { KEEP(*(.__bss_end)); } .dynsym _image_binary_end : { *(.dynsym) } .dynbss : { *(.dynbss) } .dynstr : { *(.dynstr*) } .dynamic : { *(.dynamic*) } .plt : { *(.plt*) } .interp : { *(.interp*) } .gnu.hash : { *(.gnu.hash) } .gnu : { *(.gnu*) } .ARM.exidx : { *(.ARM.exidx*) } .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) } }.text这一节是最常见的用于存放代码。整个链接文件从中断向量(.vectors)开始随后是start.o文件。.__efi_runtime_start、.efi_runtime、.__efi_runtime_stop与UEFI有关暂不分析。.text_rest.text的剩余部分。.__secure_start、.secure_text、.secure_data、.secure_stack、.__secure_end与安全相关stm32mp特有的暂不分析。.rodata、.data该部分用于存放只读数据和已经初始化的全局变量。.u_boot_list在u-boot的linker_list.h中通过宏定义让编译器在编译阶段生成了一些顺序链表.u_boot_list*然后在链接阶段顺序存放到这个u_boot_list节中。u-boot启动过程中会从这个节读取模块驱动、命令行支持的命令等。.efi_runtime_rel_start、.efi_runtime_rel、.efi_runtime_rel_stop与UEFI相关暂不分析。.image_copy_end与.text中__image_copy_start相对应。.rel_dyn_start、.rel.dyn、.rel_dyn_end该部分提供了程序的重定位支持。.mmutable该节用于存放mmu表。.bss_start、.bss、.bss_end.bss部分包含了程序中所有未初始化的全局变量。由链接指令(OVERLAY)可见.bss_start与__rel_dyn_start、.bss与__bss_base、.bss_end与__bss_limit是重叠的。OVERLAY其他段剩余的段都是在编译链接时自动生成的主要用于动态链接或调试使用。- .plt: 程序连接表Procddure Linkage Table是实现动态链接的必要数据 - .interp: 解释器interpreter的缩写 - .dynsym动态符号表dynamic symbol但与.symtab不同.dynsym只保存动态链接相关的符号而.symtab通常保存了所有的符号 - .dynbss: 动态未初始化数据表dynamic bss; - .dynstr: 动态字符串表dynamic string用于保存符号名的字符串表; - .dynamic: 保存了动态链接所需要的基本信息例如依赖哪些共享对象动态链接符号表的位置动态链接重定位表的位置共享对象初始化代码的地址等 - .ARM.exidx和.gnu.linkonce.armexidx是针对arm体系专门生成的段用于调试时函数调用的backtrace如果不需要调试则可以不用这两段。3. 其他在u-boot的编译过程中会生成3个符号表文件u-boot.mapu-boot.symSystem.map查看.u-boot.cmd显示u-boot的链接命令cmd_u-boot : arm-openstlinux_eglfs-linux-gnueabi-ld.bfd -pie --gc-sections -Bstatic --no-dynamic-linker -Ttext 0xC0100000 -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 ... ... 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-Ttext 0xC0100000指定链接的起始地址为0xC0100000-T u-boot.lds指定了链接使用的脚本文件。实际上在这个脚本文件的.text段开始前同样指定了起始地址. 0x00000000; . ALIGN(4); .text : { *(.__image_copy_start) *(.vectors) arch/arm/cpu/armv7/start.o (.text*) }链接器手册中指出如果链接器的命令行和脚本中同时指定地址则会采用命令行指定的地址值。除了这里的-Ttext外还有 -Tdata、-Tbss等也可以在命令行指定相应段的地址。其中还有-pie在ld的手册上是这样说的http://sourceware.org/binutils/docs/ld/Options.html#Options-pie--pic-executableCreate a position independent executable. This is currently only supported on ELF platforms. Position independent executables are similar to shared libraries in that they are relocated by the dynamic linker to the virtual address the OS chooses for them (which can vary between invocations). Like normal dynamically linked executables they can be executed and symbols defined in the executable cannot be overridden by shared libraries.-pic和-pie的功能和作用看起来差不多一个用于动态库的位置无关一个用于动态可执行文件的位置无关。4. 疑问链接指定的地址-Ttext 0xC0100000是在哪里定义的呢经过查找在arch/arm/mach-stm32mp目录下的Kconfig中找到。config SYS_TEXT_BASE prompt U-Boot base address default 0xC0100000 help configure the U-Boot base address when DDR driver is used: DDR 1MB (0xC0100000)修改这里的值链接时-Ttext的值也会随着改变。那么如果遇到新的开发板该怎么查找-Ttext的值呢-Ttext后面的值在.config文件中用宏CONFIG_SYS_TEXT_BASE来表示查找make xxx_defconfig文件看是否包含CONFIG_SYS_TEXT_BASE这个配置项查找Kconfig文件看是否包含SYS_TEXT_BASE选项Kconfig文件一般在arch/$(ARCH)/$(CPU)目录查找Makefile文件这一步由于没用到不太清楚查看include/configs目录下$(BOARD)相关的头文件中包不包含CONFIG_SYS_TEXT_BASE这个宏按理解链接指定的地址应该在xxx_defconfig、Kconfig或Makefile中定义为什么会有最后一步呢因为u-boot是在2014年引入的Kconfig系统在引入Kconfig系统之前使用的就是第4种方式。当前正在一点一点地从第四种方式向Kconfig系统迁移当前u-boot的版本是2018.11目前还没有完全切换过来因此才会存在第4步。那么如果是第4步的方式它是怎么样将头文件中的宏定义转换为命令行参数的在scripts/Makefile.autoconf中定义了转换方式# 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 \ sed -n -f $(srctree)/tools/scripts/define2mk.sed $ | \ while read line; do \ if [ -n ${KCONFIG_IGNORE_DUPLICATES} ] || \ ! grep -q $${line%*} include/config/auto.conf; then \ echo $$line; \ fi \ done $此处的注释也说明了这个原因从$(BOARD)头文件向Kconfig的迁移是一点一点进行的所以目前还需要将$(BOARD)头文件转换为autoconf.mk然后通过编译系统包含进来在编译或链接阶段使用这些宏定义的设置包括这里我们讨论的链接地址-Ttext和CONFIG_SYS_TEXT_BASE的关系。这里的-Ttext是这样定义并作用的$(BOARD)头文件 -- autoconf.mk -- LDPPFLAGS -- -Ttext。