zoukankan      html  css  js  c++  java
  • gcc选项-g与-rdynamic的异同

    https://blog.csdn.net/bobbypollo/article/details/79888753
    注意,这是
    链接选项,而不是编译选项。

    在将c文件编译成.o的过程中,使用rdynamic是没有任何效果的。

    只有将.o链接成elf时,才有效果。

    这主要是对可执行程序而言的,而编译动态库时,即使没有rdynamic选项,默认也会将非静态函数放入动态符号表中(刻意隐藏的函数除外)。

    一个验证方法

    显示可执行程序文件内的动态符号(注意,仅仅是动态符号):

    readelf -Ds a.out

    默认情况下,可执行程序(非动态库)文件内我们定义的非静态函数,是不放到动态符号表中的,链接时只有加上-rdynamic才能将所有非静态函数加到动态符号表中。

    但qin_dev中编译生成的resource_manager,不加rdynamic的情况下,里面依然会有大量动态符号,why?什么情况下,即使没有-rdynamic选项,可执行程序内的很多函数也放到动态符号表中???

    在《深入理解计算机系统》7.11节中,有如下描述:

    “dlopen函数加载和链接共享库filename。用已用带RTLD_GLOBAL选项打开了的库解析filename中的外部符号。

    如果当前可执行文件是带-rdynamic选项编译的,那么对于符号解析而言,它的全局符号也是可用的。”

    1.这段话中“用已用带RTLD_GLOBAL选项打开了的库”一句,感觉非常拗口,请问如何理解?

    2.外部符号的定义是:由其他模块定义并被模块m引用的全局符号。我不理解在什么情况下,一个可执行文件会定义一些在动态库中被引用的全局变量?能否举个例子?

    3.按照我目前的理解:若可执行文件A调用了动态库B,而A与B均引用新的动态库C,那么在库C中的全局变量t,在linux环境下,A与B应将t视为同一份,那么-rdynamic是否在linux下是无用的呢?
    https://www.cnblogs.com/lidabo/p/6203397.html
    遇到如下情况,主程序通过dlopen来打开.so文件,但是.so用到了主程序的log函数。

    编译so时,通过引用主程序头文件来编译通过,头文件有log函数声明:

    extern "C" {
    void print()
    }

    在主程序的.c文件里有函数的具体实现。

    但是dlopen后运行so中函数时,出现找不到相应的symbol。

    这时候就需要在编译主程序ld时加上参数-rdynamic,该参数的作用是:将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)都添加到动态符号表(即.dynsym表)里,以便那些通过dlopen()或backtrace()(这一系列函数使用.dynsym表内符号)这样的函数使用。

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

    -g是编译选项,而-rdynamic是链接选项

    参考:http://www.lenky.info/archives/2013/01/2190

    小例子:

    a.cc

    [cpp] view plain copy

    include "stdio.h"

    include <dlfcn.h>

    extern "C" {
    void print()
    {
    printf("I am in so file! ");
    }

    void fun()
    {
    void * err = dlopen("./libtmp.so", RTLD_LAZY);
    printf("dlopen = %p ", err);
    if (err == NULL) {
    printf("err=%s ", dlerror());
    }
    }
    }

    a.h

    [cpp] view plain copy

    extern "C" void print();
    extern "C" void fun(); // 函数声明和定义都要有extern “C”,或者都没有,否则调用时出现undefined symbol fun

    define NODE_MODULE

    extern "C" {
    static void print_main() attribute((constructor)) // dlopen时会自动调用该contructor函数
    static void print_main() {
    print();
    }
    }

    so.cc

    [cpp] view plain copy

    include "a.h"

    include "stdio.h"

    NODE_MODULE

    foo.h

    [python] view plain copy

    void foo();

    foo.cc

    [cpp] view plain copy

    include "stdio.h"

    void foo()
    {
    printf("foo === ");
    }

    main.cc

    [cpp] view plain copy

    include "a.h"

    int main(void)
    {
    fun();
    return 0;
    }
    Makefile

    [cpp] view plain copy

    all:dynamic

    libtmp.so:so.cc
    g++ -fPIC -shared -o $@ $^

    a.o:
    g++ -c a.cc -fPIC

    liba.a:a.o
    ar -r $@ $^

    libso.so: foo.cc liba.a
    g++ -fPIC -shared -o $@ $< -L./ -la -Wl,--whole-archive -la -Wl,--no-whole-archive -ldl

    dynamic:libso.so libtmp.so
    g++ -o $@ main.cc -Wl,--rpath=. -L./ -lso rdynamic

    clean:
    rm dynamic liba.a a.o libtmp.so

    运行dynamic后输出为:

    [python] view plain copy

    I am in so file!
    dlopen = 0xdeb030
    如果没有-rdynamic,则输出为:

    [python] view plain copy

    dlopen = (nil)
    err=./libtmp.so: undefined symbol: print
    如果没有-Wl,--whole-archive -la -Wl,--no-whole-archive,也会有错误:undefined symbol: print

    --whole-archive 可以把 在其后面出现的静态库包含的函数和变量输出到动态库,--no-whole-archive 则关掉这个特性

    使用readelf -s libso.so | grep fun来查看libso.so的符号表里是否有fun这个函数暴露出来。有--whole-archive的可以查到fun,而没有--whole-archive的,则找不到fun

    先理清一下code

    可执行文件dynamic依赖与libso.so,而libso.so有包含liba.a,在liba.a的函数fun调用dlopen来打开libtmp.so

    主函数调用liba.a的函数来打开libtmp.so

    -fvisibility=hidden

      设置默认的ELF镜像中符号的可见性为隐藏。使用这个特性可以非常充分的提高连接和加载共享库的性能,生成更加优化的代码,提供近乎完美的API输出和防止符号碰撞。我们强烈建议你在编译任何共享库的时候使用该选项。

    -fvisibility-inlines-hidden

        默认隐藏所有内联函数,从而减小导出符号表的大小,既能缩减文件的大小,还能提高运行性能,我们强烈建议你在编译任何共享库的时候使用该选项
    

    所以编译的时候也不能有-fvisibility=hidden和-fvisibility-inlines-hidden。如果有,也会在dlopen时造成错误:undefined symbol

    总结:

    本实例虽小,但用到了不少编译选项

    a: attribute((constructor))
    主程序main函数之前被执行或dlopen时被执行

    b: -rdynamic

    ld时将动态库的的所有符号都输出到符号表,以便dlopen和backtrace也能调用

    c: --whole-archive -la -Wl,--no-whole-archive

    静态库的符号导入到动态库的符号表中,默认是hidden的

    d: -fvisibility=hidden和-fvisibility-inlines-hidden

    ELF镜像中符号的可见性为隐藏(在实验过程中不太好用,待研究)

    在编译nodejs第三方模块时都会碰到这样的问题,第三方模块依赖与nodejs进行编译,而第三方模块又是通过dlopen来打开的,这就要求nodejs编译时将一下第三方模块需要的函数都暴露出来。

    参考:

    http://www.fx114.net/qa-225-106759.aspx

    http://os.chinaunix.net/a2010/0112/1060/000001060902_3.shtml

    https://www.cnblogs.com/LiuYanYGZ/p/5550544.html
    https://www.it1352.com/358375.html
    摘自http://www.tuicool.com/articles/EvIzUn

    gcc选项-g与-rdynamic的异同
    gcc 的 -g ,应该没有人不知道它是一个调试选项,因此在一般需要进行程序调试的场景下,我们都会加上该选项,并且根据调试工具的不同,还能直接选择更有针对性的说明,比如 -ggdb 。-g是一个编译选项,即在源代码编译的过程中起作用,让gcc把更多调试信息(也就包括符号信息)收集起来并将存放到最终的可执行文件内。
    相比-g选项, -rdynamic 却是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。

    看示例:

    [root@www c]# cat t.c

    include <stdio.h>

    void bar() {}
    void baz() {}
    void foo() {}
    int main() { foo(); printf("test"); return 0; }
    对于上面的示例代码,普通和加-g编译:

    [root@www c]# uname -a
    Linux www.t1.com 2.6.38.8 #2 SMP Wed Nov 2 07:52:53 CST 2011 x86_64 x86_64 x86_64 GNU/Linux
    [root@www c]# gcc -O0 -o t t.c
    [root@www c]# gcc -O0 -g -o t.g t.c
    [root@www c]# readelf -a t > t.elf
    [root@www c]# readelf -a t.g > t.g.elf
    [root@www c]# ls -lh *.elf t t.g
    -rwxr-xr-x. 1 root root 6.6K Jul 24 06:50 t
    -rw-r--r--. 1 root root 15K Jul 24 06:51 t.elf
    -rwxr-xr-x. 1 root root 7.9K Jul 24 06:50 t.g
    -rw-r--r--. 1 root root 16K Jul 24 06:51 t.g.elf
    加-g编译后,因为包含了debug信息,因此生成的可执行文件偏大(程序本身非常小,所以增加的调试信息不多)。
    看-g编译的符号表:

    [root@www c]# readelf -s t

    Symbol table '.dynsym' contains 4 entries:
    Num: Value Size Type Bind Vis Ndx Name
    0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
    1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
    2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
    3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)

    Symbol table '.symtab' contains 67 entries:
    Num: Value Size Type Bind Vis Ndx Name
    ...
    48: 00000000004003e0 0 FUNC GLOBAL DEFAULT 13 _start
    49: 00000000004004c4 6 FUNC GLOBAL DEFAULT 13 bar
    ...
    53: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@@GLIBC_2.2.5
    54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
    55: 00000000004005e8 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
    56: 00000000004004d0 6 FUNC GLOBAL DEFAULT 13 foo
    ...
    64: 00000000004004d6 31 FUNC GLOBAL DEFAULT 13 main
    65: 0000000000400390 0 FUNC GLOBAL DEFAULT 11 _init
    66: 00000000004004ca 6 FUNC GLOBAL DEFAULT 13 baz
    注意.dynsym表,只有该程序用到的几个外部动态符号存在。
    加-rdynamic选项编译,readelf查看:

    [root@www c]# gcc -O0 -rdynamic -o t.rd t.c
    [root@www c]# readelf -s t.rd

    Symbol table '.dynsym' contains 20 entries:
    Num: Value Size Type Bind Vis Ndx Name
    0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
    1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
    2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
    3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
    4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
    5: 0000000000400724 6 FUNC GLOBAL DEFAULT 13 bar
    6: 0000000000400730 6 FUNC GLOBAL DEFAULT 13 foo
    7: 0000000000600b68 0 NOTYPE GLOBAL DEFAULT 24 __data_start
    8: 0000000000600b80 0 NOTYPE GLOBAL DEFAULT ABS _end
    9: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS _edata
    10: 0000000000600b68 0 NOTYPE WEAK DEFAULT 24 data_start
    11: 0000000000400640 0 FUNC GLOBAL DEFAULT 13 _start
    12: 0000000000400848 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
    13: 0000000000400770 137 FUNC GLOBAL DEFAULT 13 __libc_csu_init
    14: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
    15: 0000000000400736 39 FUNC GLOBAL DEFAULT 13 main
    16: 00000000004005f0 0 FUNC GLOBAL DEFAULT 11 _init
    17: 0000000000400760 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
    18: 0000000000400838 0 FUNC GLOBAL DEFAULT 14 _fini
    19: 000000000040072a 6 FUNC GLOBAL DEFAULT 13 baz

    Symbol table '.symtab' contains 67 entries:
    Num: Value Size Type Bind Vis Ndx Name
    ...
    50: 0000000000400640 0 FUNC GLOBAL DEFAULT 13 _start
    51: 0000000000400724 6 FUNC GLOBAL DEFAULT 13 bar
    ...
    55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@@GLIBC_2.2.5
    56: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _libc_start_main@@GLIBC
    57: 0000000000400848 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
    58: 0000000000400730 6 FUNC GLOBAL DEFAULT 13 foo
    ...
    64: 0000000000400736 31 FUNC GLOBAL DEFAULT 13 main
    65: 00000000004005f0 0 FUNC GLOBAL DEFAULT 11 _init
    66: 000000000040072a 6 FUNC GLOBAL DEFAULT 13 baz
    [root@www c]#
    可以看到添加-rdynamic选项后,.dynsym表就包含了所有的符号,不仅是已使用到的外部动态符号,还包括本程序内定义的符号,比如bar、foo、baz等。
    .dynsym表里的数据并不能被strip掉:

    [root@www c]# strip t.rd
    [root@www c]# readelf -s t.rd

    Symbol table '.dynsym' contains 20 entries:
    Num: Value Size Type Bind Vis Ndx Name
    0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
    1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
    2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND gmon_start
    3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
    4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
    5: 0000000000400724 6 FUNC GLOBAL DEFAULT 13 bar
    6: 0000000000400730 6 FUNC GLOBAL DEFAULT 13 foo
    7: 0000000000600b68 0 NOTYPE GLOBAL DEFAULT 24 __data_start
    8: 0000000000600b80 0 NOTYPE GLOBAL DEFAULT ABS _end
    9: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS _edata
    10: 0000000000600b68 0 NOTYPE WEAK DEFAULT 24 data_start
    11: 0000000000400640 0 FUNC GLOBAL DEFAULT 13 _start
    12: 0000000000400848 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
    13: 0000000000400770 137 FUNC GLOBAL DEFAULT 13 __libc_csu_init
    14: 0000000000600b6c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
    15: 0000000000400736 39 FUNC GLOBAL DEFAULT 13 main
    16: 00000000004005f0 0 FUNC GLOBAL DEFAULT 11 _init
    17: 0000000000400760 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
    18: 0000000000400838 0 FUNC GLOBAL DEFAULT 14 _fini
    19: 000000000040072a 6 FUNC GLOBAL DEFAULT 13 baz
    简单总结一下-g选项与-rdynamic选项的差别:
    1,-g选项新添加的是调试信息(一系列.debug_xxx段),被相关调试工具,比如gdb使用,可以被strip掉。

    2,-rdynamic选项新添加的是动态连接符号信息,用于动态连接功能,比如dlopen()系列函数、backtrace()系列函数使用,不能被strip掉,即强制strip将导致程序无法执行:

    [root@www c]# ./t.rd
    test[root@www c]# strip -R .dynsym t.rd
    [root@www c]# ./t.rd
    ./t.rd: relocation error: ./t.rd: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference
    [root@www c]#
    3,.symtab表在程序加载时会被加载器 丢弃 ,gdb等调试工具由于可以直接访问到磁盘上的二进制程序文件:

    [root@www c]# gdb t.g -q
    Reading symbols from /home/work/dladdr/c/t.g...done.
    (gdb)
    因此可以使用所有的调试信息,这包括.symtab表;而backtrace()系列函数作为程序执行的逻辑功能,无法去读取磁盘上的二进制程序文件,因此只能使用.dynsym表。
    其它几个工具可以动态指定查看,比如nm、objdump:

    [root@www c]# nm t.rd
    nm: t.rd: no symbols
    [root@www c]# nm -D t.rd
    0000000000400848 R _IO_stdin_used
    w _Jv_RegisterClasses
    0000000000600b6c A __bss_start
    0000000000600b68 D __data_start
    w gmon_start
    0000000000400760 T __libc_csu_fini
    0000000000400770 T __libc_csu_init
    U __libc_start_main
    0000000000600b6c A _edata
    0000000000600b80 A _end
    0000000000400838 T _fini
    00000000004005f0 T _init
    0000000000400640 T _start
    0000000000400724 T bar
    000000000040072a T baz
    0000000000600b68 W data_start
    0000000000400730 T foo
    0000000000400736 T main
    U printf
    [root@www c]#
    [root@www c]# objdump -T t.rd

    t.rd: file format elf64-x86-64

    DYNAMIC SYMBOL TABLE:
    0000000000000000 DF UND 0000000000000000 GLIBC_2.2.5 printf
    0000000000000000 w D UND 0000000000000000 gmon_start
    0000000000000000 w D UND 0000000000000000 _Jv_RegisterClasses
    0000000000000000 DF UND 0000000000000000 GLIBC_2.2.5 __libc_start_main
    0000000000400724 g DF .text 0000000000000006 Base bar
    0000000000400730 g DF .text 0000000000000006 Base foo
    0000000000600b68 g D .data 0000000000000000 Base __data_start
    0000000000600b80 g D ABS 0000000000000000 Base _end
    0000000000600b6c g D ABS 0000000000000000 Base _edata
    0000000000600b68 w D .data 0000000000000000 Base data_start
    0000000000400640 g DF .text 0000000000000000 Base _start
    0000000000400848 g DO .rodata 0000000000000004 Base _IO_stdin_used
    0000000000400770 g DF .text 0000000000000089 Base __libc_csu_init
    0000000000600b6c g D ABS 0000000000000000 Base __bss_start
    0000000000400736 g DF .text 0000000000000027 Base main
    00000000004005f0 g DF .init 0000000000000000 Base _init
    0000000000400760 g DF .text 0000000000000002 Base __libc_csu_fini
    0000000000400838 g DF .fini 0000000000000000 Base _fini
    000000000040072a g DF .text 0000000000000006 Base baz
    4,-rdynamic选项不产生任何调试信息,因此在一般情况下,新增的附加信息比-g选项要少得多。除非是完全的静态连接,否则即便是没有加-rdynamic选项,程序使用到的外部动态符号,比如前面示例里的printf,也会被自动加入到.dynsym表。

    完全参考:
    http://stackoverflow.com/questions/8623884/gcc-debug-symbols-g-flag-vs-linkers-rdynamic-option

  • 相关阅读:
    vue+element-UI实现分页效果
    vue实现点击其他地方隐藏div
    nodejs、数据库(基本指令)基础要点总结
    vue项目中封装axios的请求拦截器和响应拦截器
    echarts树形图,分支过多,页面放不下,高度自适应方法
    echarts的树形结构图及参数
    如何使用nprogress实现页面加载进度条
    抗锯齿相关技术介绍:MSAA、FXAA、SMAA、TXAA、MSAA
    配置虚拟机从下载到安装的小问题总结——2020.3.7
    Lecture08_着色 1(光照、着色和图形管线)_GAMES101 课堂笔记——2020.3.6
  • 原文地址:https://www.cnblogs.com/marklove/p/14269385.html
Copyright © 2011-2022 走看看