Dynamic Linking Contract¶
Status: Phase 1 (experimental eager binding)
This page defines what dynamic linking means on P1Start and what is deliberately out of scope today.
See also
Dynamic linking ABI — code index (curated) — code-indexed DT_* and R_X86_64_* tables (newdocs/dynamic-linking-abi.md); pair this policy page with that map when debugging ld.elf.
Why This Is Conservative¶
P1Start prioritizes deterministic behavior, clear failure modes, and low runtime complexity.
Dynamic linking is introduced in narrow, testable slices so static applications remain stable while loader policy becomes explicit.
Current Support (Phase 1 Experimental)¶
Static linking remains the only production SDK/runtime contract for applications.
ld.elfparsesPT_DYNAMICand selectedDT_*metadata for runtime binding.ld.elfmaps candidate dependencies and applies eager relocations for the x86_64 subset listed under Implemented Phase 1 Runtime Scope below (includingRELATIVE,GLOB_DAT,JUMP_SLOT, absolute/PC-relative relocations,GOTPCREL*, andR_X86_64_32/32S).p1dlexposes stable API entry points (dlopen/dlsym/dlclose) with deterministic argument validation and a loader-bridge dispatch path.
Hard Runtime Policy (Phase 1 Experimental)¶
If
ld.elfdetectsDT_NEEDED, it must attempt deterministic eager binding.Launch continues only when all required dependencies resolve and supported relocations bind successfully.
Unsupported metadata/relocations or unresolved symbols must fail loudly with
DL_E_*diagnostics.Silent continuation on partial failure is forbidden.
Implemented Phase 1 Runtime Scope (Experimental)¶
The current runtime dynamic-linking implementation supports only:
ELF64 / x86_64 userland objects.
PIE/shared-object position-independent code only.
Relocation types:
R_X86_64_RELATIVER_X86_64_GLOB_DATR_X86_64_JUMP_SLOTR_X86_64_64R_X86_64_PC32,R_X86_64_PLT32(PLT32 is applied with the same PC-relative eager semantics as PC32)R_X86_64_PC64R_X86_64_GOTPCREL,R_X86_64_GOTPCRELX,R_X86_64_REX_GOTPCRELX(requires a priorGLOB_DAT/JUMP_SLOTrecording the GOT slot address for the symbol index)R_X86_64_32,R_X86_64_32S
Eager binding only (no lazy PLT binding).
Dependency init hooks:
DT_INITandDT_INIT_ARRAY.Runtime-handle teardown hooks on close-to-zero:
DT_FINIandDT_FINI_ARRAY.Library discovery from explicit embedded path metadata plus
/lib.Bridge-backed
p1dlcalls for deterministic runtime loads from policy-approved paths.
This scope is implemented and boot-smoke validated, but remains experimental rather than a production SDK compatibility promise.
Explicitly Out Of Scope (Phase 1)¶
Symbol versioning (
DT_VER*).LD_LIBRARY_PATH-style environment overrides.Lazy binding.
Text relocations.
Non-PIE shared objects.
R_X86_64_COPYand other copy-relocation semantics.
Library Search Path Policy Direction¶
Deterministic order is required.
No implicit current-working-directory lookup.
Phase-1 goal order (implemented for the main executable search list used by startup
DL_NEEDEDresolution and thep1dlbridge):DT_RUNPATHentries in declaration order, each segment with$ORIGIN/${ORIGIN}expanded using the main program directory fromargv[0](see final bullet),then
DT_RPATHentries in declaration order (same expansion),then fixed system path
/lib(and a deterministic\libVFS probe variant).
Nested
DT_NEEDED: each DSO searches (1) its own expandedDT_RUNPATH, (2) its expandedDT_RPATH, (3) then the list inherited from the parent (for main’s direct deps, that is the main’s global list: runpath + rpath +/lib). Recursion passes each object’s merged list to its children.$ORIGIN/${ORIGIN}: expand to the directory of the ELF that owns the tag (from the resolved load path). The main executable uses the directory ofargv[0].$PLATFORMand$LIBare not implemented in Phase 1.
Versioning And Compatibility Direction¶
Initial dynamic-linking phase does not claim SONAME/version conflict resolution support.
Unsupported version metadata must produce explicit loader failure, not best-effort binding.
SONAME matching/version negotiation is deferred until after base relocation/symbol binding is stable.
Current loader policy fails with
DL_E_VERSION_UNSUPPORTEDwhenDT_VER*metadata is detected in the main executable or dependencies.
Symbol Visibility And Binding Direction¶
Initial runtime binding scope is process-local and conservative.
Default expectation is local-only behavior unless explicit global export policy is introduced in a later phase.
Current resolver policy:
ignore
STB_LOCALexports for inter-object resolution,ignore non-default visibility exports,
prefer strong global over weak for same symbol name,
fail on duplicate strong globals with
DL_E_DUPLICATE_SYMBOL,keep first weak definition when only weak candidates exist.
Duplicate weak symbols are allowed (first-seen wins). Duplicate strong global symbols are errors (
DL_E_DUPLICATE_SYMBOL). Local symbols are ignored for inter-object binding.
PLT/GOT Direction¶
Because
R_X86_64_JUMP_SLOTis in scope, PLT/GOT relocation handling is required.Phase-1 remains eager-only: PLT entries are resolved during load, not lazy-bound at first call.
p1dl API Contract (Current)¶
dlopen()/dlsym()/dlclose()are stable API entry points.During current experimental bring-up they validate arguments/handles first (
InvalidArgument/InvalidHandle), then call a loader-installed bridge dispatch when present, and otherwise return an explicit unsupported error (NotSupported).Phase-1 bridge behavior supports deterministic runtime
dlopenmapping for policy-approved ELF64 shared objects.Phase-1 bridge contract details:
dlopen(path)first checks already-loaded handles, then attempts deterministic runtime mapping from configured loader search paths.dlsym(handle, name)resolves only within that handle’s export table.dlclose(handle)decrements the bridge refcount for runtime-loaded objects; when the refcount reaches zero, the loader runsDT_FINI_ARRAY(reverse order) thenDT_FINIfor that handle. Memory unmapping (full unload) and teardown of startup-pinnedDT_NEEDEDdependencies remain out of scope — see Init/fini policy bullets below.
Init/fini policy in this phase:
DT_INIT/DT_INIT_ARRAYare executed after dependency mapping/relocation.Runtime-loaded handles execute
DT_FINI_ARRAY(reverse order) thenDT_FINIwhendlclosedrops refcount to zero.Startup-pinned
DT_NEEDEDdependencies are not finalized by bridgedlclose.
A runtime smoke harness is available (
scripts/run_p1dl_runtime_boot_smoke.py) to stage fixture artifacts, boot QEMU, and assert bridge +p1dlpass markers from serial output (kernel_serial.logat repo root). The script pre-flightsqemu-system-x86_64andrun_qemu.bat, warns ifOVMF.fdis missing, and accounts for longbuild_image.batruns before the serial file appears; seeSDK/loader-fixtures/p1dl-runtime-smoke/README.mdandSDK/shared-library-development.md.Current fixture status:
p1dl_runtime_smoke.elfpasses in-QEMU with successfulhello.soprobe/load, bridge install, and[p1dl-smoke] PASS.As loader support arrives, unsupported objects or metadata must map to explicit error variants, never panic paths.
Loader Error Codes (Contract)¶
ld.elf diagnostics should include stable machine-friendly codes.
DL_E_NEEDED_UNSUPPORTED— executable hasDT_NEEDEDwhile runtime DSO loading is disabled.DL_E_UNSUPPORTED_RELOC— relocation type is outside currently supported relocation subset.DL_E_UNSUPPORTED_OBJECT— object metadata violates current phase policy (for example non-PIE in phase-1 scope).DL_E_VERSION_UNSUPPORTED— symbol versioning metadata encountered before versioning support exists.DL_E_POLICY_DENIED— loading denied by runtime security/search-path policy.DL_E_SYMBOL_NOT_FOUND— required symbol for eager relocation was not resolved from loaded dependency exports.DL_E_DUPLICATE_SYMBOL— duplicate strong global symbol definition detected during dependency export table merge.
Acceptance Criteria (Phase 1 Experimental)¶
Existing static SDK apps run unchanged.
ld.elfreports deterministicPT_DYNAMIC/DT_*metadata and deterministic search-path probing.ld.elfbinds supported relocation types eagerly and fails loudly for unsupported/unresolved cases.p1dlremains a stable surface with deterministic unsupported behavior.
Developer workflow (building and staging .so files)¶
Step-by-step recipes (Clang + LLD, hello.so fixture, build/image.manifest E lines, runtime dlopen via p1dl, QEMU smoke): SDK/shared-library-development.md in the main repository.
Coverage-matrix summary (paths, relocation list, status): reference/coverage-matrix section Dynamic linking & shared objects.