动静库

动静库
2010年12月16日
  1, Linux下动态库查看方法:nm -D libavformat.so
  Linux下静态库查看方法:ar -t libavformat.a
  2,
  基本概念
  库有动态与静态两种,
  动态通常用.so为后缀,静态用.a为后缀。例如:libhello.so libhello.a
  为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,
  由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。 ln -s libhello.so.1.0 libhello.so.1 ln -s libhello.so.1 libhello.so
  使用库
  当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。
  然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记'指明当程序执行时,首先必须载入这个库。
  由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。
  现在假设有一个叫hello的程序开发包,它提供一个静态库libhello.a 一个动态库libhello.so,一个头文件hello.h,头文件中提供sayhello()这个函数 /* hello.h */ void sayhello(); 另外还有一些说明文档。这一个典型的程序开发包结构
  1.与动态库连接 linux默认的就是与动态库连接,下面这段程序testlib.c使用hello库中的sayhello()函数 /*testlib.c*/ #include #include int main() { sayhello(); return 0; }
  使用如下命令进行编译 $gcc -c testlib.c -o testlib.o
  用如下命令连接: $gcc testlib.o -lhello -o testlib 在连接时要注意,假设libhello.o 和libhello.a都在缺省的库搜索路径下/usr/lib下,如果在其它位置要加上-L参数与与静态库连接麻烦一些,主要是参数问题。
  还是上面的例子: $gcc testlib.o -o testlib -WI,-Bstatic -lhello 注:这个特别的"-WI,-Bstatic"参数,实际上是传给了连接器ld. 指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数了。
  如果要和多个库相连接,而每个库的连接方式不一样,比如上面的程序既要和libhello进行静态连接,又要和libbye进行动态连接,其命令应为: $gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye
  3.动态库的路径问题
  为了让执行程序顺利找到动态库,有三种方法:
  (1)把库拷贝到/usr/lib和/lib目录下。
  (2)在LD_LIBRARY_PATH环境变量中加上库所在路径。例如动态库libhello.so在/home/ting/lib目录下,以bash 为例,使用命令: $export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
  (3) 修改/etc/ld.so.conf文件,把库所在的路径加到文件末尾,并执行ldconfig刷新。这样,加入的目录下的所有库文件都可见、
  4.查看库中的符号
  有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。
  库既可以是静态的也可以是动态的。
  nm列出的符号有很多,
  常见的有三种,
  一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
  一种是库中定义的函数,用T表示,这是最常见的;
  另外一种是所谓的"弱态"符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
  例如,假设开发者希望知道上央提到的hello库中是否定义了 printf(): $nm libhello.so |grep printf U printf U表示符号printf被引用,但是并没有在函数内定义,由此可以推断,要正常使用hello库,必须有其它库支持,再使用ldd命令查看hello依赖于哪些库: $ldd hello libc.so.6=>/lib/libc.so.6(0x400la000) /lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000) 从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on 生成库第一步要把源代码编绎成目标代码。
  以下面的代码为例,生成上面用到的hello库: /* hello.c */ #include void sayhello() { printf("hello,world "); } 用gcc编绎该文件,在编绎时可以使用任何全法的编绎参数,例如-g加入调试代码等: gcc -c hello.c -o hello.o
  1.连接成静态库 连接成静态库使用ar命令,其实ar是archive的意思 $ar cqs libhello.a hello.o
  2.连接成动态库 生成动态库用gcc来完成,由于可能存在多个版本,因此通常指定版本号: $gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o 另外再建立两个符号连接: $ln -s libhello.so.1.0 libhello.so.1 $ln -s libhello.so.1 libhello.so 这样一个libhello的动态连接库就生成了。最重要的是传gcc -shared 参数使其生成是动态库而不是普通执行程序。 -Wl 表示后面的参数也就是-soname,libhello.so.1直接传给连接器ld进行处理。
  实际上,每一个库都有一个soname,当连接器发现它正在查找的程序库中有这样一个名称,连接器便会将soname嵌入连结中的二进制文件内,而不是它正在运行的实际文件名,在程序执行期间,程序会查找拥有 soname名字的文件,而不是库的文件名,换句话说,soname是库的区分标志。这样做的目的主要是允许系统中多个版本的库文件共存,习惯上在命名库文件的时候通常与soname相同 libxxxx.so.major.minor 其中,xxxx是库的名字,major是主版本号,minor 是次版本号
  3,
  一、链接器的基本知识
  在说库之前,先简单介绍一下链接器的原理。
  编译的时候,每个.c(这里简单一点,只考虑C程序,C++原理类似)源程序会被编译成.o文件。在C源代码中声明的符号,如函数,全局变量等等,编译器在当前的C源代码中能够找到定义的会被直接编译,而不能找到定义,只有声明的会作为外部符号,存放在目标文件的导入表中。(有人问,如果是又没声明又没定义的符号呢?废话,当然通不过编译了-_-)嗯…………编译器没有办法确定这个未知符号的定义,但它相信这个定义是肯定存在的,自己既然无能为力,就只能交给链接器去办啦。而对于C源程序中定义的全局函数、变量等等,编译器会把它放到目标文件的导出表中,因为它相信,这些符号也许其他模块需要的。
  轮到链接器上场的时候,简单的讲,它会把这一堆的目标文件"组合"起来组装成一个可执行文件(Linux是ELF、COFF或者 a.out格式的文件,Windows是PE或者LE文件)。组装的过程是什么?简单的讲,它从这一堆的目标文件中抽出机器码部分,把这些机器码组合起来,然后生成输出文件的文件头等等结构,最后拼装成最终的可执行文件。这个过程是比较复杂的,我们暂时不深究。但是,我们对这个过程中链接器处理符号的方式比较感兴趣:它是怎么处理上面提到的那些导入和导出的符号的?答案是,链接器从目标文件中取出符号表并进行合并操作,具体就是,如果目标文件甲中声明了导出符号abcd,而目标文件乙中声明了同名的导入符号abcd,两个符号会被合并,导入符号会从最终文件的导入表中去除。处理机器码时,链接器会把目标文件乙中所有对外部符号abcd的引用替换成目标文件甲中定义的符号abcd的地址。当然,链接器不可能处理完所有的符号,如果它还有导入符号无法处理,但是它相信操作系统的装载器能够处理的话,它会把这些符号保留在最终可执行文件的导入表中,交由操作系统的装载器(可以认为包含一个链接器)去处理。当然如果它发现操作系统仍然无法处理这个导入符号的话,就是大家熟悉的错误 啦:ld error xxxx: yyyy.o: Cannot resolve external symbol zzzzzzz。对于导出符号,可 以原样放在导出表中以供其他模块使用,也可以去除。
  最终的可执行文件产生了,是不是链接任务完成了呢?没有!生成的可执行文件中仍然会有导入和导出表,需要在执行的时候链接,不过这个任务杀操作系统的加载器完成的。
  总结一下,链接主要分以下两种:编译时链接:GCC的ld链接器完成的任务运行时链接:操作系统的加载器完成的任务
  以上就是链接器的基本原理,它对于理解静态和动态库的原理很重要。
  FAQ: 为什么我的程序编译能通过,链接的时候报错:xxxx :Cannot resolve/find symbol/function/variable yyyy? 初学者提问频率比较高的问题之一,如果你看完上面一段的话就不难自己回答这个问题了。
  TIP:查看nm命令和objdump命令的man手册…………
  二、静态库问题:如果我有一些可复用的代码,用什么方式"复用"?(这里的"复用代码"是模块级的,而非代码片断)
  当然,如果每写一个新程序就把那些可复用的代码源程序原样拷贝过来,随新程序一起编译链接的话,当然可以达到目的,不过这样每次构建都得把那些复用代码一起编译链接,如果复用代码很大的话,费时又费力,要是能够把这些代码预先编译好,一定能够节省不少时间和精力。可是,如果分别把源代码编译成目标文件,会产生大量目标文件,不便使用和管理。有什么好办法?静态库就是解决方案。
  创建静态库用ar命令,库文件习惯以lib打头,扩展名.a
  TIP: 使用man ar查看ar的说明
  [案例分析] 一个简单的程序程序由a.c, b.asm, main.c组成。a.c中实现了加法函数add(),b.asm是用汇编语言实现的减法运算(用汇编语言的目的是为了说明链接原理和编程语言无关)substract()函数。main.c使用了以上两个函数
  a.c
  Code view plainprint? int add(int a, int b) { return a + b; } b.asm Code view plainprint? section .text global substract ;声明导出符号substract substract: push ebp mov ebp, esp push ebx mov eax, dword [ebp + 8] ;a mov ebx, dword [ebp + 12] ;b sub eax, ebx ;result pop ebx leave ret
  main.c
  Code view plainprint? 假设a.c b.asm是复用的代码
  编译:
  gcc -c -g -o a.o a.c yasm -g dwarf2 -f elf -o b.o b.asm gcc -c -g -o main.o main.c
  链接:
  gcc -g -o main a.o b.o main.o
  使用nm查看符号: > nm a.o 00000000 T add
  nm b.o 00000000 T substract
  nm main.o 00000000 T main 可以看到标着U(Undefined)的符号是导入符号,而T(Text,定义在.text段中的符号)是导出符号。
  现在我们用ar命令创建一个库: ar rc libdemo.a a.o b.o
  怎么使用它?
  gcc -g -o main main.o -L./ -ldemo
  -L指明附加库的路径(这里是当前目录),-l 指明附加库名,这里链接demo库,文件名是libdemo.a,就是我们刚才用ar命令创建的。
  这是什么原理?答案是,ar命令只是简单的把目标文件打包,链接时,链接器从打包的库中取出各个目标文件和程序产生的目标文件链接。链接的原理请看文章第一部分
  TIP: 使用ar t libdemo.a查看库中包含的目标文件
  > ar t libdemo.a a.o b.o
  二、共享库 Linux支持共享库已经有很久远的历史了,它十分类似于Windows操作系统中的dll文件,但是提供了更加完备的机制来防止出现一些Windows系统中老大难的问题。
  问题:为什么要动态链接的共享库?静态库不是很好了么?举C库为例,如果C库做成静态的当然没有问题,但是如果系统中有100个进程的映像文件链接的时候都使用了C库,内存中就会有100份相同代码的拷贝。这是一种极大的浪费!如果让这100个程序共享同一个C库,自然就可以节约不少内存空间了。
  如何编写和使用共享库?
  [案例分析] 程序同静态库部分的,2个C文件加一个asm汇编文件。
  现在按照共享库的方式编译 gcc -c -fpic -g -o a.o a.c gcc -c -fpic -g -o main.o main.c yasm -f elf -g dwarf2 -o b.o b.asm
  -fpic生成与加载地址无关的代码(可重定位)
  链接库: gcc -shared -g -fpic -o libdemo.so a.o b.o
  关键是一个-shared参数,让gcc产生共享库
  TIPS: man gcc看看这个参数的详细解释
  链接程序: gcc -g -o main main.o -L./ -ldemo
  现在运行,嗯,怎么不能运行? > ./main ./main: error while loading shared libraries: libdemo.so: cannot open shared object file: No such file or directory
  什么?找不到?不就在当前目录下嘛,哦,想起来了,Linux的惯例,不会到自动到当前目录下查找文件的,我们必须手动设置一下。设置什么?LD_LIBRARY_PATH环境变量, 这个环境变量告诉装载器到哪里去找共享库.
  > export LD_LIBRARY_PATH=./
  > ./main
  这下可以了。
  我们看一下可执行文件中extern节的内容 extern:804A028 ; extern extern:804A028 extrn __libc_start_main@@GLIBC_2_0:near extern:804A02C extrn scanf@@GLIBC_2_0:near extern:804A030 extrn printf@@GLIBC_2_0:near extern:804A034 extrn add:near ; CODE XREF: _add j extern:804A034 ; DATA XREF: .got.plt:off_804A010 o extern:804A038 extrn __libc_start_main:near ; CODE XREF: ___libc_start_main j extern:804A038 ; DATA XREF: .got.plt:off_804A004 o extern:804A03C ; int scanf(const char *,...) extern:804A03C extrn scanf:near ; CODE XREF: _scanf j extern:804A03C ; DATA XREF: .got.plt:off_804A008 o extern:804A040 ; int printf(const char *,...) extern:804A040 extrn printf:near ; CODE XREF: _printf j extern:804A040 ; DATA XREF: .got.plt:off_804A00C o extern:804A044 extrn __gmon_start__ ; weak ; DATA XREF: .got:08049FF0 o extern:804A044 ; .got.plt:off_804A000 o extern:804A048 extrn _Jv_RegisterClasses ; weak extern:804A04C extrn substract ; DATA XREF: .got.plt:off_804A014 o 可以看到熟悉的add 和substract ^_^
  这种方式是运行时静态链接,装载器在装入可执行程序的时候会一并把库也装入并且做好链接
  怎么?没有下文啦?共享库不会这么简单吧,当然不会。共享库最大的魅力还没有介绍呢──动态载入
  前面提到链接的方式有两种,实际上,还有一种链接方式也是很常用的,那就是运行时动态链接。补上前面两种,链接方式有: 1.编译时静态链接 2.运行时静态链接 3.运行时动态链接
  下面介绍第3种链接方式
  [案例分析] 程序需要之前给出的libdemo.so库
  dymlnk.c
  Code view plainprint? 1. #include 2. #include 3. #include 4. 5. 6. int main(int argc, char *argv[]) { 7. 8. char *errmsg; // error message 9. void *lib; // shared library handle 10. int (*add)(int a, int b); // add() function 11. int (*substract)(int a, int b); // substract() function 12. 13. // OPen the library 14. lib = dlopen("./libdemo.so", RTLD_LAZY); 15. if (!lib) { 16. fprintf(stderr, dlerror()); 17. exit(-1); 18. } 19. 20. // get export symble address 21. add = dlsym(lib, "add"); 22. errmsg = dlerror(); 23. if (errms 24. 25. substract = dlsym(lib, "substract"); 26. errmsg = dlerror(); 27. if (errmsg != NULL) { 28. fprintf(stderr, errmsg); 29. exit(-1); 30. } 31. 32. // the same as the old one 33. int a, b, c, tmp; 34. 35. printf("Input 3 numbers:"); 36. scanf("%d%d%d", &a, &b, &c); 37. tmp = add(a, b); 38. tmp = substract(tmp, c); 39. printf("result:%d\n", tmp); 40. 41. // close lib 42. dlclose(lib); 43. 44. return 0; 45. }
  include #include #include int main(int argc, char *argv[]) { char *errmsg; // error message void *lib; // shared library handle int (*add)(int a, int b); // add() function int (*substract)(int a, int b); // substract() function // OPen the library lib = dlopen("./libdemo.so", RTLD_LAZY); if (!lib) { fprintf(stderr, dlerror()); exit(-1); } // get export symble address add = dlsym(lib, "add"); errmsg = dlerror(); if (errms substract = dlsym(lib, "substract"); errmsg = dlerror(); if (errmsg != NULL) { fprintf(stderr, errmsg); exit(-1); } // the same as the old one int a, b, c, tmp; printf("Input 3 numbers:"); scanf("%d%d%d", &a, &b, &c); tmp = add(a, b); tmp = substract(tmp, c); printf("result:%d\n", tmp); // close lib dlclose(lib); return 0; }
  程序编译: gcc -o dymlnk dymlnk.c -ldl 需要链接库dl
  这个程序有点复杂,简单解释一下程序中首先调用dlopen打开一个库,这里就是我们刚才写的库, 这个函数会返回一个共享库的"handle"(怎么象Windows了?句柄?) 然后调用dlsym获取add和substract函数的指针计算的代码和之前的main.c一样最后关闭共享库
  当然这个程序运行的时候不需要LD_LIBRARY_PATH变量,因为是全路径指明共享库路径的.
  TIP: man dlopen可以查到以上函数的详细说明
  4,
  静态连接库(扩展名为 .a)是.o文件的简单集合。在 linux/unix下,使用 ar 命令生成静态连接库。
  动态连接库(扩展名为.so) 是将.o文件集合,并增加了导出表。导出表是一个函数名、函数索引、函数地址的数组。因此,应用程序可以装载(使用 ldopen函数)后,根据函数名,导出函数的索引位置来调用函数。
  动态连接库的优点在于:程序可以独立于连接库,即不需要包含头文件。
  两种连接库都可以减少模块间的依赖。
  两种连接库的文件名都必须有 lib前缀。
  可以使用 nm 命令查看连接库有哪些导出选项。
  可以使用 ldd 命令查看应用程序需要哪些连接库。
  可以一次性地指定编译当前目录下的所有 .cpp 文件为一个 连接库文件:在makefile 中
  OBJ = $(shell find . -name "*.cpp" -printf "%p " | sed -e "s/.cpp/.o/g" )
  find 参数解释:
  . 当前目录及所有子目录。如果要指定只有当前目录,则配合使用 参数-maxdepth 1
  -name 文件名。可使用通配符。
  -printf表示不加换行符地输出, 格式化参数 "%p"表示输出完整的文件名及路径。如果不需要使用路径,可使用 "%f"。
  sed 参数解释:
  -e 表示使用正则表达式
  s 表示执行文本替换,后面的两个参数表示把 .cpp 替换为 .o , g表示全部替换( global replace )。如果没有 g ,则只替换一次。
  5,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值