linker.ld在連結階段的行為

上山砍大树發表於2024-11-15

抽出編譯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的行為就分析完了。

相關文章