rdynamic和-whole-archive

简介: 遇到如下情况,主程序通过dlopen来打开.so文件,但是.so用到了主程序的log函数。 编译so时,通过引用主程序头文件来编译通过,头文件有log函数声明:   extern "C" {    void print()  } 在主程序的.c文件里有函数的具体实现。

遇到如下情况,主程序通过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
 
  1. #include "stdio.h"  
  2. #include <dlfcn.h>  
  3.   
  4. extern "C" {  
  5. void print()  
  6. {  
  7.     printf("I am in so file!\n");  
  8. }  
  9.   
  10. void fun()  
  11. {  
  12.     void * err = dlopen("./libtmp.so", RTLD_LAZY);  
  13.     printf("dlopen = %p\n", err);  
  14.     if (err == NULL) {  
  15.         printf("err=%s\n", dlerror());  
  16.     }     
  17. }  
  18. }  


a.h

 

 

[cpp]  view plain  copy
 
  1. extern "C" void print();  
  2. extern "C" void fun();  // 函数声明和定义都要有extern “C”,或者都没有,否则调用时出现undefined symbol fun  
  3.   
  4. #define NODE_MODULE \  
  5. extern "C" { \  
  6.    static void print_main() __attribute__((constructor)) // dlopen时会自动调用该contructor函数</span>  
  7.    static void print_main() { \  
  8.        print(); \  
  9.    } \  
  10. }   

 

 

so.cc

 

[cpp]  view plain  copy
 
  1. #include "a.h"  
  2. #include "stdio.h"  
  3.   
  4. NODE_MODULE  

 

 

foo.h

 

[python]  view plain  copy
 
  1. <span style="font-size:18px;">void foo();</span>  

 

 

foo.cc

 

[cpp]  view plain  copy
 
  1. #include "stdio.h"  
  2.   
  3. void foo()  
  4. {  
  5.     printf("foo === \n");  
  6. }  



 

main.cc

[cpp]  view plain  copy
 
  1. #include "a.h"  
  2.   
  3. int main(void)  
  4. {  
  5.     fun();  
  6.     return 0;  
  7. }  

Makefile

 

[cpp]  view plain  copy
 
  1. all:dynamic  
  2.   
  3. libtmp.so:so.cc  
  4.     g++ -fPIC -shared -o $@ $^   
  5.   
  6. a.o:  
  7.     g++ -c a.cc -fPIC  
  8.   
  9. liba.a:a.o  
  10.     ar -r $@  $^   
  11.   
  12. libso.so: foo.cc liba.a  
  13.     g++ -fPIC -shared -o $@ $< -L./ -la -Wl,--whole-archive -la  -Wl,--no-whole-archive -ldl  
  14.   
  15. dynamic:libso.so libtmp.so  
  16.     g++ -o $@ main.cc -Wl,--rpath=. -L./ -lso rdynamic  
  17.   
  18. clean:  
  19.     rm dynamic liba.a a.o libtmp.so  



 

运行dynamic后输出为:

 

[python]  view plain  copy
 
  1. I am in so file!  
  2. dlopen = 0xdeb030  

如果没有-rdynamic,则输出为:

 

 

[python]  view plain  copy
 
  1. dlopen = (nil)  
  2. 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

目录
相关文章
|
算法 安全 网络安全
简单认识一下mbedTLS
简单认识一下mbedTLS
1492 0
|
Rust 自然语言处理 安全
【Rust日报】用 Rust 从头实现一个 C 编译器
【6月更文挑战第9天】使用 Rust 实现 C 编译器的挑战性项目,利用 Rust 的内存安全和高性能优势。涉及词法分析、语法分析等步骤,示例代码展示简单的词法分析过程。实际实现需处理更多复杂环节,如抽象语法树构建和代码生成。此项目能深化对编译器原理的理解,提升 Rust 技能,并有望推动更多高质量 Rust 编译器的诞生。
388 7
在Linux中,如何获取CPU的总核心数?
在Linux中,如何获取CPU的总核心数?
|
11月前
|
缓存 Ubuntu 网络安全
docker pull失败:x509: certificate has expired or is not yet
遇到“x509: certificate has expired or is not yet valid”错误时,首要步骤是校正系统时间并确保Docker客户端是最新的。如果问题依旧,检查和更新证书或考虑使用镜像加速服务也是可行的解决方案。通过这些步骤,大多数与证书相关的 `docker pull`问题都能得到有效解决。
1370 0
|
Prometheus 监控 Cloud Native
简单搭建基本Prometheus监控系统
简单搭建基本Prometheus监控系统
232 0
|
SQL 开发者 索引
【深入浅出】阿里自研开源搜索引擎Havenask变更表结构
本文介绍了Havenask的表结构变更,包括表结构简介、全量构建流程和变更表结构三个部分。表结构由schema配置,字段类型包括INT、FLOAT、STRING等,索引有倒排、正排和摘要索引。全量表变更会触发全量构建,完成后自动切换,但直写表不支持直接变更。变更过程涉及使用hape命令更新schema并触发全量build。最后还有全量构建的流程图和具体操作步骤。
62093 2
|
SQL 消息中间件 Swift
【一文看懂】Havenask单机模式创建
本次分享内容为Havenask单机模式,由下面3个部分组成(Hape工具介绍、创建单机版Havenask、Hape问题排查),希望可以帮助大家更好了解和使用Havenask。
128953 1
|
编译器 程序员 C语言
【GCC 参数】 深入C++编译器常用标志:C/C++ 开发者必备的编译器参数
【GCC 参数】 深入C++编译器常用标志:C/C++ 开发者必备的编译器参数
236 0
|
消息中间件 前端开发 测试技术
DDD - 分层架构:有效降低层与层之间的依赖
DDD - 分层架构:有效降低层与层之间的依赖
652 0
|
传感器 物联网 SoC
嵌入式系统中I2C总线通信基本方法(上)
嵌入式系统中I2C总线通信基本方法(上)
384 0