When two libraries share a file name (here a/lib.so and b/lib.so) and are both opened with RTLD_GLOBAL, dlsym() on the second handle returns the first library's symbol. glibc built with gcc returns the second library's symbol. Fil-C also does with RTLD_LOCAL.
// a/lib.c (b/lib.c is the same but prints "b/lib.so")
#include <stdio.h>
void init(void) { puts("a/lib.so"); }
// main.c
#include <dlfcn.h>
int main(void) {
void *a = dlopen("./a/lib.so", RTLD_LAZY | RTLD_GLOBAL);
void *b = dlopen("./b/lib.so", RTLD_LAZY | RTLD_GLOBAL);
void (*init_b)(void) = dlsym(b, "init");
init_b();
}$ filcc -shared -fPIC -o a/lib.so a/lib.c && filcc -shared -fPIC -o b/lib.so b/lib.c && filcc -o main main.c && ./main
a/lib.soExpected b/lib.so. Fil-C 0.686 (optfil, linux aarch64).
Found while porting Ruby to Fil-C: Ruby's cgi/escape.so and erb/escape.so both export Init_escape, so require "erb" ran the wrong init function.
Activity
3Thanks mate! Fil-C is a really cool project. I'm working on getting it working w/Ruby 4.0.7 so just reporting stuff as I go along.
This is AI-assisted. Model: gpt-6-astra
Correction: identical file names aren't necessary. RTLD_LOCAL on the second library also reproduces it if the first is global.
a.c:
#include <stdio.h>
void Init_eval(void) { puts("a"); }b.c is the same but prints b. main.c:
#include <dlfcn.h>
int main(void) {
dlopen("./a.so", RTLD_LAZY | RTLD_GLOBAL);
void *b = dlopen("./b.so", RTLD_LAZY | RTLD_LOCAL);
void (*fn)(void) = dlsym(b, "Init_eval");
fn();
}filcc -shared -fPIC -o a.so a.c
filcc -shared -fPIC -o b.so b.c
filcc -o main main.c
./mainPrints a on optfil 0.686, Linux aarch64. Rebuilding all three with GCC prints b. Adding RTLD_DEEPBIND to the second dlopen also gives b with Fil-C.
Weird!
I'll take a look.
Fil-C's dlsym just uses glibc's dlsym, for the most part. So this is a super strange bug!