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 -std=c99 -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-it=always -mthumb -mthumb-interwork -mabi=aapcs-linux -mword-relocations
-fno-pic -mno-unaligned-access
-ffunction-sections -fdata-sections -fno-common -ffixed-r9
-msoft-float -pipe
-march=armv7-a -D__LINUX_ARM_ARCH__=7 -mtune=generic-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
-DCPUDIR=arch/arm/cpu/armv7 -D__ASSEMBLY__
-x assembler-with-cpp -std=c99 -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.map
- u-boot.sym
- System.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-executable
Create 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。