一. 前言
了解OpenSBI的镜像的整体架构, 可以先从链接脚本入手,这一篇就来分享OpenSBI的链接脚本,看程序镜像是如何布局的。
二. 链接脚本生成与生成elf
先来看炼焦脚本在哪里,如何生成elf的。
opensbi/Makefile中
$(platform_build_dir)/%.ld: $(src_dir)/%.ldS
$(call compile_cpp,$@,$<)
依赖输入文件即$(src_dir)/%.ldS
即opensbi/%.ldS
$<表示第一个依赖文件
$@表示目标文件
compile_cpp = $(CMD_PREFIX)mkdir -p `dirname $(1)`; \
echo " CPP $(subst $(build_dir)/,,$(1))"; \
$(CPP) $(CPPFLAGS) -x c $(2) | grep -v "\#" > $(1)
对应编译打印
CPP platform/generic/firmware/fw_dynamic.elf.ld
mkdir -p `dirname $(1)即创建目标文件elf.ld,比如build/platform/generic/firmware/fw_jump.elf.ld
-x c 表示表示按照c编译
$(CPP) $(CPPFLAGS) -x c $(2) 表示从参数2即$(src_dir)/%.ldS,进行编译,
输出到参数1即elf.ld文件。
grep -v "\#"表示只输出不匹配\#的行,即删除注释。
所以以上就是将lds文件,按照c预处理且删除注释,输出为elf.ld文件。
链接脚本来源于opensbi/firmware/*.elf.ldS,最终生成opensbi/build/platform/generic/firmware/*.elf.ld使用。
以下就是根据elf.ld文件和.a文件链接生成elf文件。
$(platform_build_dir)/%.elf: $(platform_build_dir)/%.o $(platform_build_dir)/%.elf.ld $(platform_build_dir)/lib/libplatsbi.a
$(call compile_elf,$@,$@.ld,$< $(platform_build_dir)/lib/libplatsbi.a)
platform_build_dir为目录build/platform/generic
%表示匹配0或者若干个字符
$<表示第一个依赖文件
$@表示目标文件
compile_elf = $(CMD_PREFIX)mkdir -p `dirname $(1)`; \
echo " ELF $(subst $(build_dir)/,,$(1))"; \
$(CC) $(CFLAGS) $(3) $(ELFFLAGS) -Wl,-T$(2) -o $(1)
-T$(2)其中-T表示指定链接脚本,$(2)表示第二个参数,即$(platform_build_dir)/%.elf.ld
-o $(1)输出为第一个参数$(platform_build_dir)/%.o
$(3)是输入文件即第三个参数$(platform_build_dir)/lib/libplatsbi.a
-Wl表示将后面的参数传递给连接器ld
三. 链接脚本解读
接下来来看链接脚本的内容
fw_base.ldS
几个镜像fw_dynamic,fw_jump,fw_payload都是基于fw_base.lds的,前两者和fw_base一样,后者在fw_base基础上后面加了payload段。
我们来看fw_base.lds的内容:
基地址
1. . = FW_TEXT_START;
2. /* Don't add any section between FW_TEXT_START and _fw_start */
3. PROVIDE(_fw_start = .);
(1).表示当前位置, .=FW_TEXT_START;表示当前位置链接地址是FW_TEXT_START, 而FW_TEXT_START这个宏可以在make命令行时指定,比如make PLATFORM=generic FW_TEXT_START=0x80000000, 也可以在opensbi/platform/generic/objects.mk中指定比如
# Blobs to build
FW_TEXT_START=0x80000000
FW_DYNAMIC=y
FW_JUMP=y
最终会在opensbi/firmware/objects.mk中
指定编译器宏-DFW_TEXT_START
ifdef FW_TEXT_START
firmware-genflags-y += -DFW_TEXT_START=$(FW_TEXT_START)
else
firmware-genflags-y += -DFW_TEXT_START=0x0
endif
代码段text
(1)PROVIDE(_fw_start=.); 表示提供一个编译器符号,_fw_start其地址为当前位置的地址。
代码中可以使用该符号_fw_start. 比如汇编代码指令
lla a0, _fw_start即获取该地址值。
1. . = ALIGN(0x1000); /* Need this to create proper sections */
2. /* Beginning of the code section */
3. .text :
4. {
5. PROVIDE(_text_start = .);
6. *(.entry)
7. *(.text)
8. . = ALIGN(8);
9. PROVIDE(_text_end = .);
10. }
11. /* End of the code sections */
(1).=ALIGN(0x1000); 表示从当前位置开始需要0x1000大小对齐,即后面的.text段要0x1000大小对齐。
(3) .text:{} 定义了.text段。
(5)PROVIDE(_text_start=.); 提供了一个编译器符号_text_start, 代码可以通过其获取text段的基地址。
(6).entry和.text段都放在这里
(8) .=ALIGN(8) 表示当前位置8字节对齐,即本段大小8字节对齐。
(9) PROVIDE(_text_end=.); 提供了一个编译器符号_text_end, 代码可以通过其获取text段的结束地址。
Rodata段
1. . = ALIGN(0x1000); /* Ensure next section is page aligned */
2. /* Beginning of the read-only data sections */
3. PROVIDE(_rodata_start = .);
4. .rodata :
5. {
6. *(.rodata .rodata.*)
7. . = ALIGN(8);
8. }
9. .dynsym :
10. {
11. *(.dynsym)
12. }
13. . = ALIGN(0x1000); /* Ensure next section is page aligned */
14. .rela.dyn : {
15. PROVIDE(__rel_dyn_start = .);
16. *(.rela*)
17. PROVIDE(__rel_dyn_end = .);
18. }
19. PROVIDE(_rodata_end = .);
20. /* End of the read-only data sections */
(1)本段还是从0x1000对齐地址开始
(2)(19) 提供本段基地址,结束地址符号,_rodata_start,_rodata_end
(3).rodata .rodata.*段大小8字节对齐,放在.rodata段中
(9).dynsym段放在.dynsym段中,动态链接中的动态符号表。
(13)rela.dyn段也是从0x1000对齐地址开始.rela*放在该段中,提供两个符号__rel_dyn_start,__rel_dyn_end表示其开始和结束地址。保存的是重定位信息,后面重定位处理需要用到。
Rwdata
包括rwdata段和bss段
1. /*
2. * PMP regions must be to be power-of-2. RX/RW will have separate
3. * regions, so ensure that the split is power-of-2.
4. */
5. . = ALIGN(1 << LOG2CEIL((SIZEOF(.rodata) + SIZEOF(.text)
6. + SIZEOF(.dynsym) + SIZEOF(.rela.dyn))));
7. PROVIDE(_fw_rw_start = .);
8. /* Beginning of the read-write data sections */
9. .data :
10. {
11. PROVIDE(_data_start = .);
12. *(.sdata)
13. *(.sdata.*)
14. *(.data)
15. *(.data.*)
16. *(.readmostly.data)
17. *(*.data)
18. . = ALIGN(8);
19. PROVIDE(_data_end = .);
20. }
21. . = ALIGN(0x1000); /* Ensure next section is page aligned */
22. .bss :
23. {
24. PROVIDE(_bss_start = .);
25. *(.sbss)
26. *(.sbss.*)
27. *(.bss)
28. *(.bss.*)
29. . = ALIGN(8);
30. PROVIDE(_bss_end = .);
31. }
32. /* End of the read-write data sections */
(5) . = ALIGN(1 << LOG2CEIL((SIZEOF(.rodata) + SIZEOF(.text)
+ SIZEOF(.dynsym) + SIZEOF(.rela.dyn))));
表示前面四个段的大小,按照2的指数倍数对齐。
(7)提供符号_fw_rw_start
(9).data段,.sdata,.sdata.*,.data,.data.*,*.data,.readmostly.data(__read_mostly指定的变量)都放在这里,且大小是8字节对齐。提供符号_data_start,_data_end表示开始和结束地址。
(22) .bss段,.sbss,.sbss.*,.bss.*,.bss都放在这里,大小8字节对齐。提供_bss_start和_bss_end符号表示开始结束地址。
1. . = ALIGN(0x1000); /* Need this to create proper sections */
2. PROVIDE(_fw_end = .);
(1)提供结束地址符号_fw_end, 该地址0x1000对齐。
下一阶段执行地址
payload模式,执行payload_bin
Jump模式,FW_JUMP_ADDR或者FW_JUMP_OFFSET+_fw_start
Dynamic模式,地址_dynamic_next_addr处的值,a2寄存器传入参数,FW_DYNAMIC_INFO_NEXT_ADDR_OFFSET(a2)
栈和SCRATCH空间
_fw_end开始为栈和scratch空间,
每个hart的栈大小为platform.hart_stack_size
该大小又划分为栈空间下面浅绿色部分,和scratch部分橘色部分
fw_base.s的如下代码,设置SP寄存器和scratch寄存器。
四.总结
以上分享了各个模式,lds链接脚本的内容,从链接脚本可知镜像的布局。