Linux下C程序的編輯,編譯和運行以及調試

Linux下C程序的編輯,編譯和運行以及調試linux

要使用的工具:
編輯:vim(vi)
編譯和運行:gcc
調試:gdbredis

安裝很簡單(如下是以在CentOS中安裝爲例):
shell

yum vim gcc gdb

1.使用vim編輯源文件
首先,打開終端練下手:
express

vim hello.c

(進入通常模式)
按下"i",進入編輯模式,在編輯模式下輸入:
vim

#include <stdio.h>
int main(){
    printf("Hello, World!\n");
    return 0;
}

輸入完成,按"ESC"鍵,回到通常模式,而後按下":wq",便可保存並退出vim。數組

附註:
在通常模式下,按下":%!xxd"查看hello.c的16進制形式,回到文本格式按下":%!xxd -r"。
查看hello.c的二進制形式,按下":%!xxd -b",這是hello.c保存在磁盤上的存儲狀態。
至此,在vim已完成C源文件的編輯。
關於vim的使用,直接上網搜索vim,相關的文章是至關多的;或者參考vim的聯機幫助,在命令行上鍵入"man vim"便可。less

2.編譯和運行函數

gcc命令的基本用法:
工具

gcc[options] [filenames]

其中,filenames爲文件名;options爲編譯選項
當不使用任何編譯選項編譯hello.c時,gcc將會自動編譯產生一個a.out的可執行文件:
優化

[root@localhost c]# ls
hello.c
[root@localhost c]# gcc hello.c
[root@localhost c]# ls
a.out  hello.c

執行:

[root@localhost c]# ./a.out
Hello, World!

使用-o編譯選擇,能夠爲編譯後的文件指定一個名字:

[root@localhost c]# ls
a.out  hello.c
[root@localhost c]# gcc hello.c -o hello
[root@localhost c]# ls
a.out  hello  hello.c

執行:

[root@localhost c]# ./hello
Hello, World!

注意:使用-o選項時,-o後面必須跟一個文件名,即:-o outfile。

爲了便於描述後面的選項,刪除hello和a.out可執行文件。

結合介紹gcc的編譯選項,分析hello.c的編譯和執行過程:
(1)預處理階段:使用-E選項,對輸入文件只作預處理不編譯。當使用這個選項時,預處理器的輸出被送到標準輸出而不是存儲到文件。若是想將預處理的輸出存儲到文件,可結合-o選項使用,使用以下:

[root@localhost c]# ls
hello.c
[root@localhost c]# gcc -E hello.c -o hello.i
[root@localhost c]# ls
hello.c  hello.i

使用less查看下hello.i:

[root@localhost c]# less hello.i

(2)編譯階段:使用-S選項,將C程序編譯爲彙編語言文件後中止編譯,gcc編譯產生彙編文件的默認後綴爲.s。

[root@localhost c]# ls
hello.c  hello.i
[root@localhost c]# gcc -S hello.c
[root@localhost c]# ls
hello.c  hello.i  hello.s

在gcc -S hello.c處,使用C源文件編譯,也能夠用gcc -S hello.i的預處理文件編譯,結果同樣。

使用-S編譯時,也能夠和-o結合使用指定編譯產生的彙編語言文件的名字:

[root@localhost c]# ls
hello.c  hello.i  hello.s
[root@localhost c]# gcc -S hello.i -o hello_s.s
[root@localhost c]# ls
hello.c  hello.i  hello.s  hello_s.s

可以使用less命令查看彙編代碼。

(3)彙編階段:使用-c選項,將C源文件或者彙編語言文件編譯成可重定向的目標文件(二進制形式),其默認後綴爲.o。

[root@localhost c]# ls
hello.c  hello.i  hello.s  hello_s.s
[root@localhost c]# gcc -c hello.s
[root@localhost c]# ls
hello.c  hello.i  hello.o  hello.s  hello_s.s

也能夠和-o結合使用指定編譯產生的目標文件的名字:

[root@localhost c]# gcc -c hello.s -o hello.o

因爲hello.o是二進制文件,使用less查看顯示爲亂碼;

而後使用vim hello.o打開也顯示爲亂碼,按下":%!xxd"查看其16進制形式,按下":%!xxd -r"退出 16進制查看模式,回到亂碼狀態。在退出vim時,若提示已經修改了文件,則使用":q!"強制退出。

(4)連接階段:連接器將可重定向的目標文件hello.o以及庫文件(如printf.o)執行併入操做,造成最終可執行的可執行目標文件。

[root@localhost c]# ls
hello.c  hello.i  hello.o  hello.s  hello_s.s
[root@localhost c]# gcc hello.o
[root@localhost c]# ls
a.out  hello.c  hello.i  hello.o  hello.s  hello_s.s

可以使用-o選項,指定輸出文件(便可執行目標文件)的名字:

[root@localhost c]# gcc hello.o -o hello
[root@localhost c]# ls
a.out  hello  hello.c  hello.i  hello.o  hello.s  hello_s.s

(5)執行階段

[root@localhost c]# ./a.out
Hello, World!
[root@localhost c]# ./hello
Hello, World!

由此,看出前面使用的gcc hello.c -o hello命令,將hello.c直接編譯爲可執行的目標文件,中間通過於處理器的預處理階段(源文件到預處理文件),編譯器的編譯階段(預處理文件到彙編文件),彙編器的彙編階段(彙編文件到可重定向的目標文件),連接器的連接階段(可重定向的目標文件到可執行的目標文件)。

還有其餘的選項以下:
-Idir:dir是頭文件所在的目錄
-Ldir:dir是庫文件所在的目錄

-Wall:打印全部的警告信息
-Wl,options:options是傳遞給連接器的選項


編譯優化選項:-O和-O2
-O選項告訴GCC 對源代碼進行基本優化。這些優化在大多數狀況下都會使程序執行的更快。-O2選項告訴GCC產生儘量小和儘量快的代碼。
-O2選項將使編譯的速度比使用-O時慢。但一般產生的代碼執行速度會更快。

除了-O和-O2優化選項外,還有一些低級選項用於產生更快的代碼。這些選項很是的特殊,並且最好只有當你徹底理解這些選項將會對編譯後的代碼產生什麼樣的效果時再去使用。這些選項的詳細描述,請參考GCC的聯機幫助,在命令行上鍵入"man gcc"便可。

調試選項:-g(使用詳情見第3部分)
-g選項告訴GCC產生能被GNU調試器使用的調試信息以便調試你的程序。
即:在生成的目標文件中添加調試信息,所謂調試信息就是源代碼和指令之間的對應關係,在gdb調試和objdump反彙編時要用到這些信息。

3.調試
雖然GCC提供了調試選項,可是自己不能用於調試。Linux 提供了一個名爲gdb的GNU調試程序。gdb是一個用來調試C和C++程序的調試器。它使你能在程序運行時觀察程序的內部結構和內存的使用狀況。如下是gdb所提供的一些功能:
a.它使你能監視你程序中變量的值;
b.它使你能設置斷點以使程序在指定的代碼行上中止執行;
c.它使你能一行行的執行你的代碼。

(1)啓動gdb
在命令行上鍵入"gdb"並按回車鍵就能夠運行gdb了,以下:

[root@localhost c]# gdb
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 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-redhat-linux-gnu".
For bug reporting instructions, please see:<>.
(gdb)

當啓動gdb以後,便可在命令行上輸入命令進行相關的調試操做。
也能夠如下面的方式來啓動gdb:

[root@localhost c]# gdb hello

這種方式啓動gdb,直接將指定調試的程序文件裝載到調試環境中。也就是讓gdb裝入名稱爲filename的可執行文件,從而準備調試。
爲了可以進行調試,當前調試的程序文件中必須包含調試信息。其中調試信息包含程序中的每一個變量的類型和其在可執行文件裏的地址映射以及源代碼的行號,gdb利用這些信息使源代碼和機器碼相關聯。所以在使用gcc編譯源程序的時候必須使用-g選項,以便將調試信息包含在可執行文件中。
例如:

[root@localhost c]# gcc -g hello.c -o hello

gdb還提供了其餘的啓動選項,請參考gdb的聯機幫助。在命令行上鍵入"man gdb"並回車便可。

(2)gdb基本命令
<1>單步執行和跟蹤函數調用
程序編輯以下:

#include <stdio.h>
int add_range(int low, int high){
    int i;
    int sum;
    for(i = low; i <= high; i++){
        sum = sum + i;
    }
    return sum;
}

int main(){
    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;

}

編譯和運行以下:

[root@localhost gdb_demo]# vim test1.c 
[root@localhost gdb_demo]# gcc test1.c -o test1
[root@localhost gdb_demo]# ls
test1  test1.c
[root@localhost gdb_demo]# ./test1
result[0] = 55
result[1] = 5105

以上程序的結果中,顯然第二個結果是不正確的,有基礎的人會一眼看出問題處在哪裏,呵呵,這裏只是爲了演示使用gdb調試而故意爲之。固然在開發人員最好不要太過於依賴gdb才能找到錯誤。

在編譯時加上-g選項,生成的目標文件才能用gdb進行調試:

[root@localhost gdb_demo]# gcc test1.c -g -o test1
[root@localhost gdb_demo]# gdb test1
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 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-redhat-linux-gnu".
For bug reporting instructions, please see:<>...
Reading symbols from /root/code/c/gdb_demo/test1...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.

能夠進一步查看某一個類別中有哪些命令,例如查看files類別下有哪些命令能夠用:

(gdb) help files
Specifying and examining files.
List of commands:
add-symbol-file -- Load symbols from FILE
add-symbol-file-from-memory -- Load the symbols out of memory from a dynamically loaded object file
cd -- Set working directory to DIR for debugger and program being debugged
core-file -- Use FILE as core dump for examining memory and registers
directory -- Add directory DIR to beginning of search path for source files
edit -- Edit specified file or function
exec-file -- Use FILE as program for getting contents of pure memory
file -- Use FILE as program to be debugged
forward-search -- Search for regular expression (see regex(3)) from last line listed
generate-core-file -- Save a core file with the current state of the debugged process
list -- List specified function or line
load -- Dynamically load FILE into the running program

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

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

一次只列出10行,若是要從11行開始繼續列出源代碼能夠輸入:

(gdb) list

也能夠什麼都不輸入直接敲回車,gdb提供類一個方便的功能,在提示符下直接敲回車表示用適當的參數重複上一條命令。

(gdb) (直接回車)
11 
12 int main(){
13     int result[100];
14     result[0] = add_range(1, 10);
15     result[1] = add_range(1, 100);
16     printf("result[0] = %d\nresult[1] = %d\n", result[0], result[1]);
17     return 0;
18 }

gdb的不少經常使用命令有簡寫形式,例如list命令能夠寫成l,要列出一個函數的源碼也能夠用函數名作list的參數:

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

如今退出gdb的環境(quit或簡寫形式q):

(gdb) quit

如今把源代碼更名或移動到別處,再用gdb調試目標文件,就列不出源代碼了:

[root@localhost gdb_demo]# ls
test1  test1.c
[root@localhost gdb_demo]# mv test1.c test.c
[root@localhost gdb_demo]# ls
test1  test.c
[root@localhost gdb_demo]# gdb test1
......
(gdb) l
5 test1.c: 沒有那個文件或目錄.
 in test1.c
(gdb)

可見gcc的-g選項並非把源代碼嵌入到目標文件中的,在調試目標文件時也須要源文件。

如今把源代碼恢復原樣,繼續調試。首先使用start命令開始執行程序:

[root@localhost gdb_demo]# mv test.c test1.c
[root@localhost gdb_demo]# gdb test1
......
(gdb) start
Temporary breakpoint 1 at 0x4004f8: file test1.c, line 14.
Starting program: /root/code/c/gdb_demo/test1
Temporary breakpoint 1, main () at test1.c:14
14     result[0] = add_range(1, 10);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.x86_64
(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)進入函數中去執行:

(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 3 at 0x4004f8: file test1.c, line 14.
Starting program: /root/code/c/gdb_demo/test1
Temporary breakpoint 3, main () at test1.c:14
14     result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at test1.c:6
6     for(i = low; i <= high; i++){

此次進入add_range函數中,停在函數中定義變量以後的第一條語句處。
在函數中有幾種查看狀態的辦法,backtrace(簡寫爲bt)能夠查看函數調用的棧幀:

(gdb) bt
#0  add_range (low=1, high=10) at test1.c:6
#1  0x0000000000400507 in main () at test1.c:14

可見當前的add_range函數是被main函數調用的,main函數中傳給add_range的參數是low=1, high=10。main函數的棧幀編號是1,add_range函數的棧幀編號是0。
如今可使用info命令(簡寫爲i)查看add_range局部變量的值:

(gdb) i locals
i = 0
sum = 0

若是想查看main函數中的當前局部變量的值也能夠作到的,先用frame命令(簡寫爲f)選擇1號棧幀,而後再查看main中的局部變量:

(gdb) f 1
#1  0x0000000000400507 in main () at test1.c:14
14     result[0] = add_range(1, 10);
(gdb) i locals
result = {4195073, 0, -1646196904, 50, 4195016, 0, 0, 1, 2041, 1, -1654612390, 
  50, 0, 0, -1652419360, 50, -7728, 32767, -7688, 32767, -1652420216, 50, 
  -134227048, 32767, -163754450, 0, -1654612390, 50, 0, 0, -134227048, 32767, 
  1, 0, 0, 0, 1, 50, -1652420216, 50, -7848, 32767, 750006344, 0, 6, 0, -7826, 
  32767, 0, 0, -1652419360, 50, -7808, 32767, -1645672825, 50, -7784, 32767, 0, 
  1, 0, 0, 4195073, 0, 191, 0, -7826, 32767, -7825, 32767, 1, 0, 0, 0, 
  -1645672168, 50, 0, 0, 4195680, 0, 0, 0, 4195235, 0, -7512, 32767, 4195749, 
  0, -1646199904, 50, 4195680, 0, 0, 0, 4195296, 0, -7536, 32767, 0, 0}

注意到result數組中的不少元素具備雜亂無章的值,由於未經初始化的局部變量具備不肯定的值。

到目前爲止(即已經進入第一次的函數調用的函數體內),是正常的。
繼續用s或n往下走,而後用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

注意:這裏的$1表示gdb保存着這些中間結果,$後面的編號會自動增加,在命令中能夠用$一、$二、$3等編號代替相應的值。
-----------------------------------------------------
以上的執行過程使用下面的方法可能看得更清楚(這裏的步驟不是繼續跟着上面步驟,是在另外一個終端中調試的):

(gdb) f 0
#0  add_range (low=1, high=10) at test1.c:7
7         sum = sum + i;
(gdb) i locals
i = 1
sum = 0
(gdb) s
6     for(i = low; i <= high; i++){
(gdb) i locals
i = 1
sum = 1
(gdb) s
7         sum = sum + i;
(gdb) i locals
i = 2
sum = 1
(gdb) s
6     for(i = low; i <= high; i++){
(gdb) i locals
i = 2
sum = 3

-----------------------------------------------------
由此看出,第一次循環的結果是正確的,再往下單步調試已經沒有意義了,可使用finish命令讓程序一直運行到從當前函數返回爲止:

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

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

(gdb) s
15     result[1] = add_range(1, 100);
(gdb) print result
$2 = {55, 0, -1646196904, 50, 4195016, 0, 0, 1, 2041, 1, -1654612390, 50, 0, 0, 
  -1652419360, 50, -7728, 32767, -7688, 32767, -1652420216, 50, -134227048, 
  32767, -163754450, 0, -1654612390, 50, 0, 0, -134227048, 32767, 1, 0, 0, 0, 
  1, 50, -1652420216, 50, -7848, 32767, 750006344, 0, 6, 0, -7826, 32767, 0, 0, 
  -1652419360, 50, -7808, 32767, -1645672825, 50, -7784, 32767, 0, 1, 0, 0, 
  4195073, 0, 191, 0, -7826, 32767, -7825, 32767, 1, 0, 0, 0, -1645672168, 50, 
  0, 0, 4195680, 0, 0, 0, 4195235, 0, -7512, 32767, 4195749, 0, -1646199904, 
  50, 4195680, 0, 0, 0, 4195296, 0, -7536, 32767, 0, 0}

第一個值是55確實賦值給類result數組的第0個元素。

使用s命令進入第二次add_range調用,進入以後首先查看參數和局部變量:

(gdb) s
add_range (low=1, high=100) at test1.c:6
6     for(i = low; i <= high; i++){
(gdb) bt
#0  add_range (low=1, high=100) at test1.c:6
#1  0x000000000040051c in main () at test1.c:15
(gdb) i locals
i = 11
sum = 55

到這裏,看出了問題:因爲局部變量i和sum沒有初始化,因此具備不肯定的值,又因爲連次調用是連續的,i和sum正好取類上次調用時的值。i的初始值不是0沒關係,由於在for循環開始從新賦值了,但若是sum的處置不是0,累加獲得的結果就錯了。
問題找到了,能夠退出gdb修改源代碼了。然而咱們不想浪費一次調試機會,能夠在gdb中立刻把sum的初始值改成0,繼續運行,看看改了以後有沒有其餘的bug:

(gdb) set var sum=0
(gdb) finish
Run till exit from #0  add_range (low=1, high=100) at test1.c:6
0x000000000040051c in main () at test1.c:15
15     result[1] = add_range(1, 100);
Value returned is $3 = 5050
(gdb) s
16     printf("result[0] = %d\nresult[1] = %d\n", result[0], result[1]);
(gdb) s
result[0] = 55
result[1] = 5050
17     return 0;

這樣結果就對了。
修改變量的值除了用set命令以外也可使用print命令,由於print命令後跟的是表達式,而咱們知道賦值和函數調用都是表達式,因此還能夠用print來修改變量的值,或者調用函數:

(gdb) print result[2]=88
$4 = 88
(gdb) p printf("result[2]=%d\n", result[2])
result[2]=88
$5 = 13

咱們知道:printf函數的返回值表示實際打印的字符數,因此$5的結果是13。

總結一下本節使用過的gdb命令:

list(l):列出源代碼,接着上次的位置往下列,每次列10行
list 行號:列出產品從第幾行開始的源代碼
list 函數名:列出某個函數的源代碼
start:開始執行程序,停在main函數第一行語句前面等待命令
next(n):執行下一列語句
step(s):執行下一行語句,若是有函數調用則進入到函數中
breaktrace(或bt):查看各級函數調用及參數
frame(f) 幀編號:選擇棧幀
info(i) locals:查看當前棧幀局部變量的值
finish:執行到當前函數返回,而後挺下來等待命令
print(p):打印表達式的值,經過表達式能夠修改變量的值或者調用函數
set var:修改變量的值
quit:退出gdb

<2>斷點
程序編輯以下:

# include <stdio.h>
int main(){
    int sum =0;
    int i = 0;
    char input[5];
    
    while(1){
        scanf("%s", input);//在輸入字符後自動加'\0'造成字符串
        for(i = 0; input[i] != '\0'; i++){
            sum = sum * 10 + input[i] - '0';//'1'-'0'=1,'\0'=0
        }
        printf("input = %d\n", sum);
    }
    return 0;
}

編譯和運行:

[root@localhost gdb_demo]# vim test2.c
[root@localhost gdb_demo]# gcc test2.c -g -o test2
[root@localhost gdb_demo]# ls
test1  test1.c  test2  test2.c
[root@localhost gdb_demo]# ./test2
123
input = 123
12345
input = 12345
12345678
input = -268647318
(Ctrl-C退出程序)

從結果看出程序明顯是有問題的。
下面來調試:

[root@localhost gdb_demo]# gdb test2
......
(gdb) start
Temporary breakpoint 1 at 0x40053c: file test2.c, line 4.
Starting program: /root/code/c/gdb_demo/test2
Temporary breakpoint 1, main () at test2.c:4
4     int sum =0;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.x86_64
(gdb)

可見,start不會跳過賦值語句,經過第一次單步調試的例子,sum可列爲重點觀察對象,可使用display命令使得每次停下來的時候都顯示當前的sum值,繼續往下走:

(gdb) display sum
1: sum = 0
(gdb) n
5     int i = 0;
1: sum = 0
(gdb) 
9         scanf("%s", input);
1: sum = 0
(gdb) 
123
10         for(i = 0; input[i] != '\0'; i++){
1: sum = 0
(gdb)

這個循環應該是沒有問題的,由於循環開始sum的值是正確的。可使用undisplay取消先前設置的那些變量的跟蹤。

若是不想一步一步跟蹤這個循環,可使用break(簡寫爲b)在第8行設置一個斷點(Breakpoint):

(gdb) l
5     int i = 0;
6     char input[5];
7     
8     while(1){
9         scanf("%s", input);
10         for(i = 0; input[i] != '\0'; i++){
11             sum = sum * 10 + input[i] - '0';
12         }
13         printf("input = %d\n", sum);
14     }
(gdb) b 8
Breakpoint 2 at 0x40054a: file test2.c, line 8.

break命令的參數也能夠是函數名,表示在某一個函數開頭設置斷點。如今用continue命令(簡寫爲c)連續運行而非單步運行,程序到達斷點會自動停下來,這樣就能夠停在下一次循環的開頭:

(gdb) c
Continuing.
input = 123
Breakpoint 2, main () at test2.c:9
9         scanf("%s", input);
1: sum = 123

而後輸入新的字符串準備轉換:

(gdb) n
12345
10         for(i = 0; input[i] != '\0'; i++){
1: sum = 123

此時問題已經暴露出來了,新的轉換應該是從0開始累加的,而如今sum倒是123,緣由在於新的循環沒有把sum歸零。

可見斷點有助於快速跳過與問題無關的代碼,而後在有問題的代碼上慢慢走慢慢分析,「斷點加單步」是使用調試器的基本方法。至於應該在哪裏設置斷點,怎麼知道哪些代碼能夠跳過而哪些代碼要慢慢走,也要經過對錯誤現象的分析和假設來肯定。

一次調試能夠設置多個斷點,用info命令(簡寫爲i)能夠查看已經設置的斷點:

(gdb) b 11
Breakpoint 3 at 0x40056c: file test2.c, line 11.
(gdb) i breakpoints
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x000000000040054a in main at test2.c:8
 breakpoint already hit 2 times
3       breakpoint     keep y   0x000000000040056c in main at test2.c:11

每個斷點都有一個編號,能夠用編號指定刪除某個斷點:

(gdb) delete breakpoints 2
(gdb) i breakpoints
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x000000000040056c in main at test2.c:11

有時候一個斷點不想用能夠禁用而沒必要刪除,這樣之後想用的時候能夠直接啓用,而沒必要從新從代碼裏找應該在哪一行設置斷點:

(gdb) disable breakpoints 3
(gdb) i breakpoints
Num     Type           Disp Enb Address            What
3       breakpoint     keep n   0x000000000040056c in main at test2.c:11
(gdb) enable breakpoints 3
(gdb) i breakpoints
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x000000000040056c in main at test2.c:11
(gdb) delete breakpoints
Delete all breakpoints? (y or n) y
(gdb) i breakpoints
No breakpoints or watchpoints.

gdb設置斷點功能很是靈活,還能夠設置斷點在知足某個條件時才激活,例如咱們仍然在循環開頭設置斷點,可是僅當sum不等於0時才中斷,而後用run(簡寫爲r)從新從程序開頭連續執行:

(gdb) break 10 if sum != 0
Breakpoint 5 at 0x400563: file test2.c, line 10.
(gdb) i breakpoints
Num     Type           Disp Enb Address            What
5       breakpoint     keep y   0x0000000000400563 in main at test2.c:10
 stop only if sum != 0
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/code/c/gdb_demo/test2 
123
input = 123
123
Breakpoint 5, main () at test2.c:10
10         for(i = 0; input[i] != '\0'; i++){
1: sum = 123

結果:第一次執行輸入以後沒有中斷,第二次卻中斷了,由於第二次循環開始sum的值爲123。

總結一下本節使用到的gdb命令:

break(b) 行號:在某一行設置斷點
break 函數名:在某個函數開頭設置斷點
break...if...:設置條件斷點
continue(或c):從當前位置開始連續而非單步執行程序
delete breakpoints:刪除全部斷點
delete breakpoints n:刪除序號爲n的斷點
disable breakpoints:禁用斷點
enable breakpoints:啓用斷點
info(或i) breakpoints:參看當前設置了哪些斷點
run(或r):從開始連續而非單步執行程序
display 變量名:跟蹤查看一個變量,每次停下來都顯示它的值
undisplay:取消對先前設置的那些變量的跟蹤

下面再看一個例子:
程序以下:

#include <stdio.h>
int main(){
    int i;
    char str[6] = "hello";
    char reverse_str[6] = "";
    printf("%s\n", str);
    for(i = 0; i < 5; i ++){
        reverse_str[5-i] = str[i];
    }
    printf("%s\n", reverse_str);
    return 0;
}

運行結果:

[root@localhost gdb_demo]# gcc test3.c -g -o test3
[root@localhost gdb_demo]# ./test3
hello

其中,第二次打印空白,結果顯然是不正確的。
調試過程以下:

[root@localhost gdb_demo]# gdb test3
......
(gdb) l
1 #include <stdio.h>
2 
3 int main(){
4     int i;
5     char str[6] = "hello";
6     char reverse_str[6] = "";
7 
8     printf("%s\n", str);
9     for(i = 0; i < 5; i ++){
10         reverse_str[5-i] = str[i];
(gdb) 
11     }
12     printf("%s\n", reverse_str);
13     return 0;
14 }
(gdb) i breakpoints
No breakpoints or watchpoints.
(gdb) b 10
Breakpoint 1 at 0x4004fb: file test3.c, line 10.
(gdb) i breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000004004fb in main at test3.c:10
(gdb) r
Starting program: /root/code/c/gdb_demo/test3 
hello
Breakpoint 1, main () at test3.c:10
10         reverse_str[5-i] = str[i];
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.x86_64
(gdb) p reverse_str
$1 = "\000\000\000\000\000"
(gdb) c
Continuing.
Breakpoint 1, main () at test3.c:10
10         reverse_str[5-i] = str[i];
(gdb) p reverse_str
$2 = "\000\000\000\000\000h"
(gdb) c
Continuing.
Breakpoint 1, main () at test3.c:10
10         reverse_str[5-i] = str[i];
(gdb) p reverse_str
$3 = "\000\000\000\000eh"
(gdb) c
Continuing.
Breakpoint 1, main () at test3.c:10
10         reverse_str[5-i] = str[i];
(gdb) p reverse_str
$4 = "\000\000\000leh"
(gdb) c
Continuing.
Breakpoint 1, main () at test3.c:10
10         reverse_str[5-i] = str[i];
(gdb) p reverse_str
$5 = "\000\000lleh"
(gdb) c
Continuing.
Program exited normally.
(gdb)

由上面的觀察可知:
在於將str數組中的值賦值到reverse_str的時候,將str的第1個元素賦值給類reverse_str的第6個元素,而該循環只循環了5次(即str數組元素個數),從而致使reverse_str的第一個元素爲'\000',因此輸出爲空白。
修改以下:

#include <stdio.h>
#include <string.h>
int main(){
    int i;
    char str[6] = "hello";
    char reverse_str[6] = "";
    printf("%s\n", str);
    int len = strlen(str);
    for(i = 0; i <= len-1; i ++){
        reverse_str[len-1-i] = str[i];
    }
    printf("%s\n", reverse_str);
    return 0;
}

再次運行就行了:

[root@localhost gdb_demo]# gcc test3.c -o test3
[root@localhost gdb_demo]# ./test3
hello
olleh

<3>觀察點(Watchpoint)
斷點是當程序執行到某一代碼行時中斷,而觀察點通常來觀察某個表達式(變量也是一種表達式)的值是否有變化了,若是有變化,立刻停住程序。
vim test4.c

# include <stdio.h>
int main(){
    int sum =0;
    int i;
    for(i = 1; i <= 10; i++){
        sum = sum +i;
    }
    printf("sum = %d\n", sum);
    return 0;
}

編譯運行:

[root@localhost gdb_demo]# gcc test4.c -g -o test4
[root@localhost gdb_demo]# ./test4
sum = 55

設置觀察點進行調試:

[root@localhost gdb_demo]# gdb test4
......
(gdb) start
Temporary breakpoint 1 at 0x4004cc: file test4.c, line 4.
Starting program: /root/code/c/gdb_demo/test4
Temporary breakpoint 1, main () at test4.c:4
4     int sum =0;
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.132.el6.x86_64
(gdb) l
1 # include <stdio.h>
2 
3 int main(){
4     int sum =0;
5     int i;
6     for(i = 1; i <= 10; i++){
7         sum = sum +i;
8     }
9     printf("sum = %d\n", sum);
10     return 0;
(gdb) 
11 }
(gdb) watch sum
Hardware watchpoint 2: sum
(gdb) c
Continuing.
Hardware watchpoint 2: sum
Old value = 0
New value = 1
main () at test4.c:6
6     for(i = 1; i <= 10; i++){
(gdb) c
Continuing.
Hardware watchpoint 2: sum
Old value = 1
New value = 3
main () at test4.c:6
6     for(i = 1; i <= 10; i++){
(gdb) c
Continuing.
Hardware watchpoint 2: sum
Old value = 3
New value = 6
main () at test4.c:6
6     for(i = 1; i <= 10; i++){
(gdb) c
Continuing.
Hardware watchpoint 2: sum
Old value = 6
New value = 10
main () at test4.c:6
6     for(i = 1; i <= 10; i++){
(gdb)

總結一下本節使用到的gdb命令:

watch:設置觀察點
info(或i) watchpoints:查看當前設置了哪些觀察點


GDB的補充:

輸出格式:
通常來講,GDB會根據變量的類型輸出變量的值。但你也能夠自定義GDB的輸出的格式。
例如,你想輸出一個整數的十六進制,或是二進制來查看這個整型變量的中的位的狀況。要
作到這樣,你可使用GDB的數據顯示格式:
x 按十六進制格式顯示變量。
d 按十進制格式顯示變量。
u 按十六進制格式顯示無符號整型。
o 按八進制格式顯示變量。
t 按二進制格式顯示變量。
a 按十六進制格式顯示變量。
c 按字符格式顯示變量。
f 按浮點數格式顯示變量。

(gdb) p sum
$1 = 10
(gdb) p/a sum
$2 = 0xa
(gdb) p/x sum
$3 = 0xa
(gdb) p/o sum
$4 = 012
(gdb) p/t sum
$5 = 1010
(gdb) p/f sum
$6 = 1.40129846e-44
(gdb) p/c sum
$7 = 10 '\n'

查看內存:你可使用examine命令(簡寫是x)來查看內存地址中的值。x命令的語法以下所示: x/ n、f、u是可選的參數。 n 是一個正整數,表示顯示內存的長度,也就是說從當前地址向後顯示幾個地址的內容。 f 表示顯示的格式,參見上面。若是地址所指的是字符串,那麼格式能夠是s,若是地十是指令地址,那麼格式能夠是i。u 表示從當前地址日後請求的字節數,若是不指定的話,GDB默認是4個bytes。u參數能夠用下面的字符來代替,b表示單字節,h表示雙字節,w表示四字節,g表示八字節。當咱們指定了字節長度後,GDB會從指內存定的內存地址開始,讀寫指定字節,並把其看成一個值取出來。 表示一個內存地址。 n/f/u三個參數能夠一塊兒使用。例如: 命令:x/3uh 0x54320 表示,從內存地址0x54320讀取內容,h表示以雙字節爲一個單位,3表示三個單位,u表示按十六進制顯示。

相關文章
相關標籤/搜索