1. 程式人生 > >嵌入式 使用gdb除錯段錯誤(segment fault)

嵌入式 使用gdb除錯段錯誤(segment fault)

我們打算使用gdb去解決為什麼下面的程式(檔案為segfault.c)引起了段錯誤的問題。下面的這段程式是從使用者那裡讀入一行文字字串然後顯示在螢幕上。然而,如下當前的程式並不會如期執行...

[cpp] view plaincopyprint?
  1. <span style="font-size:18px;">#include <stdio.h>  
  2. #include <stdlib.h>
  3. int main(int argc, char **argv)  
  4. {  
  5.    char *buf;  
  6.    buf = malloc(1<<31);  
  7.    fgets(buf, 1024, stdin);  
  8.    printf("%s\n", buf);  
  9.    return 1;  
  10. }</span>  

 第一步是使用帶有除錯標誌(debugging flags)的方式編譯這段程式碼,如下:

~# gcc -g segfault.c

然後執行:

~# a.out

Hello World!

Segmentation fault

這並不是我們所期待的。是時候啟動強大的gdb了。

~# gdb a.out
GNU gdb 5.0
Copyright 2000 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 the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) 

我們直接執行就來看看到底發生了什麼:

(gdb) run
Starting program: /home/dgawd/cpsc/363/a.out 
test string

Program received signal SIGSEGV, Segmentation fault.
0x4007fc13 in _IO_getline_info () from /lib/libc.so.6

我們收到了來自作業系統的SIGSEGV訊號。這就意味著我們試圖去訪問一段非法的記憶體,讓我們試試backtrace(= bt)命令:

(gdb) backtrace
#0  0x4007fc13 in _IO_getline_info () from /lib/libc.so.6
#1  0x4007fb6c in _IO_getline () from /lib/libc.so.6
#2  0x4007ef51 in fgets () from /lib/libc.so.6
#3  0x80484b2 in main (argc=1, argv=0xbffffaf4) at segfault.c:10
#4  0x40037f5c in __libc_start_main () from /lib/libc.so.6

這裡我們只關心我們自己的程式碼,因此我們就切換到3號堆疊幀(stack frame3)來看看程式在哪裡崩潰的:

(gdb) frame 3
#3  0x80484b2 in main (argc=1, argv=0xbffffaf4) at segfault.c:10
10        fgets(buf, 1024, stdin)

哦,原來是呼叫fgets引起的崩潰。一般的,我們都假設庫函式比如fgets都可以正確地工作(如果不是這樣的話,我們的麻煩就大了)。因此這個問題的原因就一定是其中我們的一個引數的問題。你也許不知道‘stdin’是一個全域性的變數,它是被stdio 庫建立的。因此我們假定這個引數是正確的。那麼剩下的就只能是‘buf’了,然後檢視buf當前的值:

(gdb) print buf
$1 = 0x0

buf的值是0x0,也就是NULL指標。這並不是我們鎖期待的 —— buf應該指向第8行程式碼分配到的記憶體。因此我們需要返回到第8行並看看在哪裡發生了什麼。首先kill掉我們程式當前執行的呼叫:

(gdb) kill
Kill the program being debugged? (y or n) y

(注意:不用使用quit直接退出gdb,這樣比較麻煩。直接kill掉當前的程式呼叫即可

然後在第8行設定一個斷點:

(gdb) break segfault.c:8
Breakpoint 1 at 0x8048486: file segfault.c, line 8.

再次執行程式:

(gdb) run
Starting program: /home/dgawd/cpsc/363/a.out 

Breakpoint 1, main (argc=1, argv=0xbffffaf4) at segfault.c:8
8         buf = malloc(1<<31);

我們檢查malloc呼叫前後buf值的變化。初始化buf以前,其值應該是一個隨機雜亂值(garbage),就像這裡的:

(gdb) print buf
$2 = 0xbffffaa8 "鰓?\177\[email protected]`\[email protected]\001"

我們step over(單步執行)malloc呼叫然後再次檢查buf的值:

(gdb) next
10        fgets(buf, 1024, stdin);
(gdb) print buf
$3 = 0x0

可見呼叫了malloc之後,buf是NULL。如果你檢視malloc的手冊頁(man page),你就會發現malloc在不能分配夠所需的記憶體的時候就會返回NULL。因此確定是我們的malloc失敗了。讓我們返回到程式碼再次看看:

7 :   buf = malloc(1<<31);

哦,表示式1<<31(整型1左移31次,原文錯寫為右移)是429497295, 或4GB (gigabytes).很少有機器會有這樣的記憶體——大多數只有256MB(顯然這篇文章有年頭了,都什麼年代了,這點記憶體作業系統估計啟動一半就掛了)。因此malloc必然會失敗。此外,在fgets中我們只讀入1024位元組。所有的額外空間都會白白浪費掉,儘管我們可以分配到。這裡我們將1<<31改為1024(或者1<<9),這樣程式就會按照我們的期望運行了:

~# a.out
Hello World!
Hello World!

這樣你就可以知道怎樣使用gdb來除錯段錯誤了,這是非常有用的。