Uboot-3链接脚本lds分析

发布时间 2023-12-15 10:39:47作者: fuzidage

1 u-boot.lds解读(armv8)

文件位于u-boot-2021.10\arch\arm\cpu\armv8\u-boot.lds。分析过程已在lds内部注释了.

/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * (C) Copyright 2013
 * David Feng <fenghua@phytium.com.cn>
 *
 * (C) Copyright 2002
 * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de>
 */

#include <config.h>
#include <asm/psci.h>

OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start) -------------------------------------------------------------------- (1)
/*
 *(1)首先定义了二进制程序的输出格式为"elf64-littleaarch64",
 *    架构是"aarch64",程序入口为"_start"符号;
 */
SECTIONS
{
#ifdef CONFIG_ARMV8_SECURE_BASE -------------------------------------------------- (2)
/*
 *(2)ARMV8_SECURE_BASE是u-boot对PSCI的支持,在定义时可以将PSCI的文本段,
 *    数据段,堆栈段重定向到指定的内存,而不是内嵌到u-boot中。
 *    不过一般厂商实现会使用atf方式使其与bootloader分离,这个功能不常用;
 */
 /DISCARD/ : { *(.rela._secure*) }
#endif
 . = 0x00000000; -------------------------------------------------------------- (3)
/*
 *(3)定义了程序链接的基地址,默认是0,通过配置CONFIG_SYS_TEXT_BASE可修改
 *    这个默认值。
 */
 . = ALIGN(8);
 .text :
 {
  *(.__image_copy_start) --------------------------------------------------- (4)
/*
 *(4)__image_copy_start和__image_copy_end用于定义需要重定向的段,
 *    u-boot将启动初始化分为了两个部分,重定向前初始化board_f和
 *    重定向后初始化  board_r,在重定向之前完成一些必要初始化,
 *    包括可能的ddr初始化,然后通过__image_copy_start和__image_copy_end
 *    将u-boot搬运到ddr中,并在ddr中进行重定向后初始化。
  CPUDIR/start.o (.text*) -------------------------------------------------- (5)
/*
 *(5)定义了链接程序的头部文本段,armv8就是
 *    arch/arm/cpu/armv8/start.S,
 *    start.S中所有文本段将会链接到此段中并且段入口符号就是_start;
 */
 }

 /* This needs to come before *(.text*) */
 .efi_runtime : { ------------------------------------------------------------ (6)
/*
 *(6)在定义了efi运行时相关支持时才会出现使用的段,一般不用关心;
 */
		__efi_runtime_start = .;
  *(.text.efi_runtime*)
  *(.rodata.efi_runtime*)
  *(.data.efi_runtime*)
		__efi_runtime_stop = .;
 }

 .text_rest : ---------------------------------------------------------------- (7)
/*
 *(7)除了start.o,其他的所有文本段将会链接到此段中;
 */
 {
  *(.text*)
 }

#ifdef CONFIG_ARMV8_PSCI -------------------------------------------------------- (8)
/*
 *(8)同(2),是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*))) } ------------------- (9)
/*
 *(9)所有仅读数据将会在这个段中对齐排序存放好;
 */

 . = ALIGN(8);
 .data : { -------------------------------------------------------------------- (10)
/*
 *(10)所有数据段将会链接到此段中;
 */
  *(.data*)
 }

 . = ALIGN(8);

 . = .;

 . = ALIGN(8);
 .u_boot_list : { ------------------------------------------------------------- (11)
/*
 *(11)u_boot_list段定义了系统中当前支持的所有命令和设备驱动,此段把散落在各个文件中
 *     通过U_BOOT_CMD的一系列拓展宏定义的命令和U_BOOT_DRIVER的拓展宏定义的设备驱动收集到一起,
 *     并按照名字排序存放,以便后续在命令行快速检索到命令并执行和检测注册的设备和设备树匹配
 *     probe设备驱动初始化;(设备驱动的probe只在定义了dm模块化驱动时有效)
 */
  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 : -------------------------------------------------------- (12)
/*
 *(12)一般u-boot运行时是根据定义的基地址开始执行,如果加载地址和链接地址
 *     不一致则会出现不能执行u-boot的问题。通过一个
 *     配置CONFIG_POSITION_INDEPENDENT即可打开地址无关功能,
 *     此选项会在链接u-boot时添加-PIE参数。此参数会在u-boot ELF文件中
 *     生成rela*段,u-boot通过读取此段中表的相对地址值与实际运行时地址值
 *     依次遍历进行修复当前所有需要重定向地址,使其可以实现地址无关运行;
 *     即无论链接基地址如何定义,u-boot也可以在任意ram地址
 *     运行(一般需要满足最低4K或者64K地址对齐);
 * 
 *     注意此功能只能在sram上实现,因为此功能会在运行时修改文本段数据段中的地址,
 *     如果此时运行在片上flash,则不能写flash,导致功能失效无法实现地址无关;
 */
 {
  *(.__rel_dyn_start)
 }

 .rela.dyn : {
  *(.rela*)
 }

 .rel_dyn_end :
 {
  *(.__rel_dyn_end)
 }

 _end = .;

 . = ALIGN(8);

 .bss_start : { -------------------------------------------------------- (13)
/*
 *(13)众所周知的bbs段;
 */
  KEEP(*(.__bss_start));
 }

 .bss : {
  *(.bss*)
   . = ALIGN(8);
 }

 .bss_end : {
  KEEP(*(.__bss_end));
 }

 /DISCARD/ : { *(.dynsym) } -------------------------------------------- (14)
/*
 *(14)一些在链接时无用需要丢弃的段;
 */
 /DISCARD/ : { *(.dynstr*) }
 /DISCARD/ : { *(.dynamic*) }
 /DISCARD/ : { *(.plt*) }
 /DISCARD/ : { *(.interp*) }
 /DISCARD/ : { *(.gnu*) }

#ifdef CONFIG_LINUX_KERNEL_IMAGE_HEADER ----------------------------------- (15)
/*
 *(15)在efi加载时会很有用,主要在u-boot的二进制头部添加了一些头部信息,
 *     包括大小端,数据段文本段大小等,以便于efi相关的加载器读取信息,
 *     此头部信息来自于Linux arm64的Image的头部信息;该头部也不属于u-boot的
 *     一部分只是被附加上去的;
 */
#include "linux-kernel-image-header-vars.h"
#endif
}

2 u-boot-spl.ldc解读(armv8)

文件位于u-boot-2021.10\arch\arm\cpu\armv8\u-boot-spl.lds。一般u-boot-spl只有很小的可运行内存块,所以spl中会舍去大量不需要用的段只保留关键的文本段数据段等,并且通过>.sram的形式将不在ddr初始化前用到的段定义到sdram中。

/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * (C) Copyright 2013
 * David Feng <fenghua@phytium.com.cn>
 *
 * (C) Copyright 2002
 * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de>
 *
 * (C) Copyright 2010
 * Texas Instruments, <www.ti.com>
 *	Aneesh V <aneesh@ti.com>
 */

MEMORY { .sram : ORIGIN = IMAGE_TEXT_BASE,
		LENGTH = IMAGE_MAX_SIZE }
MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR,
		LENGTH = CONFIG_SPL_BSS_MAX_SIZE }

OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SECTIONS
{
	.text : {
		. = ALIGN(8);
		*(.__image_copy_start)
		CPUDIR/start.o (.text*)
		*(.text*)
	} >.sram

	.rodata : {
		. = ALIGN(8);
		*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
	} >.sram

	.data : {
		. = ALIGN(8);
		*(.data*)
	} >.sram

#ifdef CONFIG_SPL_RECOVER_DATA_SECTION
	.data_save : {
		*(.__data_save_start)
		. = SIZEOF(.data);
		*(.__data_save_end)
	} >.sram
#endif

	.u_boot_list : {
		. = ALIGN(8);
		KEEP(*(SORT(.u_boot_list*)));
	} >.sram

	.image_copy_end : {
		. = ALIGN(8);
		*(.__image_copy_end)
	} >.sram

	.end : {
		. = ALIGN(8);
		*(.__end)
	} >.sram

	_image_binary_end = .;

	.bss_start (NOLOAD) : {
		. = ALIGN(8);
		KEEP(*(.__bss_start));
	} >.sdram

	.bss (NOLOAD) : {
		*(.bss*)
		 . = ALIGN(8);
	} >.sdram

	.bss_end (NOLOAD) : {
		KEEP(*(.__bss_end));
	} >.sdram

	/DISCARD/ : { *(.rela*) }
	/DISCARD/ : { *(.dynsym) }
	/DISCARD/ : { *(.dynstr*) }
	/DISCARD/ : { *(.dynamic*) }
	/DISCARD/ : { *(.plt*) }
	/DISCARD/ : { *(.interp*) }
	/DISCARD/ : { *(.gnu*) }
}

链接脚本的开头定义了两段内存空间,分别定义了sram和sdram的起始地址和长度。在i.MX8中,include/config/imx8mp_evk.h, 这两段定义对应于CPU内部的sram和外部的ddr。
image

这里定义了spl-uboot两段空间,一段是从0x920000开始的152K空间,这段空间是内部RAM中的一段。
而0x96e000开始的8K空间则是用来存放未初始化的全局变量和未初始化的静态局部变量的BSS数据段,位于外部存储即SDRAM如DDR上,如下图:
image
bss段存放的是未初始化的全局变量和局部静态变量,.bss不占据实际的文件大小,只在段表中记录大小,在符号表中记录符号。当文件加载运行时,才分配空间以及初始化。所以实际.bss段只会在运行时才分配空间,分配的空间起始地址也就是从.sdram定义的空间里面。此外,一般重定向也是将boot拷贝搬移到外部sdram中去运行,而对于cpu来说这里bss指定的地址本身就已经在sdram中了。这样也说明了,对于 cpu来说,要使用bss的数据,需要将外部sdram初始化后才能使用。s3c2440裸机-清bss原理及实现

3 u-boot.lds (armv7)

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }
 . = 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);
 .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.*) }
}

_start 在文件 arch/arm/lib/vectors.S 中有定义,表示代码执行入口,也就是第一条指令要放的位置。注意armv7的入口在vectors.S(armv8在start.s),中断向量表放在指令入口最开始的位置:可以看到—_start后面就是中断向量表,从图中的“.section ".vectors", "ax”可以得到,此代码存放在.vectors 段里面。

image
打开u-boot.map如下图:因此代码段的排列顺序为:先放中断向量表,也就是vectors.s,然后再放start.s相关内容,最后放其他的.text段(一大堆built-in.o)。
image
image
注意这里为什么uboot.map中_start入口地址为什么是0x8780,0000。 链接脚本指定了程序的运行(链接)地址:
程序链接时会指定程序的运行(链接)地址:

arm-linux-gnueabihf-ld.bfd   -pie  --gc-sections -Bstatic -Ttext 0x87800000 -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
arch/arm/imx-common/built-in.o
arch/arm/lib/built-in.o
board/freescale/common/built-in.o
......
-L /media/cvitek/robin.lee/my_test/study/openedv/toolchain/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/../lib/gcc/arm-linux-gnueabihf/4.9.4
-lgcc -Map u-boot.map

运行地址0x87800000定义在:

include/configs/mx6_common.h:86:#define CONFIG_SYS_TEXT_BASE    0x87800000