gdb調試完整示例(教程):ncurses
我對在網上找到顯示命令而不顯示其輸出的“ gdb示例”感到有些沮喪。 gdb是GNU調試器,Linux上的標準調試器。在觀看《給我15分鐘》鏈接: link.時,我被提醒缺乏示例輸出,我將改變Greg Law在CppCon 2015上對GDB演講的看法,所幸其中包括輸出! 15分鐘是值得的。
這也啓發了我分享一個完整的gdb調試示例,其中包括輸出和涉及的每個步驟,包括死衚衕。這不是一個特別有趣或陌生的問題,它只是例行的gdb調試會話。但是它涵蓋了基礎知識,並且可以用作各種教程,請記住,gdb的功能比我在這裏使用的要多得多。
由於我正在調試需要root訪問權限的工具,因此我將以root用戶身份運行以下命令。根據需要替換非root用戶和sudo。您也不應該通讀所有這些內容:我已經列舉了每個步驟,因此您可以瀏覽它們並找到感興趣的步驟。
1. The Problem
BPF工具的密件抄送集合有一個對緩存頂部的拉取請求,該請求使用類似頂部的顯示來按進程顯示頁面緩存統計信息。大!但是,當我對其進行測試時,它遇到了段錯誤:
# ./cachetop.py
Segmentation fault
請注意,它顯示的是“分段故障”,而不是“分段故障(核心已轉儲)”。我想要一個核心轉儲來調試它。 (核心轉儲是進程內存的副本–名稱來自磁核心內存的時代–可以使用調試器進行調查。)
核心轉儲分析是一種調試方法,但不是唯一的一種。我可以在gdb中實時運行該程序以檢查問題。我還可以使用外部跟蹤器來捕獲數據並在segfault事件上跟蹤堆棧。我們將從核心轉儲開始。
2. Fixing Core Dumps
我將檢查覈心轉儲設置:
# ulimit -c
0
# cat /proc/sys/kernel/core_pattern
core
ulimit -c 顯示核心轉儲最大的創建數,現在被設置成0:關閉核心轉儲(對當前進程和他的子進程)
/proc/…/core_pattern設置爲“ core”,它將在當前目錄中刪除一個名爲“ core”的核心轉儲文件。暫時可以,但是我將展示如何在全球位置進行設置:
# ulimit -c unlimited
# mkdir /var/cores
# echo "/var/cores/core.%e.%p" > /proc/sys/kernel/core_pattern
您可以進一步自定義該core_pattern;例如,%h表示主機名,%t表示轉儲時間。這些選項記錄在Linux內核源文件的Documentation / sysctl / kernel.txt下。
要使core_pattern永久且在重啓後仍然有效,可以通過/etc/sysctl.conf中的“ kernel.core_pattern”進行設置。
再試一次:
# ./cachetop.py
Segmentation fault (core dumped)
# ls -lh /var/cores
total 19M
-rw------- 1 root root 20M Aug 7 22:15 core.python.30520
# file /var/cores/core.python.30520
/var/cores/core.python.30520: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from 'python ./cachetop.py'
我們有核心轉儲文件了。
3. Starting GDB
現在,我將使用目標程序位置運行gdb(使用shell替換,“`”,儘管您應該指定完整路徑,除非您確定可以使用),並使用核心轉儲文件:
# gdb `which python` /var/cores/core.python.30520
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
warning: core file may not match specified executable file.
[New LWP 30520]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
warning: JITed object file architecture unknown is not compatible with target architecture i386:x86-64.
Core was generated by `python ./cachetop.py'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00007f0a37aac40d in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5
最後兩行特別有趣:它告訴我們這是libncursesw庫的doupdate()函數中的分段錯誤。如果這是一個衆所周知的問題,那麼值得快速進行網絡搜索。我快速瀏覽了一下,但沒有找到一個常見原因。
我已經可以猜出libncursesw是幹什麼用的,但是如果您不熟悉libncursesw,那麼在“ / lib”下並以“ .so。*”結尾表示這是一個共享庫,其中可能包含手冊頁,網站,軟件包說明等
# dpkg -l | grep libncursesw
ii libncursesw5:amd64 6.0+20160213-1ubuntu1 amd64
shared libraries for terminal handling (wide character support)
我碰巧正在Ubuntu上調試它,但是Linux發行版對於gdb的使用並不重要。
4. Back Trace
堆棧跟蹤顯示了我們如何到達故障點,並且通常足以幫助識別常見問題。通常這是我在gdb會話中使用的第一個命令:bt(回溯的縮寫):
gdb) bt
#0 0x00007f0a37aac40d in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5
#1 0x00007f0a37aa07e6 in wrefresh () from /lib/x86_64-linux-gnu/libncursesw.so.5
#2 0x00007f0a37a99616 in ?? () from /lib/x86_64-linux-gnu/libncursesw.so.5
#3 0x00007f0a37a9a325 in wgetch () from /lib/x86_64-linux-gnu/libncursesw.so.5
#4 0x00007f0a37cc6ec3 in ?? () from /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
#5 0x00000000004c4d5a in PyEval_EvalFrameEx ()
#6 0x00000000004c2e05 in PyEval_EvalCodeEx ()
#7 0x00000000004def08 in ?? ()
#8 0x00000000004b1153 in PyObject_Call ()
#9 0x00000000004c73ec in PyEval_EvalFrameEx ()
#10 0x00000000004c2e05 in PyEval_EvalCodeEx ()
#11 0x00000000004caf42 in PyEval_EvalFrameEx ()
#12 0x00000000004c2e05 in PyEval_EvalCodeEx ()
#13 0x00000000004c2ba9 in PyEval_EvalCode ()
#14 0x00000000004f20ef in ?? ()
#15 0x00000000004eca72 in PyRun_FileExFlags ()
#16 0x00000000004eb1f1 in PyRun_SimpleFileExFlags ()
#17 0x000000000049e18a in Py_Main ()
#18 0x00007f0a3be10830 in __libc_start_main (main=0x49daf0 <main>, argc=2, argv=0x7ffd33d94838, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7ffd33d94828) at ../csu/libc-start.c:291
#19 0x000000000049da19 in _start ()
從下至上閱讀,從父母到孩子。 “?”條目是符號轉換失敗的地方。產生堆棧跟蹤的堆棧遍歷也可能失敗。在這種情況下,您可能會看到一個有效的幀,然後是少量的虛假地址。如果符號或堆棧損壞得太嚴重而無法理解堆棧跟蹤,則通常可以通過以下方法來解決:安裝調試信息包(爲gdb提供更多符號,並讓其執行基於DWARF的堆棧遍歷),或重新編譯軟件從源中獲取具有幀指針和調試信息的(-fno-omit-frame-pointer -g)。上面的許多“ ??”可以通過添加python-dbg軟件包來修復條目。
這個特定的堆棧看起來不太有用:第5幀到第17幀(在左側索引)是Python的內部結構,儘管我們尚未看到Python方法。然後第4幀是_curses庫,然後在libncursesw中。看起來像wgetch()-> wrefresh()-> doupdate()。僅根據名稱,我想會刷新窗口。爲什麼要進行核心轉儲?
5. Disassembly
我將從反彙編我們的段錯誤的函數doupdate()開始:
(gdb) disas doupdate
Dump of assembler code for function doupdate:
0x00007f0a37aac2e0 <+0>: push %r15
0x00007f0a37aac2e2 <+2>: push %r14
0x00007f0a37aac2e4 <+4>: push %r13
0x00007f0a37aac2e6 <+6>: push %r12
0x00007f0a37aac2e8 <+8>: push %rbp
0x00007f0a37aac2e9 <+9>: push %rbx
0x00007f0a37aac2ea <+10>: sub $0xc8,%rsp
[...]
---Type <return> to continue, or q <return> to quit---
[...]
0x00007f0a37aac3f7 <+279>: cmpb $0x0,0x21(%rcx)
0x00007f0a37aac3fb <+283>: je 0x7f0a37aacc3b <doupdate+2395>
0x00007f0a37aac401 <+289>: mov 0x20cb68(%rip),%rax # 0x7f0a37cb8f70
0x00007f0a37aac408 <+296>: mov (%rax),%rsi
0x00007f0a37aac40b <+299>: xor %eax,%eax
=> 0x00007f0a37aac40d <+301>: mov 0x10(%rsi),%rdi
0x00007f0a37aac411 <+305>: cmpb $0x0,0x1c(%rdi)
0x00007f0a37aac415 <+309>: jne 0x7f0a37aac6f7 <doupdate+1047>
0x00007f0a37aac41b <+315>: movswl 0x4(%rcx),%ecx
0x00007f0a37aac41f <+319>: movswl 0x74(%rdx),%edi
0x00007f0a37aac423 <+323>: mov %rax,0x40(%rsp)
[...]
輸出被截斷。 (我也可以只鍵入“ disas”,並且默認情況下爲doupdate。)
箭頭“ =>”指向我們的段錯誤地址,它正在執行mov 0x10(%rsi),%rdi:從%rsi寄存器中指向的內存加上偏移量0x10的移動到%rdi寄存器。接下來,我將檢查寄存器的狀態。
6. Check Registers
使用i r打印寄存器狀態(short for info registers):
(gdb) i r
rax 0x0 0
rbx 0x1993060 26816608
rcx 0x19902a0 26804896
rdx 0x19ce7d0 27060176
rsi 0x0 0
rdi 0x19ce7d0 27060176
rbp 0x7f0a3848eb10 0x7f0a3848eb10 <SP>
rsp 0x7ffd33d93c00 0x7ffd33d93c00
r8 0x7f0a37cb93e0 139681862489056
r9 0x0 0
r10 0x8 8
r11 0x202 514
r12 0x0 0
r13 0x0 0
r14 0x7f0a3848eb10 139681870703376
r15 0x19ce7d0 27060176
rip 0x7f0a37aac40d 0x7f0a37aac40d <doupdate+301>
eflags 0x10246 [ PF ZF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
好吧,%rsi爲零。這是我們的問題!零不太可能是有效地址,這種類型的段錯誤是常見的軟件錯誤:取消引用未初始化或NULL指針。
7. Memory Mappings
您可以使用i proc m再次檢查零是否有效(short for info proc mappings):
(gdb) i proc m
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x6e7000 0x2e7000 0x0 /usr/bin/python2.7
0x8e6000 0x8e8000 0x2000 0x2e6000 /usr/bin/python2.7
0x8e8000 0x95f000 0x77000 0x2e8000 /usr/bin/python2.7
0x7f0a37a8b000 0x7f0a37ab8000 0x2d000 0x0 /lib/x86_64-linux-gnu/libncursesw.so.5.9
0x7f0a37ab8000 0x7f0a37cb8000 0x200000 0x2d000 /lib/x86_64-linux-gnu/libncursesw.so.5.9
0x7f0a37cb8000 0x7f0a37cb9000 0x1000 0x2d000 /lib/x86_64-linux-gnu/libncursesw.so.5.9
0x7f0a37cb9000 0x7f0a37cba000 0x1000 0x2e000 /lib/x86_64-linux-gnu/libncursesw.so.5.9
0x7f0a37cba000 0x7f0a37ccd000 0x13000 0x0 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
0x7f0a37ccd000 0x7f0a37ecc000 0x1ff000 0x13000 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
0x7f0a37ecc000 0x7f0a37ecd000 0x1000 0x12000 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
0x7f0a37ecd000 0x7f0a37ecf000 0x2000 0x13000 /usr/lib/python2.7/lib-dynload/_curses.x86_64-linux-gnu.so
0x7f0a38050000 0x7f0a38066000 0x16000 0x0 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f0a38066000 0x7f0a38265000 0x1ff000 0x16000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f0a38265000 0x7f0a38266000 0x1000 0x15000 /lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f0a38266000 0x7f0a3828b000 0x25000 0x0 /lib/x86_64-linux-gnu/libtinfo.so.5.9
0x7f0a3828b000 0x7f0a3848a000 0x1ff000 0x25000 /lib/x86_64-linux-gnu/libtinfo.so.5.9
[...]
第一個有效的虛擬地址是0x400000。低於該值的任何內容(如果被引用)將觸發分段錯誤。
此時,有幾種不同的方法可以進一步挖掘。我將從一些說明步驟開始。
8. Breakpoints
Back to the disassembly:
0x00007f0a37aac401 <+289>: mov 0x20cb68(%rip),%rax # 0x7f0a37cb8f70
0x00007f0a37aac408 <+296>: mov (%rax),%rsi
0x00007f0a37aac40b <+299>: xor %eax,%eax
=> 0x00007f0a37aac40d <+301>: mov 0x10(%rsi),%rdi
閱讀這四條指令:似乎是從棧中將某些東西拉到%rax中,然後將%rax引用到%rsi中,將%eax設置爲零(xor是一種優化,而不是移動$ 0),然後儘管我們知道%rsi爲零,但我們用偏移量取消了對%rsi的引用。此序列用於遍歷數據結構。也許%rax很有趣,但是先前的指令已將其設置爲零,因此我們無法在覈心轉儲寄存器狀態下看到它。
我可以在doupdate + 289上設置一個斷點,然後單步執行每條指令以瞭解如何設置和更改寄存器。首先,我需要啓動gdb以便我們實時執行程序:
# gdb `which python`
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
(gdb) b *doupdate + 289
No symbol table is loaded. Use the "file" command.
哎呀。我想顯示此錯誤,以解釋爲什麼我們通常以main上的斷點開始,然後在該點上加載符號,然後設置感興趣的實際斷點。我將直接進入doupdate函數條目,運行問題,然後在碰到函數時設置偏移斷點:
(gdb) b doupdate
Function "doupdate" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (doupdate) pending.
(gdb) r cachetop.py
Starting program: /usr/bin/python cachetop.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
warning: JITed object file architecture unknown is not compatible with target architecture i386:x86-64.
Breakpoint 1, 0x00007ffff34ad2e0 in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5
(gdb) b *doupdate + 289
Breakpoint 2 at 0x7ffff34ad401
(gdb) c
Continuing.
Breakpoint 2, 0x00007ffff34ad401 in doupdate () from /lib/x86_64-linux-gnu/libncursesw.so.5
我們到了斷點。
如果您以前沒有做過,r(運行)命令將使用將傳遞給我們先前在命令行(python)上指定的gdb目標的參數。因此,這最終運行了“ python cachetop.py”。
原文鏈接地址:http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html