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觀察者模式)我們就應做初始化多線程調試功能。