Linker Scripts

Architecture-specific linker scripts for Multiboot2, Limine, UEFI, and custom boot protocols.

Documentation

Overview

Every Helix kernel profile requires a linker script that controls how the kernel binary is laid out in memory. The linker script determines:

  • Load address — where the bootloader places the kernel in physical memory
  • Virtual address — the kernel's virtual memory mapping (for higher-half kernels)
  • Section layout — ordering of .text, .rodata, .data, .bss
  • Program headers — ELF segment permissions (R/W/X)
  • Special sections — boot protocol headers, relocation tables, GOT

Helix provides four linker scripts for different boot protocols and memory models.

ScriptBoot ProtocolMemory ModelKASLR
profiles/minimal/linker.ldMultiboot2Flat physical, 1 MBNo
profiles/minimal/linker_pie.ldMultiboot2PIE, higher-halfYes
profiles/limine/linker.ldLiminePIE + HHDMYes
profiles/common/linker_base.ldUniversalPIE, higher-halfYes

Minimal — Multiboot2

The simplest linker script. Loads the kernel at physical address 1 MB with a flat memory model (no virtual addressing). Best for quick prototyping and embedded targets.

profiles/minimal/linker.ld
ld
1
ENTRY(_start)
2
3
SECTIONS
4
{
5
. = 1M;
6
7
.multiboot_header : ALIGN(8) {
8
KEEP(*(.multiboot_header))
9
}
10
11
.boot : ALIGN(4K) { *(.boot) }
12
.text : ALIGN(4K) { *(.text .text.*) }
13
.rodata : ALIGN(4K) { *(.rodata .rodata.*) }
14
.data : ALIGN(4K) { *(.data .data.*) }
15
16
.bss : ALIGN(4K) {
17
__bss_start = .;
18
*(.bss .bss.*) *(COMMON)
19
__bss_end = .;
20
}
21
22
_kernel_end = .;
23
}

Minimal — PIE

Higher-half variant of the minimal linker script. Produces a Position-Independent Executable (PIE) that supports KASLR. The kernel runs at virtual address -2GB (0xFFFFFFFF80000000) while loaded at physical address 1 MB.

profiles/minimal/linker_pie.ld
ld
1
KERNEL_VIRT_BASE = 0xFFFFFFFF80000000; /* -2GB higher-half */
2
KERNEL_PHYS_BASE = 0x100000; /* 1MB physical */
3
4
PHDRS {
5
boot PT_LOAD FLAGS(5); /* R-X: Multiboot + early boot */
6
text PT_LOAD FLAGS(5); /* R-X: Executable code */
7
rodata PT_LOAD FLAGS(4); /* R--: Read-only data */
8
data PT_LOAD FLAGS(6); /* RW-: Read-write data */
9
dynamic PT_DYNAMIC FLAGS(6); /* RW-: Relocation info */
10
}
11
12
/* Multiboot2 header in first 32KB, then .text, .rodata, .data,
13
.bss, .rela.dyn, .dynamic, .got — full PIE layout */

Limine — PIE + HHDM

The Limine linker script produces a PIE binary with Higher-Half Direct Mapping (HHDM). This is the recommended script for production kernels.

profiles/limine/linker.ld
ld
1
KERNEL_VMA = 0xFFFFFFFF80000000;
2
3
PHDRS {
4
limine PT_LOAD FLAGS(4); /* R--: Limine requests */
5
text PT_LOAD FLAGS(5); /* R-X: Code */
6
rodata PT_LOAD FLAGS(4); /* R--: Read-only data */
7
data PT_LOAD FLAGS(6); /* RW-: Data */
8
bss PT_LOAD FLAGS(6); /* RW-: Zero-init */
9
dynamic PT_DYNAMIC FLAGS(6); /* RW-: Relocations */
10
relro PT_GNU_RELRO FLAGS(4); /* R--: RELRO */
11
gnu_stack PT_GNU_STACK FLAGS(6); /* Non-executable stack */
12
}
13
14
/* .limine_requests first, then .text (with .text.boot, .text.hot),
15
.rodata, .data, .bss, .rela.dyn, .dynamic, .got — full KASLR */

Common Base Script

The universal base linker script that other scripts can include. Defines the standard section layout, symbol exports, and program headers for a PIE higher-half kernel.

profiles/common/linker_base.ld
ld
1
__KERNEL_VMA_BASE = 0xFFFFFFFF80000000; /* -2 GiB virtual */
2
__KERNEL_LMA_BASE = 0x0000000000200000; /* 2 MiB physical */
3
__PAGE_SIZE = 0x1000;
4
__HUGE_PAGE_SIZE = 0x200000;
5
6
PHDRS {
7
text PT_LOAD FLAGS(5);
8
rodata PT_LOAD FLAGS(4);
9
data PT_LOAD FLAGS(6);
10
bss PT_LOAD FLAGS(6);
11
dynamic PT_DYNAMIC FLAGS(6);
12
relro PT_GNU_RELRO FLAGS(4);
13
gnu_stack PT_GNU_STACK FLAGS(6);
14
}
15
16
/* Sections: .boot.entry → .multiboot → .limine_reqs → .text → .plt
17
→ .rodata → .eh_frame_hdr → .data → .got → .dynamic → .bss
18
Includes RELRO support and non-executable stack */

Exported Symbols

All linker scripts export symbols that the kernel uses at runtime for memory management, relocation, and section boundary detection.

SymbolScriptDescription
_startAllEntry point — 32-bit trampoline to long mode
__bss_start / __bss_endAllZero-initialized data — cleared to 0 during boot
_kernel_endminimalEnd of all kernel sections
__kernel_start / __kernel_endlimine, uefi, commonKernel image boundaries
__text_start / __text_endlimine, uefi, commonCode segment — R-X
__rodata_start / __rodata_endlimine, uefi, commonRead-only data — R--
__data_start / __data_endlimine, uefi, commonMutable data — RW-
__rela_start / __rela_endPIE scriptsRelocation entries for KASLR
__got_start / __got_endPIE scriptsGlobal Offset Table

Writing Your Own Linker Script

When creating a custom linker script for your profile:

  1. Choose your base — Start from the closest existing script (minimal for flat, common for higher-half)
  2. Set the entry point — Must match your helix.toml boot.entry value
  3. Align sections to 4K — Required for page-granular memory protection
  4. Export BSS symbols — The kernel zeroes .bss during early boot
  5. Add boot protocol headers — Multiboot2 header, Limine requests, or UEFI entry
  6. Test with objdump — Verify section layout with objdump -h build/output/helix-kernel