このページの目次

リンカスクリプトと再配置

カバー範囲: ld スクリプト構文 → 再配置タイプ → GOT/PLT 再配置 → リンカリラクセーション → LLD vs GNU ld vs gold → R_AARCH64_ADR_GOT_PAGE 適用: binutils ld, LLD, 静的/動的リンク

再配置: リンカの役割

コンパイル時 (gcc -c):
  .o ファイル内に RELA エントリが存在 → 「このアドレスに値を埋め込む必要がある。対象シンボルはこれだ」と記録
  例: R_X86_64_PLT32: この 4バイトのオフセットには、シンボルの PLT 番地を埋め込む必要がある

リンク時 (ld):
  全 .o の RELA を走査 → シンボルを解決 → 番地を埋め込む
  動的シンボルの場合 → .rela.plt を生成 (実行時に ld.so によって解決される)

一般的な再配置タイプ

// x86-64 (AMD64):
R_X86_64_64:        直接の 64ビット絶対番地
R_X86_64_PC32:      32ビット PC 相対オフセット (call/jmp のターゲット)
R_X86_64_PLT32:     PC32 に類似するが、対象が PLT 内にある
R_X86_64_GOTPCREL:  GOT エントリの PC 相対オフセット
R_X86_64_COPY:      共有ライブラリ内の変数をメインプログラムの .bss にコピー

// ARM64 (AArch64):
R_AARCH64_ABS64:    絶対番地
R_AARCH64_ADR_PREL_PG_HI21:  PC 相対、ページ粒度 (ADR)
R_AARCH64_ADD_ABS_LO12_NC:   ページ内オフセット (ADD)
R_AARCH64_LDST64_ABS_LO12_NC: 読み込み/書き込み、ページ内オフセット
R_AARCH64_ADR_GOT_PAGE:       GOT エントリの PC 相対、ページ粒度 + 読み込み

ld スクリプト: リンカスクリプト

/* デフォルトの ld スクリプト: /usr/lib/ldscripts/elf_x86_64.x */

SECTIONS
{
    /* 開始: コードセグメント (Read + Execute) */
    . = 0x400000 + SIZEOF_HEADERS;  /* PIE 以外 */
    .text : { *(.text.unlikely .text.*_unlikely .text.unlikely.*)
              *(.text.exit .text.exit.*)
              *(.text.startup .text.startup.*)
              *(.text.hot .text.hot.*)
              *(.text .stub .text.* .gnu.linkonce.t.*) }

    /* 読み取り専用データ */
    .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
    . = ALIGN(0x1000);
    __init_array_start = .;
    .init_array : { *(.init_array) }
    __init_array_end = .;

    /* 読み書き可能セグメント (次のページから開始) */
    . = ALIGN(0x1000);
    .data : { *(.data .data.* .gnu.linkonce.d.*) }
    .bss : { *(.bss .bss.* .gnu.linkonce.b.*) }
}

LLD vs GNU ld vs gold

GNU ld (BFD)goldLLD (LLVM)
速度ベンチマーク2〜3倍高速3〜5倍高速
メモリ高いより低い低い
LTO対応 (GCC LTO)対応 (GCC LTO)対応 (ThinLTO)
リンカスクリプト完全対応一部大部分
デフォルト伝統的保守終了将来標準

リンカリラクセーション

特定の再配置は「リラクセーション」によって最適化できる:
  RISC-V / ARM64 でサポート:
    例: R_RISCV_CALL → リンク時にターゲットまでの距離が分かる → より短い命令列を使用
    → call (32ビットオフセット) → jal (20ビットオフセット) 距離が十分であれば
    → コードが短くなり + 高速化

  x86 はリラクセーションに対応していない (命令サイズが固定で、最適化の余地がない)

デバッグ

# 再配置テーブルを表示
readelf -r file.o

# デフォルトの ld スクリプトを表示
ld --verbose | less

# リンクプロセスのトレース
ld -Map output.map -o prog *.o  # 詳細なマッピングファイルを出力
gcc -Wl,--trace -o prog *.o     # リンクされる各ファイルを表示

# 未定義シンボル
ld -o prog *.o 2>&1 | grep undefined

参考

  • ドキュメント⁠: info ld, ELF spec (System V ABI, Chapter 4)
  • ソースコード⁠: binutils/ld/, lld/ELF/
  • LWN: "Linker relaxation"

キーワード: relocation, R_X86_64_PLT32, R_AARCH64_ADR_GOT_PAGE, ld script, LLD, gold, linker relaxation