Linux學習之gdb調試

一.gdb常用命令:

命令 描述
backtrace(或bt) 查看各級函數調用及參數
finish 連續運行到當前函數返回爲止,然後停下來等待命令
frame(或f) 幀編號 選擇棧幀
info(或i) locals 查看當前棧幀局部變量的值
list(或l) 列出源代碼,接着上次的位置往下列,每次列10行
list 行號 列出從第幾行開始的源代碼
list 函數名 列出某個函數的源代碼
next(或n) 執行下一行語句
print(或p) 打印表達式的值,通過表達式可以修改變量的值或者調用函數
quit(或q) 退出gdb調試環境
set var 修改變量的值
start 開始執行程序,停在main函數第一行語句前面等待命令
step(或s) 執行下一行語句,如果有函數調用則進入到函數中

注意常用命令的簡寫形式,實際使用中,我們常用的就是簡寫形式.


二.gdb學習小例:

#include <stdio.h>

int add_range(int low, int high)
{
	int i, sum;
	for (i = low; i <= high; i++)
		sum = sum + i;
	return sum;
}

int main(void)
{
	int result[100];
	result[0] = add_range(1, 10);
	result[1] = add_range(1, 100);
	printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
	return 0;
}

add_range函數從low加到high,在main函數中首先從1加到10,把結果保存下來,然後從1加到100,再把結果保存下來,最後打印的兩個結果是:

result[0]=55
result[1]=5105
第一個結果正確,第二個結果顯然不正確。我相信基礎好不錯的小夥伴能看出來端倪。
下面着重來看看使用gdb工具來調試的過程:

在編譯時要加上-g選項,生成的可執行文件才能用gdb進行源碼級調試:

 lpq@lpq-OptiPlex-9020:~/Linux_pro/code_resource/gdb_test$ gdb main
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main...done.
(gdb) 
注:-g選項的作用是在可執行文件中加入源代碼的信息,比如可執行文件中第幾條機器指令對應源代碼的第幾行,但並不是把整個源文件嵌入到可執行文件中,
所以在調試時必須保證gdb能找到源文件。gdb提供一個類似Shell的命令行環境,上面的(gdb)就是提示符,在這個提示符下輸入help可以查看命令的類別:
(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.

下面是真正的調試過程了:

現在試試用list命令從第一行開始列出源代碼:

(gdb) list 1 1    #include <iostream> 2    using namespace std; 3     4    int add_range(int low, int high) 5    { 6        int i, sum; 7        for (i = low; i <= high; i++) 8            sum = sum + i; 9        return sum; 10    }

一次只列10行,如果要從第11行開始繼續列源代碼可以輸入
(gdb) list

也可以什麼都不輸直接敲回車,gdb提供了一個很方便的功能,在提示符下直接敲回車表示重複上一條命令。

(gdb) (直接回車)
11    
12    int main(void)
13    {
14        int result[2];
15        result[0] = result[1] = 0;
16        result[0] = add_range(1, 10);
17        result[1] = add_range(1, 100);
18        cout << "result[0] = " << result[0] << endl;
19        cout << "result[1] = " << result[1] << endl;
20        return 0;

gdb的很多常用命令有簡寫形式,例如list命令可以寫成l,要列一個函數的源代碼也可以用函數名做參數:

(gdb) l add_range
1	#include <stdio.h>
2	
3	int add_range(int low, int high)
4	{
5		int i, sum;
6		for (i = low; i <= high; i++)
7			sum = sum + i;
8		return sum;
9	}
10

現在退出gdb的環境:

(gdb) quit

我們做一個實驗,把源代碼改名或移到別處再用gdb調試,這樣就列不出源代碼了:

$ mv main.c mian.c
$ gdb main
...
(gdb) l
5	main.c: No such file or directory.
	in main.c

可見gcc-g選項並不是把源代碼嵌入到可執行文件中的,在調試時也需要源文件。現在把源代碼恢復原樣,我們繼續調試。首先用start命令開始執行程序:

$ gdb main
...
(gdb) start
Breakpoint 1 at 0x80483ad: file main.c, line 14.
Starting program: /home/akaedu/main 
main () at main.c:14
14		result[0] = add_range(1, 10);
(gdb)
接着調試:
(gdb) start
Temporary breakpoint 1 at 0x4008cf: file main.cpp, line 15.
Starting program: /home/lpq/Linux_pro/code_resource/gdb_test/main

Temporary breakpoint 1, main () at main.cpp:15
15        result[0] = result[1] = 0;
(gdb)

gdb停在main函數中變量定義之後的第一條語句處等待我們發命令,gdb列出的這條語句是即將執行的下一條語句。我們可以用next命令(簡寫爲n)控制這些語句一條一條地執行:

(gdb) n
15		result[1] = add_range(1, 100);
(gdb) (直接回車)
16		printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
(gdb) (直接回車)
result[0]=55
result[1]=5105
17		return 0;

n命令依次執行兩行賦值語句和一行打印語句,在執行打印語句時結果立刻打出來了,然後停在return語句之前等待我們發命令。雖然我們完全控制了程序的執行,

但仍然看不出哪裏錯了,因爲錯誤不在main函數中而在add_range函數中,現在用start命令重新來過,這次用step命令(簡寫爲s)鑽進add_range函數中去跟蹤執行:

15        result[0] = result[1] = 0;
(gdb) next
16        result[0] = add_range(1, 10);
(gdb)
17        result[1] = add_range(1, 100);
(gdb)
18        cout << "result[0] = " << result[0] << endl;
(gdb)
result[0] = 55
19        cout << "result[1] = " << result[1] << endl;
(gdb)
result[1] = 5105
20        return 0;
(gdb)
n命令依次執行兩行賦值語句和一行打印語句,在執行打印語句時結果立刻打出來了,然後停在return語句之前等待我們發命令。雖然我們完全控制了程序的執行,

但仍然看不出哪裏錯了,因爲錯誤不在main函數中而在add_range函數中,現在用start命令重新來過,這次用step命令(簡寫爲s)鑽進add_range函數中去跟蹤執行:

這就是類似一般的IDE中的step into了

(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 2 at 0x4008cf: file main.cpp, line 15.
Starting program: /home/lpq/Linux_pro/code_resource/gdb_test/main

Temporary breakpoint 2, main () at main.cpp:15
15        result[0] = result[1] = 0;
(gdb)
(gdb)
(gdb) n
16        result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at main.cpp:7
7        for (i = low; i <= high; i++)
(gdb)
這次停在了add_range函數中變量定義之後的第一條語句處。在函數中有幾種查看狀態的辦法,backtrace命令(簡寫爲bt)可以查看函數調用的棧幀:
(gdb) backtrace
#0  add_range (low=1, high=10) at main.cpp:7
#1  0x00000000004008eb in main () at main.cpp:16
(gdb)

可見當前的add_range函數是被main函數調用的,main傳進來的參數是low=1, high=10main函數的棧幀編號爲1,add_range的棧幀編號爲0。

現在可以用info命令(簡寫爲i)查看add_range函數局部變量的值

(gdb) i locals
i = 0
sum = 0

如果想查看main函數當前局部變量的值也可以做到,先用frame命令(簡寫爲f)選擇1號棧幀然後再查看局部變量:

(gdb) f 1
#1  0x080483c1 in main () at main.c:14
14		result[0] = add_range(1, 10);
(gdb) i locals 
result = {0, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, 
...
  -1208623680}

注意到result數組中有很多元素具有雜亂無章的值,我們知道未經初始化的局部變量具有不確定的值。到目前爲止一切正常。用sn往下走幾步,

然後用print命令(簡寫爲p)打印出變量sum的值:

(gdb) s
7			sum = sum + i;
(gdb) (直接回車)
6		for (i = low; i <= high; i++)
(gdb) (直接回車)
7			sum = sum + i;
(gdb) (直接回車)
6		for (i = low; i <= high; i++)
(gdb) p sum
$1 = 3

第一次循環i是1,第二次循環i是2,加起來是3,沒錯。這裏的$1表示gdb保存着這些中間結果,$後面的編號會自動增長,

在命令中可以用$1$2$3等編號代替相應的值。由於我們本來就知道第一次調用的結果是正確的,再往下跟也沒意義了,

可以用finish命令讓程序一直運行到從當前函數返回爲止:

(gdb) finish
Run till exit from #0  add_range (low=1, high=10) at main.c:6
0x080483c1 in main () at main.c:14
14		result[0] = add_range(1, 10);
Value returned is $2 = 55

返回值是55,當前正準備執行賦值操作,用s命令賦值,然後查看result數組:

(gdb) s
15		result[1] = add_range(1, 100);
(gdb) p result
$3 = {55, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, 
...
  -1208623680}
(gdb) s
17        result[1] = add_range(1, 100);
(gdb) p result
$7 = {55, 0}
(gdb)
第一個值55確實賦給了result數組的第0個元素。下面用s命令進入第二次add_range調用,進入之後首先查看參數和局部變量:
(gdb) s
add_range (low=1, high=100) at main.cpp:7
7        for (i = low; i <= high; i++)
(gdb) bt
#0  add_range (low=1, high=100) at main.cpp:7
#1  0x00000000004008fd in main () at main.cpp:17
(gdb) i locals
i = 11
sum = 55
(gdb)
重點在這,sum的值是55,不是0開始,所以出現了錯誤。
由於局部變量isum沒初始化,所以具有不確定的值,又由於兩次調用是挨着的,isum正好取了上次調用時的值了,我們已經找到錯誤原因,
可以退出gdb修改源代碼了。如果我們不想浪費這次調試機會,可以在gdb中馬上把sum的初值改爲0繼續運行,看看這一處改了之後還有沒有別的Bug:
(gdb) set var sum = 0
(gdb) finish
Run till exit from #0  add_range (low=1, high=100) at main.cpp:7
0x00000000004008fd in main () at main.cpp:17
17        result[1] = add_range(1, 100);
Value returned is $8 = 5050
(gdb) n
18        cout << "result[0] = " << result[0] << endl;
(gdb)
result[0] = 55
19        cout << "result[1] = " << result[1] << endl;
(gdb)
result[1] = 5050
20        return 0;
(gdb)
21    }
(gdb)
這樣結果就對了。










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