U-Boot源码分析4—链接脚本分析
- 1 u-boot-spl.lds
- 1.1 链接脚本的生成
- 1.2 u-boot-spl.lds内容分析
- 1.3 text - 程序代码段
- 1.4 `sram`其它段定义
- 1.4.1 `.rodata`只读数据段
- 1.4.2 `.data`数据段
- 1.4.3 `.u_boot_list`段
- 1.5 `BSS`段
- 1.6 `/DISCARD/`
从上一篇文章【嵌入式移植】6、U-Boot源码分析3—make可以知道U-Boot编译过程最后按照spl/u-boot-spl.lds
链接脚本进行链接,因此启动过程分析首先分析此链接脚本
1 u-boot-spl.lds
1.1 链接脚本的生成
从scripts/Makefile.spl
第105行~第129行可知,
由于$(srctree)/board/$(BOARDDIR)/u-boot-spl.lds
即./board/sunxi/u-boot-spl.lds
不存在,因此继续往下执行,LDSCRIPT
变量的值为$(srctree)/$(CPUDIR)/u-boot-spl.lds
即./arch/arm/cpu/armv8/u-boot-spl.lds
然后在第370行~371行,调用if_changed_dep
函数,根据./arch/arm/cpu/armv8/u-boot-spl.lds
的内容生成spl/u-boot-spl.lds
,因此可以看到此两文件基本一致(宏定义的值也转换过来了)
1.2 u-boot-spl.lds内容分析
这里对./arch/arm/cpu/armv8/u-boot-spl.lds
进行分析
第15行~18行定义了2段内存空间
其中CONFIG_SPL_TEXT_BASE
、CONFIG_SPL_MAX_SIZE
、CONFIG_SPL_BSS_START_ADDR
、CONFIG_SPL_BSS_MAX_SIZE
均在include/configs/sunxi-common.h
中定义(值与spl/u-boot-spl.lds
中的值一致)
#define CONFIG_SPL_TEXT_BASE 0x10060 /* sram start+header */
#define CONFIG_SPL_MAX_SIZE 0x7fa0 /* 32 KiB */
#define CONFIG_SPL_BSS_START_ADDR 0x4ff80000
#define CONFIG_SPL_BSS_MAX_SIZE 0x00080000 /* 512 KiB */
其中TEXT
为程序代码段,BSS
为Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序运行初始前会清0
第20行~22行指定输出格式,以及入口地址,这里为小端aarch64,入口地址为_start
接下来是各段的定义,SECTIONS
关键字表示后续将描述输出文件的内存布局,一般包含text
、rodata
、data
、bss
4种类型的段空间
1.3 text - 程序代码段
第25行~30行,定义了程序代码段
第30行的>.sram
表示将这一段放进上面定义的sram
对应的地址中,即放入0x10060
位置
起始的. = ALIGN(8);
表示首地址8字节对齐:其中.
为定位计数器,表示当前地址,这里为0x10060
;ALIGN(8)
表示插入填充字节,直到当前位置在 8 字节边界上对齐。这里首地址0x10060
已经为8字节对齐
随后存放.__image_copy_start
段,这里.__image_copy_start
在arch/arm/lib/section.c
文件中定义:
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
即定义了一个长度为0(不占存储空间)的字符数组,并通过attribute
属性声明,通过属性section
规定放在.__image_copy_start
段中;
对应的还有第47行~50行中的.__image_copy_end
char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
在链接脚本中,.__image_copy_start
放在代码段的前面,.__image_copy_end
放在.u_boot_list
段后面,由于其为0长度数组,不占用内存空间,因此仅表示当前的sram地址,作为U-Boot拷贝自身代码的起始地址和结束地址
接下来第28行将arch/arm/cpu/armv8/start.S
中的代码段单独拿出来,保证start.S文件编译后的代码放在最终生成的u-boot-spl
文件的最前面
第29行为所有其它文件编译的代码段
在u-boot-spl,map
文件中可查看编译生成的目标文件的内存分布,最开始是内存定义,可见定义了sram
和sdram
两段内存空间对应的起始地址和长度;随后为各段的定义,对于代码段,从0x10060
地址开始,8字节对齐,同时.__image_copy_start
符号对应的地址也为0x10060
,即U-Boot拷贝自身代码的起始地址;随后是 arch/arm/cpu/armv8/start.o
的代码段;最后是各个.o
文件的代码段,与链接脚本定义的一致
1.4 sram
其它段定义
第32行~57行为sram
中其它段的定义
1.4.1 .rodata
只读数据段
.rodata
为只读数据段,通常保存一些常量值等;这里同样首地址8字节对齐,然后通过SORT_BY_ALIGNMENT
,按照对齐需求以降序的方式排列在输出文件u-boot-spl
中,即大的对齐放在小的对齐前面,可以减少为了对齐需要的额外空间;通过SORT_BY_NAME
按照名字上升顺序排列在输出文件中;且这里是先按对齐方式排,再按名字排
1.4.2 .data
数据段
.data
为数据段,主要用于存放全局已初始化的数据段和已初始化的局部静态变量
1.4.3 .u_boot_list
段
.u_boot_list
段用于存放所有的U-Boot命令,所有通过attribute
属性声明放置在.u_boot_list*
的U-Boot命令都存放在此段,且通过SORT
关键字按照名称递增排序;通过KEEP
关键字保留所有.u_boot_list
段的内容,即使这一段中的符号在程序中未被直接引用
通过查找.u_boot_list
关键字,发现在include/linker_lists.h
中有相关内容,结合u-boot-spl,map
文件中相关内容,大多为.u_boot_list_2_xxx_1
、.u_boot_list_2_xxx_2_xx
、.u_boot_list_2_xxx_3
的形式
因此.u_boot_list
段来源与include/linker_lists.h
中的ll_entry_declare
、ll_entry_start
、ll_entry_end
有关:
#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_start(_type, _list) \
({ \
static char start[0] __aligned(4) __attribute__((unused, \
section(".u_boot_list_2_"#_list"_1"))); \
(_type *)&start; \
})
#define ll_entry_end(_type, _list) \
({ \
static char end[0] __aligned(4) __attribute__((unused, \
section(".u_boot_list_2_"#_list"_3"))); \
(_type *)&end; \
})
以ll_entry_declare
为例搜索
以cmd/help.c
中的内容为例
可知此.c
文件中定义了一个do_help
函数,并通过ll_entry_start
将.u_boot_list_2_cmd_1
的地址赋值给start
变量,这里.u_boot_list_2_cmd_1
正好在u-boot-spl.map
第2260行,这里顺便提一下,从2258行的u_boot_list_2_blk_driver_3
到2278行的.u_boot_list_2_spl_image_loader_2_spl_mmc_load_image0BOOT_DEVICE_MMC1
,其对应的地址均为0x0000000000017708
,这是因为程序中未直接引用这些符号,但通过KEEP
关键字仍进行保留。
最后调用_do_help
函数真正实现其功能,_do_help
函数位于common/command.c
文件中,事实上所有U-Boot命令均在此文件中定义
后续通过U_BOOT_CMD
宏定义
#define U_BOOT_CMD(_name,_maxargs,_rep,_cmd,_usage,_help) U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
Expands to:
cmd_tbl_t _u_boot_list_2_cmd_2_help __aligned(4) __attribute__((unused, section(".u_boot_list_2_""cmd""_2_""help"))) = { "help", 16, 1, do_help, "print command description/usage", "\n" " - print brief description of all commands\n" "help command ...\n" " - print detailed usage of 'command'", ((void *)0), };
即定义了一个cmd_tbl_t
结构体,并将其存放在.u_boot_list_2_cmd_2_help
段中
其它相关的宏定义位于include/command.h
文件中:
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
1.5 BSS
段
最后则是将.bss
段规划到外部存储sdram
中,并同样定义了两个空字符数组变量__bss_start
和__bss_end
指定其起始和结束地址
但这里.bss
段无需搬运,因为.bss
段存放的是未初始化的全局变量和局部静态变量,不占据实际的文件大小,只在段表中记录大小,在符号表中记录符号。当文件加载运行时,才分配空间以及初始化。所以实际.bss
段只会在运行时才分配空间,分配的空间起始地址也就是从.sdram
定义的空间里面。
此外,一般重定向也是将boot启动代码拷贝搬移到外部sdram中去运行,这里bss指定的地址本身就已经在sdram中了,因此要使用bss的数据,需要将外部sdram初始化后才能使用
1.6 /DISCARD/
/DISCARD/
关键字用于指定后续的段不会出现在输出文件中。
本章分析完毕~
完结撒花✿✿ヽ(°▽°)ノ✿