SYS_DLOPEN opened a path through the kernel's VFS, so no library a file
server holds could load, and the kernel kept a cross-process cache of
every library it loaded that never evicted. The small-kernel track's
step 5 asks for dlopen from an image handle, and the orchestrator ruled,
under the memory design, that the cache goes and each process holds its
own library pages; that design's later stages bring sharing back through
memory objects.
- ABI: SYS_DLOPEN takes a DlopenArgs: the name the module goes by, the
image handle and its length, and init_out. The kernel opens nothing by
the name; it is what SYS_QUERY_MODULES answers and what a second load
of the same name is known by, which answers the module already loaded.
- Kernel: sys_dlopen loads from the SharedImage; elf/cache.rs, its
budget and identity (vfs::BackingId, open_backing_identified) and
LibMemory's shared arm are deleted, and a LoadedLib owns its one
allocation.
- SDK: toyos::files::read_image reads a file into an image wherever it is
served, and toyos::files::dl_open loads one under its absolute path.
std's read_image is that helper; libc's dlopen calls dl_open, and its
posix_spawn of a served program reads it into an image as std's
Command does (ENOEXEC for an empty file).
- The libloading fork's ToyOS backend calls toyos::files::dl_open
(ToyOSOrg/rust_libloading toyos-sdk-0.12 da187608c), pinned here.
- Guest tests call the SDK's dl_open; abuse_kernel_addr builds its
DlopenArgs around an image read once.
- Issues: step 5 is done; the shared-object cache's residual record is
deleted with the cache; the refusal-log record and the parsing track no
longer cite it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Draft. The middle of a stack: #988 (landed) → this → #994 (the boot handoff). It is based on
main, and #994 rebases onto it.Problem
SYS_DLOPENopened its path through the kernel's VFS, so no library on a file server could load. That covered/appspackages, and/systemonce #994 moves it onto the file protocol. The kernel also kept a cross-process cache of every library it loaded; nothing was ever evicted, and it was keyed by the spelling of a path. This is step 5 of the small-kernel track, whose exit is "SYS_DLOPENtakes no path".Resulting behaviour
SYS_DLOPENtakes aDlopenArgs: the name the module goes by, an image handle carryingMAPand the image's length, andinit_out.SYS_QUERY_MODULESanddladdranswer, and a second load under the same name answers the module already loaded without running its initialisers again.toyos_abi::syscall::dl_open(name, image, len)runs the module's.init_array.dl_open_argsmakes the bare call.sys_dlopenloads from theSharedImagethe image handle names.kernel/src/elf/cache.rs(the cache, its byte budget and its stale-file refusal),vfs::BackingId/open_backing_identified, andLibMemory's shared arm.LoadedLibnow owns its one allocation, so each process holds its own library pages.toyos::files::read_image(path)reads a file into an image wherever it is served, on a file server or in the kernel's/tmp.toyos::files::dl_open(path)loads a library under its absolute path.read_imageis now that same helper.dlopengoes throughtoyos::files::dl_openand setserrnoon a refusal.posix_spawnof a program on a file server reads it into an image, the way std'sCommanddoes (ENOEXECfor an empty file).toyos-sdk-0.12atda187608c. Its ToyOS backend callstoyos::files::dl_open, and the fork now depends ontoyosby version range. Pinned intests/guest/rust-tests/Cargo.lock. Nothing else in the tree or the forks callsdl_open.Decisions
LibMemoryanyway, and sharing comes back through memory objects in its stages 4 and 6. The backing-memory implementer (agents/backing) is aware that this PR changesLibMemoryfirst.argv[0]. Deduplication by that name stays in the kernel, which keeps adlopenloop from growing the address space; the cost of a misleading name falls on the caller alone.read_imageanddl_openlive intoyos::files, the client that std and libc already share (files: one client of the file servers, in toyos, for std and libc alike #988).Checks (head
6ae79ccaa, base5e01f8b87)cargo run -- --ci host(87 steps)cargo run -- --build-only(x86_64)libc_childrendlopen_dedup,abuse_dlopen_ledger,std_tls_multi_crate,std_tls_dlopen,std_unwind_so, the C cases204_dl_iterate_phdrand207_libc_nameson the shipping kernel, andabuse_kernel_addron the test kernelThe scratch run built the guest tests with libloading patched to a local worktree of the fork. That worktree had the same tree as the pushed
da187608c; only the commit was made afterwards.The dlopen guest tests run only in metal's shared boot, which is why the QEMU evidence is a scratch test rather than a committed row.
Evidence at
9c1851478: the host suite, the build,libc_childrenand the scratch dlopen run, with the patch that run used.Risks
🤖 Generated with Claude Code