[实践OK]Linux 动态链接库编程基础,以及生成的动态链接库里的函数和地址查看之nm -D  /usr/local/php/lib/php/extensions/no-debug-non-zts-20180731/memcache.so

jackxiang 2008-12-8 10:33 | |
动态链接库是一种通用的软件组件技术,是多种操作系统中提供基本服务的方式。比如Win32内核就是3个DLL文件构成。这种技术在Linux操作系统下也有对应的实现,就是Linux标准对象Standard Ojbect,对应的文件扩展名为.so。

  下面通过一个简单的例子开始介绍Linux标准对象。
  我们的标准对象文件含有一个函数,不需要声明export导出符号,只需要编译器设置即可。如下:
#include <stdio.h>
#include <stdlib.h>

void show() {
  printf("Standard Object by gashero\n");
}


  保存为myso.c文件,按照如下编译:
$ gcc -fPIC -shared -o libmyso.so myso.c


  执行生成一个libmyso.so文件,按照Linux标准对象的命名惯例,应该在库名称之前加上"lib"前缀,尽管不是必须的。编译开关-fPIC代表函数符号可以重定向,-shared代表编译结果是一个标准对象。

  不同于Win32DLL,Linux标准对象中的所有函数都是直接导出的,都可以被调用程序所访问。下面我们编写调用程序:
#include <stdio.h>

int main() {
  printf("Invoke my so\n");
  show();
  return 0;
}


  保存为invoke.c,按照如下gcc开关编译:

$ gcc -o test invoke.c ./libmyso.so


  编译生成test可执行文件。如上编译条件的最后一条需要是所调用的标准对象文件名,注意必须含有路径。如果只是使用libmyso.so,则必须确保这个文件在可访问的PATH下面。本例所使用的文件名"./libmyso.so"是当前路径下的,使用了相对路径。

  如下测试结果:


$ ./test
Invoke my so
Standard Object by gashero


  希望上文的例子可以对大家有所帮助。

如何在 Linux 下调试动态链接库

大家都知道在 Linux 可以用 gdb 来调试应用程序,当然前提是用 gcc 编译程序时要加上


-g 参数

我这篇文章里将讨论一下用 gdb 来调试动态链接库的问题。


  首先,假设我们准备这样的一个动态链接库:

QUOTE:
库名称是: ggg
动态链接库文件名是: libggg.so
头文件是: get.h
提供这样两个函数调用接口:

    int get ();
    int set (int a);


要生成这样一个动态链接库,我们首先编写这样一个头文件:
CODE:

/************关于本文档********************************************
*filename: get.h
*purpose:  一个动态链接库头文件示例
*tided by: zhoulifa(zhoulifa@163.com) 周立发 (http://zhoulifa.9999mb.com)
Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言
*date time: 2006-11-15 21:11:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循 GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*感谢 vcclass@hotmail.com 提供原始代码,

我在他的基础上整理了此文
*********************************************************************/
int get ();
int set (int a);


然后准备这样一个生成动态链接库的源文件:
CODE:

/************关于本文档********************************************
*filename:  get.cpp
*purpose: 一个动态链接库源文件示例
*tided by: zhoulifa(zhoulifa@163.com) 周立发 (http://zhoulifa.9999mb.com)
Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言
*date time:2006-11-15 21:11:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循 GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*感谢 vcclass@hotmail.com 提供原始代码,

我在他的基础上整理了此文
*********************************************************************/
#include <stdio.h>

#include "get.h"



static int x=0;

int get ()

{

        printf ("get x=%d\n", x);

        return x;

}

int set (int a)

{

        printf ("set a=%d\n", a);

        x = a;

        return x;

}

然后我们用 GNU 的 C/C++ 编译器来生成动态链接库,编译命令如下:
QUOTE:
g++ get.cpp -shared -g -DDEBUG -o libggg.so

实践发现会报错,
/usr/bin/ld: /tmp/ccYLWPaw.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
实践成功命令如下:
g++ get.cpp -shared -g -DDEBUG -o libggg.so -fPIC



这样我们就准备好了动态链接库了,下面我们编写一个应用程序来调用此动态链接库,源代码如下:
CODE:

/************关于本文档********************************************
*filename: pk.cpp
*purpose: 一个调用动态链接库的示例
*tided by: zhoulifa(zhoulifa@163.com) 周立发 (http://zhoulifa.9999mb.com)
Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言
*date time:2006-11-15 21:11:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循 GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*感谢 vcclass@hotmail.com 提供原始代码,

我在他的基础上整理了此文
*********************************************************************/
#include <stdio.h>

#include "get.h"

int main (int argc, char** argv)

{

        int a = 100;

        int b = get ();

        int c = set (a);

        int d = get ();



        printf ("a=%d,b=%d,c=%d,d=%d\n",a,b,c,d);

        return 0;

}

编译此程序用下列命令,如果已经把上面生成的 libggg.so 放到了库文件搜索路径指定的文件目录,比如 /lib 或 /usr/lib 之类的,就用下面这条命令:
CentOS64位的lib目录在:/usr/lib64/ ,得:cp -rf libggg.so /usr/lib64/.
QUOTE:
g++ pk.cpp -o app -Wall -g -lggg

否则就用下面这条命令:
QUOTE:
g++ pk.cpp -o app -Wall -g -lggg -L`pwd`


下面我们就开始调试上面命令生成的 app 程序吧。如果已经把上面生成的 libggg.so 放到了库文件搜索路径指定的文件目录,比如 /lib或 /usr/lib 之类的,调试就顺利完成,如下:

QUOTE:

linux#gdb">zhoulifa@linux#gdb ./app
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation,Inc.
GDB is free software, covered by the GNU

General Public License, and you are
welcome to change it and/or distribute

copies of it under certain conditions.
Type "show copying" to see theconditions.


There is absolutely no warranty for GDB.

Type "show warranty" for details.This GDB was configured as "i486-linux-

gnu"...Using host libthread_db library"/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) b main    /* 这是在程序的 main 处设置断点 */
Breakpoint 1 at 0x804853c: file pk.cpp,line 7.
(gdb) b set      /* 这是在程序的 set 处设置断点 */
Function "set" not defined.
Make breakpoint pending on future shared

library load? (y or [n]) y /* 这里必须选择 y 调试程序才会跟踪到动态链接库内部去

*/Breakpoint 2 (set) pending.
(gdb) run /* 开始运行我们的程序,直到遇见断点时暂停 */
Starting program: /data/example/c/app
Breakpoint 3 at 0xb7f665f8: file get.cpp,line 11.
Pending breakpoint "set" resolved

Breakpoint 1, main (argc=1,argv=0xbf990504) at pk.cpp:7
7               int a = 100;
(gdb) n     /* 继续执行程序的下一行代码

*/
8               int b = get ();
(gdb) n      /* 程序执行到了我们断点所在的动态链接库了 */
get x=0
9               int c = set (a);(gdb) n

Breakpoint 3, set (a=100) at get.cpp:11
11              printf ("set a=%d\n", a);

(gdb) list   /* 查看当前代码行周围的代码,证明我们已经跟踪到动态链接库的源代码里面了 */
6               printf ("get x=%d\n", x);
7               return x;
8       }
9       int set (int a)
10      {
11              printf ("set a=%d\n", a);
12              x = a;
13              return x;
14      }
(gdb) n
set a=100
12              x = a;(gdb) n
13              return x;(gdb) n
14      }
(gdb) n
main (argc=1, argv=0xbf990504) at

pk.cpp:10
10              int d = get ();
(gdb) n
get x=100
11              printf ("a=%d,b=%d,c=%

d,d=%d\n",a,b,c,d);
(gdb) n
a=100,b=0,c=100,d=100
12              return 0;
(gdb) c
Continuing.

Program exited normally.
(gdb) quit  /* 程序顺利执行结束 */zhoulifa@linux#

如果我们没有把动态链接库放到指定目录,比如/lib里面,调试就会失败,过程如下:

QUOTE:

zhoulifa@linux# gdb ./app
GNU gdb 6.4-debian
Copyright 2005 Free Software Foundation,

Inc.
GDB is free software, covered by the GNU

General Public License, and you arewelcome to change it and/or distribute

copies of it under certain conditions.


Type "show copying" to see theconditions.
There is absolutely no warranty for GDB.

Type "show warranty" for details.
This GDB was configured as "i486-linux-

gnu"...Using host libthread_db library

"/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) b main
Breakpoint 1 at 0x804853c: file pk.cpp,

line 7.
(gdb) b set
Function "set" not defined.
Make breakpoint pending on future shared

library load? (y or [n]) y
Breakpoint 2 (set) pending.
(gdb) run  /* 虽然调试操作都一样,但程序执行失败 */
Starting program: /data/example/c/app
/data/example/c/app: error while loading

shared libraries: libggg.so: cannot open

shared object file: No such file or

directory

Program exited with code 0177.
(gdb) quit
zhoulifa@linux#
本次实验的环境是:
CPU:AMD Athlon(tm) 64 Processor 3000+
内存:512M
OS:Ubuntu GNU/Linux 6.06 dapper LTS
gcc:gcc 版本 4.0.3 (Ubuntu 4.0.3-1ubuntu5)

Linux下使用动态链接库

使用动态链接库,我认为,再比较大的程序运行过程中,是一种很有优势的。所以就花了一天时间来学习一下。

使用动态链接库,需要了解一下内容

头文件:

<dlfcn.h>
函数:
void *dlopen(const char *filename, int flag);
const char *dlerror(void);
void *dlsym(void *handle, char *symbol);
int dlclose(void *handle);


相关的信息可以通过 man dlopen查询

在编译动生成态链接库的时候,
需要参数 -shared

在使用动态链接库的时候,
需要参数 -ldl

其他相关参数有
-fpic -fPIC  -rdynamic

如有库函数文件Lib.c, 主函数文件Main.c
则有如下Makefile


all: comple link

comple:
    gcc -c Lib.c -o Lib.o
    gcc -c Main.c -o Main.o

link:
    gcc -shared Lib.o -o Lib.so
    gcc -ldl Main.o -o Main



另外,在C++中使用动态连接库的时候,请注意:
必须用
extern "C"
{
}
将动态苦定义为C的编译连接方式

否则由于C++命名方式于C不同,会造成生成的动态链接库不能使用(无法定位或函数)

文章选取的例子非常简单,上手容易,只是为了讲述静态与动态链接库的生成和链接过
    程,还有他们之间的区别。以下例子在 gcc 4.1.1 下顺利通过。


文件预览 (补充)
文件目录树如下,如你所见,非常简单。


   1. libtest/  
   2. |-- lt.c  
   3. |-- lt.h  
   4. `-- test.c  




代码

#lt.c

   1. /* lt.c
   2.  *
   3.  */  
   4.  
   5. #include <stdio.h>  
   6.  
   7. void myprint(void)  
   8. {  
   9.   printf("Linux library test!\n");  
  10. }  



# lt.h

   1. /* lt.h
   2.  *  
   3.  */  
   4.  
   5. void myprint(void);  





#test.c

   1. /* test.c
   2.  *
   3.  */  
   4.  
   5. #include "lt.h"  
   6.  
   7. int main(void)  
   8. {  
   9.   myprint();  
  10.   return 0;  
  11. }  


先看静态库
首先做成静态库 liblt.a 。


   1. $ gcc -c lt.c -o lt.o  
   2. $ ar cqs liblt.a lt.o  





再者,链接,这里指定了静态库的位置,注意文件顺序不可乱序。


   1. $ gcc test.o liblt.a -o test  




这个时候再来看他的引用库情况。


   1. $ ldd test  
   2.         linux-gate.so.1 =>  (0xffffe000)  
   3.         libc.so.6 => /lib/libc.so.6 (0xb7e29000)  
   4.         /lib/ld-linux.so.2 (0xb7f6e000)  




动态库
做成动态库 liblt.so 。


   1. $ gcc -c lt.c -o lt.o  
   2. $ gcc -shared -Wall -fPIC lt.o -o liblt.so  




链接方法I,拷贝到系统库里再链接,让gcc自己查找


   1. $ sudo cp liblt.so /usr/lib  
   2. $ gcc -o test test.o -llt  



这里我们可以看到了 -llt 选项,-l[lib_name] 指定库名,他会主动搜索
lib[lib_name].so 。这个搜索的路径可以通过 gcc --print-search-dirs来查找。

链接方法II,手动指定库路径


   1. $ cc -o test test.o -llt -B /path/to/lib



这里的-B 选项就添加 /path/to/lib 到gcc搜索的路径之中。这样链接没有问题但是方法II
中手动链接好的程序在执行时候仍旧需要指定库路径(链接和执行是分开的)。需要添加系
统变量 LD_LIBRARY_PATH :


   1. $ export LD_LIBRARY_PATH=/path/to/lib  




这个时候再来检测一下test程序的库链接状况(方法I情况)


   1. $ ldd test  
   2.         linux-gate.so.1 =>  (0xffffe000)  
   3.         liblt.so => /usr/lib/liblt.so (0xb7f58000)  
   4.         libc.so.6 => /lib/libc.so.6 (0xb7e28000)  
   5.         /lib/ld-linux.so.2 (0xb7f6f000)  



恩,是不是比静态链接的程序多了一个 liblt.so ?恩,这就是静态与动态的最大区别,静
态情况下,他把库直接加载到程序里,而在动态链接的时候,他只是保留接口,将动态库与
程序代码独立。这样就可以提高代码的可复用度,和降低程序的耦合度。




[xiangdong2@vm19 so2]$ export LD_LIBRARY_PATH=""
[xiangdong2@vm19 so2]$ ./app
./app: error while loading shared libraries: libggg.so: cannot open shared object file: No such file or directory
[xiangdong2@vm19 so2]$ export LD_LIBRARY_PATH=/home/xiangdong2/so2/pwd
[xiangdong2@vm19 so2]$ ./app
get x=0
set a=100
get x=100
a=100,b=0,c=100,d=100
[xiangdong2@vm19 so2]$ export LD_LIBRARY_PATH=/home/xiangdong2/so2/pwd2
[xiangdong2@vm19 so2]$ ./app
./app: error while loading shared libraries: libggg.so: cannot open shared object file: No such file or directory

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:https://jackxiang.com/post/1476/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2024-2-14 00:22
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]