The Kernel Loader ("KernelLdr"/"Kernelldr") was added in 8.0.0. It is responsible for applying relocations to the Kernel, and mapping the Kernel's .text/.rodata/.data/.bss at a random slide.
Kernel Loader
KernelLdr is called immediately by the Kernel's crt0 (after it deprivileges from EL2 to EL1, if required), with the following signature:
void KernelLoader_Main(uintptr_t kernel_base_address, KernelMap *kernel_map, uintptr_t ini1_base_address);
KernelLdr_Main
First, it clears BSS, and then sets SP = <BSS end>.
for (uint64_t *i = __bss_start; iĀ != __bss_end; i++) { *i = 0; } SP = __bss_end;
Next, it applies relocations to itself and calls its init array.
KernelLdr_ApplyRelocations(&KernelLdr_Main, __dynamic_start); KernelLdr_libc_init_array();
Then, it calls the function which relocates the kernel, and jumps back to the kernel entrypoint.
// KernelLdr_LoadKernel returns (relocated_kernel_base - original_kernel_base). uintptr_t kernel_relocation_offset = KernelLdr_LoadKernel(kernel_base, kernel_map, ini_base); // dtor called for static page allocator. g_InitialPageAllocator.~KInitialPageAllocator(); // Jumps back to the kernel code that called KernelLdr_Main. ((void (*)(void))(kernel_relocation_offset + LR))();
KernelLdr_ApplyRelocations
This does standard ELF relocation using .dynamic.
First, it iterates over all entries in .dynamic, extracting .rel.dyn, .rela.dyn, relent, relatent, relcount, relacount from the relevant entries.
Then it does the following two loops to apply R_AARCH64_RELATIVE relocations:
for (size_t i = 0; i < rel_count; i++) { const Elf64_Rel *rel = dyn_rel_start + rel_ent * i; while (uint32_t(rel->r_info)Ā != R_AARCH64_RELATIVE) { /* Invalid entry, infloops */ } *((Elf64_Addr *)(base_address + rel->r_offset)) += base_address; }
for (size_t i = 0; i < rela_count; i++) { const Elf64_Rela *rela = dyn_rela_start + rela_ent * i; while (uint32_t(rela->r_info)Ā != R_AARCH64_RELATIVE) { /* Invalid entry, infloops */ } *((Elf64_Addr *)(base_address + rela->r_offset)) = base_address + rela->r_addend; }
KernelLdr_libc_init_array()
This is just standard libc init array code. .init_array is empty in all available binaries.
KernelLdr_LoadKernel
TODO: Fill this out
KInitialPageAllocator::KInitialPageAllocator
This sets the allocator's next address to 0 (guessed, since this is done statically in KernelLoader).
constexpr KInitialPageAllocator::KInitialPageAllocatorĀ : next_address(0) {}
KInitialPageAllocator::~KInitialPageAllocator
This just clears the allocator's next address.
this->next_address = 0;
KInitialPageAllocator::Initialize
This sets the allocator's next address (function inferred as it is (presumably) inlined and next_address is (presumably) private).
this->next_address = address;
KInitialPageAllocator::Allocate
This linearly allocates a page.
virtual void *KInitialPageAllocator::Allocate() { void *address = reinterpret_cast<void *>(this->next_address); if (address == nullptr) { // If called on uninitialized allocator, panic by infinite looping while (true) {} } this->next_address += 0x1000; memset(address, 0, 0x1000); return address; }
KInitialPageAllocator::Free
This frees a page (implemented as noop in KernelLoader)
virtual void KInitialPageAllocator::Free(void *address) { // Does Nothing }
Structures
KernelMap
Offset | Size | Description |
---|---|---|
0x0 | 4 | .text offset |
0x4 | 4 | .text end offset |
0x8 | 4 | .rodata end offset |
0xC | 4 | .rodata end offset |
0x10 | 4 | .rwdata offset |
0x14 | 4 | .rwdata end offset |
0x18 | 4 | .bss offset |
0x1C | 4 | .bss end offset |
0x20 | 4 | INI1 load offset |
0x24 | 4 | .dynamic end offset |
0x28 | 4 | .init_array end offset |
0x2C | 4 | .init_array end offset |
KInitialPageAllocator
KInitialPageAllocator is just a simple linear allocator.
Offset | Size | Description |
---|---|---|
0x0 | 8 | vtable; |
0x8 | 8 | Next Address; |
KInitialPageAllocator::vtable
Offset | Size | Description |
---|---|---|
0x0 | 8 | void *(*Allocate)(KInitialPageAllocator *this); |
0x8 | 8 | void (*Free)(KInitialPageAllocator *this, void *address); |