gdb查看空指针 linux_5 个鲜为人知 GNU 调试器(GDB)技巧
了解如何使用 gdb 的一些鮮為人知的功能來(lái)檢查和修復(fù)代碼。-- Tim Waugh(作者)
GNU 調(diào)試器 (gdb)是一種寶貴的工具,可用于在開(kāi)發(fā)程序時(shí)檢查正在運(yùn)行的進(jìn)程并解決問(wèn)題。
你可以在特定位置(按函數(shù)名稱(chēng)、行號(hào)等)設(shè)置斷點(diǎn)、啟用和禁用這些斷點(diǎn)、顯示和更改變量值,并執(zhí)行所有調(diào)試器希望執(zhí)行的所有標(biāo)準(zhǔn)操作。但是它還有許多其它你可能沒(méi)有嘗試過(guò)的功能。這里有五個(gè)你可以嘗試一下。
條件斷點(diǎn)
設(shè)置斷點(diǎn)是學(xué)習(xí)使用 GNU 調(diào)試器的第一步。程序在達(dá)到斷點(diǎn)時(shí)停止,你可以運(yùn)行 gdb 的命令對(duì)其進(jìn)行檢查或更改變量,然后再允許該程序繼續(xù)運(yùn)行。
例如,你可能知道一個(gè)經(jīng)常調(diào)用的函數(shù)有時(shí)會(huì)崩潰,但僅當(dāng)它獲得某個(gè)參數(shù)值時(shí)才會(huì)崩潰。你可以在該函數(shù)的開(kāi)始處設(shè)置一個(gè)斷點(diǎn)并運(yùn)行程序。每次碰到該斷點(diǎn)時(shí)都會(huì)顯示函數(shù)參數(shù),并且如果未提供觸發(fā)崩潰的參數(shù)值,則可以繼續(xù)操作,直到再次調(diào)用該函數(shù)為止。當(dāng)這個(gè)惹了麻煩的參數(shù)觸發(fā)崩潰時(shí),你可以單步執(zhí)行代碼以查看問(wèn)題所在。
(gdb) break sometimes_crashesBreakpoint 1 at 0x40110e: file prog.c, line 5.(gdb) run[...]Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:55 fprintf(stderr,(gdb) continueBreakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:55 fprintf(stderr,(gdb) continue為了使此方法更具可重復(fù)性,你可以在你感興趣的特定調(diào)用之前計(jì)算該函數(shù)被調(diào)用的次數(shù),并在該斷點(diǎn)處設(shè)置一個(gè)計(jì)數(shù)器(例如,continue 30 以使其在接下來(lái)的 29 次到達(dá)該斷點(diǎn)時(shí)忽略它)。
但是斷點(diǎn)真正強(qiáng)大的地方在于它們?cè)谶\(yùn)行時(shí)評(píng)估表達(dá)式的能力,這使你可以自動(dòng)化這種測(cè)試。
break [LOCATION] if CONDITION(gdb) break sometimes_crashes if !fBreakpoint 1 at 0x401132: file prog.c, line 5.(gdb) run[...]Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:55 fprintf(stderr,(gdb)條件斷點(diǎn)使你不必讓 gdb 每次調(diào)用該函數(shù)時(shí)都去問(wèn)你要做什么,而是讓條件斷點(diǎn)僅在特定表達(dá)式的值為 true 時(shí)才使 gdb 停止在該位置。如果執(zhí)行到達(dá)條件斷點(diǎn)的位置,但表達(dá)式的計(jì)算結(jié)果為 false,調(diào)試器會(huì)自動(dòng)使程序繼續(xù)運(yùn)行,而無(wú)需詢(xún)問(wèn)用戶(hù)該怎么做。
斷點(diǎn)命令
GNU 調(diào)試器中斷點(diǎn)的一個(gè)甚至更復(fù)雜的功能是能夠編寫(xiě)對(duì)到達(dá)斷點(diǎn)的響應(yīng)的腳本。斷點(diǎn)命令使你可以編寫(xiě)一系列 GNU 調(diào)試器命令,以在到達(dá)該斷點(diǎn)時(shí)運(yùn)行。
我們可以使用它來(lái)規(guī)避在 sometimes_crashes 函數(shù)中我們已知的錯(cuò)誤,并在它提供空指針時(shí)使其無(wú)害地從該函數(shù)返回。
我們可以使用 silent 作為第一行,以更好地控制輸出。否則,每次命中斷點(diǎn)時(shí),即使在運(yùn)行斷點(diǎn)命令之前,也會(huì)顯示堆棧幀。
(gdb) break sometimes_crashesBreakpoint 1 at 0x401132: file prog.c, line 5.(gdb) commands 1Type commands for breakpoint(s) 1, one per line.End with a line saying just "end".>silent>if !f >frame >printf "Skipping call" >return 0 >continue >end>printf "Continuing">continue>end(gdb) runStarting program: /home/twaugh/Documents/GDB/progwarning: Loadable section ".note.gnu.property" outside of ELF segmentsContinuingContinuingContinuing#0 sometimes_crashes (f=0x0) at prog.c:55 fprintf(stderr,Skipping call[Inferior 1 (process 9373) exited normally](gdb)轉(zhuǎn)儲(chǔ)二進(jìn)制內(nèi)存
GNU 調(diào)試器內(nèi)置支持使用 x 命令以各種格式檢查內(nèi)存,包括八進(jìn)制、十六進(jìn)制等。但是我喜歡并排看到兩種格式:左側(cè)為十六進(jìn)制字節(jié),右側(cè)為相同字節(jié)表示的 ASCII 字符。
當(dāng)我想逐字節(jié)查看文件的內(nèi)容時(shí),經(jīng)常使用 hexdump -C(hexdump 來(lái)自 util-linux 軟件包)。這是 gdb 的 x 命令顯示的十六進(jìn)制字節(jié):
(gdb) x/33xb mydata0x404040 : 0x02 0x01 0x00 0x02 0x00 0x00 0x00 0x010x404048 : 0x01 0x47 0x00 0x12 0x61 0x74 0x74 0x720x404050 : 0x69 0x62 0x75 0x74 0x65 0x73 0x2d 0x630x404058 : 0x68 0x61 0x72 0x73 0x65 0x75 0x00 0x050x404060 : 0x00如果你想讓 gdb 像 hexdump 一樣顯示內(nèi)存怎么辦?這是可以的,實(shí)際上,你可以將這種方法用于你喜歡的任何格式。
通過(guò)使用 dump 命令以將字節(jié)存儲(chǔ)在文件中,結(jié)合 shell 命令以在文件上運(yùn)行 hexdump 以及define 命令,我們可以創(chuàng)建自己的新的 hexdump 命令來(lái)使用 hexdump 顯示內(nèi)存內(nèi)容。
(gdb) define hexdumpType commands for definition of "hexdump".End with a line saying just "end".>dump binary memory /tmp/dump.bin $arg0 $arg0+$arg1>shell hexdump -C /tmp/dump.bin>end這些命令甚至可以放在 ~/.gdbinit 文件中,以永久定義 hexdump 命令。以下是它運(yùn)行的例子:
(gdb) hexdump mydata sizeof(mydata)00000000 02 01 00 02 00 00 00 01 01 47 00 12 61 74 74 72 |.........G..attr|00000010 69 62 75 74 65 73 2d 63 68 61 72 73 65 75 00 05 |ibutes-charseu..|00000020 00 |.|00000021行內(nèi)反匯編
有時(shí)你想更多地了解導(dǎo)致崩潰的原因,而源代碼還不夠。你想查看在 CPU 指令級(jí)別發(fā)生了什么。
disassemble 命令可讓你查看實(shí)現(xiàn)函數(shù)的 CPU 指令。但是有時(shí)輸出可能很難跟蹤。通常,我想查看與該函數(shù)源代碼的特定部分相對(duì)應(yīng)的指令。為此,請(qǐng)使用 /s 修飾符在反匯編中包括源代碼行。
(gdb) disassemble/s mainDump of assembler code for function main:prog.c:11 { 0x0000000000401158 : push %rbp 0x0000000000401159 : mov %rsp,%rbp 0x000000000040115c : sub $0x10,%rsp12 int n = 0; 0x0000000000401160 : movl $0x0,-0x4(%rbp)13 sometimes_crashes(&n); 0x0000000000401167 : lea -0x4(%rbp),%rax 0x000000000040116b : mov %rax,%rdi 0x000000000040116e : callq 0x401126 [...snipped...]這里,用 info 寄存器查看所有 CPU 寄存器的當(dāng)前值,以及用如 stepi 這樣命令一次執(zhí)行一條指令,可以使你對(duì)程序有了更詳細(xì)的了解。
反向調(diào)試
有時(shí),你希望自己可以逆轉(zhuǎn)時(shí)間。想象一下,你已經(jīng)達(dá)到了變量的監(jiān)視點(diǎn)。監(jiān)視點(diǎn)像是一個(gè)斷點(diǎn),但不是在程序中的某個(gè)位置設(shè)置,而是在表達(dá)式上設(shè)置(使用 watch 命令)。每當(dāng)表達(dá)式的值更改時(shí),執(zhí)行就會(huì)停止,并且調(diào)試器將獲得控制權(quán)。
想象一下你已經(jīng)達(dá)到了這個(gè)監(jiān)視點(diǎn),并且由該變量使用的內(nèi)存已更改了值。事實(shí)證明,這可能是由更早發(fā)生的事情引起的。例如,內(nèi)存已釋放,現(xiàn)在正在重新使用。但是它是何時(shí)何地被釋放的呢?
GNU 調(diào)試器甚至可以解決此問(wèn)題,因?yàn)槟憧梢苑聪蜻\(yùn)行程序!
它通過(guò)在每個(gè)步驟中仔細(xì)記錄程序的狀態(tài)來(lái)實(shí)現(xiàn)此目的,以便可以恢復(fù)以前記錄的狀態(tài),從而產(chǎn)生時(shí)間倒流的錯(cuò)覺(jué)。
要啟用此狀態(tài)記錄,請(qǐng)使用 target record-full 命令。然后,你可以使用一些聽(tīng)起來(lái)不太可行的命令,例如:
- reverse-step,倒退到上一個(gè)源代碼行
- *reverse-next,它倒退到上一個(gè)源代碼行,向后跳過(guò)函數(shù)調(diào)用
- reverse-finish,倒退到當(dāng)前函數(shù)即將被調(diào)用的時(shí)刻
- reverse-continue,它返回到程序中的先前狀態(tài),該狀態(tài)將(現(xiàn)在)觸發(fā)斷點(diǎn)(或其他導(dǎo)致斷點(diǎn)停止的狀態(tài))
這是運(yùn)行中的反向調(diào)試的示例:
(gdb) b mainBreakpoint 1 at 0x401160: file prog.c, line 12.(gdb) rStarting program: /home/twaugh/Documents/GDB/prog[...]Breakpoint 1, main () at prog.c:1212 int n = 0;(gdb) target record-full(gdb) cContinuing.Program received signal SIGSEGV, Segmentation fault.0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:77 return *f;(gdb) reverse-finishRun back to call of #0 0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:70x0000000000401190 in main () at prog.c:1616 sometimes_crashes(0);這些只是 GNU 調(diào)試器可以做的一些有用的事情。還有更多有待發(fā)現(xiàn)。你最喜歡 gdb 的哪個(gè)隱藏的、鮮為人知或令人吃驚的功能?請(qǐng)?jiān)谠u(píng)論中分享。
總結(jié)
以上是生活随笔為你收集整理的gdb查看空指针 linux_5 个鲜为人知 GNU 调试器(GDB)技巧的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 实例37:python
- 下一篇: lwip+freeRTOS 实现热插拔