The Pedigree Project 0.1
KernelElf-runtime.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/linker/KernelElf.h"
3#include "pedigree/kernel/linker/ModuleImage.h"
4#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
5#include "pedigree/kernel/processor/VirtualAddressSpace.h"
6#include "pedigree/kernel/utilities/StringView.h"
7
8#if HOSTED
9namespace __pedigree_hosted {
10#include <dlfcn.h>
11}
12#endif
13
15 enum State { Unavailable, Free, Reserved, Live, Quarantined } state = Unavailable;
16 Module module;
17 ModuleImage plan;
18 uint8_t source[ModuleImage::MaximumImageBytes];
19 uintptr_t resolved[ModuleImage::MaximumSymbols];
20 const char* dependencies[ModuleImage::MaximumDependencies + 1];
21 const char* optionalDependencies[ModuleImage::MaximumDependencies + 1];
22 uintptr_t base = 0;
23 bool lifecycleStarted = false;
24 bool lifecycleComplete = true;
25};
26
27namespace {
28constexpr size_t SlotCount = 4;
29// Bulky source buffers must not extend the early bootstrap image mapping.
30RuntimeModuleSlot* slots[SlotCount] = {};
31#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
32size_t failProtectionAfter = ~size_t{0};
33#endif
34
35bool protect(uintptr_t address, size_t flags) {
36#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
37 if (failProtectionAfter != ~size_t{0} && failProtectionAfter-- == 0) {
38 failProtectionAfter = ~size_t{0};
39 return false;
40 }
41#endif
43 reinterpret_cast<void*>(address), flags | VirtualAddressSpace::KernelMode);
44}
45
46bool writable(RuntimeModuleSlot& slot) {
47 bool result = true;
48 for (size_t offset = 0; offset < ModuleImage::MaximumMappedBytes;
50 // Keep restoring subsequent pages after an error. A partially restored
51 // slot stays quarantined and is never written or handed to another load.
52 if (!protect(slot.base + offset, VirtualAddressSpace::Write))
53 result = false;
54 }
55 return result;
56}
57
58bool seal(RuntimeModuleSlot& slot) {
59 const size_t pageSize = PhysicalMemoryManager::getPageSize();
60 for (size_t offset = 0; offset < ModuleImage::MaximumMappedBytes; offset += pageSize) {
61 size_t flags = 0;
62 for (size_t i = 0; i < slot.plan.segmentCount; ++i) {
63 const auto& segment = slot.plan.segments[i];
64 if (segment.memsz && offset >= (segment.vaddr & ~(pageSize - 1)) &&
65 offset < segment.vaddr + segment.memsz) {
66 if (segment.flags & PF_W)
68 if (segment.flags & PF_X)
70 }
71 }
72 if (!protect(slot.base + offset, flags))
73 return false;
74 }
75 return true;
76}
77
78bool addSigned(uintptr_t value, int64_t addend, uintptr_t& result) {
79 if (addend < 0) {
80 const uint64_t magnitude = uint64_t{0} - static_cast<uint64_t>(addend);
81 if (value < magnitude)
82 return false;
83 result = value - magnitude;
84 } else {
85 if (value > ~uintptr_t{0} - static_cast<uint64_t>(addend))
86 return false;
87 result = value + addend;
88 }
89 return true;
90}
91} // namespace
92
93KernelElf::RuntimeLoad::RuntimeLoad() : lifetime(), slot(nullptr), length(0) {}
94KernelElf::RuntimeLoad::~RuntimeLoad() {
95 if (slot)
96 KernelElf::instance().abandonRuntimeModuleLoad(*this);
97}
98uint8_t* KernelElf::RuntimeLoad::data() const {
99 return slot ? slot->source : nullptr;
100}
101
103#if STATIC_DRIVERS || !BITS_64 || (!X64 && !HOSTED)
104 return false;
105#endif
106 if (!beginModuleLoad())
107 return false;
108 if (m_RuntimeModulesPrepared) {
109 bool available = false;
110 for (const auto* slot : slots) {
111 if (slot &&
112 (slot->state == RuntimeModuleSlot::Free || slot->state == RuntimeModuleSlot::Live))
113 available = true;
114 }
115 finishModuleLoad();
116 return available;
117 }
119 if (!m_ModuleAllocatorInitialised) {
120 const uintptr_t start = space.getKernelModulesStart();
121 m_ModuleAllocator.free(start, space.getKernelModulesEnd() - start);
122 m_ModuleAllocatorInitialised = true;
123 }
124 // These are trusted boot allocations. No syscall can grow this arena or
125 // trigger the kernel's infallible heap/physical-page allocation paths.
126 lockModules();
127 m_Modules.reserve(m_Modules.count() + SlotCount + 1, true);
129 const size_t pageSize = PhysicalMemoryManager::getPageSize();
130 size_t available = 0;
131 for (size_t index = 0; index < SlotCount; ++index) {
132 RuntimeModuleSlot* prepared = new RuntimeModuleSlot;
133 if (!prepared)
134 break;
135 auto& slot = *prepared;
136 if (!m_ModuleAllocator.allocate(ModuleImage::MaximumMappedBytes, slot.base)) {
137 delete prepared;
138 break;
139 }
140 size_t mapped = 0;
141 for (; mapped < ModuleImage::MaximumMappedBytes; mapped += pageSize) {
142 const physical_uintptr_t page = PhysicalMemoryManager::instance().allocatePage();
143 if (!space.map(page, reinterpret_cast<void*>(slot.base + mapped),
146 break;
147 }
148 }
149 if (mapped != ModuleImage::MaximumMappedBytes) {
150 for (size_t offset = 0; offset < mapped; offset += pageSize) {
151 void* address = reinterpret_cast<void*>(slot.base + offset);
152 physical_uintptr_t page = 0;
153 size_t flags = 0;
154 space.getMapping(address, page, flags);
155 space.unmap(address);
157 }
158 m_ModuleAllocator.free(slot.base, ModuleImage::MaximumMappedBytes);
159 delete prepared;
160 break;
161 }
162 slot.module.name.reserve(ModuleImage::NameBytes);
163 slot.module.runtime = &slot;
164 slot.module.status = Module::Unloaded;
165 slot.module.unloadComplete = true;
166 slot.state = writable(slot) ? RuntimeModuleSlot::Free : RuntimeModuleSlot::Quarantined;
167 lockModules();
168 slots[index] = prepared;
169 m_Modules.pushBack(&slot.module);
171 if (slot.state == RuntimeModuleSlot::Free)
172 ++available;
173 }
174 lockModules();
175 // Preparation runs only once, including partial boot allocation. Existing
176 // live slots must never be remapped by a subsequent preparation request.
177 m_RuntimeModulesPrepared = true;
179 finishModuleLoad();
180 return available != 0;
181}
182
183KernelElf::RuntimeLoadResult KernelElf::beginRuntimeModuleLoad(size_t length, RuntimeLoad& load) {
184 if (load.slot)
185 return RuntimeLoadResult::Busy;
186 if (!length)
187 return RuntimeLoadResult::InvalidImage;
188 if (length > ModuleImage::MaximumImageBytes)
189 return RuntimeLoadResult::ImageTooLarge;
190#if STATIC_DRIVERS || !BITS_64 || (!X64 && !HOSTED)
191 return RuntimeLoadResult::UnsupportedImage;
192#endif
193 lockModules();
194 RuntimeLoadResult result = RuntimeLoadResult::NoMemory;
195 if (m_ModuleShutdown)
196 result = RuntimeLoadResult::Shutdown;
197 else if (m_ModuleLoading || m_UnloadingModule || m_ModuleExecutions || m_ModuleExecutionPasses)
198 result = RuntimeLoadResult::Busy;
199 else if (m_RuntimeModulesPrepared) {
200 for (auto* prepared : slots) {
201 if (!prepared || prepared->state != RuntimeModuleSlot::Free)
202 continue;
203 auto& slot = *prepared;
204 slot.state = RuntimeModuleSlot::Reserved;
205 slot.lifecycleStarted = false;
206 slot.lifecycleComplete = true;
207 load.slot = &slot;
208 load.length = length;
209 m_ModuleLoading = true;
210 result = RuntimeLoadResult::Ready;
211 break;
212 }
213 }
215 return result;
216}
217
218uintptr_t KernelElf::runtimeExportLocked(const char* name, Module* owner) const {
219 uintptr_t weak = 0;
220 for (const auto* prepared : slots) {
221 if (!prepared)
222 continue;
223 const auto& slot = *prepared;
224 if (!slot.module.isActive() || (owner && owner != &slot.module))
225 continue;
226 for (size_t i = 1; i < slot.plan.symbolCount; ++i) {
228 if (!slot.plan.exportedSymbol(i, symbol) || StringCompare(slot.plan.symbolName(symbol), name))
229 continue;
230 const uintptr_t address = slot.base + symbol.value;
231 if (ST_BIND(symbol.info) == STB_GLOBAL)
232 return address;
233 if (!weak)
234 weak = address;
235 }
236 }
237 return weak;
238}
239
240const char* KernelElf::runtimeLookupSymbolLocked(uintptr_t addr, uintptr_t* startAddr) const {
241 for (const auto* prepared : slots) {
242 if (!prepared || !(prepared->module.isActive() || prepared->module.isExecuting())) {
243 continue;
244 }
245
246 const auto& slot = *prepared;
248 for (size_t i = 1; slot.plan.symbol(i, symbol, false); ++i) {
249 // Diagnostics use the retained full table, independently of exports.
250 const unsigned type = ST_TYPE(symbol.info);
251 if (!symbol.shndx || symbol.shndx >= 0xff00 || (type != STT_FUNC && type != STT_NOTYPE) ||
252 ST_BIND(symbol.info) > STB_WEAK || symbol.value > ~uintptr_t{0} - slot.base ||
253 !slot.plan.contains(symbol.value, symbol.size ? symbol.size : 1, PF_R | PF_X)) {
254 continue;
255 }
256
257 const uintptr_t symbolAddress = slot.base + symbol.value;
258 const size_t symbolSize = symbol.size ? symbol.size : 1;
259 if (addr >= symbolAddress && addr - symbolAddress < symbolSize) {
260 const char* name = slot.plan.symbolName(symbol, false);
261 if (name && *name) {
262 if (startAddr) {
263 *startAddr = symbolAddress;
264 }
265 return name;
266 }
267 }
268 }
269 }
270 return nullptr;
271}
272
273uintptr_t KernelElf::resolveRuntimeImport(const char* name, Module* consumer) {
274 const HashedStringView symbol(name);
275 uintptr_t result = m_SymbolTable.lookupOwned(symbol, this);
276#if HOSTED
277 if (!result)
278 result = reinterpret_cast<uintptr_t>(__pedigree_hosted::dlsym(RTLD_DEFAULT, name));
279#endif
280 if (result)
281 return result;
282 for (auto provider : m_Modules) {
283 if (provider == consumer || !provider->isActive() || !moduleDependsOn(consumer, provider))
284 continue;
285 result = provider->runtime ? runtimeExportLocked(name, provider)
286 : m_SymbolTable.lookupOwned(symbol, provider->elf);
287 if (result)
288 return result;
289 }
290 return 0;
291}
292
293KernelElf::RuntimeLoadResult KernelElf::loadModuleRuntime(RuntimeLoad& load) {
294 if (!load.slot)
295 return RuntimeLoadResult::Busy;
296 auto& slot = *load.slot;
297 auto& plan = slot.plan;
298 auto& module = slot.module;
299 const auto preflight = plan.preflight(slot.source, load.length);
300 switch (preflight) {
301 case ModuleImage::Result::Malformed:
302 return RuntimeLoadResult::InvalidImage;
303 case ModuleImage::Result::Unsupported:
304 return RuntimeLoadResult::UnsupportedImage;
305 case ModuleImage::Result::TooLarge:
306 return RuntimeLoadResult::ImageTooLarge;
307 case ModuleImage::Result::Valid:
308 break;
309 }
310 lockModules();
311 for (auto existing : m_Modules) {
312 if (!existing->isUnloaded() && !StringCompare(existing->name.cstr(), plan.name)) {
314 return RuntimeLoadResult::Duplicate;
315 }
316 }
317 module.name.assign(plan.name);
318 module.loadBase = slot.base;
319 module.loadSize = plan.mappedBytes;
320 module.depends = slot.dependencies;
321 module.depends_opt = slot.optionalDependencies;
322 for (size_t i = 0; i <= plan.dependencyCount; ++i)
323 slot.dependencies[i] = i < plan.dependencyCount ? plan.dependencies[i] : nullptr;
324 for (size_t i = 0; i <= plan.optionalDependencyCount; ++i)
325 slot.optionalDependencies[i] =
326 i < plan.optionalDependencyCount ? plan.optionalDependencies[i] : nullptr;
328 for (size_t i = 0; i < plan.dependencyCount; ++i) {
329 bool found = false;
330 for (auto provider : m_Modules) {
331 if (provider->isActive() && !StringCompare(provider->name.cstr(), plan.dependencies[i]))
332 found = true;
333 }
334 if (!found) {
335 return RuntimeLoadResult::MissingDependency;
336 }
337 }
338 // The loader claim pins all providers until publication or rollback. The
339 // copied dependency lists then provide the ordinary unload ordering barrier.
340 for (size_t i = 0; i < plan.symbolCount; ++i) {
342 plan.symbol(i, symbol);
343 if (symbol.shndx) {
344 slot.resolved[i] = slot.base + symbol.value;
345 } else if (i) {
346 slot.resolved[i] = resolveRuntimeImport(plan.symbolName(symbol), &module);
347 if (!slot.resolved[i] && ST_BIND(symbol.info) != STB_WEAK) {
348 return RuntimeLoadResult::MissingDependency;
349 }
350 } else
351 slot.resolved[i] = 0;
352 }
353 ByteSet(reinterpret_cast<void*>(slot.base), 0, ModuleImage::MaximumMappedBytes);
354 for (size_t i = 0; i < plan.segmentCount; ++i) {
355 const auto& segment = plan.segments[i];
356 MemoryCopy(reinterpret_cast<void*>(slot.base + segment.vaddr), plan.bytes + segment.offset,
357 segment.filesz);
358 }
359 for (size_t i = 0; i < plan.relocationCount; ++i) {
360 ModuleImage::Relocation relocation;
361 plan.relocation(i, relocation);
362 uintptr_t value = 0;
363 if (!addSigned(R_TYPE(relocation.info) == 8 ? slot.base : slot.resolved[R_SYM(relocation.info)],
364 relocation.addend, value))
365 return RuntimeLoadResult::InvalidImage;
366 MemoryCopy(reinterpret_cast<void*>(slot.base + relocation.offset), &value, sizeof(value));
367 }
368 if (!plan.validateMaterialized(slot.base))
369 return RuntimeLoadResult::InvalidImage;
370 if (!seal(slot))
371 return RuntimeLoadResult::ProtectionFailed;
372 lockModules();
373 module.entry = reinterpret_cast<ModuleEntry>(slot.base + plan.entry);
374 module.exit = reinterpret_cast<void (*)()>(slot.base + plan.exit);
375 module.unloadComplete = false;
376 module.status = Module::Executing;
377 module.unloadable = true;
378 module.runtimeUnloadable = true;
379 slot.lifecycleStarted = true;
380 slot.lifecycleComplete = false;
382 for (size_t i = 0; i < plan.constructorCount; ++i)
383 reinterpret_cast<void (*)()>(slot.base + plan.constructors[i])();
384 if (!module.entry())
385 return RuntimeLoadResult::EntryFailed;
386 lockModules();
387 module.status = Module::Active;
388 slot.state = RuntimeModuleSlot::Live;
389 load.slot = nullptr;
390 m_ModuleLoading = false;
392 return RuntimeLoadResult::Loaded;
393}
394
395bool KernelElf::retireRuntimeModule(Module* module, bool runLifecycle) {
396 auto& slot = *module->runtime;
397 if (runLifecycle && slot.lifecycleStarted && !slot.lifecycleComplete) {
398 TerminalQuiesceHook hook = nullptr;
399 lockModules();
400 if (m_TerminalQuiesceOwner == module)
401 hook = m_TerminalQuiesceHook;
403 if (hook && !hook()) {
404 slot.state = RuntimeModuleSlot::Quarantined;
405 module->unloadable = false;
406 module->runtimeUnloadable = false;
407 return false;
408 }
409 lockModules();
410 if (m_TerminalQuiesceOwner == module) {
411 m_TerminalQuiesceOwner = nullptr;
412 m_TerminalQuiesceHook = nullptr;
413 }
415 module->exit();
416 for (size_t i = 0; i < slot.plan.destructorCount; ++i)
417 reinterpret_cast<void (*)()>(slot.base + slot.plan.destructors[i])();
418 slot.lifecycleComplete = true;
419 }
420 const bool restored = writable(slot);
421 if (restored) {
422 ByteSet(reinterpret_cast<void*>(slot.base), 0, ModuleImage::MaximumMappedBytes);
423 ByteSet(slot.source, 0, sizeof(slot.source));
424 }
425 lockModules();
426 slot.state = restored ? RuntimeModuleSlot::Free : RuntimeModuleSlot::Quarantined;
427 module->entry = nullptr;
428 module->exit = nullptr;
429 module->unloadAdmission = nullptr;
430 module->depends = nullptr;
431 module->depends_opt = nullptr;
433 return restored;
434}
435
436void KernelElf::abandonRuntimeModuleLoad(RuntimeLoad& load) {
437 auto& module = load.slot->module;
438 lockModules();
439 module.status = Module::Failed;
441 const bool admitted = !load.slot->lifecycleStarted || !module.unloadAdmission ||
442 module.unloadAdmission(false) == Module::UnloadAdmission::Ready;
443 if (admitted) {
444 retireRuntimeModule(&module, load.slot->lifecycleStarted);
445 } else {
446 load.slot->state = RuntimeModuleSlot::Quarantined;
447 module.unloadable = false;
448 module.runtimeUnloadable = false;
449 }
450 lockModules();
451 module.status = Module::Failed;
452 module.unloadComplete = load.slot->lifecycleComplete;
453 load.slot = nullptr;
454 m_ModuleLoading = false;
456}
457
458#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
459void KernelElf::failRuntimeProtectionForTest(size_t operationsBeforeFailure) {
460 failProtectionAfter = operationsBeforeFailure;
461}
462#endif
bool load(uint8_t *pBuffer, size_t length, uintptr_t loadBase, SymbolTable *pSymtab=0, uintptr_t nStart=0, uintptr_t nEnd=~0, bool relocate=true, uintptr_t destinationBase=0)
uintptr_t resolveRuntimeImport(const char *name, Module *consumer)
MemoryAllocator m_ModuleAllocator
Definition KernelElf.h:398
const char * runtimeLookupSymbolLocked(uintptr_t addr, uintptr_t *startAddr) const
Vector< Module * > m_Modules
Definition KernelElf.h:396
bool prepareRuntimeModules()
void unlockModules()
static KernelElf & instance()
Definition KernelElf.h:135
void lockModules()
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
bool allocate(T length, T &address)
Definition RangeList.h:318
void free(T address, T length, bool merge=true)
Definition RangeList.h:157
uintptr_t lookupOwned(const HashedStringView &name, Elf *owner)
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
virtual MUST_USE_RESULT bool trySetFlags(void *virtualAddress, size_t newFlags)
void reserve(size_t size, bool copy)
Definition Vector.h:453
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270