What exactly does `-rdynamic` do and when exactly

2020-01-30 05:27发布

问题:

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.

回答1:

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

$ 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


回答2:

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.



回答3:

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