笨辦法學(xué)C 練習(xí)31:代碼調(diào)試

練習(xí)31:代碼調(diào)試

原文:Exercise 31: Debugging Code

譯者:飛龍

我已經(jīng)教給你一些關(guān)于我的強(qiáng)大的調(diào)試宏的技巧蹋宦,并且你已經(jīng)開始用它們了。當(dāng)我調(diào)試代碼時猫十,我使用debug()宏,分析發(fā)生了什么以及跟蹤問題。在這個練習(xí)中我打算教給你一些使用gdb的技巧,用于監(jiān)視一個不會退出的簡單程序累提。你會學(xué)到如何使用gdb附加到運行中的進(jìn)程,并掛起它來觀察發(fā)生了什么磁浇。在此之后我會給你一些用于gdb的小提示和小技巧斋陪。

調(diào)試輸出、GDB或Valgrind

我主要按照一種“科學(xué)方法”的方式來調(diào)試置吓,我會提出可能的所有原因无虚,之后排除它們或證明它們導(dǎo)致了缺陷。許多程序員擁有的問題是它們對解決bug的恐慌和急躁使他們覺得這種方法會“拖慢”他們衍锚。它們并沒有注意到友题,它們已經(jīng)失敗了,并且在收集無用的信息戴质。我發(fā)現(xiàn)日志(調(diào)試輸出)會強(qiáng)迫我科學(xué)地解決bug度宦,并且在更多情況下易于收集信息。

此外告匠,使用調(diào)試輸出來作為我的首要調(diào)試工具的理由如下:

  • 你可以使用變量的調(diào)試輸出戈抄,來看到程序執(zhí)行的整個軌跡,它讓你跟蹤變量是如何產(chǎn)生錯誤的后专。使用gdb的話划鸽,你必須為每個變量放置查看和調(diào)試語句,并且難以獲得執(zhí)行的實際軌跡行贪。
  • 調(diào)試輸出存在于代碼中漾稀,當(dāng)你需要它們是你可以重新編譯使它們回來模闲。使用gdb的話,你每次調(diào)試都需要重新配置相同的信息崭捍。
  • 當(dāng)服務(wù)器工作不正常時尸折,它的調(diào)試日志功能易于打開,并且在它運行中可以監(jiān)視日志來查看哪里不對殷蛇。系統(tǒng)管理員知道如何處理日志实夹,他們不知道如何使用gdb。
  • 打印信息更加容易粒梦。調(diào)試器通常由于它奇特的UI和前后矛盾顯得難用且古怪亮航。debug("Yo, dis right? %d", my_stuff);就沒有那么麻煩。
  • 編寫調(diào)試輸出來發(fā)現(xiàn)缺陷匀们,強(qiáng)迫你實際分析代碼缴淋,并且使用科學(xué)方法。你可以認(rèn)為它是泄朴,“我假設(shè)這里的代碼是錯誤的”重抖,你可以運行它來驗證你的假設(shè),如果這里沒有錯誤那么你可以移動到其它地方祖灰。這看起來需要更長時間钟沛,但是實際上更快,因為你經(jīng)歷了“鑒別診斷”的過程局扶,并排除所有可能的原因恨统,直到你找到它。
  • 調(diào)試輸入更適于和單元測試一起運行三妈。你可以實際上總是編譯調(diào)試語句畜埋,單元測試時可以隨時查看日志。如果你用gdb沈跨,你需要在gdb中重復(fù)運行單元測試由捎,并跟蹤他來查看發(fā)生了什么兔综。
  • 使用Valgrind可以得到和調(diào)試輸出等價的內(nèi)存相關(guān)的錯誤饿凛,所以你并不需要使用類似gdb的東西來尋找缺陷。

盡管所有原因顯示我更傾向于debug而不是gdb软驰,我還是在少數(shù)情況下回用到gdb涧窒,并且我認(rèn)為你應(yīng)該選擇有助于你完成工作的工具。有時锭亏,你只能夠連接到一個崩潰的程序并且四處轉(zhuǎn)悠纠吴。或者慧瘤,你得到了一個會崩潰的服務(wù)器戴已,你只能夠獲得一些核心文件來一探究竟固该。這些貨少數(shù)其它情況中,gdb是很好的辦法糖儡。你最好準(zhǔn)備盡可能多的工具來解決問題伐坏。

接下來我會通過對比gdb、調(diào)試輸出和Valgrind來詳細(xì)分析握联,像這樣:

  • Valgrind用于捕獲所有內(nèi)存錯誤桦沉。如果Valgrind中含有錯誤或Valgrind會嚴(yán)重拖慢程序,我會使用gdb金闽。
  • 調(diào)試輸出用于診斷或修復(fù)有關(guān)邏輯或使用上的缺陷纯露。在你使用Valgrind之前,這些共計90%的缺陷代芜。
  • 使用gdb解決剩下的“謎之bug”埠褪,或如要收集信息的緊急情況。如果Valgrind不起作用挤庇,并且我不能打印出所需信息组橄,我就會使用gdb開始四處搜索。這里我僅僅使用gdb來收集信息罚随。一旦我弄清發(fā)生了什么玉工,我會回來編程單元測試來引發(fā)缺陷,之后編程打印語句來查找原因淘菩。

調(diào)試策略

這一過程適用于你打算使用任何調(diào)試技巧遵班,無論是Valgrind、調(diào)試輸出潮改,或者使用調(diào)試器狭郑。我打算以使用gdb的形式來描述他,因為似乎人們在使用調(diào)試器是會跳過它汇在。但是應(yīng)當(dāng)對每個bug使用它翰萨,直到你只需要在非常困難的bug上用到。

  • 創(chuàng)建一個小型文本文件叫做notes.txt糕殉,并且將它用作記錄想法亩鬼、bug和問題的“實驗記錄”。
  • 在你使用gdb之前阿蝶,寫下你打算修復(fù)的bug雳锋,以及可能的產(chǎn)生原因。
  • 對于每個原因羡洁,寫下你所認(rèn)為的玷过,問題來源的函數(shù)或文件,或者僅僅寫下你不知道。
  • 現(xiàn)在啟動gdb并且使用file:function挑選最可能的因素辛蚊,之后在那里設(shè)置斷點粤蝎。
  • 使用gdb運行程序,并且確認(rèn)它是否是真正原因袋马。查明它的最好方式就是看看你是否可以使用set命令诽里,簡單修復(fù)問題或者重現(xiàn)錯誤。
  • 如果它不是真正原因飞蛹,則在notes.txt中標(biāo)記它不是谤狡,以及理由。移到下一個可能的原因卧檐,并且使最易于調(diào)試的墓懂,之后記錄你收集到的信息。

這里你并沒有注意到霉囚,它是最基本的科學(xué)方法捕仔。你寫下一些假設(shè),之后調(diào)試來證明或證偽它們盈罐。這讓你洞察到更多可能的因素榜跌,最終使你找到他。這個過程有助于你避免重復(fù)步入同一個可能的因素盅粪,即使你發(fā)現(xiàn)它們并不可能钓葫。

你也可以使用調(diào)試輸出來執(zhí)行這個過程。唯一的不同就是你實際在源碼中編寫假設(shè)來推測問題所在票顾,而不是notes.txt中础浮。某種程度上,調(diào)試輸出強(qiáng)制你科學(xué)地解決bug奠骄,因為你需要將假寫為打印語句豆同。

使用 GDB

我將在這個練習(xí)中調(diào)試下面這個程序,它只有一個不會正常終止的while循環(huán)含鳞。我在里面放置了一個usleep調(diào)用影锈,使它循環(huán)起來更加有趣。

#include <unistd.h>

int main(int argc, char *argv[])
{
    int i = 0;

    while(i < 100) {
        usleep(3000);
    }

    return 0;
}

像往常一樣編譯蝉绷,并且在gdb下啟動它鸭廷,例如:gdb ./ex31

一旦它運行之后潜必,我打算讓你使用這些gdb命令和它交互靴姿,并且觀察它們的作用以及如何使用它們。

help COMMAND

獲得COMMAND的簡單幫助磁滚。

break file.c:(line|function)

在你希望暫停之星的地方設(shè)置斷點。你可以提供行號或者函數(shù)名稱,來在文件中的那個地方暫停垂攘。

run ARGS

運行程序维雇,使用ARGS作為命令行參數(shù)。

cont

繼續(xù)執(zhí)行程序晒他,直到斷點或錯誤吱型。

step

單步執(zhí)行代碼,但是會進(jìn)入函數(shù)內(nèi)部陨仅。使用它來跟蹤函數(shù)內(nèi)部津滞,來觀察它做了什么。

next

就像是step灼伤,但是他會運行函數(shù)并步過它們触徐。

backtrace (or bt)

執(zhí)行“跟蹤回溯”,它會轉(zhuǎn)儲函數(shù)到當(dāng)前執(zhí)行點的執(zhí)行軌跡狐赡。對于查明如何執(zhí)行到這里非常有用撞鹉,因為它也打印出傳給每個函數(shù)的參數(shù)。它和Valgrind報告內(nèi)存錯誤的方式很接近颖侄。

set var X = Y

將變量X設(shè)置為Y鸟雏。

print X

打印出X的值,你通忱雷妫可以使用C的語法來訪問指針的值或者結(jié)構(gòu)體的內(nèi)容孝鹊。

ENTER

重復(fù)上一條命令。

quit

退出gdb展蒂。

這些都是我使用gdb時的主要命令惶室。你現(xiàn)在的任務(wù)是玩轉(zhuǎn)它們和ex31,你會對它的輸出更加熟悉玄货。

一旦你熟悉了gdb之后皇钞,你會希望多加使用它。嘗試在更復(fù)雜的程序松捉,例如devpkg上使用它夹界,來觀察你是否能夠改函數(shù)的執(zhí)行或分析出程序在做什么。

附加到進(jìn)程

gdb最實用的功能就是附加到運行中的程序隘世,并且就地調(diào)試它的能力可柿。當(dāng)你擁有一個崩潰的服務(wù)器或GUI程序,你通常不需要像之前那樣在gdb下運行它丙者。而是可以直接啟動它复斥,希望它不要馬上崩潰,之后附加到它并設(shè)置斷點械媒。練習(xí)的這一部分中我會向你展示怎么做目锭。

當(dāng)你退出gdb之后评汰,如果你停止了ex31我希望你重啟它,之后開啟另一個中斷窗口以便于啟動gdb并附加痢虹。進(jìn)程附加就是你讓gdb連接到已經(jīng)運行的程序被去,以便于你實時監(jiān)測它。它會掛起程序來讓你單步執(zhí)行奖唯,當(dāng)你執(zhí)行完之后程序會像往常一樣恢復(fù)運行惨缆。

下面是一段會話,我對ex31做了上述事情丰捷,單步執(zhí)行它坯墨,之后修改while循環(huán)并使它退出。

$ ps ax | grep ex31
10026 s000  S+     0:00.11 ./ex31
10036 s001  R+     0:00.00 grep ex31

$ gdb ./ex31 10026
GNU gdb 6.3.50-20050815 (Apple version gdb-1705) (Fri Jul  1 10:50:06 UTC 2011)
Copyright 2004 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 "x86_64-apple-darwin"...Reading symbols for shared libraries .. done

/Users/zedshaw/projects/books/learn-c-the-hard-way/code/10026: No such file or directory
Attaching to program: `/Users/zedshaw/projects/books/learn-c-the-hard-way/code/ex31', process 10026.
Reading symbols for shared libraries + done
Reading symbols for shared libraries ++........................ done
Reading symbols for shared libraries + done
0x00007fff862c9e42 in __semwait_signal ()

(gdb) break 8
Breakpoint 1 at 0x107babf14: file ex31.c, line 8.

(gdb) break ex31.c:11
Breakpoint 2 at 0x107babf1c: file ex31.c, line 12.

(gdb) cont
Continuing.

Breakpoint 1, main (argc=1, argv=0x7fff677aabd8) at ex31.c:8
8      while(i < 100) {

(gdb) p i
$1 = 0

(gdb) cont
Continuing.

Breakpoint 1, main (argc=1, argv=0x7fff677aabd8) at ex31.c:8
8      while(i < 100) {

(gdb) p i
$2 = 0

(gdb) list
3  
4  int main(int argc, char *argv[])
5  {
6      int i = 0;
7  
8      while(i < 100) {
9          usleep(3000);
10     }
11 
12     return 0;

(gdb) set var i = 200

(gdb) p i
$3 = 200

(gdb) next

Breakpoint 2, main (argc=1, argv=0x7fff677aabd8) at ex31.c:12
12     return 0;

(gdb) cont
Continuing.

Program exited normally.
(gdb) quit
$

在OSX上你可能會看到輸入root密碼的GUI輸入框病往,并且即使你輸入了密碼還是會得到來自gdb的“Unable to access task for process-id XXX: (os/kern) failure.”的錯誤捣染。這種情況下,你需要停止gdbex31程序荣恐,并重新啟動程序使它工作液斜,只要你成功輸入了root密碼。

我會遍歷整個會話叠穆,并且解釋我做了什么:

gdb:1

使用ps來尋找我想要附加的ex31的進(jìn)程ID少漆。

gdb:5

我使用gdb ./ex31 PID來附加到進(jìn)程,其中PID替換為我所擁有的進(jìn)程ID硼被。

gdb:6-19

gdb打印出了一堆關(guān)于協(xié)議的信息示损,接著它讀取了所有東西。

gdb:21

程序被附加嚷硫,并且在當(dāng)前執(zhí)行點上停止检访。所以現(xiàn)在我在文件中的第8行使用break設(shè)置了斷點。我假設(shè)我這么做的時候仔掸,已經(jīng)在這個我想中斷的文件中了脆贵。

gdb:24

執(zhí)行break的更好方式,是提供file.c line的格式起暮,便于你確保定位到了正確的地方卖氨。我在這個break中這樣做。

gdb:27

我使用cont來繼續(xù)運行负懦,直到我命中了斷點筒捺。

gdb:30-31

我已到達(dá)斷點,于是gdb打印出我需要了解的變量(argcargv)纸厉,以及停下來的位置系吭,之后打印出斷點的行號。

gdb:33-34

我使用print的縮寫p來打印出i變量的值颗品,它是0肯尺。

gdb:36

繼續(xù)運行來查看i是否改變沃缘。

gdb:42

再次打印出i,顯然它沒有變化蟆盹。

gdb:45-55

使用list來查看代碼是什么孩灯,之后我意識到它不可能退出闺金,因為我沒有自增i逾滥。

gdb:57

確認(rèn)我的假設(shè)是正確的,即i需要使用set命令來修改為i = 200败匹。這是gdb最優(yōu)秀的特性之一寨昙,讓你“修改”程序來讓你快速知道你是否正確。

gdb:59

打印i來確保它已改變掀亩。

gdb:62

使用next來移到下一段代碼舔哪,并且我發(fā)現(xiàn)命中了ex31.c:12的斷點,所以這意味著while循環(huán)已退出槽棍。我的假設(shè)正確捉蚤,我需要修改i

gdb:67

使用cont來繼續(xù)運行炼七,程序像往常一樣退出缆巧。

gdb:71

最后我使用quit來退出gdb

GDB 技巧

下面是你可以用于GDB的一些小技巧:

gdb --args

通常gdb獲得你提供的變量并假設(shè)它們用于它自己豌拙。使用--args來向程序傳遞它們陕悬。

thread apply all bt

轉(zhuǎn)儲所有線程的執(zhí)行軌跡,非常有用按傅。

gdb --batch --ex r --ex bt --ex q --args

運行程序捉超,當(dāng)它崩潰時你會得到執(zhí)行軌跡。

?

如果你有其它技巧唯绍,在評論中寫下它吧拼岳。

附加題

  • 找到一個圖形化的調(diào)試器,將它與原始的gdb相比况芒。它們在本地調(diào)試程序時非常有用惜纸,但是對于在服務(wù)器上調(diào)試沒有任何意義。
  • 你可以開啟OS上的“核心轉(zhuǎn)儲”牛柒,當(dāng)程序崩潰時你會得到一個核心文件堪簿。這個核心文件就像是對程序的解剖,便于你了解崩潰時發(fā)生了什么皮壁,以及由什么原因?qū)е峦指P薷?code>ex31.c使它在幾個迭代之后崩潰,之后嘗試得到它的核心轉(zhuǎn)儲并分析蛾魄。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末虑瀑,一起剝皮案震驚了整個濱河市湿滓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舌狗,老刑警劉巖叽奥,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異痛侍,居然都是意外死亡朝氓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門主届,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赵哲,“玉大人,你說我怎么就攤上這事君丁》愣幔” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵绘闷,是天一觀的道長橡庞。 經(jīng)常有香客問我,道長印蔗,這世上最難降的妖魔是什么扒最? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮喻鳄,結(jié)果婚禮上扼倘,老公的妹妹穿的比我還像新娘。我一直安慰自己除呵,他們只是感情好再菊,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著颜曾,像睡著了一般纠拔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泛豪,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天稠诲,我揣著相機(jī)與錄音,去河邊找鬼诡曙。 笑死臀叙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的价卤。 我是一名探鬼主播劝萤,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慎璧!你這毒婦竟也來了床嫌?” 一聲冷哼從身側(cè)響起跨释,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厌处,沒想到半個月后鳖谈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡阔涉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年缆娃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洒敏。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡龄恋,死狀恐怖疙驾,靈堂內(nèi)的尸體忽然破棺而出凶伙,到底是詐尸還是另有隱情,我是刑警寧澤它碎,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布函荣,位于F島的核電站,受9級特大地震影響扳肛,放射性物質(zhì)發(fā)生泄漏傻挂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一挖息、第九天 我趴在偏房一處隱蔽的房頂上張望金拒。 院中可真熱鬧,春花似錦套腹、人聲如沸绪抛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽幢码。三九已至,卻和暖如春尖飞,著一層夾襖步出監(jiān)牢的瞬間症副,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工政基, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留贞铣,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓沮明,卻偏偏與公主長得像辕坝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子珊擂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 程序調(diào)試的基本思想是“分析現(xiàn)象->假設(shè)錯誤原因->產(chǎn)生新的現(xiàn)象去驗證假設(shè)”這樣一個循環(huán)過程圣勒,根據(jù)現(xiàn)象如何假設(shè)錯誤原...
    Manfred_Zone閱讀 16,512評論 0 26
  • iOS包含許多“秘密”調(diào)試工具费变,包括環(huán)境變量、偏好圣贸、GCB的常規(guī)調(diào)用挚歧,等等。本技術(shù)說明描述了這些工具吁峻。如果你開發(fā)i...
    栗子烤肉閱讀 3,146評論 1 7
  • 你是否曾經(jīng)苦惱于理解你的代碼滑负,而去嘗試打印一個變量的值? NSLog(@"%@", whatIsInsideThi...
    paraneaeee閱讀 1,186評論 0 7
  • 【晨起感恩】 感恩這每一個醒來的早晨用含,我還可以呼吸矮慕,可以生而為人。 感恩父母的生養(yǎng)啄骇,感恩生命經(jīng)由你們流淌向我痴鳄。 感...
    黛兒微笑閱讀 295評論 3 4
  • 共一輪明月中 同一片星空下 未來的啊 我在望著你 在心中已經(jīng)畫了一百幅畫 你到底像哪一個呢 比這些都美吧 當(dāng)未來的...
    用不完的橡皮擦閱讀 212評論 0 0