2026-04-22
The asker is building a bare-metal AArch64 kernel and wants to add runtime relocation support. Their linker script defines memory regions at non-zero addresses, yet the relocation entries in the resulting ELF all use a base address of zero. This is confusing because intuitively, if your MEMORY region starts at, say, 0x40000000, you'd expect relocations to be computed relative to that address.
This question touches on one of the trickiest aspects of ELF: the distinction between link-time addresses (the VMA assigned by the linker script) and relocation addends stored in the ELF's relocation tables. These are fundamentally different concepts, and conflating them is a common source of confusion in bare-metal work.
Why relocations show zero: On AArch64, the dominant relocation type for position-independent bare-metal code is R_AARCH64_RELATIVE. For this relocation type, the dynamic linker (or your runtime relocation code) computes the final address as base + addend, where base is the actual load address at runtime. The addend stored in the ELF is the link-time virtual address of the symbol. If the linker is producing a PIE or shared object with a base of zero in the ELF headers (p_vaddr of the first PT_LOAD segment), then all addends are effectively absolute VMAs from the linker script.
The key insight is this: the "base address" isn't stored in the relocation entries themselves. It's computed at runtime by your relocation code as:
runtime_base = actual_load_address - link_time_base
Then for each R_AARCH64_RELATIVE entry, you compute:
*(base + offset) = runtime_base + addend
If the linker emits the ELF with p_vaddr = 0 despite your MEMORY regions being non-zero, check these things:
-pie or -shared? These flags cause the linker to rebase segments to zero, expecting the loader to fix things up.PT_LOAD segments with readelf -l. If p_vaddr matches your linker script addresses but relocations still seem zero-based, your relocation walker is likely misinterpreting the addend field.readelf -r to dump the actual relocation entries. For R_AARCH64_RELATIVE, the addend should contain the link-time address, not zero. If the addend is literally zero, the issue is likely in how the linker script defines sections within the .rela.dyn output.A common gotcha in bare-metal relocation: if you're using -pie with a non-zero load address, you must account for the difference between the ELF's assumed base (zero) and your actual link address when writing the relocation loop. Many tutorials assume a zero-based link, which silently breaks when your kernel lives at 0x40000000.
