======linux
瞭解如何使用 gdb 的一些不爲人知的功能來檢查和修復代碼。git
GNU 調試器(gdb
)是一種寶貴的工具,可用於在開發程序時檢查正在運行的進程並解決問題。github
你能夠在特定位置(按函數名稱、行號等)設置斷點、啓用和禁用這些斷點、顯示和更改變量值,並執行全部調試器但願執行的全部標準操做。可是它還有許多其它你可能沒有嘗試過的功能。這裏有五個你能夠嘗試一下。shell
設置斷點是學習使用 GNU 調試器的第一步。程序在達到斷點時中止,你能夠運行 gdb
的命令對其進行檢查或更改變量,而後再容許該程序繼續運行。sass
例如,你可能知道一個常常調用的函數有時會崩潰,但僅當它得到某個參數值時纔會崩潰。你能夠在該函數的開始處設置一個斷點並運行程序。每次碰到該斷點時都會顯示函數參數,而且若是未提供觸發崩潰的參數值,則能夠繼續操做,直到再次調用該函數爲止。當這個惹了麻煩的參數觸發崩潰時,你能夠單步執行代碼以查看問題所在。bash
(gdb) break sometimes_crashes
Breakpoint 1 at 0x40110e: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
複製代碼
爲了使此方法更具可重複性,你能夠在你感興趣的特定調用以前計算該函數被調用的次數,並在該斷點處設置一個計數器(例如,continue 30
以使其在接下來的 29 次到達該斷點時忽略它)。ide
可是斷點真正強大的地方在於它們在運行時評估表達式的能力,這使你能夠自動化這種測試。函數
break [LOCATION] if CONDITION
(gdb) break sometimes_crashes if !f
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
(gdb)
複製代碼
條件斷點使你沒必要讓 gdb
每次調用該函數時都去問你要作什麼,而是讓條件斷點僅在特定表達式的值爲 true
時才使 gdb
中止在該位置。若是執行到達條件斷點的位置,但表達式的計算結果爲 false
,調試器會自動使程序繼續運行,而無需詢問用戶該怎麼作。工具
GNU 調試器中斷點的一個甚至更復雜的功能是可以編寫對到達斷點的響應的腳本。斷點命令使你能夠編寫一系列 GNU 調試器命令,以在到達該斷點時運行。學習
咱們可使用它來規避在 sometimes_crashes
函數中咱們已知的錯誤,並在它提供空指針時使其無害地從該函數返回。
咱們可使用 silent
做爲第一行,以更好地控制輸出。不然,每次命中斷點時,即便在運行斷點命令以前,也會顯示堆棧幀。
(gdb) break sometimes_crashes
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if !f
>frame
>printf "Skipping call\n"
>return 0
>continue
>end
>printf "Continuing\n"
>continue
>end
(gdb) run
Starting program: /home/twaugh/Documents/GDB/prog
warning: Loadable section ".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0 sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
Skipping call
[Inferior 1 (process 9373) exited normally]
(gdb)
複製代碼
GNU 調試器內置支持使用 x
命令以各類格式檢查內存,包括八進制、十六進制等。可是我喜歡並排看到兩種格式:左側爲十六進制字節,右側爲相同字節表示的 ASCII 字符。
當我想逐字節查看文件的內容時,常用 hexdump -C
(hexdump
來自 util-linux 軟件包)。這是 gdb
的 x
命令顯示的十六進制字節:
(gdb) x/33xb mydata
0x404040 <mydata> : 0x02 0x01 0x00 0x02 0x00 0x00 0x00 0x01
0x404048 <mydata+8> : 0x01 0x47 0x00 0x12 0x61 0x74 0x74 0x72
0x404050 <mydata+16>: 0x69 0x62 0x75 0x74 0x65 0x73 0x2d 0x63
0x404058 <mydata+24>: 0x68 0x61 0x72 0x73 0x65 0x75 0x00 0x05
0x404060 <mydata+32>: 0x00
複製代碼
若是你想讓 gdb
像 hexdump
同樣顯示內存怎麼辦?這是能夠的,實際上,你能夠將這種方法用於你喜歡的任何格式。
經過使用 dump
命令以將字節存儲在文件中,結合 shell
命令以在文件上運行 hexdump
以及define
命令,咱們能夠建立本身的新的 hexdump
命令來使用 hexdump
顯示內存內容。
(gdb) define hexdump
Type 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
命令。如下是它運行的例子:
(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
複製代碼
有時你想更多地瞭解致使崩潰的緣由,而源代碼還不夠。你想查看在 CPU 指令級別發生了什麼。
disassemble
命令可以讓你查看實現函數的 CPU 指令。可是有時輸出可能很難跟蹤。一般,我想查看與該函數源代碼的特定部分相對應的指令。爲此,請使用 /s
修飾符在反彙編中包括源代碼行。
(gdb) disassemble/s main
Dump of assembler code for function main:
prog.c:
11 {
0x0000000000401158 <+0>: push %rbp
0x0000000000401159 <+1>: mov %rsp,%rbp
0x000000000040115c <+4>: sub $0x10,%rsp
12 int n = 0;
0x0000000000401160 <+8>: movl $0x0,-0x4(%rbp)
13 sometimes_crashes(&n);
0x0000000000401167 <+15>: lea -0x4(%rbp),%rax
0x000000000040116b <+19>: mov %rax,%rdi
0x000000000040116e <+22>: callq 0x401126 <sometimes_crashes>
[...snipped...]
複製代碼
這裏,用 info
寄存器查看全部 CPU 寄存器的當前值,以及用如 stepi
這樣命令一次執行一條指令,可使你對程序有了更詳細的瞭解。
有時,你但願本身能夠逆轉時間。想象一下,你已經達到了變量的監視點。監視點像是一個斷點,但不是在程序中的某個位置設置,而是在表達式上設置(使用 watch
命令)。每當表達式的值更改時,執行就會中止,而且調試器將得到控制權。
想象一下你已經達到了這個監視點,而且由該變量使用的內存已更改了值。事實證實,這多是由更早發生的事情引發的。例如,內存已釋放,如今正在從新使用。可是它是什麼時候何地被釋放的呢?
GNU 調試器甚至能夠解決此問題,由於你能夠反向運行程序!
它經過在每一個步驟中仔細記錄程序的狀態來實現此目的,以即可以恢復之前記錄的狀態,從而產生時間倒流的錯覺。
要啓用此狀態記錄,請使用 target record-full
命令。而後,你可使用一些聽起來不太可行的命令,例如:
reverse-step
,倒退到上一個源代碼行*reverse-next
,它倒退到上一個源代碼行,向後跳過函數調用reverse-finish
,倒退到當前函數即將被調用的時刻reverse-continue
,它返回到程序中的先前狀態,該狀態將(如今)觸發斷點(或其餘致使斷點中止的狀態)這是運行中的反向調試的示例:
(gdb) b main
Breakpoint 1 at 0x401160: file prog.c, line 12.
(gdb) r
Starting program: /home/twaugh/Documents/GDB/prog
[...]
Breakpoint 1, main () at prog.c:12
12 int n = 0;
(gdb) target record-full
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:7
7 return *f;
(gdb) reverse-finish
Run back to call of #0 0x0000000000401154 in sometimes_crashes (f=0x0)
at prog.c:7
0x0000000000401190 in main () at prog.c:16
16 sometimes_crashes(0);
複製代碼
這些只是 GNU 調試器能夠作的一些有用的事情。還有更多有待發現。你最喜歡 gdb
的哪一個隱藏的、不爲人知或使人吃驚的功能?請在評論中分享。
via: opensource.com/article/19/…