Here is the sample code of shared library:
#include <stdio.h>
extern void test();
__attribute__((used, constructor)) static void so_init()
{
printf("loaded\n");
test();
}
and here is the main:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
__attribute__((used)) void test()
{
printf("test\n");
}
int main(int argc, char** argv)
{
printf("hello world\n");
return 0;
}
and the makefile:
so:a.c
$(CC) -fPIC -c a.c -o a.o
$(CC) a.o -fPIC -shared -o liba.so
test:m.c
$(CC) -fPIC -c m.c -o m.o
$(CC) m.o -fPIC -o test -L./ -la -Wl,-rpath,./
It works as I expect:
loaded
test
hello world
BUT, when I use dlopen to load that so, something goes wrong. Now, main is:
__attribute__((used)) void test()
{
printf("test\n");
}
int main(int argc, char** argv)
{
printf("hello world\n");
void* so = dlopen("./liba.so", RTLD_LAZY);
printf("%p\n", so);
dlclose(so);
return 0;
}
and Makefile is:
so:a.c
$(CC) -fPIC -c a.c -o a.o
$(CC) a.o -fPIC -shared -o liba.so
test:m.c
$(CC) -fPIC -c m.c -o m.o
$(CC) m.o -fPIC -o test -ldl
and the result of running is:
hello world
loaded
./test: symbol lookup error: ./liba.so: undefined symbol: test
It seems dlopen calls function so_init, but function so_init can not find symbol: test which is at main.
So, focus on constructor, what's the difference between linked at compile time and load at run time? Why dlopen can not find symbol? How can I fix it?
Thanks.
what's the difference between linked at compile time and load at run time?
By default, symbols from the main binary are not exported in its dynamic symbol table, unless they are referenced by a shared library participating in the link.
You can compare the output from nm -D ./test | grep test
in the two cases. When linking test
with -la
, the test()
symbol will appear in nm -D
output. When linking without -la
, the symbol will not appear (but will still be present in nm ./test
output).
To force the symbol to be exported, you can use -rdynamic
(exports all symbols) or -Wl,--export-dynamic-symbol=test
if your linker supports it (related answer).
P.S. Naming the binary test
is not a great idea, because /usr/bin/test
likely exists on your system as well.