跳轉到內容

Linux 應用程式除錯技巧/偵錯程式

來自 Wikibooks,開放世界的開放書籍

準備工作

[編輯 | 編輯原始碼]

總有一天,生產機器上會發現一些難以重現的問題。通常,這樣的機器難以訪問,沒有開發環境,也無法在其上安裝任何東西。最多它會有gdb,但很可能沒有。通常,最頻繁的使用者是第一個發現問題的人,而且通常,最頻繁的使用者也是花錢最多的人。並且必須對該問題進行根本原因診斷並修復。

除非應用程式已為此取證收集時刻做好準備,否則無法做太多事情來診斷問題所在。因此,準備工作應從編譯開始

  • 擁有一個“符號伺服器”並確保其可訪問。在符號伺服器上編譯。
  • 使用除錯符號編譯釋出版本。如果不想分發它們,請刪除它們,但請保留它們。
  • 隨應用程式一起分發 gdbserver 以進行遠端除錯。

這些準備工作將允許您

  • 除錯在任何機器上執行的應用程式,包括沒有安裝gdb的機器。
  • 從任何可以訪問符號伺服器的機器進行除錯。

此外,事先考慮如何在機器上進行除錯

  • 在程式碼中嵌入斷點,在感興趣的難以到達的位置,然後
    • 啟動應用程式
    • 使用偵錯程式附加到它
    • 等待斷點被命中


“符號伺服器”

[編輯 | 編輯原始碼]

一種輕鬆地在偵錯程式內部訪問正確程式碼的方法是在自動掛載的資料夾內構建二進位制檔案,每個構建都在其自己的子資料夾中。您正在除錯的機器也應該可以訪問相同的自動掛載共享。

  • 安裝 autofs
  • 在 /etc/auto.master 中取消註釋該行
/net	-hosts
  • /etc/exports中匯出資料夾
# Build directory: rw for localhost, read-only for any other host
/home/amelinte/projects/lpt/lpt  localhost(rw,sync,no_subtree_check)  *(ro,root_squash,no_subtree_check)
  • 以 root 身份:重新啟動 autofs & nfs 並匯出構建共享
/etc/init.d/autofs restart
/etc/init.d/nfs-kernel-server restart
/usr/sbin/exportfs -a
  • 匯出資料夾:編輯/etc/exports
  • 以 root 身份(RedHat)service autofs start

最後,在構建機器上的自動掛載目錄中構建二進位制檔案(此處構建機器為bear):

cd /net/bear/home/amelinte/projects/lpt/lpt.destructor
make clean
make

注意使用符號資訊解析的檔名路徑

[0x413464] lpt::stack::call_stack<40ul>::call_stack(bool)+0x52
        At /net/bear/home/amelinte/projects/lpt/lpt.destructor/lpt/include/lpt/call_stack.hpp:74
        In binaries/bin/stacktest

如果符號已從二進位制檔案中剝離,請指向gdb包含符號的資料夾,並使用debug-file-directory指令。


原始碼

[編輯 | 編輯原始碼]

要將偵錯程式指向原始檔

(gdb) set substitute-path /from/path1 /to/path1
(gdb) set substitute-path /from/path2 /to/path2

遠端除錯

[編輯 | 編輯原始碼]
  • 在應用程式執行的機器上(appmachine):
    • 如果 gdbserver 不存在,請將其複製過來。
    • 啟動應用程式。
    • 啟動 gdbservergdbserver gdbmachine:2345 --attach program
  • gdbmachine:
    • 上在 gdb 提示符下,輸入target remote appmachine:2345

有時您可能需要透過 ssh 進行隧道

  • 在 gdbmachine 上
    • ssh -L 5432:appmachine:2345 user@appmachine
    • 在 gdb 提示符下target remote localhost:5432


附加到程序

[編輯 | 編輯原始碼]

找出程序的 PID,然後

(gdb) attach 1045
Attaching to process 1045
Reading symbols from /usr/lib64/firefox-3.0.18/firefox...(no debugging symbols found)...done.
Reading symbols from /lib64/libpthread.so.0...(no debugging symbols found)...done.
[Thread debugging using libthread_db enabled]
[New Thread 0x448b4940 (LWP 1063)]
[New Thread 0x428b0940 (LWP 1054)]
....
(gdb) detach


除錯生成多個子程序的程式

[編輯 | 編輯原始碼]
  • set detach-on-fork off
  • 請參閱 GDB 文件中“all-stop”與“non-stop”模式及其相關設定


在原始碼中嵌入斷點

[編輯 | 編輯原始碼]

在 x86 平臺上

#define EMBEDDED_BREAKPOINT  asm volatile ("int3;")

或更復雜的

#define EMBEDDED_BREAKPOINT \
    asm("0:"                              \
        ".pushsection embed-breakpoints;" \
        ".quad 0b;"                       \
        ".popsection;")

這將在難以到達的條件下進入偵錯程式

    if (cpuload > 3.1416 && started > 111*MINS*AGO && whatever-dynamic-condition) 
    {
        EMBEDDED_BREAKPOINT;
    }


在斷點處執行命令

[編輯 | 編輯原始碼]
b bigFoot
commands
bt
continue
end


在每次函式呼叫時中斷

[編輯 | 編輯原始碼]
set logging on
set confirm off
rbreak .


資料斷點(監視點)

[編輯 | 編輯原始碼]

監視點可以在軟體中實現,如果 CPU 支援,也可以在硬體中實現。通常在英特爾處理器上,有八個除錯暫存器,其中只有四個可以用於硬體斷點,這限制了系統範圍內的監視點數。

在硬體監視點上安裝條件,以便僅停止增加變數值的訪問

watch -location i
set var $prev = i
command $bpnum
if $prev < i
  printf "increase prev %d %d \n", $prev, i
  set var $prev = i
else
  printf "decrease prev %d %d \n", $prev, i
  set var $prev = i
  continue
end
end

根據呼叫者設定條件斷點

[編輯 | 編輯原始碼]

這需要 gdb 7.9 或更高版本,並配置 Python 支援。

(gdb) break funcA if $_caller_is("funcB")

文字使用者介面

[編輯 | 編輯原始碼]
GDB TUI

GDB 提供了一個用於程式碼、反彙編程式和暫存器的文字使用者介面。例如

  • Ctrl-x 1 將顯示程式碼窗格
  • Ctrl-x a 將隱藏 TUI 窗格

所有 GUI 介面都不能gdb(Qt Creator 因其直觀易用而脫穎而出) 提供對所有gdb功能的訪問。

curses gdb 提供了一個改進的 TUI。偵錯程式 GUI 的完整列表可在此處獲得 這裡


反向除錯
[編輯 | 編輯原始碼]

例如,反向除錯是任何 GUI 都無法訪問的功能。

(gdb) l
1	/* 1 */ int var;
2	/* 2 */ int main (void) {
3	/* 3 */   int i; for (i=0;i<100;i++)
4	/* 4 */     var=i/10;
5	/* 5 */  return 0; }

(gdb) start
(gdb) record 
(gdb) adv 5
main () at history.c:5
5	/* 5 */  return 0; }

(gdb) watch var
Hardware watchpoint 2: var

(gdb) reverse-continue 
Continuing.
Hardware watchpoint 2: var

Old value = 9
New value = 8
0x00000000004004c3 in main () at history.c:4
4	/* 4 */     var=i/10;
(gdb) p i
$1 = 90

(gdb) reverse-continue 
Continuing.
Hardware watchpoint 2: var

Old value = 8
New value = 7
0x00000000004004c3 in main () at history.c:4
4	/* 4 */     var=i/10;

(gdb) p i
$2 = 80

另請參閱

暫存器監視
[編輯 | 編輯原始碼]

您可以監視暫存器。請注意,這將強制偵錯程式單步執行被除錯程式,並且執行速度會非常慢。

(gdb) watch $ebp

需要注意的是,在即將釋出的 gdb 版本中,.gdbinit將被替換為gdb-gdb.gdb:

gdb-gdb.gdb
 ^   ^   ^
 |   |   |---- It's a gdb script.
 |   |         If it were Python this would be .py.
 |   |
 |   --------- "-gdb" is a gdb convention, it's the suffix added to a file
 |             for auxiliary support.
 |             E.g., gdb will auto-load libstdc++.so-gdb.py (version elided)
 |             which contains the std c++ pretty-printers.
 |
 ------------- This init script is for the program named "gdb".
               If this were for readelf the script would be named
               readelf-gdb.gdb.

C++ 支援

[編輯 | 編輯原始碼]
預置 gdb 宏
[編輯 | 編輯原始碼]
名稱改編
[編輯 | 編輯原始碼]

gdb可能需要對 C++11 二進位制檔案進行一些指導。

(gdb) set cp-abi gnu-v3
(gdb) set language c++
(gdb) maintenance demangle _ZNSt16nested_exceptionD2Ev
std::nested_exception::~nested_exception()

最終可以使用 templight 除錯和分析模板。

從基類指標獲取子類
[編輯 | 編輯原始碼]
(gdb) python print(x.type)
SuperClass *
(gdb) python print(x.dynamic_type)
SubClass *
華夏公益教科書