`-rdynamic` 到底做了什么以及什么时候需要它?
What exactly does `-rdynamic` do and when exactly is it needed?
-rdynamic
(或链接器级别的 --export-dynamic
)究竟做了什么,它与 -fvisibility*
标志定义的符号可见性或可见性 pragma
s 和 __attribute__
s?
对于 --export-dynamic
,ld(1) 提及:
...
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. ...
我不确定我是否完全理解这一点。您能否提供一个示例,如果没有 -rdynamic
就不能工作,但是有它就可以吗?
编辑:
我实际上尝试编译了几个虚拟库(单个文件、多文件、各种 -O 级别、一些函数间调用、一些隐藏符号、一些可见),有和没有 -rdynamic
,到目前为止我'已经得到 byte-identical 输出(当然,当保持所有其他标志不变时),这非常令人费解。
这里有一个简单的示例项目来说明-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;
}
生成文件
.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
./$<
这里,bar.c
成为共享库libbar.so
,main.c
成为
dlopen
s libbar
并从该库调用 bar()
的程序。
bar()
调用 foo()
,它在 bar.c
中是外部的,在 main.c
中定义。
所以,没有 -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
与 -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 使用 Glibc 的 backtrace()
/backtrace_symbols()
打印回溯。
没有-rdynamic
,您无法获取函数名称。
要了解有关 backtrace()
的更多信息,请阅读 here。
-rdynamic
导出 executable 的符号,这主要解决了 Mike Kinghan 的回答中描述的场景,但它也有帮助,例如Glibc 的 backtrace_symbols()
表示回溯。
这里做个小实验(测试程序复制自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;
}
编译程序:gcc main.c
和运行它,输出:
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]
现在,用 -rdynamic
,即 gcc -rdynamic main.c
和 运行 再次编译:
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]
如您所见,我们现在获得了正确的堆栈跟踪!
现在,如果我们调查 ELF 的符号 table 条目 (readelf --dyn-syms a.out
):
没有-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__
和-rdynamic
,我们有更多的符号,包括executable的:
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
希望对您有所帮助!
来自Linux编程接口:
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
.
我猜这只适用于使用 dlopen()
打开的动态加载的共享库。如果我错了请纠正我。
-rdynamic
(或链接器级别的 --export-dynamic
)究竟做了什么,它与 -fvisibility*
标志定义的符号可见性或可见性 pragma
s 和 __attribute__
s?
对于 --export-dynamic
,ld(1) 提及:
... 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. ...
我不确定我是否完全理解这一点。您能否提供一个示例,如果没有 -rdynamic
就不能工作,但是有它就可以吗?
编辑:
我实际上尝试编译了几个虚拟库(单个文件、多文件、各种 -O 级别、一些函数间调用、一些隐藏符号、一些可见),有和没有 -rdynamic
,到目前为止我'已经得到 byte-identical 输出(当然,当保持所有其他标志不变时),这非常令人费解。
这里有一个简单的示例项目来说明-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;
}
生成文件
.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
./$<
这里,bar.c
成为共享库libbar.so
,main.c
成为
dlopen
s libbar
并从该库调用 bar()
的程序。
bar()
调用 foo()
,它在 bar.c
中是外部的,在 main.c
中定义。
所以,没有 -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
与 -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 使用 Glibc 的 backtrace()
/backtrace_symbols()
打印回溯。
没有-rdynamic
,您无法获取函数名称。
要了解有关 backtrace()
的更多信息,请阅读 here。
-rdynamic
导出 executable 的符号,这主要解决了 Mike Kinghan 的回答中描述的场景,但它也有帮助,例如Glibc 的 backtrace_symbols()
表示回溯。
这里做个小实验(测试程序复制自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;
}
编译程序:gcc main.c
和运行它,输出:
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]
现在,用 -rdynamic
,即 gcc -rdynamic main.c
和 运行 再次编译:
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]
如您所见,我们现在获得了正确的堆栈跟踪!
现在,如果我们调查 ELF 的符号 table 条目 (readelf --dyn-syms a.out
):
没有-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__
和-rdynamic
,我们有更多的符号,包括executable的:
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
希望对您有所帮助!
来自Linux编程接口:
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
.
我猜这只适用于使用 dlopen()
打开的动态加载的共享库。如果我错了请纠正我。