针对u-boot的spl一直想研究,苦于一直没时间,最近手上有个主线uboot没有支持的板子,想用来研究下uboot的spl过程,现在将研究过程记录下,首先我们是armv8的板子,首先看下编译链接文件arch/arm/cpu/armv8/u-boot-spl.lds,这里会定义文件的起始啥的
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*) }
#ifdef CONFIG_LINUX_KERNEL_IMAGE_HEADER
#include "linux-kernel-image-header-vars.h"
#endif
}
编译后生成的文件如下,脚本的最开始定义了两段内存空间,分别为sram和sdram的起始地址和长度。一般来说,这两段空间对应的就是CPU内部的sram和单板外部的sdram如ddr。
MEMORY { .sram : ORIGIN = 0x28060,// 这里使用的起始地址为0x28060
LENGTH = 0xc000 } //这里是定义spl的大小,取用了内部sram的一段ddr
MEMORY { .sdram : ORIGIN = 0x4ff80000,
LENGTH = 0x80000 } //这里为524288字节,即为512KB大小
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") //指定为小端格式
OUTPUT_ARCH(aarch64)//指定平台为arm64
ENTRY(_start) //Entry用于指定指定入口地址,注意这里使用的是代码中定义的_start符号,该符号定义在start.S,arch\arm\cpu\armv8 最上方,也就是整个SPL-UBOOT的入口
SECTIONS
{
.text : {
. = ALIGN(8);
__image_copy_start = .;
arch/arm/cpu/armv8/start.o (.text*)
*(.text*)
} >.sram
/*
上面这段定义了一段代码段,并指示连接器将其放入到上面定义的.sram内存区域中。其中.ALIGN(8)说明首地址8字节对齐。随后紧接着是存放.__image_copy_start段,该段定义在arch/arm/lib/sections.c中,定义如下
```c
char __bss_start[0] __section(".__bss_start");
char __bss_end[0] __section(".__bss_end");
char __image_copy_start[0] __section(".__image_copy_start");
char __image_copy_end[0] __section(".__image_copy_end");
char __rel_dyn_start[0] __section(".__rel_dyn_start");
char __rel_dyn_end[0] __section(".__rel_dyn_end");
char __secure_start[0] __section(".__secure_start");
char __secure_end[0] __section(".__secure_end");
char __secure_stack_start[0] __section(".__secure_stack_start");
char __secure_stack_end[0] __section(".__secure_stack_end");
char __efi_runtime_start[0] __section(".__efi_runtime_start");
char __efi_runtime_stop[0] __section(".__efi_runtime_stop");
char __efi_runtime_rel_start[0] __section(".__efi_runtime_rel_start");
char __efi_runtime_rel_stop[0] __section(".__efi_runtime_rel_stop");
char _end[0] __section(".__end");
从上面代码可以看出,定义一个0长度也就是不占存储空间的字符数组,并将其规定放置到.__image_copy_start段中,注意该段位于.text段之前,紧接着便是start.o的代码段以及所有其他文件的.text段。这里之所以要将start.o的代码段单独拎出来,主要的目的在于确保start.s文件编译后的代码段位于最终生成spl-uboot文件代码段的最前面
*/
.rodata : {
. = ALIGN(8);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
} >.sram
/*
rodata段用于存放只读数据段。通常,链接器会把匹配的文件和段按照发现的顺序放置,此外可以使用关键字按照一定规则进行顺序修改。其中SORT_BY_ALIGNMENT对段的对齐需求使用降序方式排序放入输出文件中,大的对齐被放在小的对齐前面,这样可以减少为了对齐需要的额外空间。而SORT_BY_NAME关键字则会让链接器将文件或者段的名字按照上升顺序排序后放入输出文件。这里SORT_BY_ALIGNMENT(SORT_BY_NAME(wildcard section pattern)). 先按对齐方式排,再按名字排
*/
.data : {
. = ALIGN(8);
*(.data*)
} >.sram
/*
.data数据段主要用于存放全局已初始化的数据段和已初始化的局部静态变量。
*/
__u_boot_list : {
. = ALIGN(8);
KEEP(*(SORT(__u_boot_list*)));
} >.sram
/*
u_boot_list段用于存放所有的uboot命令
*/
.image_copy_end : {
. = ALIGN(8);
*(.__image_copy_end)
} >.sram
.end : {
. = ALIGN(8);
*(.__end)
} >.sram
_image_binary_end = .;
/*
接下来与__image_copy_start对应,同样定义一个不占空间的空字符数组__image_copy_end用于表示拷贝结尾地址。其实__image_copy_start和__image_copy_end主要作用是用于重定位,即可以知道将什么地址开始,什么地址结束的这一段空间中的代码和数据段进行搬运,并且它们是不占空间的,举例来说,我们知道对于A133cpu 来说,sram起始地址是0x28060, 本身就是8字节对齐的,所以的__image_copy_start存放的地址就是0x28060,而__image_copy_start本身就一个数组名,也即是表示该数组存放的地址,所以其值即为0x28060。随后定义的__end段以及将当前地址赋值给_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
/*
最后则是将.bss段规划到外部存储sdram中,并同样定义了两个空字符数组变量__bss_start和__bss_end指定其起始和结束地址。为什么bss段和其他段不同无需搬运?因为首先bss段存放的是未初始化的全局变量和局部静态变量,.bss不占据实际的文件大小,只在段表中记录大小,在符号表中记录符号。当文件加载运行时,才分配空间以及初始化。所以实际.bss段只会在运行时才分配空间,分配的空间起始地址也就是从.sdram定义的空间里面。此外,一般重定向也是将boot拷贝搬移到外部sdram中去运行,而对于cpu来说这里bss指定的地址本身就已经在sdram中了。这样也说明了,对于cpu来说,要使用bss的数据,需要将外部sdram初始化后才能使用
*/
/DISCARD/ : { *(.rela*) }
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
最后有几个特殊的输出section,名为/DISCARD/,被该section引用的任何输入section将不会出现在输出文件内,分析完lds文件后,就可以开始进行源码分析了,首先我们需要明确代码起始的点,从上面的lds文件我们可以知道代码是从arch/arm/cpu/armv8/start.S
开始运行的