Bug description
Actual behavior
Actual behavior:
RTLD.LOCAL = 4 on Linux. This is RTLD_NOLOAD (from <bits/dlfcn.h>), which tells dlopen to not actually load the library — just check if it's already loaded. Using OwnedDLHandle(path, RTLD.NOW | RTLD.LOCAL) passes flags=6 to dlopen, which returns NULL, causing a segfault.
Verified via objdump: mov $0x6,%esi before call dlopen@plt.
Linux <bits/dlfcn.h> values:
RTLD_LAZY = 0x001
RTLD_NOW = 0x002
RTLD_NOLOAD = 0x004 ← this is what RTLD.LOCAL currently maps to
RTLD_GLOBAL = 0x100
RTLD_LOCAL = 0x000 ← correct value (it's the default, no bit set)
From the Linux man page (man dlopen):
▎ RTLD_LOCAL
▎ This is the converse of RTLD_GLOBAL, and the default if neither flag is specified.
▎ Symbols defined in this shared object are not made available to resolve references
▎ in subsequently loaded shared objects.
Source: https://man7.org/linux/man-pages/man3/dlopen.3.html
And from <bits/dlfcn.h>:
#define RTLD_LOCAL 0 /* defined in glibc source */
Source: https://sourceware.org/git/?p=glibc.git;a=blob;f=bits/dlfcn.h
Expected behavior
RTLD.LOCAL = 0 on Linux. RTLD_LOCAL is the default dlopen behavior — symbols are not exported to other shared libraries. It requires no flag bit; it's the absence of RTLD_GLOBAL.
Steps to reproduce
# test_rtld.mojo
from sys.ffi import OwnedDLHandle, RTLD
from gpu.host import DeviceContext
fn main() raises:
var ctx = DeviceContext()
var lib = OwnedDLHandle("/tmp/any_valid.so", RTLD.NOW | RTLD.LOCAL)
print("ok")
Create a dummy .so to load
echo 'void dummy() {}' | gcc -shared -o /tmp/any_valid.so -x c -
This works (RTLD.NOW only, flags=2):
mojo run test_rtld_now.mojo
This segfaults (RTLD.NOW | RTLD.LOCAL, flags=6):
mojo run test_rtld.mojo
Confirmed via objdump that RTLD.NOW | RTLD.LOCAL produces mov $0x6,%esi (RTLD_NOW=2 | RTLD_NOLOAD=4) instead of mov $0x2,%esi (RTLD_NOW=2 | RTLD_LOCAL=0).
objdump -d test_rtld | grep -B5 "call.*dlopen" | grep "mov.*esi"
Output: mov $0x6,%esi
System information
Pixi version: 0.66.0
Platform: linux-64
Virtual packages: __glibc=2.41, __cuda=13.0, __archspec=icelake
Mojo: 0.26.1.0 (release)
mojo-compiler: 0.26.1.0
Channel: https://conda.modular.com/max
Bug description
Actual behavior
Actual behavior:
RTLD.LOCAL = 4 on Linux. This is RTLD_NOLOAD (from <bits/dlfcn.h>), which tells dlopen to not actually load the library — just check if it's already loaded. Using OwnedDLHandle(path, RTLD.NOW | RTLD.LOCAL) passes flags=6 to dlopen, which returns NULL, causing a segfault.
Verified via objdump: mov $0x6,%esi before call dlopen@plt.
Linux <bits/dlfcn.h> values:
From the Linux man page (man dlopen):
▎ RTLD_LOCAL
▎ This is the converse of RTLD_GLOBAL, and the default if neither flag is specified.
▎ Symbols defined in this shared object are not made available to resolve references
▎ in subsequently loaded shared objects.
Source: https://man7.org/linux/man-pages/man3/dlopen.3.html
And from <bits/dlfcn.h>:
#define RTLD_LOCAL 0 /* defined in glibc source */
Source: https://sourceware.org/git/?p=glibc.git;a=blob;f=bits/dlfcn.h
Expected behavior
RTLD.LOCAL = 0 on Linux. RTLD_LOCAL is the default dlopen behavior — symbols are not exported to other shared libraries. It requires no flag bit; it's the absence of RTLD_GLOBAL.
Steps to reproduce
Create a dummy .so to load
echo 'void dummy() {}' | gcc -shared -o /tmp/any_valid.so -x c -This works (RTLD.NOW only, flags=2):
mojo run test_rtld_now.mojoThis segfaults (RTLD.NOW | RTLD.LOCAL, flags=6):
mojo run test_rtld.mojoConfirmed via objdump that
RTLD.NOW | RTLD.LOCALproducesmov $0x6,%esi (RTLD_NOW=2 | RTLD_NOLOAD=4)instead ofmov $0x2,%esi (RTLD_NOW=2 | RTLD_LOCAL=0).objdump -d test_rtld | grep -B5 "call.*dlopen" | grep "mov.*esi"Output:
mov $0x6,%esiSystem information
Pixi version: 0.66.0
Platform: linux-64
Virtual packages: __glibc=2.41, __cuda=13.0, __archspec=icelake
Mojo: 0.26.1.0 (release)
mojo-compiler: 0.26.1.0
Channel: https://conda.modular.com/max