[转]Linux动态库的种种要点
linux下使用动态库,基本用起来还是很容易。但如果我们的程序中大量使用动态库来实现各种框架/插件,那么就会遇到一些坑,掌握这些坑才有利于程序更稳健地运行。
本篇先谈谈动态库符号方面的问题。
测试代码可以在github上找到
符号查找
一个应用程序test
会链接一个动态库libdy.so
,如果一个符号,例如函数callfn
定义于libdy.so中,test要使用该函数,简单地声明即可:
// dy.cpp libdy.so
void callfn() {
...
}
// main.cpp test
extern void callfn();
callfn();
在链接test的时候,链接器会统一进行检查。
同样,在libdy.so中有相同的规则,它可以使用一个外部的符号,在它被链接/载入进一个可执行程序时才会进行符号存在与否的检查。这个符号甚至可以定义在test中,形成一种双向依赖,或定义在其他动态库中:
// dy.cpp libdy.so
extern void mfunc();
mfunc();
// main.cpp test
void mfunc() {
...
}
在生成libdy.so时mfunc
可以找不到,此时mfunc
为未定义:
$ nm libdy.so | grep mfun
U _Z5mfuncv
但在libdy.so被链接进test时则会进行检查,试着把mfunc
函数的定义去掉,就会得到一个链接错误:
./libdy.so: undefined reference to `mfunc()'
同样,如果我们动态载入libdy.so,此时当然可以链接通过,但是在载入时同样得到找不到符号的错误:
#ifdef DY_LOAD
void *dp = dlopen("./libdy.so", RTLD_LAZY);
typedef void (*callfn)();
callfn f = (callfn) dlsym(dp, "callfn");
f();
dlclose(dp);
#else
callfn();
#endif
得到错误:
./test: symbol lookup error: ./libdy.so: undefined symbol: _Z5mfuncv
结论:基于以上,我们知道,如果一个动态库依赖了一些外部符号,这些外部符号可以位于其他动态库甚至应用程序中。我们可以再链接这个动态库的时候就把依赖的其他库也链接上,或者推迟到链接应用程序时再链接。而动态加载的库,则要保证在加载该库时,进程中加载的其他动态库里已经存在该符号。
例如,通过LD_PRELOAD
环境变量可以让一个进程先加载指定的动态库,上面那个动态加载启动失败的例子,可以通过预先加载包含mfunc
符号的动态库解决:
$ LD_PRELOAD=libmfun.so ./test
...
但是如果这个符号存在于可执行程序中则不行:
$ nm test | grep mfunc
0000000000400a00 T _Z5mfuncv
$ nm test | grep mfunc
0000000000400a00 T _Z5mfuncv
$ ./test
...
./test: symbol lookup error: ./libdy.so: undefined symbol: _Z5mfuncv
符号覆盖
前面主要讲的是符号缺少的情况,如果同一个符号存在多分,则更能引发问题。这里谈到的符号都是全局符号,一个进程中某个全局符号始终是全局唯一的。为了保证这一点,在链接或动态载入动态库时,就会出现忽略重复符号的情况。
这里就不提同一个链接单位(如可执行程序、动态库)里符号重复的问题了
函数
当动态库和libdy.so可执行程序test中包含同名的函数时会怎样?根据是否动态加载情况还有所不同。
当直接链接动态库时,libdy.so和test都会链接包含func
函数的fun.o,为了区分,我把func
按照条件编译得到不同的版本:
// fun.cpp
#ifdef V2
extern "C" void func() {
printf("func v2\n");
}
#else
extern "C" void func() {
printf("func v1\n");
}
#endif
// Makefile
test: libdy obj.o mainfn
g++ -g -Wall -c fun.cpp -o fun.o # 编译为fun.o
g++ -g -Wall -c main.cpp #-DDY_LOAD
g++ -g -Wall -o test main.o obj.o fun.o -ldl mfun.o -ldy -L.
libdy: obj
g++ -Wall -fPIC -c fun.cpp -DV2 -o fun-dy.o # 定义V2宏,编译为fun-dy.o
g++ -Wall -fPIC -shared -o libdy.so dy.cpp -g obj.o fun-dy.o
这样,test中的func
就会输出func v1
;libdy.so中的func
就会输出func v2
。test和libdy.o确实都有func
符号:
$ nm libdy.so | grep func
0000000000000a60 T func
$nm test | grep func
0000000000400a80 T func
在test和libdy.so中都会调用func
函数:
// main.cpp test
int main(int argc, char **argv) {
func();
...
callfn(); // 调用libdy.so中的函数
...
}
// dy.cpp libdy.so
extern "C" void callfn() {
...
printf("callfn\n");
func();
...
}
运行后发现,都调用的是同一个func
:
$ ./test
...
func v1
...
callfn
func v1
结论,直接链接动态库时,整个程序运行的时候符号会发生覆盖,只有一个符号被使用。在实践中,如果程序和链接的动态库都依赖了一个静态库,而后他们链接的这个静态库版本不同,则很有可能因为符号发生了覆盖而导致问题。(静态库同普通的.o性质一样,参考浅析静态库链接原理)
更复杂的情况中,多个动态库和程序都有相同的符号,情况也是一样,会发生符号覆盖。如果程序里没有这个符号,而多个动态库里有相同的符号,也会覆盖。
但是对于动态载入的情况则不同,同样的libdy.so我们在test中不链接,而是动态载入:
int main(int argc, char **argv) {
func();
#ifdef DY_LOAD
void *dp = dlopen("./libdy.so", RTLD_LAZY);
typedef void (*callfn)();
callfn f = (callfn) dlsym(dp, "callfn");
f();
func();
dlclose(dp);
#else
callfn();
#endif
return 0;
}
运行得到:
$ ./test
func v1
...
callfn
func v2
func v1
都正确地调用到各自链接的func
。
结论,实践中,动态载入的动态库一般会作为插件使用,那么其同程序链接不同版本的静态库(相同符号不同实现),是没有问题的。
变量
变量本质上也是符号(symbol),但其处理规则和函数还有点不一样(是不是有点想吐槽了)。
// object.h
class Object {
public:
Object() {
#ifdef DF
s = malloc(32);
printf("s addr %p\n", s);
#endif
printf("ctor %p\n", this);
}
~Object() {
printf("dtor %p\n", this);
#ifdef DF
printf("s addr %p\n", s);
free(s);
#endif
}
void *s;
};
extern Object g_obj;
我们的程序test和动态库libdy.so都会链接object.o。首先测试test链接libdy.so,test和libdy.so中都会有g_obj
这个符号:
// B g_obj 表示g_obj位于BSS段,未初始化段
$ nm test | grep g_obj
0000000000400a14 t _GLOBAL__I_g_obj
00000000006012c8 B g_obj
$ nm libdy.so | grep g_obj
000000000000097c t _GLOBAL__I_g_obj
0000000000200f30 B g_obj
运行:
$ ./test
ctor 0x6012c8
ctor 0x6012c8
...
dtor 0x6012c8
dtor 0x6012c8
g_obj
被构造了两次,但地址一样。全局变量只有一个实例,似乎在情理之中。
动态载入libdy.so,变量地址还是相同的:
$ ./test
ctor 0x6012a8
...
ctor 0x6012a8
...
dtor 0x6012a8
dtor 0x6012a8
结论,不同于函数,全局变量符号重复时,不论动态库是动态载入还是直接链接,变量始终只有一个。
但诡异的情况是,对象被构造和析构了两次。构造两次倒无所谓,浪费点空间,但是析构两次就有问题。因为析构时都操作的是同一个对象,那么如果这个对象内部有分配的内存,那就会对这块内存造成double free,因为指针相同。打开DF
宏实验下:
$ ./test
s addr 0x20de010
ctor 0x6012b8
s addr 0x20de040
ctor 0x6012b8
...
dtor 0x6012b8
s addr 0x20de040
dtor 0x6012b8
s addr 0x20de040
因为析构的两次都是同一个对象,所以其成员s
指向的内存被释放了两次,从而产生了double free,让程序coredump了。
总结,全局变量符号重复时,始终会只使用一个,并且会被初始化/释放两次,是一种较危险的情况,应当避免在使用动态库的过程中使用全局变量。
完
原文地址: http://codemacro.com/2014/11/04/linux-dynamic-library/
written by Kevin Lynx posted at http://codemacro.com
[转]Linux动态库的种种要点的更多相关文章
- linux动态库的种种要点
linux下使用动态库,基本用起来还是非常easy.但假设我们的程序中大量使用动态库来实现各种框架/插件,那么就会遇到一些坑,掌握这些坑才有利于程序更稳健地执行. 本篇先谈谈动态库符号方面的问题. 測 ...
- linux动态库默认搜索路径设置的三种方法
众所周知, Linux 动态库的默认搜索路径是 /lib 和 /usr/lib .动态库被创建后,一般都复制到这两个目录中.当程序执行时需要某动态库, 并且该动态库还未加载到内存中,则系统会自动到这两 ...
- 技巧:Linux 动态库与静态库制作及使用详解
技巧:Linux 动态库与静态库制作及使用详解 标准库的三种连接方式及静态库制作与使用方法 Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 L ...
- linux动态库加载RPATH, RUNPATH
摘自http://gotowqj.iteye.com/blog/1926771 linux动态库加载RPATH, RUNPATH 链接动态库 如何程序在连接时使用了共享库,就必须在运行的时候能够找到共 ...
- linux动态库编译和使用
linux动态库编译和使用详细剖析 引言 重点讲述linux上使用gcc编译动态库的一些操作.并且对其深入的案例分析.最后介绍一下动态库插件技术, 让代码向后兼容.关于linux上使用gcc基础编译, ...
- Android NDK开发及调用标准linux动态库.so文件
源:Android NDK开发及调用标准linux动态库.so文件 预备知识及环境搭建 1.NDK(native development Kit)原生开发工具包,用来快速开发C.C++动态库,并能自动 ...
- windows动态库与Linux动态库
Linux动态库和windows动态库的目的是基本一致的,但由于操作系统的不同,他们在许多方面还是不尽相同.但是尽管有差异Linux动态库的windows动态库还是可以移植的,有一些规则以及经验是必须 ...
- linux 动态库 静态库 函数覆盖
本文讨论了linux动态库 静态库中函数的覆盖问题. 测试目的: 同名函数,分别打成动态库libdync_lib.so与静态库libstatic_lib.a,并把libstatic_lib.a打到另 ...
- Linux动态库(.so)搜索路径
主要内容: 1.Linux动态库.so搜索路径 编译目标代码时指定的动态库搜索路径: 环境变量LD_LIBRARY_PATH指定的动态库搜索路径: 配置文件/etc/ld.so.conf中指定的动态库 ...
随机推荐
- Android开发之Shortcuts, LiveFolder, Widget
2013-07-05 桌面组件包括:快捷方式(Shortcuts),实时文件夹(Live Folder),桌面插件(Widget). 快捷方式用于启动应用程序的某个组件,例如Activity, S ...
- C++模板类内友元(友元函数,友元类)声明的三种情况
根据<C++ Primer>第三版16.4节的叙述,C++类模板友元分为以下几种情况 1.非模板友元类或友元函数. 书上给了一个例子: class Foo{ void bar(); ...
- 使用原生SQL返回实体类具体实现详情
注:可以直接复制粘贴,欢迎提出各种问题,谢谢! 因为网上查询大都是相同的,自己做时发现很多不懂,摸索了很久才弄懂,所以写了这个例子,比较容易看懂吧. 使用原生SQL查询并将结果返回实体中: (1)因为 ...
- (Spring Boot框架)快速入门
Spring Boot 系列文章推荐 Spring Boot 入门 Spring Boot 属性配置和使用 Spring Boot 集成MyBatis Spring Boot 静态资源处理 今天介绍一 ...
- java线程同步方法,方法块差别
先说同步方法.它究竟是锁定的当前对象,还是当前类 代码块1 package com.ssss; public class Thread1 implements Runnable { //public ...
- atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结
atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结 1. 建立AST 抽象语法树 Abstract Syntax Tree,AST) 1 ...
- 优化神器 beamoff
http://files.cnblogs.com/files/yipu/beamoff.zip csdn上有下载:http://download.csdn.net/download/bytige/83 ...
- 03、同事分享课程的笔记 —《Android应用低功耗设计》
这是安卓组的同事一个月前分享的一节课程,听课时写了一下笔记,之前是写在本子上的,感觉内容挺不错 的,就保存在博客了吧,方便回看. 他曾经在就职于英特尔公司,是与芯片设计相关的,这课程标题虽然是与安卓相 ...
- hdu1078(记忆化搜索)
题意:给出n*n的格子,每个各自里面有些食物,问一只老鼠每次走最多k步所能吃到的最多的食物 这道题目,值得我记住它,re了n次,以前写搜索没有注意的一个小地方,导致re这么多次的 ac代码: #inc ...
- 一款基于的jQuery仿苹果样式焦点图插件
这次我们要分享的这款jQuery焦点图非常特别,它的外观特别简单,但是又相当大气.焦点图的整体样式是仿苹果样式的,由于jQuery的运用,我们只要点击图片下方的缩略图即可达到图片切换的焦点图特效,这款 ...