we choose to go to the moon!😉|

上山砍大树

园龄:5年3个月粉丝:13关注:3

linker.ld在链接阶段的行为

linker.ld

抽出编译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

链接脚本linker.ld

接下来我们转移到链接脚本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 声明为一个函数类型。

结合trm.c的TRM初始化函数_trm_init()分析

void _trm_init() {
int ret = main(mainargs);
halt(ret);
}

总结

综上所有关于链接的符号和语法,我们可以得知链接后的ELF程序的一些信息是:

  • 程序入口地址_start设置为0x80000000ElfN_Ehdr.e_entry = 0x80000000
  • 程序段有两个:datatextElf32_Phdr.p_type == PT_LOAD
  • .text代码段的开始就是start.S中的.section entry 指示的地址(text.Elf32_Phdr.p_vaddr == 0x80000000
  • etext_etext符号所代表的地址为.text段的结尾处(addr(_start) + len(.text)
  • eadta_data符号位于.data的末尾

查看ELF文件

riscv64-linux-gnu-readelf -a build/amtest-riscv32-nemu.elf
  • ELF Header栏中,程序入口地址被设置为0x80000000

    Entry point address: 0x80000000
  • 在段头表Section Headers

    Section Headers:
    [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
    [ 0] NULL 00000000 000000 000000 00 0 0 0
    [ 1] .text PROGBITS 80000000 001000 001f70 00 AX 0 0 8
    [ 2] .rodata PROGBITS 80001f70 002f70 000e0f 00 A 0 0 4
    [ 3] .srodata.mainargs PROGBITS 80002d80 003d80 000002 00 A 0 0 4
    [ 4] .data PROGBITS 80002d88 003d88 00de0c 00 WA 0 0 8
    [ 5] .data.code PROGBITS 80010b94 011b94 00000f 00 WA 0 0 4
    [ 6] .sdata.first_trap PROGBITS 80010ba4 011ba4 000004 00 WA 0 0 4
    [ 7] .sdata.heap PROGBITS 80010ba8 011ba8 000008 00 WA 0 0 4
    [ 8] .data.lut PROGBITS 80010bb0 011bb0 000200 00 WA 0 0 4
    [ 9] .sdata.next PROGBITS 80010db0 011db0 000004 00 WA 0 0 4
    [10] .bss NOBITS 80010db4 011db4 084240 00 WA 0 0 4
    [11] .comment PROGBITS 00000000 011db4 00002b 01 MS 0 0 1
    [12] .riscv.attributes RISCV_ATTRIBUTE 00000000 011ddf 000023 00 0 0 1
    [13] .symtab SYMTAB 00000000 011e04 000dc0 10 14 138 4
    [14] .strtab STRTAB 00000000 012bc4 000500 00 0 0 1
    [15] .shstrtab STRTAB 00000000 0130c4 0000a0 00 0 0 1

    其中.text段在进程中的地址为0x80000000,大小为001f70,所以结束地址为0x80001f70.标志FlgAX

    • A:该节是可分配的,意味着它将被映射到进程的虚拟地址空间中
    • X:该节是可执行的,表示它包含程序的机器指令,可以被 CPU 执行

    .data地址为80002d88,大小为00de0c字节,结束地址为80010b94

  • 程序头表Program Headers

    Program Headers:
    Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
    RISCV_ATTRIBUT 0x011ddf 0x00000000 0x00000000 0x00023 0x00000 R 0x1
    LOAD 0x001000 0x80000000 0x80000000 0x02d82 0x02d82 R E 0x1000
    LOAD 0x003d88 0x80002d88 0x80002d88 0x0e02c 0x9226c RW 0x1000
    Section to Segment mapping:
    Segment Sections...
    00 .riscv.attributes
    01 .text .rodata .srodata.mainargs
    02 .data .data.code .sdata.first_trap .sdata.heap .data.lut .sdata.next .bss
    • 第一个LOAD的程序段从.text段开始,以及后面的.rodata.srodata.mainargs
  • 符号表.symtab

    • _start.text地址为80000000
    • .rodata_etextetext地址为80001f70,恰好是.text的结束地址
    • .data地址为80002d88edata_data地址为80010b94,也是.data的结束地址
    • _bss_start地址为80010db4,是.bss段的开始地址
    • end_end的地址为8009d000

至此,linker.d的行为就分析完了。

本文作者:上山砍大树

本文链接:https://www.cnblogs.com/shangshankandashu/p/18548419

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   上山砍大树  阅读(46)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起