GDB多執行緒除錯分析

我是小三發表於2017-03-19

0x00: 在Linux系統上Gdb提供了一組多執行緒除錯命令,如表所示:

多執行緒除錯的主要任務是準確及時地捕捉被除錯程式執行緒狀態的變化的事件,並且GDB針對根據捕捉到的事件做出相應的操作,其實最終的結果就是維護一根叫thread list的連結串列。上面的除錯命令都是基於thread list連結串列來實現的,後面會有講到。

0x01:Gdb在linux平臺多執行緒除錯實現主要依賴下面三個檔案

thread.c:檔案它的任務非常簡單,就是多執行緒除錯命令子集的實現,比如info threads。
當使用者在gdb命令列敲入多執行緒除錯命令子集中的命令時,就會呼叫thread.c中對應的函式。Thread.c中的實現都是基於thread list的。而gdb對於thread list的維護工作主要在另外兩個檔案中實現。
Linux-nat.c:它實現了通常的除錯功能,比如讀寫暫存器、讀寫記憶體、resume程式、wait程式、attach、detach等功能。更重要的是,在linux-nat.c中會維護一個lwp_list連結串列,表示當前程式所有的核心執行緒。

linux-thread-db.c:基於thread_db庫的一組功能函式,用在多執行緒除錯環境下的函式,比如to_get_thread_local_address。這些函式的任務就是獲取使用者態執行緒的產生、消亡事件,雙及獲取使用者態執行緒相關的資料。Linux-thread-db.c獲取使用者執行緒的發生的事件和獲取的資訊、結合linux-nat.c中維護的lwp_list核心執行緒連結串列中提供的資訊,以此維護一個完整的thread_list,該連結串列存放執行緒所有的資訊。

0x02:Gdb功能函式分層:

Gdb中的功能函式使用struct target_ops資料結構來組織。不同功能的函式集抽象成不同的target_ops。比如用於處理coredump檔案的”core” target_ops,而linux-nat.c中實現的linux應用程式本地除錯功能也抽象成一個ops”child” target_ops,linux-thread-db.c中實現的基於libpthread庫的除錯功能抽象成”multi-thread”target_ops。
整個linux多執行緒應用程式本地除錯的結構框架如下:

從上圖可以看到當除錯linux多執行緒程式時,就會使用thread_db_ops中的相應的函式。那麼問題來了,對於resume和wait這些Linux_ops中也實現的函式,會呼叫哪個呢?Gdb中實現了很多的target_ops,有功能相近也有完全不同功能的,比如linux_ops和file_ops。那麼對於功能相近的target_ops怎樣使用呢?功能不同的target_ops之間又有怎樣的關係呢?這些問題gdb分層機制能解釋。
Gdb中把target_ops分為了7層,每一層負責不同的功能。如圖所示:

0x03:GDB除錯多執行緒

除錯程式建立具體的流程下圖所示:

在建立好被除錯程式之後,gdb通過ptrace(PTRACE_SETOPTIONS)設定PTRACE_O_TRACECLONE,設定過後,當被除錯程式建立執行緒的時候,就會給自己傳送一個SIGTRAP訊號,讓被除錯程式進入stop狀態,使得gdb能夠捕捉到這些事件,獲取tid新增到lwp_list中後,gdb會讓程式繼續執行,直到被除錯程式發生一些需要通知gdb使用者的事件,比如觸發了使用者設定的斷點,下面是流程圖

Lwp_list連結串列
被除錯程式建立執行緒最終是通過clone()系統呼叫實現的。要捕捉子執行緒的建立和死亡事件,這個捕捉事件由ptrace提供的機制實現。具體機制如下圖所示。

Thread_list連結串列:
Thread_list是struct thread_info型別的一個連結串列,記錄的是被除錯程式的所有執行緒的資訊,裡面包含執行緒使用者態和核心態的一些資訊。執行緒使用者態資訊的捕獲基於libthread_db庫,該庫提供了一組除錯介面。這麼一組libpthread_db除錯介面在gdb中使用struct thread_db_info進行管理,該資料主要結構的具體資訊如下表:

在被除錯程式載入libpthread庫時,會為該程式建立這麼一個struct thread_db_info記錄該程式要使用到的libthread_db提供的除錯介面。其中比較重要的是:
td_create_bp_addr和td_death_bp_addr。這兩個地址是對應libpthread庫中的某個位置。當呼叫libpthread庫建立執行緒或者執行緒死亡時,一定會分別呼叫這麼兩個addr處的程式碼。Gdb通過在這兩個位置設定斷點來捕獲libpthread庫的執行緒建立和死亡事件,斷點的型別為bp_thread_event.
被除錯程式建立子程式或者子程式死亡,會執行到libpthread庫的td_create_bp_addr或td_death_bp_addr地址處,觸發斷點。執行緒進入stop狀態
gdb 通過waitpid()監測到被除錯程式的狀態改變,分析子程式發生的事件,判斷為bp_thread_event的斷點觸發。如果是create,獲取新建立執行緒struct thread_info的相關的資訊,並且加入到thread_list中;如果是death,從thread_list中刪除該執行緒。

0x04:總結

GDB確定我們除錯的程式是否為多執行緒, 通過判斷被除錯程式是否載入libpthread庫來判斷的。(捕捉裝載libpthread庫這個事件)也就是一旦被除錯程式裝在libpthread庫,( Observer觀察者模式)我們就應做初始化多執行緒除錯功能。

 

相關文章