libc/dlfcn: Count opens so a library can be shared - #19639
Open
casaroli wants to merge 2 commits into
Open
Conversation
casaroli
requested review from
Donny9,
anchao,
jerpelea and
xiaoxiang781216
as code owners
August 3, 2026 10:07
casaroli
force-pushed
the
dlopen-refcount
branch
from
August 3, 2026 10:09
d5f5425 to
2e6b164
Compare
Contributor
There was a problem hiding this comment.
could we delay strdup after line 121
Contributor
Author
There was a problem hiding this comment.
basename() may modify its argument; libelf_insert() takes the module name as a const string, so dlinsert() now derives the basename with strrchr(), the same way binfmt/builtin.c does
dlopen() of a library that is already loaded fails. libelf_insert() rejects a name that is already in the module registry with EEXIST, and dlinsert() passes that straight out, so the second caller gets NULL. POSIX says dlopen() shall return a handle to the object, and there is no way today for two modules to hold the same library at once -- which is what a shared library is for. So dlopen() now takes another reference on a library that is already there, and dlclose() only tears it down when the last handle goes. The count lives in the dlfcn layer rather than in libelf_insert() so that insmod keeps its own behaviour: a second insmod of the same name still fails with EEXIST, which is right for a kernel module. The module name is what makes any of this possible, and a PROTECTED build did not have one. Names were defined for CONFIG_BUILD_FLAT or the kernel side of a split build, on the reasoning that only the kernel needed them, which predates dlopen() being usable from user space. Without a name the user-space copy of libelf cannot recognise a second open of a library, cannot count opens, and cannot make dlclose() mean anything -- two dlopen()s there produce two independent copies of the library and lose track of the first. Names are therefore defined wherever CONFIG_LIBC_DLFCN is, which costs NAME_MAX per loaded module in that configuration. The path no longer has to be copied either. The module name is the basename of the file and libelf_insert() takes it as a const string, so dlinsert() finds it with strrchr() instead of handing a writable duplicate of the whole path to basename(). BUILD_KERNEL is deliberately untouched. dlopen() returns NULL there unconditionally: dlinsert() is a stub, because sharing a library between processes with separate address spaces needs the text in a shared region and the data per process at a matching virtual address, which is a different problem from this one. Built for mps3-an547:picostest with and without CONFIG_LIBC_DLFCN, and for stm32f4discovery:kostest, a PROTECTED configuration, with it enabled. Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Describe the shared library open semantics in the FLAT and PROTECTED builds: dlopen() of a library that is already loaded returns a handle to it and takes an additional reference, and the library is unloaded only when the last handle is closed. Note the consequences that follow from having a single instance: libraries are matched by basename, data is shared by all users, and constructors and destructors run once. Contrast this with insmod(), which still rejects a duplicate module name, and note that dlopen() is not implemented in the KERNEL build. Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
casaroli
force-pushed
the
dlopen-refcount
branch
from
August 3, 2026 16:28
2e6b164 to
18d8bcc
Compare
xiaoxiang781216
approved these changes
Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
dlopen()of a library that is already loaded fails.libelf_insert()rejects a name already in the module registry withEEXISTanddlinsert()passes that straight out, so the second caller getsNULL. POSIX saysdlopen()shall return a handle to the object, and today there is no way for two modules to hold the same library at once — which is what a shared library is for.dlopen()now takes another reference on a library that is already loaded, anddlclose()tears it down only when the last handle goes.The count lives in the
dlfcnlayer rather than inlibelf_insert(), soinsmodkeeps its own behaviour: a secondinsmodof the same name still fails withEEXIST, which is right for a kernel module.Module names in a PROTECTED build
The module name is what makes any of this possible, and a PROTECTED build did not have one.
Names were defined for
CONFIG_BUILD_FLATor the kernel side of a split build, on the reasoning that only the kernel needed them. That predatesdlopen()being usable from user space. Without a name the user-space copy of libelf cannot recognise a second open, cannot count opens, and cannot makedlclose()mean anything — twodlopen()s there produce two independent copies of the library and lose track of the first.Names are therefore defined wherever
CONFIG_LIBC_DLFCNis, which costsNAME_MAXper loaded module in that configuration and nothing otherwise.Impact
No change for a configuration without
CONFIG_LIBC_DLFCN.insmod/rmmodare unaffected.BUILD_KERNELis deliberately untouched:dlopen()returnsNULLthere unconditionally, becausedlinsert()is a stub. Sharing a library between processes with separate address spaces needs the text in a shared region and each process's data at a matching virtual address, which is a different problem from this one.Testing
Tested by apache/nuttx-apps#3691, which opens a library twice, closes one handle and checks the library is still usable through the other. That test fails on master and passes with this change.
Built
mps3-an547:picostestwith and withoutCONFIG_LIBC_DLFCN.Built
stm32f4discovery:kostest, aCONFIG_BUILD_PROTECTED=yconfiguration, withCONFIG_LIBC_DLFCNenabled — this is the case the name change is for.tools/checkpatch.sh -c -u -m -gpasses.