152 lines
4.2 KiB
Markdown
152 lines
4.2 KiB
Markdown
# Линкер-скрипты: архитектурные детали
|
||
|
||
## Назначение
|
||
|
||
Линкер-скрипты управляют расположением секций 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
|
||
|
||
```ld
|
||
. = 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
|
||
|
||
```ld
|
||
KEEP(*(.requests_start_marker))
|
||
KEEP(*(.requests))
|
||
KEEP(*(.requests_end_marker))
|
||
```
|
||
|
||
- `KEEP` — запрещает линкеру выбрасывать эти секции (dead code elimination).
|
||
- `.requests_start_marker` и `.requests_end_marker` — маркеры границ.
|
||
- Bootloader сканирует память между ними, чтобы найти requests.
|
||
|
||
### BSS
|
||
|
||
```ld
|
||
.bss : {
|
||
*(.bss .bss.*)
|
||
*(COMMON)
|
||
} :data
|
||
```
|
||
|
||
- BSS — неинициализированные глобальные переменные.
|
||
- Занимает место в виртуальной памяти, но не в ELF-файле.
|
||
- Bootloader обнуляет BSS перед передачей управления.
|
||
|
||
### DISCARD
|
||
|
||
```ld
|
||
/DISCARD/ : {
|
||
*(.eh_frame*)
|
||
*(.note .note.*)
|
||
}
|
||
```
|
||
|
||
- `.eh_frame*` — исключительные фреймы C++/Rust unwinding.
|
||
- `.note.*` — ELF notes.
|
||
- Не нужны bare-metal ядру, могут вызвать проблемы.
|
||
|
||
## Архитектурные различия
|
||
|
||
### RISC-V
|
||
|
||
```ld
|
||
.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.
|