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

如何在 Linux 下调试动态链接库,第1张

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

-g 参数。

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

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

QUOTE:

库名称是: ggg

动态链接库文件名是: libggg.so

头文件是: get.h

提供这样两个函数调用接口:

int get ()

int set (int a)

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

[Copy to clipboard]

CODE:

/************关于本文档********************************************

*filename: get.h

*purpose: 一个动态链接库头文件示例

*tided by: zhoulifa() 周立发 ()

Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言

*date time: 2006-11-15 21:11:54

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循 GPL

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*感谢 提供原始代码,

我在他的基础上整理了此文

*********************************************************************/

int get ()

int set (int a)

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

[Copy to clipboard]

CODE:

/************关于本文档********************************************

*filename: get.cpp

*purpose: 一个动态链接库源文件示例

*tided by: zhoulifa() 周立发 ()

Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言

*date time:2006-11-15 21:11:54

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循 GPL

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*感谢 提供原始代码,

我在他的基础上整理了此文

*********************************************************************/

#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

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

[Copy to clipboard]

CODE:

/************关于本文档********************************************

*filename: pk.cpp

*purpose: 一个调用动态链接库的示例

*tided by: zhoulifa() 周立发 ()

Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言

*date time:2006-11-15 21:11:54

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循 GPL

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*感谢 提供原始代码,

我在他的基础上整理了此文

*********************************************************************/

#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 之类的,就用下面这条命令:

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:

./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 /* 程序顺利执行结束 */#

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

QUOTE:

# 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

#

本次实验的环境是:

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)

break(b) 行号:在某一行设置断点

break 函数名:在某个函数开头设置断点

break...if...:设置条件断点

continue(或c):从当前位置开始连续而非单步执行程序

delete breakpoints:删除所有断点

delete breakpoints n:删除序号为n的断点

disable breakpoints:禁用断点

enable breakpoints:启用断点

info(或i) breakpoints:参看当前设置了哪些断点

run(或r):从开始连续而非单步执行程序

display 变量名:跟踪查看一个变量,每次停下来都显示它的值

undisplay:取消对先前设置的那些变量的跟踪

指定PATH环境变量路径就好了

 一般来说,如果库的头文件不在 /usr/include 目录中,那么在编译的时候需要用 -I 参数指定其路径。由于同一个库在不同系统上可能位于不同的目录下,用户安装库的时候也可以将库安装在不同的目录下,所以即使使用同一个库,由于库的路径的 不同,造成了用 -I 参数指定的头文件的路径也可能不同,其结果就是造成了编译命令界面的不统一。如果使用 -L 参数,也会造成连接界面的不统一。编译和连接界面不统一会为库的使用带来麻烦。

为了解决编译和连接界面不统一的问题,人们找到了一些解决办法。其基本思想就是:事先把库的位置信息等保存起来,需要的时候再通过特定的工具将其中有用的 信息提取出来供编译和连接使用。这样,就可以做到编译和连接界面的一致性。其中,目前最为常用的库信息提取工具就是下面介绍的 pkg-config。

pkg-config 是通过库提供的一个 .pc 文件获得库的各种必要信息的,包括版本信息、编译和连接需要的参数等。这些信息可以通过 pkg-config 提供的参数单独提取出来直接供编译器和连接器使用。

The pkgconfig package contains tools for passing the include path and/or library paths to build tools during the make file execution.

pkg-config is a function that returns meta information for the specified library.

The default setting for PKG_CONFIG_PATH is /usr/lib/pkgconfig because of the prefix we use to install pkgconfig. You may add to PKG_CONFIG_PATH by exporting additional paths on your system where pkgconfig files are installed. Note that PKG_CONFIG_PATH is only needed when compiling packages, not during run-time.

在默认情况下,每个支持 pkg-config 的库对应的 .pc 文件在安装后都位于安装目录中的 lib/pkgconfig 目录下。例如,我们在上面已经将 Glib 安装在 /opt/gtk 目录下了,那么这个 Glib 库对应的 .pc 文件是 /opt/gtk/lib/pkgconfig 目录下一个叫 glib-2.0.pc 的文件:

prefix=/opt/gtk/

exec_prefix=${prefix}

libdir=${exec_prefix}/lib

includedir=${prefix}/include

glib_genmarshal=glib-genmarshal

gobject_query=gobject-query

glib_mkenums=glib-mkenums

Name: GLib

Description: C Utility Library

Version: 2.12.13

Libs: -L${libdir} -lglib-2.0

Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include

使用 pkg-config 的 --cflags 参数可以给出在编译时所需要的选项,而 --libs 参数可以给出连接时的选项。例如,假设一个 sample.c 的程序用到了 Glib 库,就可以这样编译:

$ gcc -c `pkg-config --cflags glib-2.0` sample.c

然后这样连接:

$ gcc sample.o -o sample `pkg-config --libs glib-2.0`

或者上面两步也可以合并为以下一步:

$ gcc sample.c -o sample `pkg-config --cflags --libs glib-2.0`

可以看到:由于使用了 pkg-config 工具来获得库的选项,所以不论库安装在什么目录下,都可以使用相同的编译和连接命令,带来了编译和连接界面的统一。

使用 pkg-config 工具提取库的编译和连接参数有两个基本的前提:

库本身在安装的时候必须提供一个相应的 .pc 文件。不这样做的库说明不支持 pkg-config 工具的使用。

pkg-config 必须知道要到哪里去寻找此 .pc 文件。

GTK+ 及其依赖库支持使用 pkg-config 工具,所以剩下的问题就是如何告诉 pkg-config 到哪里去寻找库对应的 .pc 文件,这也是通过设置搜索路径来解决的。

对于支持 pkg-config 工具的 GTK+ 及其依赖库来说,库的头文件的搜索路径的设置变成了对 .pc 文件搜索路径的设置。.pc 文件的搜索路径是通过环境变量 PKG_CONFIG_PATH 来设置的,pkg-config 将按照设置路径的先后顺序进行搜索,直到找到指定的 .pc 文件为止。

安装完 Glib 后,在 bash 中应该进行如下设置:

$ export PKG_CONFIG_PATH=/opt/gtk/lib/pkgconfig:$PKG_CONFIG_PATH

可以执行下面的命令检查是否 /opt/gtk/lib/pkgconfig 路径已经设置在 PKG_CONFIG_PATH 环境变量中:

$ echo $PKG_CONFIG_PATH

这样设置之后,使用 Glib 库的其它程序或库在编译的时候 pkg-config 就知道首先要到 /opt/gtk/lib/pkgconfig 这个目录中去寻找 glib-2.0.pc 了(GTK+ 和其它的依赖库的 .pc 文件也将拷贝到这里,也会首先到这里搜索它们对应的 .pc 文件)。之后,通过 pkg-config 就可以把其中库的编译和连接参数提取出来供程序在编译和连接时使用。

另外还需要注意的是:环境变量的设置只对当前的终端窗口有效。如果到了没有进行上述设置的终端窗口中,pkg-config 将找不到新安装的 glib-2.0.pc 文件、从而可能使后面进行的安装(如 Glib 之后的 Atk 的安装)无法进行。

在我们采用的安装方案中,由于是使用环境变量对 GTK+ 及其依赖库进行的设置,所以当系统重新启动、或者新开一个终端窗口之后,如果想使用新安装的 GTK+ 库,需要如上面那样重新设置 PKG_CONFIG_PATH 和 LD_LIBRARY_PATH 环境变量。

-L后的应该是路径,本例应该是-L/usr/lib/,-l后应该是库的名字,注意,这里库的名字是吧库"libapi.so"去掉前面的"lib"和后面的".so",应该是-lapi.而且一个-L和-l对应一个库.所以你的应该改为:

gcc -g -o test.exe test.c -L/usr/lib/ -lapi -L/usr/lib/ -lpk11.

还有,.exe文件是windows下的可执行文件格式,linux下不这么用,但是就本例而言也能运行.


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7356881.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-04
下一篇 2023-04-04

发表评论

登录后才能评论

评论列表(0条)

保存