Loongson1B板上GDB調試段錯誤方法

文檔簡介

項目有出現段錯誤BUGS,經過Step by step探究段錯誤原因,雖然並沒有從根本上解決QT4段錯誤的問題,但是總結出了一種比較有效的跟蹤段錯誤的方法,本文檔的目的在於介紹利用gdb工具遠程調試Loongson 1B板上段錯誤的方法,共享出來希望對大家能夠有所幫助。

文檔總體來說分爲四個部分:

A.      段錯誤的產生

B.      段錯誤調試方案

C.      編譯相關工具

D.      調試跟蹤過程

 

1. 段錯誤的產生

段錯誤產生的原因是訪問的內存超出了系統給這個程序所設定的內存空間,一旦一個程序發生了越界訪問,cpu就會產生相應的異常保護,於是Segmentation Fault就出現了,下面列舉了出現段錯誤的幾個例子:

A.      訪問不存在的內存地址

#include<stdio.h>

#include<stdlib.h>

void main()

{

        int *ptr = NULL; 

        *ptr = 0; //段錯誤位置

}


B.      訪問系統保護的內存地址

#include<stdio.h>

#include<stdlib.h>

void main()

{

        int *ptr = (int *)0;

        *ptr = 100; ;//段錯誤位置

}


C.      訪問只讀的內存地址

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

void main()

{

        char *ptr = "test";

        strcpy(ptr, "TEST");//段錯誤位置

}


D.      棧溢出

#include<stdio.h>

#include<stdlib.h>

void main()

{

        main();//段錯誤位置

}

 

2. 段錯誤調試方案

2.1 常用方法

一般情況我們都在程序中會打印一些調試語句以方便定位及分析程序的執行情況,可以使用條件編譯指令#ifdef DEBUG和#endif把printf、perror、qDebug(QT)等函數包含起來。這樣在程序編譯時,如果加上-DDEBUG參數就能查看調試信息;否則不加該參數就不會顯示調試信息。這樣通過打印調試信息可以定位到程序代碼中出現段錯誤大概位置。

但是有時候一個相對龐大的程序,這種做法顯然效率較低,這裏我們採用一個常用的工具來跟蹤段錯誤:gdb調試工具。

2.2 GDB調試

GNU工具鏈中的GDB可以讀linux開發的應用程序進行調試,它能夠控制程序的執行、查看和改變程序的變量、分析程序的內在結構、研究崩潰程序的核心文件等等。在很多情況下,用戶需要對一個應用程序進行反覆調試,特別是複雜的程序,都可以採用GDB方法調試,但是由於嵌入式系統資源有限,一般不能直接在目標系統上進行調試,通常採用gdb+gdbserver的方式進行調試。gdbserver在目標系統中運行,而gdb則在linux服務器上運行。

除了採用gdb+gdbserver的調試方法,我們還可以利用系統段錯誤會觸發SIGSEGV信號,通過man 7 signal,可以看到SIGSEGV默認的handler會打印段錯誤出錯信息,併產生core文件,由此我們可以藉助於程序異常退出時生成的core文件中的調試信息,使用gdb工具來調試程序中的段錯誤。

下邊我們會介紹如何交叉編譯LOONGSON-MIPS平臺的gdb調試工具。

 

3. 編譯GDB調試工具

一般Linux發行版中都有一個可以運行的gdb,但由於我們需要調試的是嵌入式開發板上的應用程序,所以不能直接使用該發行版中的gdb來做遠程調試,而要獲取gdb的源代碼包,針對LOONGSON-MIPS平臺作一個簡單配置,重新交叉編譯得到相應gdb調試工具。

3.1 編譯環境

操作系統:ubuntu11.04

編譯工具鏈:Gcc-cross-3.4.6-2f

3.2 編譯過程

gdb的源代碼包可以在公司服務器中的軟件文件夾中找到gdb-7.3版本,也直接到GNU的網站上下載:

http://www.gnu.org/software/gdb/download/

下載了gdb-7.3版本,然後解壓到任意目錄,例如:/home/loongson。

進入/home/loongson目錄,配置編譯步驟如下:

#cd gdb-7.3

 

#./configure --target=mipsel-linux --prefix=/opt/mipsel-linux-gdb -v

 

#make

 

#make install

 

#export PATH=$PATH: /opt/mipsel-linux-gdb

 

進入gdbserver目錄(在gdb-7.3/gdb/gdbserver):

 

#./configure --target=mipsel-linux --host=mipsel-linux

 

#make CC=/home/cpu/gcc-3.4.6-2f/bin/mipsel-linux-gcc

 

(這一步要指定mipsel-linux-gcc的位置,具體的問題根據自己系統中的交叉編譯工具鏈修改這個命令行)

編譯的過程可能會遇到一下幾個錯誤:

hostio.c:67: 錯誤: `PATH_MAX' undeclared (first use in this function)

 

thread-db.c:704: 錯誤: `PATH_MAX' undeclared (first use in this function)

 

需要在hostio.c和thread-db.c文件中添加一個宏定義

#define PATH_MAX 1024 就能解決問題。

 

gdbreplay.c: In function `main':

gdbreplay.c:372: 警告: 'fromgdb' might be used uninitialized in this function

 

解決方法爲

#vi gdbreplay.c +372

然後將int fromgdb;修改成int fromgdb=0;即可解決問題

 

編譯完成後在本文件夾可以看到生成gdbserver可執行二進制文件,使用

#mipsel-linux-readelf  -d  gdbserver

查看其需要的動態鏈接庫,通過nfs或者tftp將gdbserver及其所需要的庫移植目標板,下面就可以用gdb+gdbserver調試我們開發板上的程序了。

 

4. GDB調試過程

Gdb與gdbserver之間通訊是通過網絡來實現遠程調試,所以我們需要根據目標板與linxu服務器端的IP地址來建立連接。查詢到目標板IP:192.168.20.210, linux服務器IP:192.168.20.105。

PS.命令行爲:[LOONSON@Loongson-gz:/]#則表示在目標板上執行;#表示在PC端執行。

 4.1 編譯測試程序

下邊我們編寫一個有段錯誤的小程序來測試一下過程,程序如下:

  1 #include<stdio.h>

  2 #include<stdlib.h>

  3 

  4 dummy_function (void)

  5 {

  6         unsigned char *ptr = 0x00;

  7         *ptr = 0x00;

  8 }

  9 

 10 int main (void)

 11 {

 12         dummy_function ();

 13         return 0;

 14 }


交叉編譯該程序的時候需注意gcc要加"-g -rdynamic"參數編譯,選項-g是以便後面可以對編譯的程序進行GDB調試選項,而-rdynamic 用來通知鏈接器將所有符號添加到動態符號表中。

編譯該測試小程序:

# mipsel-linux-gcc -g -rdynamic -o test  test.c

將test可執行文件及其動態庫移植到目標板子上,然後開始使用gdb調試該特使程序。

4.2 建立遠程調試網絡連接

要進行gdb調試,首先要在目標系統上啓動gdbserver服務。在gdbserver所在目錄下輸入命令:

在目標板上執行:

[LOONSON@Loongson-gz:/]#gdbserver  192.168.20.105:15000 /test

表示目標板上的gdbserver接收來自linux服務器192.168.20.105的調試指令,gdbserver監聽15000 端口,然後啓動test程序;

出現提示:

Process /test created; pid = 300

Listening on port 15000

 

(然後轉到linux服務器上)

 

進入測試程序目錄中執行一下命令:

# /opt/mipsel-linux-gdb/bin/mipsel-linux-gdb  ./test  

命令行輸入後,出現如下提示:

GNU gdb (GDB) 7.3

Copyright (C) 2011 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 "--host=i686-pc-linux-gnu --target=mipsel-linux".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from /home/ethan/Desktop/test/test...done.

(gdb)

然後在gdb中接着執行:

(gdb)target remote 192.168.20.210:15000

裏邊的192.168.20.210是目標板上的IP。

然後linux服務器端出現一下提示:

(gdb) target remote 192.168.20.210:15000

Remote debugging using 192.168.20.210:15000

warning: Unable to find dynamic linker breakpoint function.

GDB will be unable to debug shared library initializers

and track explicitly loaded dynamic code.

0x2aaa87b0 in ?? ()

轉到目標板上,也同時出現以下提示:

[LOONSON@Loongson-gz:/]#gdbserver  192.168.20.105:15000 /test

Process /test created; pid = 300

Listening on port 15000

Remote debugging from host 192.168.20.105

這個時候linux服務器的gdb與目標板的gdbserver之間就已經建立連接了,只要我們在linux服務器端發送相關的調試指令,目標板的gdbserver就會接收到相應指令執行相應動作,同時將調試信息反饋會linxu服務器端。

4.3 調試程序

現在調試的是我們編寫的測試程序,在linux服務器端gdb中執行一下調試指令可以得到相關調試信息如下:(gdb相關調試指令可輸入helo all查詢)

(gdb) l                       //list :顯示程序中的代碼

3  

4   dummy_function (void)

5   {

6           unsigned char *ptr = 0x00;

7           *ptr = 0x00;

8   }

9  

10  int main (void)

11  {

12     dummy_function ();

(gdb)                          //同上一個命令

13          return 0;

14  }

(gdb) b 6                      //break :在程序中設置斷點

Breakpoint 1 at 0x400918: file test.c, line 6.

(gdb) c                        //continue :使程序繼續運行

Continuing.

warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libgcc_s.so.1.

Use the "info sharedlibrary" command to see the complete listing.

Do you need "set solib-search-path" or "set sysroot"?

Breakpoint 1, dummy_function () at test.c:6

6           unsigned char *ptr = 0x00;

(gdb) s                      //step :單步執行語句

7           *ptr = 0x00;

(gdb) s                      // step :單步執行語句

Program received signal SIGSEGV, Segmentation fault.

0x00400924 in dummy_function () at test.c:7

7           *ptr = 0x00;

(gdb) s                     // step :單步執行語句

Program terminated with signal SIGSEGV, Segmentation fault.

The program no longer exists.

(gdb) s                     // step :單步執行語句

The program is not being run.

(gdb)

到這裏就很清楚的能看到程序出現Segmentation fault,而且提示段錯誤出現的位置爲dummy_function ()函數中的*ptr = 0x00;在.c實現文件中的位置爲第七行。

我們回過頭來分析這個位置發生段錯誤的原因:

6         unsigned char *ptr = 0x00;

7         *ptr = 0x00;

可以很容易看出其是在操作內存爲0的內存區域,這個內存區域通常是系統不可訪問的禁區,所以就出現段錯誤了。

繼續執行where指令(顯示當前程序Call stack中的函數調用順序,或者說是frame的順序。命令where和info stack和bt是一樣的。):

(gdb) where

#0  0x00400924 in dummy_function () at test.c:7

#1  0x0040096c in main () at test.c:12

(gdb) frame 1                    //frame :查看幀(#開頭的行稱爲幀)

#1  0x0040096c in main () at test.c:12

12     dummy_function ();

(gdb) frame 0                  

#0  0x00400924 in dummy_function () at test.c:7

7           *ptr = 0x00;

可以得到test程序出錯前函數調用的寄存器地址,可以看出程序的函數調用關係爲:main()->dummy_function (),同時我們可以通過反彙編test二進制文件,定位0x00400924:

#mipsel-linux-objdump  -d  test >test.S

#vim test.S

00400900 <dummy_function>:

  400900:       3c1c0005        lui     gp,0x5

  400904:       279c82f0        addiu   gp,gp,-32016

  400908:       0399e021        addu    gp,gp,t9

  40090c:       27bdffe8        addiu   sp,sp,-24

  400910:       afbe0010        sw      s8,16(sp)

  400914:       03a0f021        move    s8,sp

  400918:       afc00008        sw      zero,8(s8)

  40091c:       8fc20008        lw      v0,8(s8)

  400920:       00000000        nop

  400924:       a0400000        sb      zero,0(v0)

  400928:       03c0e821        move    sp,s8

  40092c:       8fbe0010        lw      s8,16(sp)

  400930:       27bd0018        addiu   sp,sp,24

  400934:       03e00008        jr      ra 

  400938:       00000000        nop

sb指令:把一個字節的數據從寄存器存儲到存儲器中——SB R1, 0(R2)

這裏是將0保存到寄存器v0地址指向的內存空間,再看回上邊兩句語句,將zero賦給v0,也就是說寄存器v0的值是0,那麼sb指令試圖在0地址操作,當然就出錯了。

通過調試信息,我們發現程序還收到SIGSEGV信號,然後打印再出Segmentation fault的提示信息。

由於在linux系統內,內核收到SIGSEGV信號默認的handler的動作是打印”段錯誤"的出錯信息,而且當程序中出現內存操作錯誤時,會發生崩潰併產生核心文件(core文件)。這樣就能使用GDB可以對產生的核心文件進行分析,找出程序是在什麼時候崩潰的和在崩潰之前程序都做了些什麼。那麼我們退出gdb去看一下系統是否有生成這樣的core文件,結果是當前目錄並沒有發現core文件生成,原因是系統默認是限制生成文件。

所以我們要使用這種方式,首先要啓動linux內核提供核心轉儲(core dump)機制:

在目標板命令行執行:

[LOONSON@Loongson-gz:/]#ulimit

[LOONSON@Loongson-gz:/]#ulimit -c

0

[LOONSON@Loongson-gz:/]#ulimit -c 10000

[LOONSON@Loongson-gz:/]#ulimit -c

10000

這樣就將core文件的生成大小限制在10000KB以內,然後我們接着在目標板上直接運行測試程序

[LOONSON@Loongson-gz:/]#./test

Segmentation fault (core dumped)

同時發現在程序同一目錄下,生成了一個文件名爲 core的文件,即核心文件。

將core文件下載到PC端,然後使用gdb調試提示如下:

# /opt/mipsel-linux-gdb/bin/mipsel-linux-gdb  ./test /home/ethan/tftpboot/core

GNU gdb (GDB) 7.3

Copyright (C) 2011 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 "--host=i686-pc-linux-gnu --target=mipsel-linux".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from /home/ethan/Desktop/test/test...done.

[New LWP 301]

 

warning: Could not load shared library symbols for 3 libraries, e.g. /lib/libgcc_s.so.1.

Use the "info sharedlibrary" command to see the complete listing.

Do you need "set solib-search-path" or "set sysroot"?

Core was generated by `./test'.

Program terminated with signal 11, Segmentation fault.

#0  0x00400924 in dummy_function () at test.c:7

7           *ptr = 0x00;

(gdb) where

#0  0x00400924 in dummy_function () at test.c:7

#1  0x0040096c in main () at test.c:12

(gdb)

這樣打印出來的信息跟使用gdb+gdbserver遠程調試打印的信息是一致的,這樣方法也是一種調試程序段錯誤等BUGS的有效的方法。

當然,這個僅僅是定位跟蹤到段錯誤,並且能夠或許到寄存器地址相關的調試信息,至於如何分析段錯誤產生的原因,還得結合具體系統現場及程序調用來分析原因了。

另外gdb還有很多命令能夠巧妙的結合使用,有需要的可以查新相關gdb命令使用技巧。

這裏同時介紹幾個常用的調試工具:

1.    strace(直接用於目標板)

strace 命令是一種強大的工具, 能夠顯示任何由用戶空間程式發出的系統調用。 strace 顯示這些調用的參數並返回符號形式的值。 strace 從內核接收信息, 而且無需以任何特別的方式來構建內核。 strace 的每一行輸出包括系統調用名稱, 然後是參數和返回值。

2. Valgrind(在PC端檢測程序中的內存管理問題)

Valgrind是一個GPL的軟件,用於Linux程序的內存調試和代碼剖析。你可以在它的環境中運行你的程序來監視內存的使用情況,比如C 語言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包可以自動的檢測許多內存管理和線程的bug,避免花費太多的時間在bug尋找上。

2.    Memwatch (可以通過添加到程序代碼工程裏邊使用)

Memwatch能檢測出未釋放的內存、同一段內存被釋放多次、位址存取錯誤及不當使用未分配之內存區域,但是查閱一些資料發現其在c++裏不是很好用,但是在c是絕對可用的,而且跨平臺使用起來非常方便。

以上幾個工具都能很容易在網上查到使用方法,這裏就不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章