抽出编译AM程序中的“打包用户程序am-test到ELF”步骤,看看链接脚本abstract-machine/scripts/linker.ld
如何将库函数和用户程序链接起来的。
首先看下链接命令:
echo + LD "->" build/amtest-riscv32-nemu.elf
($CROSS_COMPILE)ld -z noexecstack -melf64lriscv
-T /abstract-machine/scripts/linker.ld
--defsym=_pmem_start=0x80000000
--defsym=_entry_offset=0x0
--gc-sections
-e _start
-melf32lriscv
-o $AM_TEST/build/amtest-riscv32-nemu.elf
--start-group
$AM_TEST/build/riscv32-nemu/src/main.o
$AM_TEST/build/riscv32-nemu/src/tests/video.o
$AM_TEST/build/riscv32-nemu/src/tests/mp.o
$AM_TEST/build/riscv32-nemu/src/tests/hello.o
$AM_TEST/build/riscv32-nemu/src/tests/devscan.o
$AM_TEST/build/riscv32-nemu/src/tests/audio/audio-data.o
$AM_TEST/build/riscv32-nemu/src/tests/audio.o
$AM_TEST/build/riscv32-nemu/src/tests/keyboard.o
$AM_TEST/build/riscv32-nemu/src/tests/intr.o
$AM_TEST/build/riscv32-nemu/src/tests/rtc.o
$AM_TEST/build/riscv32-nemu/src/tests/vm.o
/abstract-machine/am/build/am-riscv32-nemu.a
/abstract-machine/klib/build/klib-riscv32-nemu.a
--end-group
这里蕴含几个关键信息:
linker.ld
作为链接的自定义脚本- 设置符号(symbol)
_pmem_start
的值为0x80000000,_entry_offset
为0。 - 设置程序的入口地址为
_start
接下来我们转移到链接脚本abstract-machine/scripts/linker.ld
的具体实现:
ENTRY(_start)
PHDRS { text PT_LOAD; data PT_LOAD; }
SECTIONS {
/* _pmem_start and _entry_offset are defined in LDFLAGS */
. = _pmem_start + _entry_offset;
.text : {
*(entry) /* 引用与符号 entry 相关的所有内容*/
*(.text*) /* 将所有文件的 .text 段的内容合并到当前段*/
} : text /* 指定 .text 段被加载到名为 text 的内存区域中*/
etext = .;
_etext = .;
.rodata : {
*(.rodata*)
}
.data : {
*(.data)
} : data
edata = .;
_data = .;
.bss : {
_bss_start = .;
*(.bss*)
*(.sbss*)
*(.scommon)
}
_stack_top = ALIGN(0x1000);
. = _stack_top + 0x8000;
_stack_pointer = .;
end = .;
_end = .;
_heap_start = ALIGN(0x1000);
}
linker.ld
的链接内容:
-
设置
_start
作为程序的入口点:ENTRY(_start)
-
加载
.text
和.data
段到内存中:PHDRS { text PT_LOAD; data PT_LOAD; }
。PT_LOAD
是Program Header的类型 -
设置当前地址
_pmem_start + _entry_offset
:.
表示当前地址,_pmem_start + _entry_offset
就是定义当前地址的位置。这里的地址就是0x80000000
-
设置
.text
、.rodata
、.data
、.bss
段。其中拿出.text
段的处理.text : { *(entry) /* 引用与符号 entry 相关的所有内容*/ *(.text*) /* 将所有文件的 .text 段的内容合并到当前段*/ } : text /* 指定 .text 段被加载到名为 text 的内存区域中*/
-
设置栈和堆的布局
_stack_top = ALIGN(0x1000);
确保栈从 4KB 对齐的位置开始。. = _stack_top + 0x8000;
设置栈的初始位置,分配了 32KB 的栈空间。stack_pointer = .;
设置_stack_pointer
符号,指示栈指针的位置
-
设置结束符号:
end
和_end
-
设置堆的起始地址,并确保堆从 4KB 对齐的位置开始
_heap_start = ALIGN(0x1000)
而其中符号entry
是在abstract-machine/am/src/riscv/nemu/start.S
中定义的
.section entry, "ax"
.globl _start
.type _start, @function
_start:
mv s0, zero # 清零s0寄存器
la sp, _stack_pointer # 将标签 _stack_pointer 的地址加载到栈指针 sp 中,符号在linker.ld中定义。
jal _trm_init # 这条指令会跳转到 _trm_init 标签,并将当前指令地址(即 jal 的下一条指令的地址)压入栈中
# _trm_init就是trm初始化函数
此程序设置了程序的入口点_start
,并且将符号 _start
声明为一个函数类型。
至此,linker.d
的行为就分析完了。