如何調試包含共享庫代碼的程序

相信有不少的同志調試過包含共享庫代碼的程序,這個時候最爲頭疼的就是不能進行單步跟蹤(當然是在你不知道如何解決的情況下^_^),本文根據一個實例來講述如何來解決這個問題。首先來看我們的程序,包含兩個文件: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的起始地址,我們就很容易以這種方法來做了。

應該還有其他的方法吧,如果你知道,請告訴我 [email protected],多謝!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章