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.
-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. -s.
Dynamic linking is a two-step process that relies on accessing the addresses of code. The first step occurs at compilation. When a file is compiled with a dynamic library, instead of copying the actual object code contained in the library, the linker simply scans the code contained and checks for missing symbols.
Statically linked files are significantly larger in size because external programs are built into the executable files. In dynamic linking only one copy of shared library is kept in memory.
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 $@ $< -L. -lbar -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
:
$ 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 -ldl ./prog ./libbar.so: undefined symbol: foo Makefile:23: recipe for target 'test' failed
And with -rdynamic
:
$ make clean rm -f *.o *.so prog $ 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 -L. -lbar -ldl ./prog Hello world
-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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With