Linux程序库(libraries)杂谈
1. 文章简介
本文主要介绍了linux系统上静态库、动态库编译方法、常见特性,以及相关的环境变量,以及动态库编程API。本文作为中级教程,未涉及教科书中常说的程序库的定义、优点等,仅为杂谈。
2. 静态库
Ø 使用ar程序打包,文件名以``.a''结尾
Ø 是Object文件集合
Object文件是elf文件,包含了数据、符号、二进制代码等
Ø 优点
库版本依赖在链接阶段已经解决
由于编译、链接阶段已解决符号地址定位,所以程序启动速度较快(<5%)
Ø 编译方法
ar rcs libmy_library.a file1.o file2.o g++ -L${LIB_PATH} –lmy_library |
3. 共享库
Ø 使用gcc/ld程序生成,文件名以``.so''结尾
Ø 共享库为elf文件,包含了数据、符号、代码等
Ø 文件命名
动态库存在soname、realname,动态库文件的位置为libpath,下面以libstdc++.so.6.0.14文件为例进行说明
文件:/usr/local/gcc4.5.1/lib/libstdc++.so.6.0.14
augustsun@linux-b47w:/usr/lib> l libstdc++.so.6 lrwxrwxrwx 1 root root 19 2010-07-24 13:49 libstdc++.so.6 -> libstdc++.so.6.0.12 |
n soname
soname:libstdc++.so.6,为编译时指定的名字,在动态库内部保存。
augustsun@linux-b47w:/usr/lib> readelf -a libstdc++.so.6.0.12 | grep SO 0x0000000e (SONAME) Library soname: [libstdc++.so.6] |
n realname
realname: /usr/local/gcc4.5.1/lib/libstdc++.so.6.0.14,动态库真实的文件名,包括文件路径
n libpath
/usr/local/gcc4.5.1/lib/
Ø 版本号以及如何支持程序的兼容
n 主版本号表明程序库版本之间的潜在不兼容性。
n 次要版本号表明只是修正了缺陷。
n 版本号实例
gcc -shared -fPIC -Wl,-soname,libd2.so.1 -o libd2.so.1.0.1 d.c ln -s libd2.so.1.0.1 libd2.so.1 ln -s libd2.so.1 libd2.so gcc -shared -fPIC -L. –ld2 -o libb.so b.c
|
编译过程中:
链接器指定了-ld2, 会去寻找libd2.so
如果libd2.so为符号链接,指向某个指向最新版本的符号链接,直到出现第一个主版本号文件(libd2.so.1)
所以会依赖libd2.so.1
n 兼容性总结
在大部分情形下,加载具有相同主版本号和更高次要版本号的程序库是安全的;而加载主版本号更高的程序是不安全的行为。
Ø 优点
程序共享,减少程序文件大小。
动态链接的可能优势之一在于修正缺陷。如果可以修正程序库中的缺陷,而且不必重新编译上千个程序,就 可以利用这一修正功能,这将是非常令人愉快的。有时,需要链接到某个较新的版本。
依赖动态链接库的程序不用重新编译
Ø 编译方法
gcc -fPIC -g -c -Wall a.c gcc -fPIC -g -c -Wall b.c gcc -shared -Wl,-soname,libmystuff.so.1 \ -o libmystuff.so.1.0.1 a.o b.o -lc |
编译选项:运行时动态库查找路径:-Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)
4. 共享库相关环境变量
Ø LD_LIBRARY_PATH
动态库优先搜索路径
Ø LD_PRELOAD
n 库中函数会覆盖其他库中函数
n 临时打补丁使用
n 其他方式
配置/etc/ld.so.preload,或者执行时命令行:/lib/ld-linux.so.2 --library-path PATH EXECUTABLE
n 例子
augustsun@linux-b47w:~/workspace> cat test.c #include <stdio.h> #include <string.h> void test(const char* a, const char* b) { if (strcmp(a, b) == 0) { printf("str('%s') equals str('%s')\n", a, b); } else { printf("str('%s') *NOT* equals str('%s')\n", a, b); } } int main(int argc, char* arvg) { test("hello", "world"); return 0; }
augustsun@linux-b47w:~/workspace> ./test str('hello') *NOT* equals str('world')
augustsun@linux-b47w:~/workspace> cat preload.c #include <string.h> int strcmp(const char* a, const char* b) { return 0; }
augustsun@linux-b47w:~/workspace> export LD_PRELOAD=./preload.so augustsun@linux-b47w:~/workspace> ./test str('hello') equals str('world')
其他说明: 以下代码没有调用strcmp,因为编译时已经优化掉了,即使使用了-O0优化!!可以通过gcc –S test.c来查看汇编代码。知道禁止编译优化的请mail [email protected],谢谢! #include <stdio.h> #include <string.h> int main(int argc, char* arvg) { if (strcmp("hello", "world") == 0) { printf("str('hello') equals str('world')\n", a, b); } else { printf("str('hello') *NOT* equals str('world')\n", a, b); } return 0; }
|
Ø LD_TRACE_LOADED_OBJECTS
执行程序时程序将不执行而是输出了动态库的依赖项
augustsun@linux-b47w:~> export LD_TRACE_LOADED_OBJECTS=1 augustsun@linux-b47w:~> ls linux-gate.so.1 => (0xffffe000) librt.so.1 => /lib/librt.so.1 (0xb78a1000) libselinux.so.1 => /lib/libselinux.so.1 (0xb7883000) libcap.so.2 => /lib/libcap.so.2 (0xb787d000) libacl.so.1 => /lib/libacl.so.1 (0xb7874000) libc.so.6 => /lib/libc.so.6 (0xb7713000) libpthread.so.0 => /lib/libpthread.so.0 (0xb76f9000) /lib/ld-linux.so.2 (0xb78c1000) libdl.so.2 => /lib/libdl.so.2 (0xb76f4000) libattr.so.1 => /lib/libattr.so.1 (0xb76ee000) |
5. 动态(加载)库
程序执行中通过dl簇函数加载。跟共享库的差别是:共享库在程序启动时加载,并且由/lib/ld-linux.so.2自动加载。
Ø API例子及其说明
|
void *dlopen(const char *filename, int flag);
n 主要flag说明
RTLD_LAZY:代码执行到时解析(重定向)
RTLD_NOW: dlopen函数返回前解析(重定向)所有符号连接
RTLD_GLOBAL: 后续动态库的符号解析时可以之后此加载的符号
RTLD_LOCAL: 后续动态库的符号解析时不可以之后此加载的符号
Ø LD_DEBUG
程序中有调用dl*函数的时候输出信息
6. 其他
Ø 库的构造和析构函数
n 不推荐使用的API
void _init(void); void _fini(void); |
n 推荐使用的API
void __attribute__ ((constructor)) my_init(void); void __attribute__ ((destructor)) my_fini(void); |
Ø 动态库可以被执行
通过设置编译参数-Wl,-e,FunctionName,动态库可以执行FunctionName函数。
const char my_interp[] __attribute__((section(".interp"), used)) = "/lib64/ld-linux-x86-64.so.2";
#ifdef __cplusplus extern "C" { #endif void ShowVersion(void) { puts("version:1.20.0-build002"); exit(0); } #ifdef __cplusplus } #endif
|
Ø 库函数兼容性
n 二进制兼容(Application Binary Interface)的好处
1. 直接替换版本不需要重新编译
2. 版本更新简单
n C语言程序不兼容的原因
1. 函数的行为改变,符合于原有的规范
2. 数据结构变了
3. 原有接口不再支持
4. 函数接口变化
n C++语言程序不兼容的原因
1. 添加新的虚函数(导致虚函数表变化)
2. 添加、删除虚函数,导致虚函数表变化
3. 外部可见的成员变量类型或者位置变化
4. 类继承变化
5. 成员变量变化,导致子类字段错乱
6. 非inline的外部可见函数被删除
7. 外部可见函数变成了inline函数
8. inline函数变化
9. 函数定义变化,导致生成的mangle名变化
10. Others
所以,建议提供C原因接口
7. 引用
http://blog.csdn.net/haoel/article/details/1602108
http://www.360doc.com/content/09/0101/23/36491_2242478.shtml
http://en.wikipedia.org/wiki/Shared_library#Shared_libraries
http://wenku.baidu.com/view/9ef3dd649b6648d7c1c74626.html
http://hi.baidu.com/gaoxiaotiger/blog/item/d963547ff2c36e2f0cd7daa8.html