Linux下.o .so .a .la

来源:互联网 发布:淘宝蔻驰代购有真的吗 编辑:程序博客网 时间:2024/05/17 03:02

linux下文件的类型是不依赖于其后缀名的,但一般来讲:
.o,是目标文件,相当于windows中的.obj文件
.so 为共享库,是shared object,用于动态连接的,和dll差不多
.a为静态库,是好多个.o合在一起,用于静态连接
.la为libtool自动生成的一些共享库,vi编辑查看,主要记录了一些设置信息。能用如下命令查看*.la文件的格式   $file *.la
      *.la: ASCII English text
所以能用vi来查看其内容。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
创建.a库文件和.o库文件:

[yufei@localhost perl_c2]$ pwd
/home/yufei/perl_c2
[yufei@localhost perl_c2]$ cat mylib.c
#include
#include
void hello(){
        printf("success call from perl to c library\n");
}
[yufei@localhost perl_c2]$ cat mylib.h
extern void hello();
[yufei@localhost perl_c2]$ gcc -c mylib.c
[yufei@localhost perl_c2]$ dir
mylib.c  mylib.h  mylib.o
[yufei@localhost perl_c2]$ ar -r mylib.a mylib.o
ar: 正在创建 mylib.a
[yufei@localhost perl_c2]$ dir
mylib.a  mylib.c  mylib.h  mylib.o
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
动态链接库*.so的编译和使用- -
                                      
动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其他正为动态库链接库而苦恼的兄弟们提供一点帮助。
1、动态库的编译
下面通过一个例子来介绍怎么生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so。
so_test.h:
#include
#include
void test_a();
void test_b();
void test_c();
test_a.c:
#include "so_test.h"
void test_a()
{
    printf("this is in test_a...\n");
}
test_b.c:
#include "so_test.h"
void test_b()
{
    printf("this is in test_b...\n");
}
test_c.c:
#include "so_test.h"
void test_c()
{
    printf("this is in test_c...\n");
}
将这几个文件编译成一个动态库:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
2、动态库的链接
在1、中,我们已成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程式来调用这个库里的函数。程式的源文件为:test.c。
test.c:
#include "so_test.h"
int main()
{
    test_a();
    test_b();
    test_c();
    return 0;
}
l         将test.c和动态库libtest.so链接生成执行文件test:
$ gcc test.c -L. -ltest -o test
l         测试是否动态连接,如果列出libtest.so,那么应该是连接正常了
$ ldd test
l         执行test,能看到他是怎么调用动态库中的函数的。
3、编译参数解析
最主要的是GCC命令行的一个选项:
          -shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程式无法连接。相当于一个可执行文件
l         -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
l         -L.:表示要连接的库在当前目录中
l         -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
l         LD_LIBRARY_PATH:这个环境变量指示动态连接器能装载动态库的路径。
l         当然如果有root权限的话,能修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
4、注意

     调用动态库的时候有几个问题会经常碰到,有时,明明已将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过
“-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活未找到你指定链接的so文件,这时你要作的就是通过修改
LD_LIBRARY_PATH或/etc/ld.so.conf文件来指定动态库的目录。通常这样做就能解决库无法链接的问题了。
makefile里面怎么正确的编译和连接生成.so库文件,然后又是在其他程式的makefile里面怎么编译和连接才能调用这个库文件的函数????
答:
       你需要告诉动态链接器、加载器ld.so在哪里才能找到这个共享库,能设置环境变量把库的路径添加到库目录/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),这种方法采用命令行方法不太方便,一种替代方法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注释^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LD_LIBRARY_PATH能在
/etc/profile还是 ~/.profile还是 ./bash_profile里设置,或.bashrc里,
改完后运行source /etc/profile或 . /etc/profile
更好的办法是添入/etc/ld.so.conf, 然后执行 /sbin/ldconfig
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注释^^^^^^^^^^^^^^^^^^^^^^^^^^^^
是把库路径添加到/etc/ld.so.conf,然后以root身份运行ldconfig
      也能在连接的时候指定文件路径和名称 -I  -L.
      GCC=gcc
CFLAGS=-Wall -ggdb -fPIC
#CFLAGS=
all: libfunc test
libfunc:func.o func1.o
        $(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $
编译目标文件时使用gcc的-fPIC选项,产生和位置无关的代码并能被加载到所有地址:
gcc ?fPIC ?g ?c liberr.c ?o liberr.o

使用gcc的-shared和-soname选项;
使用gcc的-Wl选项把参数传递给连接器ld;
使用gcc的-l选项显示的连接C库,以确保能得到所需的启动(startup)代码,从而避免程式在使用不同的,可能不兼容版本的C库的系统上不能启动执行。
gcc ?g ?shared ?Wl,-soname,liberr.so ?o liberr.so.1.0.0 liberr.o ?lc

建立相应的符号连接:
ln ?s liberr.so.1.0.0 liberr.so.1;
ln ?s liberr.so.1.0.0 liberr.so;
在MAKEFILE中:
$@
    表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$%
   
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是
"foo.a"。如果目标不是函数库文件(Unix下是[.a],视窗系统下是[.lib]),那么,其值为空。
$
*********************************************注释***********************************************************************
test: test.o libfunc
        $(GCC) -o test test.o -L. -lfunc
%.o:%.c
        $(GCC) -c $(CFLAGS) -o $@ $ref:http://niefei.blog.ccidnet.com/blog/ccid/do_showone/tid_42855.html
1. 介绍
 
 使用GNU的工具我们怎么在Linux下创建自己的程式函数库?一个“程式 函数库”简单的说就是个文件包含了一些编译好的代码和数据,这些编
译好的代码和数据能在事后供其他的程式使用。程式函数库能使整个程式更加模块化,更容易重新编译,而且更方便升级。程式函数库可分为3种类型:静态函
数库(static libraries)、共享函数库(shared libraries)和动态加载函数库(dynamically loaded
libraries)。
  静态函数库是在程式执行前就加入到目标程式中去了;而共享函数库则是在程式启动的时候加载到程式中,他能被
不同的程式共享;动态加载函数库则能在程式运行的所有时候动态的加载。实际上,动态函数库并非另外一种库函数格式,差别是动态加载函数库是怎么被程式员
使用的。后面我们将举例说明。
  本文件主要参考Program Library HOWTO,作者是luster(
hwang@ustc.edu
),所有非商业目的的再次发行本文件都是允许的,不过请保留作者信息和本版权声明。本文件首先在
www.linuxaid.com.cn
发布。
  2. 静态函数库
 
 静态函数库实际上就是简单的一个普通的目标文件的集合,一般来说习惯用“.a”作为文件的后缀。能用ar这个程式来产生静态函数库文件。Ar
是archiver的缩写。静态函数库目前已不在像以前用得那么多了,主要是共享函数库和之相比较有非常多的优势的原因。慢慢地,大家都喜欢使用共享函数
库了。不过,在一些场所静态函数库仍然在使用,一来是保持一些和以前某些程式的兼容,二来他描述起来也比较简单。
  静态库函数允许程式
员把程式link起来而不用重新编译代码,节省了重新编译代码的时间。不过,在今天这么快速的计算机面前,一般的程式的重新编译也花费不了多少时间,所以
这个优势已不是像他以前那么明显了。静态函数库对研发者来说还是非常有用的,例如你想把自己提供的函数给别人使用,不过又想对函数的原始码进行保密,你就
能给别人提供一个静态函数库文件。理论上说,使用ELF格式的静态库函数生成的代码能比使用共享函数库(或动态函数
库)的程式运行速度上快一些,大概1-5%。
  创建一个静态函数库文件,或往一个已存在地静态函数库文件添加新的目标代码,能用下面的命令:
ar rcs my_library.a file1.o file2.o
 
 这个例子中是把目标代码file1.o和file2.o加入到my_library.a这个函数库文件中,如果my_library.a不存在
则创建一个新的文件。在用ar命令创建静态库函数的时候,更有其他一些能选择的参数,能参加ar的使用帮助。这里不再赘述。
  一旦
你创建了一个静态函数库,你能使用他了。你能把他作为你编译和连接过程中的一部分用来生成你的可执行代码。如果你用gcc来编译产生可
执行代码的话,你能用“-l”参数来指定这个库函数。你也能用ld来做,使用他的“-l”和“-L”参数选项。具体用法,能参考info:gcc。
 3. 共享函数库
  共享函数库中的函数是在当一个可执行程式在启动的时候被加载。如果一个共享函数库正常安装,所有的程式在重新运行的时候都能自动加载最新的函数库中的函数。对于Linux系统更有更多的能实现的功能:
o 升级了函数库不过仍然允许程式使用老版本的函数库。 o 当执行某个特定程式的时候能覆盖某个特定的库或库中指定的函数。 o 能在库函数被使用的过程中修改这些函数库。
  3.1. 一些约定
 
 如果你要编写的共享函数库支持所有有用的特性,你在编写的过程中必须遵循一系列约定。你必须理解库的不同的名字间的差别,例如他的
“soname”和“real
name”之间的差别和他们是怎么相互作用的。你同样还要知道你应该把这些库函数放在你文件系统的什么位置等等。下面我们具体看看这些问题。
  3.1.1. 共享库的命名
  每个共享函数库都有个特别的名字,称作“soname”。Soname名字命名必须以“lib”作为前缀,然后是函数库的名字,然后是“.so”,最后是版本号信息。不过有个特例,就是非常底层的C库函数都不是以lib开头这样命名的。
  每个共享函数库都有一个真正的名字(“real name”),他是包含真正库函数代码的文件。真名有一个主版本号,和一个发行版本号。最后一个发行版本号是可选的,能没有。主版本号和发行版本号使你能知道你到底是安装了什么版本的库函数。
另外,更有一个名字是编译器编译的时候需要的函数库的名字,这个名字就是简单的soname名字,而不包含所有版本号信息。
 
 管理共享函数库的关键是区分好这些名字。当可执行程式需要在自己的程式中列出这些他们需要的共享库函数的时候,他只要用soname就能了;
反过来,当你要创建一个新的共享函数库的时候,你要指定一个特定的文件名,其中包含非常细节的版本信息。当你安装一个新版本的函数库的时候,你只要先将这些
函数库文件拷贝到一些特定的目录中,运行ldconfig这个实用就能。Ldconfig检查已存在的库文件,然后创建soname的符号链接到真正
的函数库,同时设置/etc/ld.so.cache这个缓冲文件。这个我们稍后再讨论。
  Ldconfig并不设置链接的名字,通常
的做法是在安装过程中完成这个链接名字的建立,一般来说这个符号链接就简单的指向最新的soname
或最新版本的函数库文件。最佳把这个符号链接指向soname,因为通常当你升级你的库函数的后,你就能自动使用新版本的函数库勒。
  我们来举例看看:
 
 /usr/lib/libreadline.so.3
是个完全的完整的soname,ldconfig能设置一个符号链接到其他某个真正的函数库文件,例如是
/usr/lib/libreadline.so.3.0。同时还必须有一个链接名字,例如/usr/lib/libreadline.so
就是个符号链接指向/usr/lib/libreadline.so.3。
3.1.2. 文件系统中函数库文件的位置
 
 共享函数库文件必须放在一些特定的目录里,这样通过系统的环境变量设置,应用程式才能正确的使用这些函数库。大部分的源码研发的程式都遵循
GNU的一些标准,我们能看info帮助文件获得相信的说明,info信息的位置是:info:
standards#Directory_Variables。GNU标准建议所有的函数库文件都放在/usr/local/lib目录下,而且建议命令
可执行程式都放在/usr/local/bin目录下。这都是一些习惯问题,能改动的。
  文件系统层次化标准FHS(Filesystem Hierarchy Standard)(
http://www.pathname.com/fhs
)规定了在一个发行包中大部分的函数库文件应该安装到/usr/lib目录 下,不过如果某些库是在系统启动的时候要加载的,则放到/lib目录下,而那些不是系统本身一部分的库则放到/usr/local/lib下面。
  上面两个路径的不同并没有本质的冲突。GNU提出的标准主要对于研发者研发源码的,而FHS的建议则是针对发行版本的路径的。具体的位置信息能看/etc/ld.so.conf里面的设置信息。
  3.2. 这些函数库怎么使用
 
 在基于GNU glibc的系统里,包括所有的linux系统,启动一个ELF格式的二进制可执行文件会自动启动和运行一个program
loader。对于Linux系统,这个loader的名字是/lib/ld-linux.so.X(X是版本号)。这个loader启动后,反过来就会
load所有的其他本程式要使用的共享函数库。
  到底在哪些目录里查找共享函数库呢?这些定义缺省的是放在
/etc/ld.so.conf文件里面,我们能修改这个文件,加入我们自己的一些
特别的路径需求。大多数RedHat系列的发行包的/etc/ld.so.conf文件里面不包括/usr/local/lib这个目录,如果没有这个目
录的话,我们能修改/etc/ld.so.conf,自己手动加上这个条目。
  如果你想覆盖某个库中的一些函数,用自己的函数替换他们,同时保留该库中其他的函数的话,你能在/etc/ld.so.preload中加入你想要替换的库(.o结尾的文件),这些preloading的库函数将有优先加载的权利。
 
 当程式启动的时候搜索所有的目录显然会效率非常低,于是Linux系统实际上用的是个高速缓冲的做法。Ldconfig缺省情况下读出
/etc/ld.so.conf相关信息,然后设置适当地符号链接,然后写一个cache到/etc/ld.so.cache这个文件中,而这个
/etc/ld.so.cache则能被其他程式有效的使用了。这样的做法能大大提高访问函数库的速度。这就需求每次新增加一个动态加载的函数库的时
候,就要运行ldconfig来更新这个cache,如果要删除某个函数库,或某个函数库的路径修改了,都要重新运行ldconfig来更新这个
cache。通常的一些包管理器在安装一个新的函数库的时候就要运行ldconfig。
  另外,FreeBSD使用cache的文件不相同。FreeBSD的ELF cache是/var/run/ld-elf.so.hints,而a.out的cache责是/var/run/ld.so.hints。他们同样是通过ldconfig来更新。
  3.3. 环境变量
 
 各种各样的环境变量控制着一些关键的过程。例如你能临时为你特定的程式的一次执行指定一个不同的函数库。Linux系统中,通常变量
LD_LIBRARY_PATH就是能用来指定函数库查找路径的,而且这个路径通常是在查找标准的路径之前查找。这个是非常有用的,特别是在调试一个新的
函数库的时候,或在特别的场合使用一个肥标准的函数库的时候。环境变量LD_PRELOAD列出了所有共享函数库中需要优先加载的库文件,功能和
/etc/ld.so.preload类似。这些都是有/lib/ld-linux.so这个loader来实现的。值得一提的是,
LD_LIBRARY_PATH能在大部分的UNIX-linke系统下正常起作用,不过并非所有的系统下都能使用,例如HP-UX系统下,就是用
SHLIB_PATH这个变量,而在AIX下则使用LIBPATH这个变量。
  LD_LIBRARY_PATH在研发和调试过程中经常大量使用,不过不应该被一个普通用户在安装过程中被安装程式修改,大家能去参考
http://www.visi.com/~barr/ldpath.html
,这里有一个文件专门介绍为什么不使用LD_LIBRARY_PATH这个 变量。
  事实上更有更多的环境变量影响着程式的调入过程,他们的名字通常就是以LD_或RTLD_打头。大部分这些环境变量的使用的文件都是不全,通常搞得人头昏眼花的,如果要真正弄清晰他们的用法,最佳去读loader的源码(也就是gcc的一部分)。
 
 允许用户控制动态链接函数库将涉及到setuid/setgid这个函数如果特别的功能需要的话。因此,GNU
loader通常限制或忽略用户对这些变量使用setuid和setgid。如果loader通过判断程式的相关环境变量判断程式的是否使用了
setuid或setgid,如果uid和euid不同,或gid和egid部相同,那么loader就假定程式已使用了setuid或
setgid,然后就大大的限制器控制这个老链接的权限。如果阅读GNU
glibc的库函数源码,就能清晰地看到这一点,特别的我们能看elf/rtld.c和sysdeps/generic/dl-sysdep.c这两
个文件。这就意味着如果你使得uid和gid和euid和egid分别相等,然后调用一个程式,那么这些变量就能完全起效。
3.4. 创建一个共享函数库
 
 目前我们开始学习怎么创建一个共享函数库。其实创建一个共享函数库非常容易。首先创建object文件,这个文件将加入通过gcc ?fPIC
参数命令加入到共享函数库里面。PIC的意思是“位置无关代码”(Position Independent Code)。下面是个标准的格式:
gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
  下面再给一个例子,他创建两个object文件(a.o和b.o),然后创建一个包含a.o和b.o的共享函数库。例子中”-g”和“-Wall”参数不是必须的。
gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,
-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
  下面是一些需要注意的地方:
?
不用使用-fomit-frame-pointer这个编译参数除非你不得不这样。虽然使用了这个参数获得的函数库仍然能使用,不过这使得调试程式几乎
没有用,无法跟踪调试。 ? 使用-fPIC来产生代码,而不是-fpic。 ? 某些情况下,使用gcc
来生成object文件,需要使用“-Wl,-export-dynamic”这个选项参数。通常,动态函数库的符号表里面包含了这些动态的对象的符号。
这个选项在创建ELF格式的文件时候,会将所有的符号加入到动态符号表中。能参考ld的帮助获得更周详的说明。
  3.5. 安装和使用共享函数库
  一旦你了一个共享函数库,你还需要安装他。其实简单的方法就是拷贝你的库文件到指定的标准的目录(例如/usr/lib),然后运行ldconfig。
 
 如果你没有权限去做这件事情,例如你不能修改/usr/lib目录,那么你就只好通过修改你的环境变量来实现这些函数库的使用了。首先,你需要
创建这些共享函数库;然后,设置一些必须得符号链接,特别是从soname到真正的函数库文件的符号链接,简单的方法就是运行ldconfig:
ldconfig -n directory_with_shared_libraries
  然后你就能设置你的LD_LIBRARY_PATH这个环境变量,他是个以逗号分隔的路径的集合,这个能用来指明共享函数库的搜索路径。例如,使用bash,就能这样来启动一个程式my_program:
LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program
 
 如果你需要的是重载部分函数,则你就需要创建一个包含需要重载的函数的object文件,然后设置LD_PRELOAD环境变量。通常你能非常
方便的升级你的函数库,如果某个API改动了,创建库的程式会改动soname。然而,如果一个函数升级了某个函数库而保持了原来的soname,你能
强行将老版本的函数库拷贝到某个位置,然后重新命名这个文件(例如使用原来的名字,然后后面加.orig后缀),然后创建一个小的“wrapper”脚本
来设置这个库函数和相关的东西。例如下面的例子:
#!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH exec
/usr/bin/my_program.orig $*
  我们能通过运行ldd来看某个程式使用的共享函数库。例如你能看ls这个实用工具使用的函数库:
ldd /bin/ls
    libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)
    libc.so.6 => /lib/libc.so.6 (0x40020000)
    /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
  通常我么能看到一个soname的列表,包括路径。在所有的情况下,你都至少能看到两个库:
? /lib/ld-linux.so.N(N是1或更大,一般至少2)。
这是这个用力加载其他所有的共享库的库。
? libc.so.N(N应该大于或等于6)。这是C语言函数库。
 
 值得一提的是,不要在对你不信任的程式运行ldd命令。在ldd的manual里面写得非常清晰,ldd是通过设置某些特别的环境变量(例如,对
于ELF对象,设置LD_TRACE_LOADED_OBJECTS),然后运行这个程式。这样就有可能使得某地程式可能使得ldd来执行某些意想不到的
代码,而产生不安全的隐患。
3.6. 不兼容的函数库
  如果一个新版的函数库要和老版本的二进制的库不兼容,则soname需要改动。对于C语言,一共有4个基本的理由使得他们在二进制代码上非常难兼容:
  o. 一个函数的行文改动了,这样他就可能和最开始的定义不相符合。
  o. 输出的数据项改动了。
  o. 某些输出的函数删除了。
  o. 某些输出函数的接口改动了。
  如果你能避免这些地方,你就能保持你的函数库在二进制代码上的兼容,或说,你能使得你的程式的应用二进制接口(ABI:Application Binary Interface)上兼容。
  4. 动态加载的函数库Dynamically Loaded (DL) Libraries
 
 动态加载的函数库Dynamically loaded (DL)
libraries是一类函数库,他能在程式运行过程中的所有时间加载。他们特别适合在函数中加载一些模块和plugin扩展模块的场合,因为他能在
当程式需要某个plugin模块时才动态的加载。例如,Pluggable Authentication
Modules(PAM)系统就是用动态加载函数库来使得管理员能设置和重新设置身份验证信息。
  Linux系统下,DL函数库和其
他函数库在格式上没有特别的差别,我们前面提到过,他们创建的时候是标准的object格式。主要的差别就是
这些函数库不是在程式链接的时候或启动的时候加载,而是通过一个API来打开一个函数库,寻找符号表,处理错误和关闭函数库。通常C语言环境下,需要包
含这个头文件。
  Linux中使用的函数和Solaris中相同,都是dlpoen()
API。当时不是所有的平台都使用同样的接口,例如HP-UX使用shl_load()机制,而视窗系统平台用另外的其他的调用接口。如果你的目的
是使得你的代码有非常强的移植性,你应该使用一些wrapping函数库,这样的wrapping函数库隐藏不同的平台的接口差别。一种方法是使用
glibc函数库中的对动态加载模块的支持,他使用一些潜在的动态加载函数库界面使得他们能夸平台使用。具体能参考http:
//developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html.
另外一个方法是使用libltdl,是GNU libtool的一部分,能进一步参考CORBA相关资料。
  4.1. dlopen()
  dlopen函数打开一个函数库然后为后面的使用做准备。C语言原形是:
void * dlopen(const char *filename, int flag);
  如果文件名filename是以“/”开头,也就是使用绝对路径,那么dlopne就直接使用他,而不去查找某些环境变量或系统设置的函数库所在的目录了。否则dlopen()
  就会按照下面的次序查找函数库文件:
1. 环境变量LD_LIBRARY指明的路径。 2. /etc/ld.so.cache中的函数库列表。 3. /lib目录,然后/usr/lib。不过一些非常老的a.out的loader则是采用相反的次序,也就是先查/usr/lib,然后是/lib。
 
 Dlopen()函数中,参数flag的值必须是RTLD_LAZY或RTLD_NOW,RTLD_LAZY的意思是resolve
undefined symbols as code from the dynamic library is
executed,而RTLD_NOW的含义是resolve all undefined symbols before dlopen()
returns and fail if this cannot be done’。
  如果有好几个函数库,他们之间有一些依赖关系的话,例如X依赖Y,那么你就要先加载那些被依赖的函数。例如先加载Y,然后加载X。
  dlopen()函数的返回值是个句柄,然后后面的函数就通过使用这个句柄来做进一步的操作。如果打开失败dlopen()就返回一个NULL。如果一个函数库被多次打开,他会返回同样的句柄。
  如果一个函数库里面有一个输出的函数名字为_init,那么_init就会在dlopen()这个函数返回前被执行。我们能利用这个函数在我的函数库里面做一些初始化的工作。我们后面会继续讨论这个问题的。
  4.2. dlerror()
  通过调用dlerror()函数,我们能获得最后一次调用dlopen(),dlsym(),或dlclose()的错误信息。
4.3. dlsym()
  如果你加载了一个DL函数库而不去使用当然是不可能的了,使用一个DL函数库的最主要的一个函数就是dlsym(),这个函数在一个已打开的函数库里面查找给定的符号。这个函数如下定义:
void * dlsym(void *handle, char *symbol);
  函数中的参数handle就是由dlopen打开后返回的句柄,symbol是个以NIL结尾的字符串。
 
 如果dlsym()函数没有找到需要查找的symbol,则返回NULL。如果你知道某个symbol的值不可能是NULL或0,那么就非常
好,你就能根据这个返回结果判断查找的symbol是否存在了;不过,如果某个symbol的值就是NULL,那么这个判断就有问题了。标准的判断方法
是先调用dlerror(),清除以前可能存在的错误,然后调用dlsym()来访问一个symbol,然后再调用dlerror()来判断是否出现了错
误。一个典型的过程如下:
dlerror(); /* clear error code */
s = (actual_type) dlsym(handle, symbol_being_searched_for);
if ((err = dlerror()) != NULL)
{
/* handle error, the symbol wasn’t found */
}
else
{
/* symbol found, its value is in s */
}
  4.4. dlclose()
 
 dlopen()函数的反过程就是dlclose()函数,dlclose()函数用力关闭一个DL函数库。Dl函数库维持一个资源利用的计数
器,当调用dlclose的时候,就把这个计数器的计数减一,如果计数器为0,则真正的释放掉。真正释放的时候,如果函数库里面有_fini()这个函
数,则自动调用_fini()这个函数,做一些必要的处理。Dlclose()返回0表示成功,其他非0值表示错误。
  4.5. DL Library Example
  下面是个例子。例子中调入math函数库,然后打印2.0的余弦函数值。例子中每次都检查是否出错。应该是个不错的范例:
  #include
  #include
  #include
  int main(int argc, char **argv)
  {
    void *handle;
    double (*cosine)(double);
    char *error;
    handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
    if (!handle) {
        fputs (dlerror(), stderr);
        exit(1);
    }
    cosine = dlsym(handle, "cos");
    if ((error = dlerror()) != NULL)
  {
        fputs(error, stderr);
        exit(1);
    }
    printf ("%f ", (*cosine)(2.0));
    dlclose(handle);
}
  如果这个程式名字叫foo.c,那么用下面的命令来编译:
  gcc -o foo foo.c -ldl
5. 其他
  5.1. nm命令
 
 nm命令能列出一个函数库文件中的符号表。他对于静态的函数库和共享的函数库都起作用。对于一个给定的函数库,nm命令能列出函数库中定义
的所有符号,包括每个符号的值和类型。还能给出在原程式中这个函数(符号)是在多少行定义的,不过这必须需求编译该函数库的时候加“-l”选项。
 
 关于符号的类型,这里我们再多讨论一下。符号的类型是以一个字母的形式显示的,小写字母表示这个符号是本地(local)的,而大写字母则表示
这个符号是全局的(global,externel)。一般来说,类型有一下几种:T、D、B、U、W。各自的含义如下:T表示在代码段中定义的一般变量
符号;D表示时初始化过的数据段;B表示初始化的数据段;U表示没有定义的,在这个库里面使用了,不过在其他库中定义的符号;W,weak的缩写,表示如
果其他函数库中也有对这个符号的定义,则其他符号的定义能覆盖这个定义。
  如果你知道一个函数的名字,不过你不知道这个函数在什么库中定义的,那么能用mn的“-o”选项和grep命令来查找库的名字。-o选项使得显示的每一行都有这个函数库文件名。例如,你要查找“cos”这个是在什么地方定义的,大致能用下面的命令:
nm -o /lib/* /usr/lib/* /usr/lib/*/* /usr/local/lib/* 2> /dev/null
| grep ’cos$’
  关于nm的更周详的用法我们能参考info文件,位置是info:binutils#nm。
  5.2. 特别函数_init和_fini
 
 函数库里面有两个特别的函数,_init和_fini,这个我们在前面已说过了。主要是分别用来初始化函数库和关闭的时候做一些必要的处理,
我们能把自己认为需要的代码放到这两个函数里面,他们分别在函数库被加载和释放的时候被执行。具体说,如果一个函数库里面有一个名字为“_init”的
函数输出,那么在第一次通过dlopen()函数打开这个函数库,或只是简单的作为共享函数库被打开的时候,_init函数被自动调用执行。和之相对应
的就是_fini函数,当一个程式调用dlclose()去释放对这个函数库的引用的时候,如果该函数库的被引用计数器为0了,或这个函数库是作为一般
的共享函数库被使用而使用他的程式正常退出的时候,_fini就会被调用执行。C语言定义他们的原型如下:
void _init(void); void _fini(void);
  当用gcc编译源程式为“.o”文件的时候,需要加一个“-nostartfiles”选项。这个选项使得C编译器不链接系统的启动函数库里面的启动函数。否则,就会得到一个“multiple-definition”的错误。
  5.3. 共享函数库也能使脚本(Scripts)
  GNU的loader允许使用特别格式的脚本语言来写一个函数库。这对于那些需要间接包含其他函数库的情况还是有用的。例如,下面是个/usr/lib/libc.so的例子:
/* GNU ld script. Use the shared library, but some functions are only in
the static library, so try that secondarily. */GROUP ( /lib/libc.so.6
/usr/lib/libc_nonshared.a )
  更多的信息能参考texinfo文件中关于ld链接的脚本部分。一般的信息还能参考: info:ld#Options 和info:ld#Commands,也能参考info:ld#Option Commands。
  5.4. GNU libtool
 
 如果你正在编译的系统相非常方便的移植到其他操作系统下,你能使用GNU libtool来创建和安装这个函数库。GNU
libtool是个函数库支持的典型的脚本。Libtool隐藏了使用一个可移植的函数库的负责性。Libtool提供了一个能移植的界面来创建
object文件,链接函数库(静态或共享的),并且安装这些库。他还包含了libltdl,一个可移植的动态函数库调入程式的wrapper。更多的
周详讨论,能在
http://www.gnu.org/software/libtool/manual.html
看到。
  5.5. 删除一些符号
  在一个生产的文件中非常多符号都是为了debug而包含的,占用了不少空间。如果空间不够,而且这些符号也许不再需要,就能将其中一些删除。
 
 最佳的方法就是先正常的生成你需要的object文件,然后debug和测试你需要的一些东西。一旦你完全测试完毕了,就能用strip去删
除一些不必的符号了。Strip命令能使你非常方便的控制删除什么符号,而保留什么符号。Strip的具体用法能参考其帮助文件。
  另外的方法就是使用GNU ld的选项“-S”和“-s”;“-S”会删除一些debugger的符号,而“-s”则是将所有的符号信息都删除。通常我们能在gcc中加这样的参数“-Wl,-S”和“-Wl,-s”来达到这个目的。
摘要

面是一些例子,例子中我们会使用三种函数库(静态的、共享的和动态加载的函数库)。文件libhello.c是个函数库,libhello.h
是他的头文件;demo_use.c则是个使用了libhello函数库的。Script_static和script_dynamic分别演示怎么以
静态和共享方式使用函数库,而后面的demo_dynamic.c和script_dynamic则表示演示怎么以动态加载函数库的方式来使用他。
(2002-08-25 17:38:37)
By Wing
  6. 更多的例子
 
 下面是一些例子,例子中我们会使用三种函数库(静态的、共享的和动态加载的函数库)。文件libhello.c是个函数库,
libhello.h是他的头文件;demo_use.c则是个使用了libhello函数库的。Script_static和
script_dynamic分别演示怎么以静态和共享方式使用函数库,而后面的demo_dynamic.c和script_dynamic则表示演示
怎么以动态加载函数库的方式来使用他。
  6.1. File libhello.c
/* libhello.c - demonstrate library use. */
#include
void hello(void)
{
printf("Hello, library world.
");
}
  6.2. File libhello.h
/* libhello.h - demonstrate library use. */
void hello(void);
  6.3. File demo_use.c
/* demo_use.c -- demonstrate direct use of the "hello" routine */
#include "libhello.h"
int main(void)
{
hello();
return 0;
}
  6.4. File script_static
#!/bin/sh
# Static library demo
# Create static library’s object file, libhello-static.o.
# I’m using the name libhello-static to clearly
# differentiate the static library from the
# dynamic library examples, but you don’t need to use
# "-static" in the names of your
# object files or static libraries.gcc -Wall -g -c -o libhello-static.o
libhello.c
# Create static library.ar rcs libhello-static.a libhello-static.o
# At this point we could just copy libhello-static.a
# somewhere else to use it.
# For demo purposes, we’ll just keep the library
# in the current directory.
# Compile demo_use program file.gcc -Wall -g -c demo_use.c -o demo_use.o
# Create demo_use program; -L. causes "." to be searched during
# creation of the program. Note that this command causes
# the relevant object file in libhello-static.a to be
# incorporated into file demo_use_static.gcc -g -o demo_use_static
demo_use.o -L. -lhello-static
# Execute the program../demo_use_static
  6.5. File script_shared
#!/bin/sh
# Shared library demo
# Create shared library’s object file, libhello.o.gcc -fPIC -Wall
-g -c libhello.c
# Create shared library.
# Use -lc to link it against C library, since libhello
# depends on the C library.gcc -g -shared -Wl,-soname,libhello.so.0 -o
libhello.so.0.0 libhello.o -lc# At this point we could just copy
libhello.so.0.0 into
# some directory, say /usr/local/lib.
# Now we need to call ldconfig to fix up the symbolic links.
# Set up the soname. We could just execute:
# ln -sf libhello.so.0.0 libhello.so.0
# but let’s let ldconfig figure it out./sbin/ldconfig -n .
# Set up the linker name.
# In a more sophisticated setting, we’d need to make
# sure that if there was an existing lin

原创粉丝点击