u-boot.lds is the linker script file for the u-boot project, which plays a crucial role in the compilation and linking of the project. It determines the assembly of u-boot and instructs the linker on how to combine the sections from various source files (such as assembly and C source files) into the final executable file. Additionally, the ENTRY (_start) in the u-boot.lds linker file specifies the entry address of the u-boot program. |
In ARMv8, the u-boot linker script path is: u-boot/arch/arm/cpu/armv8/u-boot.lds For u-boot-spl and u-boot-tpl in ARMv8, the linker script paths are: u-boot/arch/arm/cpu/armv8/u-boot-spl.lds (u-boot customizes u-boot-spl.lds and u-boot-tpl.lds through Kconfig) |
In ARMv7, the u-boot linker script path is: u-boot/arch/arm/cpu/u-boot.lds For u-boot-spl in ARMv7, the linker script path is: u-boot/arch/arm/cpu/u-boot-spl.lds |
Compiled u-boot linker scripts in ARMv7 and ARMv8: u-boot/u-boot.lds Compiled u-boot-spl linker scripts in ARMv7 and ARMv8: u-boot/spl/u-boot-spl.lds |
The relationship between u-boot, u-boot-spl, and u-boot-tpl: For general embedded systems, only one u-boot is needed as the bootloader. However, in cases of limited memory or when using ATF, spl and tpl can also be present. |
SPL: Secondary Program LoaderTPL: Tertiary Program LoaderThe emergence of SPL and TPL was initially due to the system SRAM being too small, or the ROM being unable to load all code from flash, eMMC, USB, etc., into SRAM for execution before DDR initialization. Alternatively, the flash may be too small to hold the entire u-boot for on-chip execution. Therefore, u-boot defined SPL and TPL, which follow the same boot process as u-boot, but most drivers and functionalities are removed, retaining only the necessary features for SPL and TPL.Control is managed through CONFIG_SPL_BUILD and CONFIG_TPL_BUILD; generally, SPL is sufficient, as it completes DDR initialization and initializes some peripheral drivers, such as USB and eMMC, to load u-boot from other peripheral devices. However, for smaller systems where SPL is still too large, TPL can be added, which only performs specific initializations like DDR to keep the code size minimal, thus loading SPL from a specified location, which then loads u-boot. |
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
Defines that all three output file formats are ELF format, 64-bit ARMv8-A instruction set, little-endian storage. |
If the command line has no parameters, the first format is used for encoding output by default. If the command line options include `-EB`, the second format is used for encoding output. If the command line options include `-EL`, the third format is used for encoding output. |
OUTPUT_ARCH(aarch64)
Defines the execution platform of the output executable file as: aarch64. |
The ARMv8 architecture is divided into AArch64 (64-bit) and AArch32 (32-bit) execution states. The CPU architecture also includes the following: (1) X86: Intel 32-bit instruction set (2) AMD64: 64-bit, AMD’s first 64-bit processor compatible with the X86 32-bit instruction set (3) IA-64: Intel 64-bit instruction set, incompatible with the 32-bit instruction set (4) X86-64: 64-bit, Intel’s extension of the AMD64 instruction set, renamed x86-64, also known as x64 (5) ARM: includes 8-bit, 16-bit, 32-bit, with advantages of low cost, high performance, and low power consumption (6) AArch32: 32-bit execution state of ARM-V8 architecture (7) AArch64: 64-bit execution state of ARM-V8 architecture |
ENTRY(_start)
The entry symbol of the executable program is _start. Defined in: u-boot/arch/arm/cpu/armv8/start.S |
#ifdef CONFIG_ARMV8_SECURE_BASE /DISCARD/ : { *(.rela._secure*) }#endif
ARMV8_SECURE_BASE is u-boot’s support for PSCI, when defined, it can redirect the text, data, and stack segments of PSCI to specified memory instead of embedding them in u-boot. However, manufacturers typically implement this using ATF to separate it from the bootloader, and this feature is not commonly used; |
. = 0x00000000;
Locator, defines the program link base address, setting the address of _start to 0x00000000, which is the address where U-BOOT starts running after power-up; This address can be modified through the configuration compilation parameter: -Ttext=$(CONFIG_TEXT_BASE) for modification, CONFIG_TEXT_BASE is generally defined in a macro form in configs (XXX_defconfig). If this macro is not defined, the default address is 0x00000000. |
. = ALIGN(8);
Configures the code segment to be aligned to 8 bytes. Since it is a 64-bit instruction set, the corresponding address and data lines are 64 lines, meaning each address corresponds to 64 data lines, which is 64 bits, or 8 bytes. |
In a 64-bit instruction set system, each address pointer points to a memory space of 64 bits, 8 bytes. In a 32-bit instruction set system, each address pointer points to a memory space of 32 bits, 4 bytes. |
.text : { *(.__image_copy_start) CPUDIR/start.o (.text*) }
*(.__image_copy_start) contains all the contents of the .__image_copy_start section, which only places a char[0] data, with a size of zero. |
__image_copy_start and __image_copy_end are used to define the segments that need to be redirected. U-boot is a bootloader divided into pre-redirection initialization and post-redirection initialization, so this section defines the starting and ending addresses of the data that needs to be moved to DDR after completing the pre-redirection initialization; |
Most of the time, u-boot runs in limited SRAM or read-only flash. To unify the startup process, u-boot does not access global variables before DDR initialization and relocation. However, to ensure that u-boot can read and write global variables, memory, and call various driver capabilities, u-boot divides the startup initialization into two parts: pre-redirection initialization (board_f) and post-redirection initialization (board_r). Before redirection, some necessary initializations are completed, including possible DDR initialization, and then u-boot is moved to DDR through __image_copy_start and __image_copy_end, and post-redirection initialization is performed in DDR, at which point u-boot can normally access global variables and other information. |
If you want to read and write some global variable information during the board_f process, how can you do it? U-boot accomplishes this by defining global_data (gd). |
CPUDIR/start.o (.text*) Defines the header text segment of the linking program, armv8 is arch/arm/cpu/armv8/start.S, all text segments in start.S will be linked to this segment, and the segment entry symbol is _start; |
/* This needs to come before *(.text*) */ .efi_runtime : { __efi_runtime_start = .; *(.text.efi_runtime*) *(.rodata.efi_runtime*) *(.data.efi_runtime*) __efi_runtime_stop = .; }
This segment is used only when defining EFI runtime support, generally not of concern; |
The main purpose of introducing EFI compatibility is to boot x86 platform architectures. The code and data segments of EFI are directly linked to the code segments following start.o. If you need to support UEFI, you can configure CONFIG_EFI_LOADER = y CONFIG_CMD_BOOTEFI = y CONFIG_EFI = y CONFIG_CMD_EFIDEBUG = y CONFIG_CMD_EFICONFIG = y CONFIG_EFI_SECURE_BOOT=y |
ARM64 architecture does not require EFI boot, so it will not be analyzed in depth. |
.text_rest : { *(.text*) }
All text segments other than start.o will be linked to this segment. |
#ifdef CONFIG_ARMV8_PSCI .__secure_start : #ifndef CONFIG_ARMV8_SECURE_BASE ALIGN(CONSTANT(COMMONPAGESIZE)) #endif { KEEP(*(.__secure_start)) } #ifndef CONFIG_ARMV8_SECURE_BASE #define CONFIG_ARMV8_SECURE_BASE #define __ARMV8_PSCI_STACK_IN_RAM #endif .secure_text CONFIG_ARMV8_SECURE_BASE : AT(ADDR(.__secure_start) + SIZEOF(.__secure_start)) { *(._secure.text) . = ALIGN(8); __secure_svc_tbl_start = .; KEEP(*(._secure_svc_tbl_entries)) __secure_svc_tbl_end = .; } .secure_data : AT(LOADADDR(.secure_text) + SIZEOF(.secure_text)) { *(._secure.data) } .secure_stack ALIGN(ADDR(.secure_data) + SIZEOF(.secure_data), CONSTANT(COMMONPAGESIZE)) (NOLOAD) : #ifdef __ARMV8_PSCI_STACK_IN_RAM AT(ADDR(.secure_stack)) #else AT(LOADADDR(.secure_data) + SIZEOF(.secure_data)) #endif { KEEP(*(.__secure_stack_start)) . = . + CONFIG_ARMV8_PSCI_NR_CPUS * ARM_PSCI_STACK_SIZE; . = ALIGN(CONSTANT(COMMONPAGESIZE)); KEEP(*(.__secure_stack_end)) } #ifndef __ARMV8_PSCI_STACK_IN_RAM . = LOADADDR(.secure_stack); #endif .__secure_end : AT(ADDR(.__secure_end)) { KEEP(*(.__secure_end)) LONG(0x1d1071c); /* Must output something to reset LMA */ } #endif
This is the support for PSCI-related functions, which is generally not used; |
. = ALIGN(8); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
Read-only data segment: SORT_BY_NAME keyword: The linker will sort the names of files or segments in ascending order and then organize their contents. SORT_BY_ALIGNMENT: All file segments are sorted in descending order, with larger alignments placed before smaller ones to reduce the extra space needed for alignment. |
. = ALIGN(8); .data : { *(.data*) }
Data segment: Stores all data segments of the source code, without distinguishing order. |
.bss: Stores uninitialized global and static variables. Uninitialized variables are zeroed out at program startup and do not actually occupy memory, thus not increasing file size. .data: Stores global and static variables that are initialized to non-zero values, occupying memory and increasing file size. .rodata: Read-only data, such as global variables modified with const (generally storing constants and string literals), shared among multiple processes to improve space utilization. |
. = ALIGN(8); . = .; . = ALIGN(8); .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); }
Defines the .u_boot_list segment, which may be used for the U-Boot bootloader. The KEEP directive ensures that the linker does not remove these symbols during optimization. |
Stores the __u_boot_list structure or __u_boot_list variable for all drivers in u-boot. Sorts and aligns them, storing the sorted results in the __u_boot_list memory segment. |
The u_boot_list segment defines all commands and device drivers currently supported in the system. This segment collects commands defined by a series of extended macros U_BOOT_CMD and device drivers defined by U_BOOT_DRIVER scattered across various files, sorting them by name for quick retrieval and execution in the command line and detecting registered devices and device tree matching probe device driver initialization; (device driver probing is only effective when the dm modular driver is defined). |
C language function command rules: __u_boot_list_ + 2_ + @_list + _2_ + @_entry For example: __u_boot_list_2_array_1 __u_boot_list_2_array_2_first __u_boot_list_2_array_2_second __u_boot_list_2_array_2_third __u_boot_list_2_array_3 |
. = ALIGN(8); .efi_runtime_rel : { __efi_runtime_rel_start = .; *(.rel*.efi_runtime) *(.rel*.efi_runtime.*) __efi_runtime_rel_stop = .; }
Used to store the EFI runtime relocation table (EFI Runtime Relocation Table), facilitating other code to dynamically locate and load at runtime. |
. = ALIGN(8); .image_copy_end : { *(.__image_copy_end) }
Defines the end position of the U-Boot code; Both __image_copy_start and __image_copy_end segments contain a char[0] data, with a size of zero; The symbols __image_copy_start and __image_copy_end are used only for code relocation. |
. = ALIGN(8); .rel_dyn_start : { *(.__rel_dyn_start) } .rela.dyn : { *(.rela*) } .rel_dyn_end : { *(.__rel_dyn_end) }
Used to store the u-boot relocation table. |
Generally, u-boot runs based on the defined base address. If the load address, link address, and run address are inconsistent, it may lead to the inability to execute u-boot. By configuring CONFIG_POSITION_INDEPENDENT, the address-independent feature can be enabled. This option adds the -PIE parameter when linking u-boot. This parameter generates rela* segments in the u-boot ELF file, and u-boot traverses the relative address values in this segment against the actual runtime address values to sequentially fix all addresses that need redirection, allowing for address-independent execution; Regardless of how the link base address is defined, u-boot can run at any RAM address (generally requiring a minimum of 4K or 64K address alignment); Note that this feature can only be implemented on SRAM, as it modifies the address in the text and data segments at runtime. If it runs on on-chip flash, it cannot write to flash, causing the feature to fail and not achieve address independence; |
_end = .;
End address, the actual content of the executable file, up to this point;ELF file, the size of the file that can be counted, is the size up to here. |
. = ALIGN(8); .bss_start : { KEEP(*(.__bss_start)); } .bss : { *(.bss*) . = ALIGN(8); } .bss_end : { KEEP(*(.__bss_end)); }
Heap memory segment: Used to store uninitialized global and static variables in the program. |
“bss” is an abbreviation for block started by symbol, used to store uninitialized global and static variables; they are dynamically allocated at runtime and do not occupy memory in the ELF file. |
/DISCARD/ : { *(.dynsym) } /DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) } /DISCARD/ : { *(.gnu*) }
Ignored section segments. At runtime, these segments used for parsing and linking information are usually not needed, so they are discarded during linking to reduce program size and save memory; /DISCARD/ is a special output segment name indicating that the linker should discard matching input segments instead of placing them in any output segment. |
#ifdef CONFIG_LINUX_KERNEL_IMAGE_HEADER #include "linux-kernel-image-header-vars.h" #endif
This is useful during EFI loading, mainly adding some header information to the binary header of u-boot, including size-endian, data segment, text segment size, etc., for EFI-related loaders to read information. This header information comes from the header information of the Linux arm64 Image; this header is not part of u-boot but is just appended; |
/* SPDX-License-Identifier: GPL-2.0+ *//* * (C) Copyright 2013 * David Feng <[email protected]> * * (C) Copyright 2002 * Gary Jennejohn, DENX Software Engineering, <[email protected]> */ #include <config.h> #include <asm/psci.h> OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") OUTPUT_ARCH(aarch64) ENTRY(_start) SECTIONS { #ifdef CONFIG_ARMV8_SECURE_BASE /DISCARD/ : { *(.rela._secure*) } #endif . = 0x00000000; . = ALIGN(8); .text : { *(.__image_copy_start) CPUDIR/start.o (.text*) } /* This needs to come before *(.text*) */ .efi_runtime : { __efi_runtime_start = .; *(.text.efi_runtime*) *(.rodata.efi_runtime*) *(.data.efi_runtime*) __efi_runtime_stop = .; } .text_rest : { *(.text*) } #ifdef CONFIG_ARMV8_PSCI .__secure_start : #ifndef CONFIG_ARMV8_SECURE_BASE ALIGN(CONSTANT(COMMONPAGESIZE)) #endif { KEEP(*(.__secure_start)) } #ifndef CONFIG_ARMV8_SECURE_BASE #define CONFIG_ARMV8_SECURE_BASE #define __ARMV8_PSCI_STACK_IN_RAM #endif .secure_text CONFIG_ARMV8_SECURE_BASE : AT(ADDR(.__secure_start) + SIZEOF(.__secure_start)) { *(._secure.text) . = ALIGN(8); __secure_svc_tbl_start = .; KEEP(*(._secure_svc_tbl_entries)) __secure_svc_tbl_end = .; } .secure_data : AT(LOADADDR(.secure_text) + SIZEOF(.secure_text)) { *(._secure.data) } .secure_stack ALIGN(ADDR(.secure_data) + SIZEOF(.secure_data), CONSTANT(COMMONPAGESIZE)) (NOLOAD) : #ifdef __ARMV8_PSCI_STACK_IN_RAM AT(ADDR(.secure_stack)) #else AT(LOADADDR(.secure_data) + SIZEOF(.secure_data)) #endif { KEEP(*(.__secure_stack_start)) . = . + CONFIG_ARMV8_PSCI_NR_CPUS * ARM_PSCI_STACK_SIZE; . = ALIGN(CONSTANT(COMMONPAGESIZE)); KEEP(*(.__secure_stack_end)) } #ifndef __ARMV8_PSCI_STACK_IN_RAM . = LOADADDR(.secure_stack); #endif .__secure_end : AT(ADDR(.__secure_end)) { KEEP(*(.__secure_end)) LONG(0x1d1071c); /* Must output something to reset LMA */ } #endif . = ALIGN(8); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(8); .data : { *(.data*) } . = ALIGN(8); . = .; . = ALIGN(8); .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(8); .efi_runtime_rel : { __efi_runtime_rel_start = .; // Current segment start position, where . represents the current memory address. *(.rel*.efi_runtime) // Place all segments named .rel*.efi_runtime at the current position *(.rel*.efi_runtime.*) // Place all segments named .rel*.efi_runtime.* at the current position __efi_runtime_rel_stop = .; // Current segment end position, where . represents the current memory address. } . = ALIGN(8); .image_copy_end : { *(.__image_copy_end) } . = ALIGN(8); .rel_dyn_start : { *(.__rel_dyn_start) } .rela.dyn : { *(.rela*) } .rel_dyn_end : { *(.__rel_dyn_end) } _end = .; . = ALIGN(8); .bss_start : { KEEP(*(.__bss_start)); } .bss : { *(.bss*) . = ALIGN(8); } .bss_end : { KEEP(*(.__bss_end)); } /DISCARD/ : { *(.dynsym) } /DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) } /DISCARD/ : { *(.gnu*) } #ifdef CONFIG_LINUX_KERNEL_IMAGE_HEADER #include "linux-kernel-image-header-vars.h" #endif
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") OUTPUT_ARCH(aarch64) ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(8); .text : { *(.__image_copy_start) arch/arm/cpu/armv8/start.o (.text*) } .efi_runtime : { __efi_runtime_start = .; *(.text.efi_runtime*) *(.rodata.efi_runtime*) *(.data.efi_runtime*) __efi_runtime_stop = .; } .text_rest : { *(.text*) } . = ALIGN(8); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(8); .data : { *(.data*) } . = ALIGN(8); . = .; . = ALIGN(8); .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(8); .efi_runtime_rel : { __efi_runtime_rel_start = .; *(.rel*.efi_runtime) *(.rel*.efi_runtime.*) __efi_runtime_rel_stop = .; } . = ALIGN(8); .image_copy_end : { *(.__image_copy_end) } . = ALIGN(8); .rel_dyn_start : { *(.__rel_dyn_start) } .rela.dyn : { *(.rela*) } .rel_dyn_end : { *(.__rel_dyn_end) } _end = .; . = ALIGN(8); .bss_start : { KEEP(*(.__bss_start)); } .bss : { *(.bss*) . = ALIGN(8); } .bss_end : { KEEP(*(.__bss_end)); } /DISCARD/ : { *(.dynsym) } /DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) } /DISCARD/ : { *(.gnu*) }}