Linux动态库相关知识整理

动态库和静态库在C/C++开发中很常见,相比静态库直接被编译到可执行程序, 动态库运行时加载使得可执行程序的体积更小,更新动态库可以不用重新编译可执 行程序等诸多好处。作者是一个Linux后台开发,这些知识经常用到,所以 整理了一下这方面的知识。静态库相对简单,本文只关心Linux平台下的动态库。

创建动态库

这里我把一个短小却很有用的哈希函数编译成动态库做为示例,ELFhash用于对字符串做哈希,返回一个无符号整数。

//elfhash.h

#include <stdio.h>

unsigned long  ELFhash(const char* key);

//http://www.qixoo.qixoo.com/elfhash.c

#include "elfhash.h"

unsigned long ELFhash(const char* key)

{

unsigned long h = 0, g;

while( *key ) {

h = ( h << 4 ) + *key++;

if ( (g = h & 0xF0000000) != 0 )

h ^= g >> 24;

h &= ~g;

}

return h;

}

接下来使用gcc编译以上代码,并用ld将编译的目标文件链接成动态库

gcc -fPIC -c -Wall elfhash.c

ld  -shared elfhash.o -o libelfhash.so

其中-fPIC意思是生成位置无关的代码(Position Independent Code),适用于动态库,在多个进程中共享动态库的同一份代码。ld的-shared选项告诉链接器创建的是动态库。gcc也可以间接调用ld生成动态库

gcc -fPIC -shared -Wall -o libelfhash.so elfhash.c

使用动态库

动态库的使用方法有两种一种是隐式使用,第二种是显式使用。隐式使用的方法很简单。

#include "elfhash.h"

int main()

{

printf("%ld\n", ElfHash("key-for-test"));

return 0;

}

显式使用动态库需要借助以下几个函数

#include <dlfcn.h>

void *dlopen(const char *filename, int flag); //flag可以是RTLD_LAZY,执行共享库中的代码时解决未定义符号,RTLD_NOW则是dlopen返回前解决未定义符号。

char *dlerror(void); //当发生错误时,返回错误信息

void *dlsym(void *handle, const char *symbol); //获取符号

int dlclose(void *handle); //关闭

应用上面几个函数,调用ELFhash实现跟隐式调用一样的功能

#include "elfhash.h"

#include <stdlib.h>

#include <dlfcn.h>

int main() {

void *handle;

unsigned long (*hash)(const char*);

char *error;

handle = dlopen ("./libelfhash.so", RTLD_LAZY);

if (!handle) {

fputs (dlerror(), stderr);

exit(1);

}

hash = dlsym(handle, "ElfHash");

if ((error = dlerror()) != NULL)  {

fputs(error, stderr);

exit(1);

}

printf ("%ld\n", (*hash)("key-for-test"));

dlclose(handle);

}

至此了解以上的知识就可以创建和使用动态库了。 实际应用中我们可能还是会遇到一些问题。

动态库的加载

动态库创建那一节,我演示如何隐式使用动态库,那么编译运行这段代码试一下。

gcc main.c -L./ -lelfhash

./a.out //执行可执行程序

//以下是输出结果

./a.out: error while loading shared libraries: libelfhash.so: cannot open shared object file: No such file or directory

结果运行时报错,可执行程序找不到动态库。 网上有一些说法是编译时设置-L选项,但在Linux上面证明是不行的(SunOS上可行),这个选项只能在编译链接时有效, 可以让你使用-l如上面的-lelfhash。使用readelf -d a.out可以看到可执行文件依赖的动态库信息。

0x0000000000000001 (NEEDED)  Shared library: [libelfhash.so]

可以看到这里面并没有包含动态库的路径信息。查阅一下动态链接器的文档man ld-linux.so可以发现这样一句话(有的没有,版本问题)

If a slash is found, then the dependency string is interpreted as a (relative or absolute) pathname, and the library is loaded using that pathname

这段话太长,我只截取一部分,大致就是说,当依赖中有/符号,那么会被解析成动态库加载的路径,隐式使用的例子换一种编译方法。

gcc main.c ./libelfhash.so

./a.out

23621492 //输出正常

再用readelf -d a.out查看会发现,依赖信息中有了一个路径。

0x0000000000000001 (NEEDED)  Shared library: [./libelfhash.so]

这种方法虽然解决了问题,但是依赖中的路径是硬编码,不是很灵活。 动态链接器是如何查找的动态库的需要进一步查阅文档。关于查找的顺序有点长,这里就不直接引用了,大致是这样:

1、(仅ELF文件) 使用可执行文件中DT_RPATH区域设置的属性,如果DT_RUNPATH被设置,那么忽略DT_RPATH(在我的Linux对应的是RPATH和RUNPATH)。

2、使用环境变量LD_LIBRARY_PATH,如果可执行文件中有set-user-id/set-group-id, 会被忽略。

3、(仅ELF文件) 使用可执行文件中DT_RUNPATH区域设置的属性

4、从/etc/ld.so.cache缓存文件中查找

5、从默认路径/lib, /usr/lib文件目录中查找

我们需要设置RPATH或者RUNPATH,可以这样做

gcc main.c -Wl,-rpath,/home/xxx,--enable-new-dtags -L./  -lelfhash

这里的-Wl选项告诉链接器ld如果如何处理,接下来传递的-rpath(或者使用-R)告诉ld动态库的路径信息(注意-Wl,和后面选项之间不能有空格)。如果没有--enable-new-dtags那么只会设置RPATH,反之,RPATH和RUNPATH会同时被设置。使用readelf -d a.out查看结果:

0x000000000000000f (RPATH)  Library rpath: [/home/xxx]

0x000000000000001d (RUNPATH)  Library runpath: [/home/xxx]

如果使用环境变量LD_LIBRARY_PATH,那么一般这样用 export

export LD_LIBRARY_PATH=/home/xxx;$LD_LIBRARY_PATH

RPATH和RUNPATH指定动态库的路径,用起来简单,但是也缺乏灵活性,LDLIBRARYPATH在临时测试的也是很有用的,但是在正式环境中,直接使用它也不是好的实践,因为环境变量跟用户的环境关系比较大。动态库不仅要考虑自己使用, 还有分发给别的用户使用的情况。

更通用的方法是使用ldconfig,有几种方法,先在/etc/ld.so.conf.d/目录下创建一个文件,然后把你的动态库路径写进去。或者将你的动态库放到/lib,/lib64(64位),/usr/lib,/usr/lib64(64位)然后运行sudo ldconfig重建/etc/ld.so.cache文件。

动态库版本

通常在使用第三方给的动态库的时候,都是带有版本(文件命名),可以在/usr/lib64下看到很多这样的动态库。现在我重新编译动态库,这次加上版本信息。

gcc -fPIC -shared -Wall -Wl,-soname,libelfhash.so.0  -o libelfhash.so.0.0.0 elfhash.c

每个动态库都有一个名字,如这里的libelfhash.so.0.0.0,叫real name,命名规则跟简单,通常是libxxx.so.MAJOR.MINOR.VERSION(有的时候VERSION会被省略),如果动态库在接口上的兼容性,比如删除了接口或者修改了接口参数,MAJOR增加,如果接口兼容,只是做了更新或者bug修复那么MINOR和VERSION增加。也就是说MAJOR相同的库接口都是兼容的,反之不兼容,如果使用不兼容的动态库需要重新编译可执行程序。

编译动态库时,通过给ld传递连接选项-soname可以指定一个soname, 如这里的libelfhash.so.0 只保留MAJOR,可执行程序运行加载动态库时,会加载这个指定名字的库。

动态库还有一个名字是link name,编译可执行程序时,传个链接器ld的动态库名字,通常是没有版本号以.so结尾的文件名。 一般作法是对soname创建软链。

按照这个规则来命名的动态库可以ldconfig识别,我们把libelfhash.so.0.0.0放到/usr/lib64文件夹中,执行以下指令

$sudo ldconfig -v | grep libelfhash.so

libelfhash.so.0 -> libelfhash.so.0.0.0

可以发现ldconfig根据libelfhash.so.0.0.0的信息,创建了一个soname指向real name的软链,当动态库更新(MINOR,VERSION增加),拷贝新库到相应的位置,再执行sudo ldconfig会自动更新软链指向最新的动态库,动态库更新就完成了。

总结

OK,关于Linux动态库知识整理就到这里了,这些知识虽说都是些基础,少有涉及动态库内部的一些原理,但是却很常用。整理过程中我带着疑问去阅读了ld和ld-linux.so的文档,收获颇丰。同样,希望本文能帮你解释遇到的部分问题或疑惑。

时间: 2024-10-29 19:08:32

Linux动态库相关知识整理的相关文章

Linux 动态库相关知识整理

动态库和静态库在C/C++开发中很常见,相比静态库直接被编译到可执行程序,动态库运行时加载使得可执行程序的体积更小,更新动态库可以不用重新编译可执行程序等诸多好处.作者是一个Linux后台开发,这些知识经常用到,所以整理了一下这方面的知识.静态库相对简单,本文只关心Linux平台下的动态库. 创建动态库 这里我把一个短小却很有用的哈希函数编译成动态库做为示例,ELFhash用于对字符串做哈希,返回一个无符号整数. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //elf

技巧:Linux 动态库与静态库制作及使用详解

技巧:Linux 动态库与静态库制作及使用详解 标准库的三种连接方式及静态库制作与使用方法 Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 Linux 版本下不兼容的问题: 2)在 Linux 静态库的制作过程中发现有别于 Windows 下静态库的制作方法:3)在 Linux 应用程序链接第三方库或者其他静态库的时候发现链接顺序的烦人问题.本文就这三个问题针对 Linux 下标准库链接和如何巧妙构建 achrive(*.a) 展开相关介

linux动态库加载的秘密

摘自http://gotowqj.iteye.com/blog/1926734 摘自http://www.360doc.com/content/14/0313/13/12747488_360246417.shtml linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名.二者都使用广泛.本文主要讲动态库方面知识. 基本上每一个linux 程序都至少会有一个动态库,查看某个程序使用了那些动态库,使用ldd命令查看 # ldd /bin/ls linux-vdso.so.1 =

链表的相关知识整理

链表的相关知识整理 什么是链表 链表是一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域. 链表与数组的区别 回忆下数组的概念 ,所谓数组,是相同数据类型的元素按一定顺序排列的集合.根据概念我们可以知道数组在内存中连续,链表不连续:由于不同的存储方式导致数组静态分配内存,链表动态分配内存,数组

Android NDK开发及调用标准linux动态库.so文件

源:Android NDK开发及调用标准linux动态库.so文件 预备知识及环境搭建 1.NDK(native development Kit)原生开发工具包,用来快速开发C.C++动态库,并能自动将so文件和java应用一起打包成apk.对应:jni层c++开发 2.Cygwin:是windows平台上模拟Linux运行环境的工具,即window平台上的linux环境工具,so文件需要在linux平台上编译运行.对应:arm linux平台 3.CDT:eclipse下的C/C++开发工具,

Linux系统的相关知识、常用命令及拓展、centos 7网卡配置

一.Linux系统的相关知识 1.Linux中根目录下所有文件夹的含义和用途 目录 功能 /bin 存放可执行文件 /dev 存放设备文件 (如:网卡.CPU) /media 存放可移除设备文件 (如:U盘.CD/DVD.VMTools) /opt 存放第三方软件的默认位置 /tmp  存放临时文件 (如:日志文件) /root root用户的家目录,主文件夹 /home 普通用户的家目录,文件夹的命名是以用户的名字来命名的 /etc 存放配置文件 /usr 存放操作系统软件资源 /var 存放

Linux动态库搜索路径的技巧

众所周知,Linux动态库的默认搜索路径是/lib和/usr/lib.动态库被创建后,一般都复制到这两个目录中.当程序执行时需要某动态库,并且该动态库还未加载到内存中,则系统会自动到这两个默认搜索路径中去查找相应的动态库文件,然后加载该文件到内存中,这样程序就可以使用该动态库中的函数,以及该动态库的其它资源了.在Linux 中,动态库的搜索路径除了默认的搜索路径外,还可以通过以下三种方法来指定. 方法一:在配置文件/etc/ld.so.conf中指定动态库搜索路径. 可以通过编辑配置文件/etc

linux动态库与静态库使用比较

在windows下,动态库dll的使用往往伴随着lib的指引,而linux使用动态库和静态库则有较大的不同. linux静态库和动态库的区别 1. 静态库 名字一般是libxxx.a:利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,编译后的执行程序不需要外部的函数库支持,但是,升级比较麻烦.每一次版本更新都需要重新编译. 2. 动态库 名字一般是libxxx.so;动态库没有被编译进最终程序,只有在需要的时候,动态加载到内存中.编译后的程序不包含动态库部分,程

MFC相关知识整理

按:在该文档中整理我在做MFC相关编程时遇到的问题以及当时解决时所参考的资料.标准C/C++相关的问题不会放在这个帖子中,请移步:http://blog.csdn.net/edychang/article/details/37561701 1._T/_L宏:http://blog.csdn.net/panjean/article/details/6011090 2.CListCtrl: http://blog.csdn.net/bingxuewujian/article/details/7050