對于GDB這里就不作介紹了,隨便找個搜索引擎一搜GDB,介紹就已經是很詳細了,這篇文章主要是來談怎么使用GDB來調試一個運行著的程序,或者說怎么調試一個進程,似乎標題有些拗口,其次也會對fork()分離出現的多子進程的調試加以說明。
下面是一段測試代碼。
test.c
#include < stdio.h >
#include < unistd.h >
?
staticvoid PrintMessage(int i);staticvoid GoToSleep(void);
?
?
int main(void){int i =100000;
?
while(1){
PrintMessage( i );
GoToSleep();
i -=1;}
?
return0;}
?
?
?
void PrintMessage(int i){char buf[1024];
sprintf(buf,"%d bottles of beer on the wall.\n", i);printf("%s",buf);
?
}
?
?
?
staticvoid GoToSleep(void){
sleep(3);}
接下來是編譯時使用的Makefile文件.
TARGET = test
SRC = $(TARGET).c
OBJ = $(TARGET).o
CC = gcc
CFLAGS = -g3-W-Wall-std=c99
?
$(TARGET): $(OBJS)
?
?
.PHONY: clean
?
clean:
$(RM) $(TARGET) $(OBJS)
此程序是一個服務程序,程序一旦啟動,將作為一個進程永駐內存,可以通過
~@hqlong ps-ef|grep"test"
來查看該進程的信息。
此程序主要實現每3秒鐘向墻上打印一瓶啤酒。對于這樣的一個啟動就作為一個進程進駐內存的程序應該怎么來進行調試呢?接下來的事情就是要來回來這個問題,
通過make來對源文件進行編譯。
這里會在當前目錄下產生一個test的可執行文件。
在對程序進行正式調試之前來回憶一個使用GDB調試一個非服務程序的步驟。假設test這個可執行文件是一個非服務程序,那么一般是通過如下幾步方式來進行調試的。
hqlong@ubuntu:/tmp$ gdbtest
GNU gdb6.8-debian
Copyright (C)2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http ://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty"for details.
This GDB was configured as"i486-linux-gnu"...
(gdb) b 1
Breakpoint 1 at 0x80483f4: file beer-process.c, line 1.
(gdb) r
Starting program: /tmp/test
?
Breakpoint 1, main () at beer-process.c:9
warning: Source file is more recent than executable.
9{(gdb) n
main () at beer-process.c:1010 int i = 100000;
(gdb) q
The program is running. Exit anyway? (y or n) y
</http>
首先是通過gdb test來調試程序。然后使用b(break) 1在第一行設置斷點,然后使用r(run) 來運行程序,最后使用n來單步運行程序,如果想要查看運行中某變量的值,可能通過p(print)來打印。如查看i的值,就可以通過p i。最后使用q(quit)來退出程序。
由于服務程序一旦啟動,就以進程的方式進駐內存,不退出,所以和非服務程序的調試方式有一些區別。
服務一旦啟動后,系統會分配一個pid,然后使用gdb綁定上這個pid,最后就可以通過通用方式進行調試了。
綁定進程的方式有下幾種。
hqlong@ubuntu:/tmp$ ./test&100000 bottles of beer on the wall.
[1]25292
方式一
通過–pid參數來綁定指定的進程程序。
方式二
通過程序和進程號來綁定。
方式二
先啟動gdb后,通過attach來綁定pid
~@hqlong gdbgdb) attach 25552
將pid和gdb綁定后,就可以來對每一段代碼進行調試。
下面是對上面例子的完整調試過程。
1. 啟動進程
hqlong@ubuntu:/tmp$ ./test&[1]25615
hqlong@ubuntu:/tmp$ 100000 bottles of beer on the wall.
99999 bottles of beer on the wall.
99998 bottles of beer on the wall.
可以看見,啟動test后,系統所分配的pid(進程號)為25615,然后每隔3秒鐘就打印出一條信息。
2.使用gdb來綁定test進程。
這里需要重新啟動一個新的shell來進行調試,也就是新開一個窗口,然后使用
hqlong@ubuntu:/tmp$ gdb--pid25615
GNU gdb6.8-debian
Copyright (C)2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http ://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty"for details.
This GDB was configured as"i486-linux-gnu".
Attaching to process 25615
Reading symbols from /tmp/test...done.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for/lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for/lib/ld-linux.so.2
0xb7ffb430 in __kernel_vsyscall ()(gdb)</http>
來將gdb與已啟動的test進行綁定,從而進入調試狀態,這里會發現,當進入gdb調試狀態后,先前啟動程序的窗口就不再每隔3秒鐘打印信息了,而是將控制權將給了gdb.
3.使用bt來顯示當前程序的函數調用棧結構(也就是函數的調用順序)。
(
gdb
) bt
#0 0xb7ffb430 in __kernel_vsyscall ()#1 0xb7f23780 in nanosleep () from /lib/tls/i686/cmov/libc.so.6#2 0xb7f235be in sleep () from /lib/tls/i686/cmov/libc.so.6#3 0x0804851e in GoToSleep () at beer-process.c:35#4 0x080484ac in main () at beer-process.c:15(gdb)
可知函數的調用過程為:main調用GoToSleep,GoToSleep調用sleep等。
使用frame來選擇程序的調試起點,也可以使用break來選擇指定的行或者函數來作為斷點。
(
gdb
) frame 4#4 0x080484ac in main () at beer-process.c:1515 GoToSleep();
(gdb)
這里我們選擇最底層的棧,也就是整個程序的入口main函數來作為起點。
4. 調試跟蹤過程
使用n(next)來一步一步的跟蹤。
(
gdb
) n
Single stepping untilexit from functionsleep,
which has no line number information.
GoToSleep () at beer-process.c:3636}(gdb)
main () at beer-process.c:1616 i -= 1;
(gdb)14 PrintMessage( i );
(gdb)
這里有個技巧,如果要重復執行上一次執行的操作,可以直接回車。
通過上面的調試,發現程序運行到了14行,也就是PrintMessage函數處,如果這個時候我們繼續n(next),程序就直接跳過函數,運行到函數的下一行,但如果想進入函數內部去調試,可以使用s(step),來進入函數體內進行調試。
進入函數體
(
gdb
)
14 PrintMessage( i );
(gdb) s
PrintMessage (i=99897) at beer-process.c:2525{(gdb) n
27 sprintf(buf,"%d bottles of beer on the wall.\n", i);
(gdb) n
28printf("%s",buf);
(gdb)
以上是進入PrintMessage體內,然后繼續使用n(next)來一步一步的運行。進行到了28行,發現有兩個變量,一個為i,另一個為buf,可以通過p(print)來打印該變量的值。
(
gdb
) p i
$1 = 99897(gdb) p buf
$2 = "99897 bottles of beer on the wall.\n\000\000R\000?0u??????$u???\227\001?\000\000\000\000p???\000\000\000\000\000\000\000\000\001\000\000\000??DB?x??8???\000\000\000\000?\217\001?p\226\001?0u??$u???\226\000?\001\000\000\000\000\000\000\000?v???\222\001?\205???", '\0'<repeats 12times>, "?\226\000??\217\001?\000\000\000\000\000u???t???\020\001?\b\000\000\000\f\000\000\000\000????v??b\221\000?\000???\020???\f\000\000\000?\217"...
(gdb)</repeats>
發現通過p i 來打印i的值,結果是正確的,但通過p buf來打印buf 的值,卻顯示了很多不可讀的亂碼。這里因為buf代表這個變量在內存中的首地址,所以p不知道變量在什么時候結束,而i則不同,因為i是整型,在內存中一般占4個字節,所以p直接從首地址向后數4位就行了。所以這里打印字符指針或者數組時,需要指定一個長度。語法為 p *buf@len. 這里的len可以通過strlen(buf)來取得。
(
gdb
) p *buf@strlen(buf)
$3 = "99897 bottles of beer on the wall.\n"(gdb)
這一下子就對了。
接下來繼續使用n(next)來單步運行。
(
gdb
)
28
printf
(
"%s",buf);
發現運行到這一步時,啟動程序的那個窗口打印出了一條信息。
99897 bottles of beer on the wall.
這說明,程序正好運行到了打印字符的地方。
如果這個時候想退出函數,直接返回,可能通過 finish.
(
gdb
) finish
Run till exit from #0 PrintMessage (i=99895) at beer-process.c:28
main () at beer-process.c:1515 GoToSleep();
(gdb)
退出PrintMessage函數,接著運行下一行代碼GoToSleep.
如果調試完畢,或能通過q(quit)來退出GDB。
(
gdb
) q
The program is running. Quit anyway (and detach it)? (y or n) y
Detaching from program: /tmp/test, process 25615
以上是調試一個進程的過程。
下面附帶說明一下怎么調試一個需要通過fork()分離進程的程序。下面是一段一個Web服務器的在接受請求時的代碼片斷。
for
(
;;
)
{
if
(
(connfd = accept(listenfd, (struct sockaddr *)&clientaddr,
&clientaddrlen)) == -1){perror("http server: accept error");
continue;
}if(fork() == 0)
accept_request(connfd);
else close(connfd);
?
int statloc;
waitpid(-1, &statloc, WNOHANG);
}
當單步運行到if(fork() == 0)這一步時,那怕是分離進程成功,也不會運行accept_request(connfd);而是直接跳到跳過,運行下面的代碼。在調試這樣的程序里,需要在進入gdb控制臺時,需要將follow-fork-mode的值設成child.
(
gdb
)
set follow-fork-mode child
本文主要是體驗一下使用GDB的調試過程,對一些指令和俗語沒有作過多的解釋,如果有不明白可直接留言,或者參考相關資料。
參數資料
http://www.gnu.org/software/gdb/
http://dirac.org/linux/gdb/06-Debugging_A_Running_Process.php
結束