Petr Skocik
Petr Skocik

Reputation: 60127

What exactly does `-rdynamic` do and when exactly is it needed?

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 pragmas 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

Answers (5)

Mike Kinghan
Mike Kinghan

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 dlopens 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.sos 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

sizif
sizif

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

Rick
Rick

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, use dlsym() to obtain the address of a function x() from that library, and then call x(). If x() in turn calls a function y(), then y() 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 of y() 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 the gcc -Wl,-E option are further

synonyms 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

Y.H.
Y.H.

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

deep_rugs
deep_rugs

Reputation: 317

I use rdynamic to print out backtraces using the backtrace()/backtrace_symbols() of Glibc.

Without -rdynamic, you cannot get function names.

To know more about the backtrace() read it over here.

Upvotes: 17

Related Questions