opensbi链接脚本解读

文摘   2024-10-03 08:00   湖南  

一. 前言

了解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,进行编译,

输出到参数1elf.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 buildFW_TEXT_START=0x80000000FW_DYNAMIC=yFW_JUMP=y

最终会在opensbi/firmware/objects.mk

指定编译器宏-DFW_TEXT_START

ifdef FW_TEXT_STARTfirmware-genflags-y += -DFW_TEXT_START=$(FW_TEXT_START)elsefirmware-genflags-y += -DFW_TEXT_START=0x0endif

代码段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链接脚本的内容,从链接脚本可知镜像的布局。





嵌入式Lee
嵌入式软硬件技术:RTOS,GUI,FS,协议栈,ARM,总线,嵌入式C,开发环境 and blablaba....多年经验分享,非硬货不发,带你扒开每一个技术背后的根本原理。
 最新文章