您的位置:首页 > 运维架构 > Linux

linux 共享库的编译和连接

2015-08-26 15:58 525 查看
当前目录下总共有7个源文件:slib.h、slib1.c、slib2.c、main.c、dlib.h、dlib1.c、dlib2.c,其中slib.h是静态库公共头文件,slib1.c将编译成静态库libslib1.a,slib2.c将编译成静态库libslib2.a,dlib.h是共享库(即通常说的动态库)公共头文件,dlib1.c将编译成共享库libdlib1.so,dlib2.c将编译成共享库libdlib2.so,main.c是主程序,链接libslib1.a、libslib1.a、libdlib1.so、libdlib2.so。

slib.h、slib1.c、slib2.c 这三个文件不变,与 linux 静态库的编译和连接过程遇到的问题 一样。

dlib.h、dlib1.c、dlib2.c、main.c 的源码:

// dlib.h
#include <stdio.h>

int dlib_add(int a, int b);
void dlib_print(const char *str);

int dlib_sub(int a, int b);
void dlib_print2(const char *str);


// dlib1.c
#include "dlib.h"

int dlib_add(int a, int b)
{
return a + b;
}

void dlib_print(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}


// dlib2.c
#include "dlib.h"

int dlib_sub(int a, int b)
{
return a - b;
}

void dlib_print2(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}


// main.c
#include "slib.h"
#include "dlib.h"

int main(int agrc, char **agrv)
{
printf("*.a .....\n");
printf("add:%d, sub:%d\n", add(1, 2), sub(3, 4));
print("Hello world!");
print2("Hi, world!");
printf("\n*.so .....\n");
printf("add:%d, sub:%d\n", dlib_add(1, 2), dlib_sub(3, 4));
dlib_print("Hello world!");
dlib_print2("Hi, world!");
return 0;
}


一、so共享库的编译、链接、运行:

1、按之前的方式编译静态库libslib1.a、libslib2.a,以及main.o

gcc -c slib1.c slib2.c main.c

ar -rs libslib1.a slib1.o

ar -rs libslib2.a slib2.o

2、编译so共享库:

gcc -fPIC -c dlib1.c # 这里必须加-fPIC选项,否则下一步生成libdlib1.so时报错

gcc -fPIC -shared -o libdlib1.so dlib1.o

gcc -fPIC -c dlib2.c # 这里必须加-fPIC选项,否则下一步生成libdlib2.so时报错

gcc -fPIC -shared -o libdlib2.so dlib2.o

或者这样,不生成中间文件:

gcc -fPIC -shared -o libdlib1.so dlib1.c

gcc -fPIC -shared -o libdlib2.so dlib2.c

3、可执行文件main:

gcc -o main main.o -L. -lslib1 -lslib2 -ldlib1 -ldlib2

或者这样,也能编译通过:

gcc -o main -ldlib1 -ldlib2 main.o -L. -lslib1 -lslib2

即共享库不像静态库那样,链接时需要放到调用者main.o的后面

4、执行main

报以下错误:

./main
./main: error while loading shared libraries: libdlib2.so: cannot open shared object file: No such file or directory


系统找不到共享库文件,ldd查看一下:

ldd main
linux-vdso.so.1 =>  (0x00007fff86974000)
libdlib1.so => not found
libdlib2.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f332bd79000)
/lib64/ld-linux-x86-64.so.2 (0x00007f332bfb9000)


果然是!

解决办法有以下三种:

1、用ln将需要的so文件链接到/usr/lib或者/lib这两个默认的目录下边:

ln -s ./*.so /usr/lib

2、修改/etc/ld.so.conf文件,向该文件加入so库文件所在的路径,然后执行ldconfig刷新:

vim /etc/ld.so.conf # 添加s
4000
o库文件所在的路径

sudo ldconfig

3、修改环境变量LD_LIBRARY_PATH,添加so库文件所在的路径(设为/home/chenwucai/tmp/lib):

export LD_LIBRARY_PATH=/home/chenwucai/tmp/lib:${LD_LIBRARY_PATH}

前面第1、2两种办法都需要root授权,没有root权限或者临时测试的话,可以用第3种办法。

再执行ldd查看:

ldd main
linux-vdso.so.1 =>  (0x00007fff73e72000)
libdlib2.so => /home/chenwucai/tmp/lib/libdlib2.so (0x00007ff6a8783000)
libdlib1.so => /home/chenwucai/tmp/lib/libdlib1.so (0x00007ff6a8682000)
libc.so.6 => /lib64/libc.so.6 (0x00007ff6a8442000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff6a8884000)


系统可以找到libdlib1.so、libdlib2.so库文件了。

可以成功执行main:

./main
*.a .....
add:3, sub:-1
file:slib1.c, fun:print, line:11, str:Hello world!
file:slib2.c, fun:print2, line:11, str:Hi, world!

*.so .....
add:3, sub:-1
file:dlib1.c, fun:dlib_print, line:11, str:Hello world!
file:dlib2.c, fun:dlib_print2, line:11, str:Hi, world!


二、so库函数重名情况:

在dlib1.c中添加print函数(与静态库的函数重名)和dlib_print2函数(与共享库libdlib2.so的函数重名)

// dlib1.c
// 添加以下函数
void print(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}

void dlib_print2(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}


先删掉上次编译的文件,rm -f .o .a *.so main。

然后重复上面的编译、链接、运行过程,执行结果如下:

./main
*.a .....
add:3, sub:-1
file:slib1.c, fun:print, line:11, str:Hello world!
file:slib2.c, fun:print2, line:11, str:Hi, world!

*.so .....
add:3, sub:-1
file:dlib1.c, fun:dlib_print, line:11, str:Hello world!
file:dlib1.c, fun:dlib_print2, line:27, str:Hi, world!


可见,静态库的同名函数print不会被共享库的覆盖(同样主程序的同名函数也不会被共享库的覆盖),或者说连接时,主程序或静态库的同名函数会覆盖共享库的,而共享库libdlib2.so的同名函数却被覆盖了,链接的是libdlib1.so库的dlib_print2函数!

再看看前面链接各个库的gcc命令:

gcc -o main main.o -L. -lslib1 -lslib2 -ldlib1 -ldlib2

dlib1在dlib2前面,即dlib1先连接,再将连接命令改成这样(将dlib2放在前面)

gcc -o main main.o -L. -lslib1 -lslib2 -ldlib2 -ldlib1

再运行一次,执行结果如下:

./main
*.a .....
add:3, sub:-1
file:slib1.c, fun:print, line:11, str:Hello world!
file:slib2.c, fun:print2, line:11, str:Hi, world!

*.so .....
add:3, sub:-1
file:dlib1.c, fun:dlib_print, line:11, str:Hello world!
file:dlib2.c, fun:dlib_print2, line:11, str:Hi, world!


链接调用的是libdlib2.so库的dlib_print2函数。

由此可以推断,当两个共享库有同名函数时,先链接的库的同名函数会覆盖后链接的。

三、静态库和共享库同名的情况:

先删掉上次编译的文件,rm -f .o .a *.so main。

然后依次执行以下编译命令:

gcc -c slib1.c slib2.c main.c

ar -rs libslib1.a slib1.o

ar -rs libslib2.a slib2.o

gcc -fPIC -shared -o libdlib1.so dlib1.c

gcc -fPIC -shared -o libdlib2.so dlib2.c

gcc -fPIC -shared -o libslib1.so dlib1.c # 这里用dlib1.c编译一个与libslib1.a同名的共享库

然后执行链接:

gcc -o main main.o -L. -lslib1 -lslib2 -ldlib1 -ldlib2

报以下错误:

main.o: In function `main':
main.c:(.text+0x36): undefined reference to `add'
collect2: ld returned 1 exit status


由于dlib1.c中并没有定义add函数,即libslib1.so库没有add函数,可推断,gcc链接时,如果目录下有同名的静态库和共享库,则优先链接共享库。

这种情况下,如果一定要链接静态库的话,可以这样指定链接的库libslib1.a(验证发现,共享库也可以这样./libslib1.so指定):

gcc -o main main.o -L. ./libslib1.a -lslib2 -ldlib1 -ldlib2

编译通过,执行结果如下:

./main
*.a .....
file:slib1.c, fun:add, line:5, it is archives, a=1, b=2  # 为便于验证,在slib1.c的add加上的
add:3, sub:-1
file:slib1.c, fun:print, line:12, str:Hello world!
file:slib2.c, fun:print2, line:11, str:Hi, world!

*.so .....
add:3, sub:-1
file:dlib1.c, fun:dlib_print, line:11, str:Hello world!
file:dlib1.c, fun:dlib_print2, line:35, str:Hi, world!


可见链接的是静态库libslib1.a

为进一步验证,库名相同的情况,gcc优先链接共享库,在dlib1.c文件中加入add函数(与slib1.c文件的add函数同名),添加完后的dlib1.c文件:

// dlib1.c
#include "dlib.h"

int dlib_add(int a, int b)
{
return a + b;
}

void dlib_print(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}

int add(int a, int b)
{
printf("file:%s, fun:%s, line:%d, here is so lib, a=%d, b=%d\n", __FILE__, __FUNCTION__, __LINE__, a, b);
return a + b;
}

void print(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}

void dlib_print2(const char *str)
{
if(str)
printf("file:%s, fun:%s, line:%d, str:%s\n", __FILE__, __FUNCTION__, __LINE__, str);
else
printf("file:%s, fun:%s, line:%d, str is NULL\n", __FILE__, __FUNCTION__, __LINE__);
}


编译:

rm -f .o .a *.so main。

gcc -c slib1.c slib2.c main.c

ar -rs libslib1.a slib1.o

ar -rs libslib2.a slib2.o

gcc -fPIC -shared -o libdlib1.so dlib1.c

gcc -fPIC -shared -o libdlib2.so dlib2.c

gcc -fPIC -shared -o libslib1.so dlib1.c

链接:

gcc -o main main.o -L. -lslib1 -lslib2 -ldlib1 -ldlib2

执行结果如下:

./main
*.a .....
file:dlib1.c, fun:add, line:18, here is so lib, a=1, b=2
add:3, sub:-1
file:dlib1.c, fun:print, line:25, str:Hello world!
file:slib2.c, fun:print2, line:11, str:Hi, world!

*.so .....
add:3, sub:-1
file:dlib1.c, fun:dlib_print, line:11, str:Hello world!
file:dlib1.c, fun:dlib_print2, line:33, str:Hi, world!


链接调用的是同名共享库libslib1.so的add和print函数。

以上链接命令也可以是这样的

gcc -o main main.o -L. -lslib1 -lslib2 -ldlib2 # 省掉后面的-ldlib1

因为libslib1.so和libdlib1.so函数完全相同,前者先链接,完全屏蔽了或者的函数了。

四、小结:

动态库.so有两种链接调用形式,一种是显式的,一种是隐式的。

上面提到的是隐式调用,即有点类似静态库,也是编译链接时指定库的路径和名称,不同于静态库的是此时编译链接器并没有真正把动态库加载到可执行文件中,而只是校验库函数接口、记录函数在所在的动态库和相对地址,到运行时才将库加载进来,共享库函数的调用地址重定向是通过全局偏移量表(GOT)来实现的。

显式的调用则是通过一组库函数(dlopen、dlsym、dlerror、dlclose)来实现的,主程序不需要在编译时链接共享库,甚至都不需要包含其头文件,完全是运行时动态加载,更加灵活。Web Server在运行过程中加载CGI模块通常就是采用这种方法,改天有空再详细研究一下这种模式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息