On Sat, 20 Dec 2025, Henryk Paluch wrote:
I have found exact cause while atexit(3) from dlopen(3) crashes later under
NetBSD (tested all major releases since 8.3) while it works under Linux,
OpenBSD and FreeBSD.
It is because NetBSD's atexit(3) on
https://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/stdlib/atexit.c?rev=1.35;content-type=text%2Fx-cvsweb-markup
calls __cxa_atexit_internal with dso=NULL (last argument):
int
atexit(void (*func)(void))
{
return (__cxa_atexit_internal((void (*)(void *))func, NULL, NULL));
}
When later __cxa_finalize is run on dlclose(3) it will not call its callback
because condition (dso == ah->ah_dso) will be always false ( ah->ah_dso ==
NULL which is != dso) - that was set in atexit(3) call. Therefore DSO
callback will be called on global exit (when DSO is no longer mapped to
memory) causing SIGSEGV.
Correct, but, ...
I looked into OpenBSD 7.8's /usr/src/lib/csu/crtbeginS.c that does something
like:
int
atexit(void (*fn)(void))
{
return (__cxa_atexit((void (*)(void *))fn, NULL, &__dso_handle));
}
asm(".hidden atexit");
which properly passes implicit __dso_handle making this case to work.
on NetBSD, this'll cause atexit(3)-registered funcs. in PIEs to be called by
__cxa_atexit(), no? (Plus, that's supposed to be `__dso_handle' passed there,
right? `__dso_handle' is already a pointer? see eg. for some other arches. here:
https://github.com/NetBSD/src/blob/trunk/lib/csu/common/crtbegin.c#L51)
See:
https://github.com/NetBSD/src/blob/trunk/lib/csu/arch/x86_64/crtbegin.S#L60
where this bit of magic is arranged (for amd64).
There might be a way to get this working the way you suggest, but that ended
up by me not only patching stdlib/atexit.c, but, lib/csu/common/crt0-common.c
also as well to explicitly set `__dso_handle = NULL' before calling main():
https://github.com/NetBSD/src/blob/trunk/lib/csu/common/crt0-common.c#L375
So I gave this up when I tried the same thing a coupla days ago.
Instead, I have a patch which does things the FreeBSD way (in __cxa_finalize()
check `dso' against the library LOAD addresses to make sure we're calling
the correct atexit handler). But, this is still a bit hackish at the mo' and
not at all fully tested.
-RVP