Files
Elyz/kernel/docs/boot/linker.md
2026-07-07 16:40:41 +03:00

4.2 KiB
Raw Permalink Blame History

Линкер-скрипты: архитектурные детали

Назначение

Линкер-скрипты управляют расположением секций ELF-образа ядра. Для каждой архитектуры — свой скрипт, но все они следуют одной схеме.

Общая структура

OUTPUT_FORMAT(...)          ← формат ELF (зависит от архитектуры)
ENTRY(kmain)                ← точка входа

PHDRS                       ← сегменты (program headers)
{
    text    PT_LOAD;
    rodata  PT_LOAD;
    data    PT_LOAD;
}

SECTIONS                    ← расположение секций
{
    . = 0xffffffff80000000; ← база higher half

    .text : { *(.text .text.*) } :text

    . = ALIGN(MAXPAGESIZE);
    .rodata : { *(.rodata .rodata.*) } :rodata

    . = ALIGN(MAXPAGESIZE);
    .data : {
        *(.data .data.*)
        KEEP(*(.requests_start_marker))
        KEEP(*(.requests))
        KEEP(*(.requests_end_marker))
    } :data

    .bss : { *(.bss .bss.*) *(COMMON) } :data

    /DISCARD/ : { *(.eh_frame*) *(.note .note.*) }
}

Детали

OUTPUT_FORMAT

Архитектура Формат
x86_64 elf64-x86-64
aarch64 elf64-littleaarch64
riscv64 elf64-littleriscv
loongarch64 elf64-loongarch

Базовый адрес: 0xFFFFFFFF80000000

Ядро размещается в higher half — верхней 2 GiB виртуального адресного пространства. Это стандартная практика для x86-64:

0x0000_0000_0000_00000 ─── user space (не используется ядром)
0xFFFF_8000_0000_00000 ─── kernel space (higher half)
0xFFFF_FFFF_FFFF_FFFF ─── конец

Любой адрес в области 0xFFFF800000000000 — 0xFFFFFFFFFFFFFFFF корректен; 0xFFFFFFFF80000000 выбран как начало typical higher half региона.

PHDRS: PT_LOAD сегменты

Bootloader загружает только PT_LOAD сегменты. Их три:

  1. text: код + inline-константы.
  2. rodata: неизменяемые данные (строки, таблицы).
  3. data: изменяемые данные + BSS.

Section alignment

. = ALIGN(CONSTANT(MAXPAGESIZE));

MAXPAGESIZE = 0x1000 (4 KiB). Каждая секция начинается с новой страницы, что даёт bootloader'у правильные MMU permissionы:

  • text = read + execute (no write)
  • rodata = read (no write, no execute)
  • data = read + write (no execute)

Limine Requests в .data

KEEP(*(.requests_start_marker))
KEEP(*(.requests))
KEEP(*(.requests_end_marker))
  • KEEP — запрещает линкеру выбрасывать эти секции (dead code elimination).
  • .requests_start_marker и .requests_end_marker — маркеры границ.
  • Bootloader сканирует память между ними, чтобы найти requests.

BSS

.bss : {
    *(.bss .bss.*)
    *(COMMON)
} :data
  • BSS — неинициализированные глобальные переменные.
  • Занимает место в виртуальной памяти, но не в ELF-файле.
  • Bootloader обнуляет BSS перед передачей управления.

DISCARD

/DISCARD/ : {
    *(.eh_frame*)
    *(.note .note.*)
}
  • .eh_frame* — исключительные фреймы C++/Rust unwinding.
  • .note.* — ELF notes.
  • Не нужны bare-metal ядру, могут вызвать проблемы.

Архитектурные различия

RISC-V

.data : {
    *(.data .data.*)
    KEEP(*(.requests_start_marker))
    KEEP(*(.requests))
    KEEP(*(.requests_end_marker))
    *(.sdata .sdata.*)       ← RISC-V: small data
} :data

.bss : {
    *(.sbss .sbss.*)         ← RISC-V: small BSS
    *(.bss .bss.*)
    *(COMMON)
} :data

RISC-V имеет .sdata/.sbss секции для small data (GP-relative addressing).

AArch64 и LoongArch64

Идентичны x86_64, за исключением OUTPUT_FORMAT.