gdb调试包含共享库代码的程序
shell export LD_PRELOAD
相信有不少的同志调试过包含共享库代码的程序,这个时候最为头疼的就是不能进行单步跟踪(当然是在你不知道如何解决的情况下^_^),本文根据一个实例来讲述如何来解决这个问题。首先来看我们的程序,包含两个文件: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 [n]) 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的起始地址,我们就很容易以这种方法来做了
$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 [n]) 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的起始地址,我们就很容易以这种方法来做了