【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*) }
}
下面,我们一行一行来分析:
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
定义支持输出文件的三种格式均为ELF
格式,64
位ARMv8-A
指令集,小端存储
注意:
假设当这三种格式不同时,则不带参数时,默认采用第一种格式进行编码输出,
如果命令行选项含 `-EB` 参数,使用第二种格式进行编码输出,
如果命令行选项含 `-EL` 参数,使用第三种格式进行编码输出。
-
OUTPUT_ARCH(aarch64)
定义输出可执行文件的运行平台为:aarch64
ARMv8
架构分为AArch64
(64
位) 和AArch32
(32
位)两种架构执行状态。CPU 架构还包含如下 :
(1)X86
:Intel
32
位指令集
(2)AMD64
:64
位,AMD 最早推出的兼容 X86 32位指令集的 64位处理器
(3)IA-64
:Intel 64
位指令集,不兼容 32位指令集
(4)X86-64
:64
位,Intel
在AMD64
指令集上扩充,更名为x86-64
,也叫x64
(5)ARM
:含 8位、16位、32位,优点是低成本、高性能、低功耗
(6)AArch32
:ARM-V8
架构的32
位执行态
(7)AArch64
:ARM-V8
架构的64
位执行态
ENTRY(_start)
,
可执行程序的 入口符号含数为_start
定义在:u-boot-2024.07-rc3\arch\arm\cpu\armv8\start.S
中
-
. = 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
地址
. = ALIGN(8);
配置代码段以 8byte 格式对齐,由于是 64位指令集,对应的地址线和数据线都是64根线,
意味着,每一个地址对应的数据线均为64根数据线,也就是 64bit,对应 8byte
拓展下:
64位指令集系统,每个地址指针 指向的内存空间为 64 bit,8 byte
32位指令集系统,每个地址指针 指向的内存空间为 32 bit,4 byte
__image_copy_start = ADDR(.text);
标志启动运行时,镜像是从 .text 段的内容开始拷贝的,.text
最头部为arch/arm/cpu/armv8/start.o
的.text
代码段
-
.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*) // 剩余的所有代码的数据段,不区分先后
}
.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
修饰的全局变量,多个进程共享,提高空间利用率
.data
数据段:保存所有源码的 data段,不区分先后顺序
. = ALIGN(8);
.data : {
*(.data*)
}
__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)
.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 = .; // 当前段的结束位置,其中.代表当前内存地址。
}
.rela.dyn
段:用于存储 uboot 重定位表
. = ALIGN(8);
__image_copy_end = .;
.rela.dyn : {
__rel_dyn_start = .;
*(.rela*)
__rel_dyn_end = .;
}
- 结束地址,实际 的可执行文件内容,到这为止了,ELF 文件,可统计的文件大小,就是到这里的大小
_end = .;
.bss
堆内存段:用来存放程序中未初始化的全局变量和静态变量
“bss”
是block started by symbol
的缩写,用来存放程序中未初始化的全局变量和静态变量
它们是在运行时动态分配的,并不会占用 ELF 文件的内存
_end = .;
.bss ALIGN(8) : {
__bss_start = .; // bss 堆段的起始地址
*(.bss*) // 所有 .bss 的内容
. = ALIGN(8);
__bss_end = .; // bss 堆段的结束地址
}
- 忽略的 section 段
在实际运行时,通常不需要这些用于解析和链接的信息,所以在链接时把这些段丢弃,可以降低程序的大小,节省内存
/DISCARD/
是 一个特殊的输出段名,表示链接器应该丢弃匹配的输入段,而不是将它们放入任何输出段中。
/DISCARD/ : { *(.dynsym) } // .dynsym这个section会被丢弃,用来存放动态链接的符号表
/DISCARD/ : { *(.dynstr*) } // 丢弃.dynstr*类型的段,用来存放动态链接的字符串表
/DISCARD/ : { *(.dynamic*) } // 丢弃.dynamic*类型的段,用来存放动态链接的信息
/DISCARD/ : { *(.plt*) } // 丢弃.plt*类型的段,包含了过程调用的指令
/DISCARD/ : { *(.interp*) } // 丢弃.interp*类型的段,包含了程序运行时解析器的路径
/DISCARD/ : { *(.gnu*) } // 丢弃.gnu*类型的段,包含了特定系统或者编译器特有的信息
至此,整个链接脚本我们分析完了,我们来汇总下, U-Boot 文件格式如下: