【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】004 - u-boot.lds 链接脚本源码 逐行深度解析

本文深度解析U-Boot 2024.07版本的u-boot.lds链接脚本,详细解读不同格式的编码输出、运行平台架构、入口符号、数据段排列以及只读数据段等内容,重点关注32位和64位指令集在启动过程中的作用。通过分析,揭示了u-boot如何根据配置进行内存管理和程序启动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】004 - u-boot.lds 链接脚本源码 逐行深度解析


系列文章汇总:《【鸿蒙OH-v5.0源码分析之 Uboot+Kernel 部分】000 - 文章链接汇总
本文链接:《【OpenHarmony4.1 之 U-Boot 2024.07源码深度解析】004 - u-boot.lds 链接脚本源码 逐行深度解析



一、u-boot.lds 链接脚本源码分析

u-boot 的链接脚本源码如下:

OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(8);
 __image_copy_start = ADDR(.text);
 .text :
 {
  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*)));
 }

 .efi_runtime_rel : {
                __efi_runtime_rel_start = .;
  *(.rel*.efi_runtime)
  *(.rel*.efi_runtime.*)
                __efi_runtime_rel_stop = .;
 }
 . = ALIGN(8);
 __image_copy_end = .;
 .rela.dyn : {
  __rel_dyn_start = .;
  *(.rela*)
  __rel_dyn_end = .;
 }
 _end = .;
 .bss ALIGN(8) : {
  __bss_start = .;
  *(.bss*)
  . = ALIGN(8);
  __bss_end = .;
 }
 /DISCARD/ : { *(.dynsym) }
 /DISCARD/ : { *(.dynstr*) }
 /DISCARD/ : { *(.dynamic*) }
 /DISCARD/ : { *(.plt*) }
 /DISCARD/ : { *(.interp*) }
 /DISCARD/ : { *(.gnu*) }
}



下面,我们一行一行来分析:

  1. OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
    定义支持输出文件的三种格式均为 ELF 格式,64ARMv8-A 指令集,小端存储


注意:
假设当这三种格式不同时,则不带参数时,默认采用第一种格式进行编码输出,
如果命令行选项含 `-EB` 参数,使用第二种格式进行编码输出,
如果命令行选项含 `-EL` 参数,使用第三种格式进行编码输出。 


  1. OUTPUT_ARCH(aarch64)
    定义输出可执行文件的运行平台为:aarch64
    ARMv8 架构分为 AArch6464位) 和 AArch3232位)两种架构执行状态。

    CPU 架构还包含如下 :
    (1)X86Intel 32位指令集
    (2)AMD6464位,AMD 最早推出的兼容 X86 32位指令集的 64位处理器
    (3)IA-64Intel 64 位指令集,不兼容 32位指令集
    (4)X86-6464位,IntelAMD64 指令集上扩充,更名为 x86-64,也叫 x64
    (5)ARM:含 8位、16位、32位,优点是低成本、高性能、低功耗
    (6)AArch32ARM-V8 架构的 32位执行态
    (7)AArch64ARM-V8 架构的 64位执行态



  1. ENTRY(_start)
    可执行程序的 入口符号含数为 _start
    定义在:u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S


  1. . = 0x00000000;
    定位符,定义此处 _start 的地址为 0x00000000,该地址为 U-BOOT 上电后开始跑的地址,
    该地址可以通过配置编译参数:-Ttext=$(CONFIG_TEXT_BASE) 进行修改,
    CONFIG_TEXT_BASE 一般以宏控的形式定义在configs

    如:u-boot-2024.07-rc3\configs\evb-px30_defconfig 定义为 CONFIG_TEXT_BASE=0x00200000
    注:u-boot-2024.07-rc3\configs\evb-rk3399_defconfig 未定义该宏控,因此还是默认的 0x00000000 地址



  1. . = ALIGN(8);
    配置代码段以 8byte 格式对齐,由于是 64位指令集,对应的地址线和数据线都是64根线,
    意味着,每一个地址对应的数据线均为64根数据线,也就是 64bit,对应 8byte

拓展下:
64位指令集系统,每个地址指针 指向的内存空间为 64 bit,8 byte
32位指令集系统,每个地址指针 指向的内存空间为 32 bit,4 byte



  1. __image_copy_start = ADDR(.text);
    标志启动运行时,镜像是从 .text 段的内容开始拷贝的,.text 最头部为 arch/arm/cpu/armv8/start.o.text 代码段


  1. .text 代码段
    u-boot.text 段结构为:
    start.o.text 代码段) + efi.text代码段)+ efi.rodata只读数据段)+ efi.data数据段)+ *.text(剩余所有的代码段)

    可以看出,引入兼容 efi 的主要目的是用于启动 x86 平台的架构, efi 的代码段和数据段,是直接链接在 start.o 之后的代码段中的。
    如果需要支持 uefi,可以配置
    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 架构,用不到 efi 启动,就不深入分析了。

 .text :
 {
  arch/arm/cpu/armv8/start.o (.text*)				// 代码段以 start.o 的代码段为开头
 }
 .efi_runtime : {
                __efi_runtime_start = .;			// efi runtime 的起始位置
  *(.text.efi_runtime*)								// efi runtime 的 代码段
  *(.rodata.efi_runtime*)							// efi runtime 的 只读数据段
  *(.data.efi_runtime*)								// efi runtime 的 数据段
                __efi_runtime_stop = .;				// efi runtime 的结束位置
 }
 .text_rest :
 {
  *(.text*)											// 剩余的所有代码的数据段,不区分先后
 }


  1. .rodata 只读数据段
 . = ALIGN(8);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

SORT_BY_NAME关键字:链接器会把文件或者段的名字按照上升顺序排序,然后再整理它的内容。
SORT_BY_ALIGNMENT:将所有文件的段,用降序方式排序,大的对齐被放在小的对齐前面,以减少为了对齐需要的额外空间。

注意:
.bss:存储 未初始化的全局变量 或 初始化为0 的全局变量,它实际不会占用内存,占用运行内存,不会导致文件size变大
.data:存储初始化不为 0 的全局变量,会占用内存,使文件size变大
.rodata:只读数据,如 const 修饰的全局变量,多个进程共享,提高空间利用率



  1. .data 数据段:保存所有源码的 data段,不区分先后顺序
 . = ALIGN(8);
 .data : {
  *(.data*)
 }


  1. __u_boot_list:存放 uboot 所有驱动程序的 __u_boot_list_结构体 或 __u_boot_list_变量
    对其进行排序和对齐,并将排序后的结果存放在__u_boot_list 内存段中。
 . = ALIGN(8);
 . = .;
 . = ALIGN(8);
 __u_boot_list : {
  KEEP(*(SORT(__u_boot_list*)));
 }

C语言函数命令规则: __u_boot_list_ + 2_ + @_list + _2_ + @_entry
如:

  __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	

其定义如下:

# u-boot-2024.07-rc3\include\linker_lists.h
#define llsym(_type, _name, _list) \
		((_type *)&_u_boot_list_2_##_list##_2_##_name)

#define U_BOOT_PCI_DEVICE(__name, __match)				\
	ll_entry_declare(struct pci_driver_entry, __name, pci_driver_entry) = {\
		.driver = llsym(struct driver, __name, driver), \
		.match = __match, \
		}
#define ll_entry_declare(_type, _name, _list)				\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
			__attribute__((unused))				\
			__section("__u_boot_list_2_"#_list"_2_"#_name)

#define ll_entry_declare_list(_type, _name, _list)			\
	_type _u_boot_list_2_##_list##_2_##_name[] __aligned(4)		\
			__attribute__((unused))				\
			__section("__u_boot_list_2_"#_list"_2_"#_name)

U_BOOT_PCI_DEVICE(ide, ide_supported); 为例,扩展开来为:

ll_entry_declare(struct pci_driver_entry, ide, pci_driver_entry) = 
{
	.driver = llsym(struct driver, ide, driver), 
		----------> ((struct driver *)&_u_boot_list_2_driver_2_ide)
	.match = ide_supported, 
}
_type _u_boot_list_2_pci_driver_entry_2_ide __aligned(4)		\
	__attribute__((unused))				\
	__section("__u_boot_list_2_pci_driver_entry_2_ide")		

可以看到,通过 U_BOOT_PCI_DEVICE(ide, ide_supported) 这个宏控,就实现定义了一个 __u_boot_list_2_pci_driver_entry_2_ide 函数。
它的内容为:
.driver = ((struct driver *)&_u_boot_list_2_driver_2_ide)
.match = ide_supported,

ps: 如果想知道uboot 所有使用__u_boot_list的驱动有哪些,可以直接整套代码搜索 ll_entry_declare
你会发现每个驱动都定义了一个宏控,示例如下:
#define U_BOOT_PHY_DRIVER(__name) \
ll_entry_declare(struct phy_driver, __name, phy_driver)



  1. .efi_runtime_rel 段:用于存储EFI运行时重定位表(EFI Runtime Relocation Table),便于其他代码在运行时进行动态定位和加载
 .efi_runtime_rel : {
                __efi_runtime_rel_start = .;	// 当前段的起始位置,其中.代表当前内存地址。
  *(.rel*.efi_runtime)							// 把所有名为.rel*.efi_runtime的段内容放到当前位置
  *(.rel*.efi_runtime.*)						// 把所有名为.rel*.efi_runtime.*的段内容放到当前位置
                __efi_runtime_rel_stop = .;		// 当前段的结束位置,其中.代表当前内存地址。
 }


  1. .rela.dyn 段:用于存储 uboot 重定位表
 . = ALIGN(8);
 __image_copy_end = .;
 .rela.dyn : {
  __rel_dyn_start = .;
  *(.rela*)
  __rel_dyn_end = .;
 }


  1. 结束地址,实际 的可执行文件内容,到这为止了,ELF 文件,可统计的文件大小,就是到这里的大小
    _end = .;


  1. .bss 堆内存段:用来存放程序中未初始化的全局变量和静态变量
    “bss”block started by symbol的缩写,用来存放程序中未初始化的全局变量和静态变量
    它们是在运行时动态分配的,并不会占用 ELF 文件的内存
 _end = .;
 .bss ALIGN(8) : {
  __bss_start = .;			// bss 堆段的起始地址
  *(.bss*)					// 所有 .bss 的内容
  . = ALIGN(8);
  __bss_end = .;			// bss 堆段的结束地址
 }


  1. 忽略的 section 段
    在实际运行时,通常不需要这些用于解析和链接的信息,所以在链接时把这些段丢弃,可以降低程序的大小,节省内存
    /DISCARD/是 一个特殊的输出段名,表示链接器应该丢弃匹配的输入段,而不是将它们放入任何输出段中。
 /DISCARD/ : { *(.dynsym) }			// .dynsym这个section会被丢弃,用来存放动态链接的符号表
 /DISCARD/ : { *(.dynstr*) }		// 丢弃.dynstr*类型的段,用来存放动态链接的字符串表
 /DISCARD/ : { *(.dynamic*) }		// 丢弃.dynamic*类型的段,用来存放动态链接的信息
 /DISCARD/ : { *(.plt*) }			// 丢弃.plt*类型的段,包含了过程调用的指令
 /DISCARD/ : { *(.interp*) }		// 丢弃.interp*类型的段,包含了程序运行时解析器的路径
 /DISCARD/ : { *(.gnu*) }			// 丢弃.gnu*类型的段,包含了特定系统或者编译器特有的信息


至此,整个链接脚本我们分析完了,我们来汇总下, U-Boot 文件格式如下:

在这里插入图片描述






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小馋喵星人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值