Reputation: 60127
What exactly does -rdynamic
(or --export-dynamic
at the linker level) do and how does it relate to symbol visibility as defined by the -fvisibility*
flags or visibility pragma
s and __attribute__
s?
For --export-dynamic
, ld(1) mentions:
... If you use "dlopen" to load a dynamic object which needs to refer back to the symbols defined by the program, rather than some other dynamic object, then you will probably need to use this option when linking the program itself. ...
I'm not sure I completely understand this. Could you please provide an example that doesn't work without -rdynamic
but does with it?
Edit:
I actually tried compiling a couple of dummy libraries (single file, multi-file, various -O levels, some inter-function calls, some hidden symbols, some visible), with and without -rdynamic
, and so far I've been getting byte-identical outputs (when keeping all other flags constant of course), which is quite puzzling.
Upvotes: 96
Views: 69817
Reputation: 61575
Here is a simple example project to illustrate the use of -rdynamic
.
bar.c
extern void foo(void);
void bar(void)
{
foo();
}
main.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
void foo(void)
{
puts("Hello world");
}
int main(void)
{
void * dlh = dlopen("./libbar.so", RTLD_NOW);
if (!dlh) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
void (*bar)(void) = dlsym(dlh,"bar");
if (!bar) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
bar();
return 0;
}
Makefile
.PHONY: all clean test
LDEXTRAFLAGS ?=
all: prog
bar.o: bar.c
gcc -c -Wall -fpic -o $@ $<
libbar.so: bar.o
gcc -shared -o $@ $<
main.o: main.c
gcc -c -Wall -o $@ $<
prog: main.o | libbar.so
gcc $(LDEXTRAFLAGS) -o $@ $< -ldl
clean:
rm -f *.o *.so prog
test: prog
./$<
Here, bar.c
becomes a shared library libbar.so
and main.c
becomes
a program that dlopen
s libbar
and calls bar()
from that library.
bar()
calls foo()
, which is external in bar.c
and defined in main.c
.
So, without -rdynamic
:
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -o prog main.o -ldl
./prog
./libbar.so: undefined symbol: foo
make: *** [Makefile:23: test] Error 1
And with -rdynamic
:
$ make test LDEXTRAFLAGS=-rdynamic
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -rdynamic -o prog main.o -ldl
./prog
Hello world
As you noted, the gcc
option -rdynamic
enables the linker option --export-dynamic
. So it
is a shorthand for passing -Wl,--export-dynamic
to gcc
.
By default, when linking a program (as opposed to a shared library) the static linker does not propagate an external symbol foo
that is statically defined by the program from its global symbol table to its dynamic symbol table unless it determines
that foo
is referenced by a shared library in the linkage - i.e. unless the linker can see that foo
needs to be dynamically exported to resolve
a reference in the linkage. If foo
is not dynamically exported it will not be visible to the
dynamic linker at runtime and will be unavailable to resolve undefined dynamic symbol references
in any shared library loaded by the program - such as libbar.so
s reference to foo
. If foo
is
referenced only by a shared library that is dlopen
-ed programatically, then the static linker cannot
determine whether or not foo
will be referenced (because it is unaware of dlopen
-ed libraries) and will not propagate foo
to the dynamic symbol table.
The --export-dynamic
option countermands this default behaviour, causing any external symbol that is statically
defined by the program to be propagated to its dynamic symbol table.
The effect of -rdynamic/--export-dynamic
is manifest not only in the dynamic symbol table of
a program that performs dynamic linkage programatically, via dlopen
and friends, although the effect will not be useful when dlopen
and friends are not in play. Here is an example of its
effect in a program that performs dynamic linkage in the usual automatic way (having it set up via the dynamic section
that the static linker writes into the program).
bar.c - no change.
main.c
#include <stdio.h>
extern void bar();
void boo(void)
{
puts("Goodbye world");
}
void foo(void)
{
puts("Hello world");
}
int main(void)
{
bar();
return 0;
}
Makefile
.PHONY: all clean test
LDEXTRAFLAGS ?=
all: prog
bar.o: bar.c
gcc -c -Wall -fpic -o $@ $<
libbar.so: bar.o
gcc -shared -o $@ $<
main.o: main.c
gcc -c -Wall -o $@ $<
prog: main.o | libbar.so
gcc $(LDEXTRAFLAGS) -o $@ $< -L. -lbar -Wl,-rpath='$$ORIGIN'
clean:
rm -f *.o *.so prog
test: prog
./$<
In this example, both boo
and foo
are defined but unreferenced in main.c
, but foo
remains
referenced by libbar.so
while boo
is not referenced in the linkage at all.
Without -Wl,--export-dynamic
:
$ make clean
rm -f *.o *.so prog
$ make test
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -o prog main.o -L. -lbar -Wl,-rpath='$ORIGIN'
./prog
Hello world
We see that foo
is propagated to
the dynamic symbol table of prog
, because it is referenced from libbar.so
:
$ readelf --dyn-syms --wide prog
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.34 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bar
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
7: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (3)
8: 0000000000001183 26 FUNC GLOBAL DEFAULT 16 foo
But boo
is absent.
With -Wl,--export-dynamic
:
$ make clean
rm -f *.o *.so prog
$ make LDEXTRAFLAGS=-Wl,--export-dynamic test
gcc -c -Wall -o main.o main.c
gcc -c -Wall -fpic -o bar.o bar.c
gcc -shared -o libbar.so bar.o
gcc -Wl,--export-dynamic -o prog main.o -L. -lbar -Wl,-rpath='$ORIGIN'
./prog
Hello world
$ readelf --dyn-syms --wide prog
Symbol table '.dynsym' contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.34 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND bar
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
7: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata
8: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
9: 0000000000001183 26 FUNC GLOBAL DEFAULT 16 foo
10: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end
11: 0000000000001169 26 FUNC GLOBAL DEFAULT 16 boo
12: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (3)
13: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
14: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
15: 0000000000001080 38 FUNC GLOBAL DEFAULT 16 _start
16: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
17: 000000000000119d 25 FUNC GLOBAL DEFAULT 16 main
boo
is added to the dynamic symbol table of prog
, as well as several other previously
absent external defined symbols which are unreferenced in the program. All these extra dynamic symbols
are idle in this particular program linkage.
--export-dynamic
is enabled unconditionally for the linkage of a shared library and disabled by
default for the linkage of a program.
Upvotes: 141
Reputation: 342
To answer the OP question literally, a quote from GCC manual:
-rdynamic
Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of dlopen or to allow obtaining backtraces from within a program.
So -rdynamic
is needed when compiling the program that uses dlopen
, to make references from the loaded module to symbols of the program resolvable.
Upvotes: 2
Reputation: 7516
From The Linux Programming Interface:
42.1.6
Accessing Symbols in the Main Program
Suppose that we use
dlopen()
to dynamically load a shared library, usedlsym()
to obtain the address of a functionx()
from that library, and then callx()
. Ifx()
in turn calls a functiony()
, theny()
would normally be sought in one of the shared libraries loaded by the program.Sometimes, it is desirable instead to have
x()
invoke an implementation ofy()
in the main program. (This is similar to a callback mechanism.) In order to do this, we must make the (global-scope) symbols in the main program available to the dynamic linker, by linking the program using the--export-dynamic
linker option:
$ gcc -Wl,--export-dynamic main.c
(plus further options and arguments)Equivalently, we can write the following:
$ gcc -export-dynamic main.c
Using either of these options allows a dynamically loaded library to access global symbols in the main program.
The
gcc -rdynamic
option and thegcc -Wl,-E
option are furthersynonyms for
-Wl,--export-dynamic
.
I guess this only works for dynamically loaded shared library, opened with dlopen()
. Correct me if I am wrong.
Upvotes: 9
Reputation: 2846
-rdynamic
exports the symbols of an executable, this mainly addresses scenarios as described in Mike Kinghan's answer, but also it helps e.g. Glibc's backtrace_symbols()
symbolizing the backtrace.
Here is a small experiment (test program copied from here)
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
void *array[10];
size_t size;
char **strings;
size_t i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
}
/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
print_trace ();
}
int
main (void)
{
dummy_function ();
return 0;
}
compile the program: gcc main.c
and run it, the output:
Obtained 5 stack frames.
./a.out() [0x4006ca]
./a.out() [0x400761]
./a.out() [0x40076d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f026597f830]
./a.out() [0x4005f9]
Now, compile with -rdynamic
, i.e. gcc -rdynamic main.c
, and run again:
Obtained 5 stack frames.
./a.out(print_trace+0x28) [0x40094a]
./a.out(dummy_function+0x9) [0x4009e1]
./a.out(main+0x9) [0x4009ed]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f85b23f2830]
./a.out(_start+0x29) [0x400879]
As you can see, we get a proper stack trace now!
Now, if we investigate ELF's symbol table entry (readelf --dyn-syms a.out
):
without -rdynamic
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace_symbols@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
8: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
with -rdynamic
, we have more symbols, including the executable's:
Symbol table '.dynsym' contains 25 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace_symbols@GLIBC_2.2.5 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND backtrace@GLIBC_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
9: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
11: 0000000000601060 0 NOTYPE GLOBAL DEFAULT 24 _edata
12: 0000000000601050 0 NOTYPE GLOBAL DEFAULT 24 __data_start
13: 0000000000601068 0 NOTYPE GLOBAL DEFAULT 25 _end
14: 00000000004009d8 12 FUNC GLOBAL DEFAULT 14 dummy_function
15: 0000000000601050 0 NOTYPE WEAK DEFAULT 24 data_start
16: 0000000000400a80 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
17: 0000000000400a00 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
18: 0000000000400850 42 FUNC GLOBAL DEFAULT 14 _start
19: 0000000000601060 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
20: 00000000004009e4 16 FUNC GLOBAL DEFAULT 14 main
21: 00000000004007a0 0 FUNC GLOBAL DEFAULT 11 _init
22: 0000000000400a70 2 FUNC GLOBAL DEFAULT 14 __libc_csu_fini
23: 0000000000400a74 0 FUNC GLOBAL DEFAULT 15 _fini
24: 0000000000400922 182 FUNC GLOBAL DEFAULT 14 print_trace
I hope that helps!
Upvotes: 33