您的位置:首页 > 编程语言

使用gdb调试共享库代码

2013-03-09 17:25 351 查看
转自:http://hi.baidu.com/lrrl_99/item/9c2dd926fe350098b632631a

相信有不少的同志调试过包含共享库代码的程序,这个时候最为头疼的就是不能进行单步跟踪(当然是在你不知道如何解决的情况下^_^),本文根据一个实例来讲述如何来解决这个问题。首先来看我们的程序,包含两个文件:dyn.c, main.c,其中dyn.c被编译成一个共享库libdyn.so,在链接的时候要用到它。有一点必须声明,就是你的共享库代码必须是带有调试信息的(比如使用 -g选项)。

$cat dyn.c

dyn ()

{

puts ("Hello.");

}

$cat main.c

main ()

{

puts ("before");

dyn ();

puts ("after");

}

$cat Makefile

main:

gcc -g -save-temps -c main.c -o main.o

gcc -g -save-temps -c -fpic -ffunction-sections dyn.c

gcc -g -save-temps -shared dyn.o -o libdyn.so

gcc -g -save-temps main.o libdyn.so -o main

clean:

rm -rf *.o *.so main

下面我们就来编译和调试程序:

$make main 这之后就会在当前目录下生成我们所要的libdyn.so 和 main程序

如果我们向往常一样直接输入 $./main 来执行程序,这是不行的,它会给我们这样的错误提示:

before

./main: relocation error: ./main: undefined symbol: dyn

为什么?原来是我们使用了共享库libdyn.so却没有告诉动态链接程序到哪里去找他!好,这回我们告诉它:

$LD_LIBRARY_PATH=`pwd` ./main

before

Hello.

after

怎么样,出现我们想要的结果了吧。

以上这些是一些小儿科啦,牛人们不要笑话啊,下面才是这次要讲的。通常情况下我们使用gdb进行调试的时候:

$ gdb -q main

(gdb) b main

Breakpoint 1 at 0x8048478: file main.c, line 3.

(gdb) r

Starting program: /home/lirui/Test/main

Breakpoint 1, main () at main.c:3

3 puts ("before");

(gdb) next

before

4 dyn ();

(gdb)

/home/lirui/Test/main: relocation error: /home/lirui/Test/main: undefined symbol: dyn

Program exited with code 0177.

(gdb)

按照我们的本意,我们是想跟踪到dyn()函数内部去看一些咚咚的,可是它却找不到对应的符号信息,怎么办?

方法一:设定gdb环境变量 LD_PRELOAD,在执行程序前先把共享库代码load进来不就能找到了吗

$gdb -q main

(gdb) set environment LD_PRELOAD ./libdyn.so

(gdb) break dyn

Breakpoint 1 at 0x80483a8

(gdb) run

Starting program: /home/lirui/Test/main

Breakpoint 1 at 0x400176ff: file dyn.c, line 3.

before

Breakpoint 1, dyn () at dyn.c:3

3 puts ("Hello.");

(gdb) list

1 dyn ()

2 {

3 puts ("Hello.");

4 }

(gdb)

这下不就找到dyn()函数了吗,这样你就可以很舒心的调试喽!

方法二:如果你使用的gdb版本中对”pending breakpoint"提供支持(V6.3当中就有支持),那么恭喜你,你可以先设定一个pending breakpoint,然后有gdb来决定到什么时候这个断点起作用。这里面有一点必须注意,你必须指定你的链接库的位置,可以通过设定环境变量LD_LIBRARY_PATH来实现。在执行gdb之前,我们可以这样做: $ export LD_LIBARY_PATH=`pwd`,告诉gdb在当前目录下查找共享库文件,然后向往常一样调试程序就可以了:

$ export LD_LIBRARY_PATH=`pwd`

$ gdb -q main

Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) b dyn

Function "dyn" not defined.

Make breakpoint pending on future shared library load? (y or
) y

Breakpoint 1 (dyn) pending.

(gdb) r

Starting program: /home/lirui/Test/tmp/main

Reading symbols from shared object read from target memory...done.

Loaded system supplied DSO at 0xffffe000

Breakpoint 2 at 0xb7f4853a: file dyn.c, line 3.

Pending breakpoint "dyn" resolved

before

Breakpoint 2, dyn () at dyn.c:3

3 puts ("Hello.");

(gdb) l

1 dyn ()

2 {

3 puts ("Hello.");

4 }

(gdb)

这样是不是也可以啊,^_^

方法三:这种情况只针对你要调试的程序整个就是一个动态链接的可执行程序,它在load到内存之后,入口地址都是动态变化的,如果你使用gdb进行调试,最初的时候你用 b function_name的话,它把断点设在了以0x0为基址的offset上,而程序load到内存之后,这个基址已经变了,所以总是不能设置成功断点。(我在调试qemu的时候就遇到这种情况),怎么办呢?最简单的方法就是不把这个程序编译成可重载的,像普通程序一样去编译它,不要为gcc 添加 -wl,-shared等参数就行了,这时候编译出来的就很容易调试了。

针对这种情况,我觉得还应该有一个办法,但是我没有试验成功,就是在使用gdb的时候,最初不要把符号表load进来,如果已经load进来的话,就使用 symbol-file命令(后面不加参数)把已经载入的符号表丢弃掉,这时候再使用add_symbol_file filename start_address命令把符号表从filename中载入到以start_address开始的地址当中,就可以调试了。我以这种方法试验过不适用共享库的程序,是可以的,因为我们知道一般程序的开始地址是0x8048000,但是对于共享对象,我们不知道它的开始地址是多少,所以就不好办了,如果有一种办法能够告诉我们它的.text的起始地址,我们就很容易以这种方法来做了。

应该还有其他的方法吧,如果你知道,请告诉我 ,多谢!

另外一种Makefile的写法是:

.PHONY:all clean

all:main

main:

gcc -g -c main.c -o main.o

gcc -g -c -fPIC -ffunction-sections dyn.c

gcc -g -shared dyn.o -o libdyn.so

gcc -g -Wl,-rpath ./ main.o ./libdyn.so -o main

clean:

rm -rf *.o *.so main
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: