張大方, 胡先浪, 王立杰
(江蘇自動化研究所,連云港 222006)
軟件調試是軟件開發(fā)流程中不可或缺的重要環(huán)節(jié),是為了能在開發(fā)階段及時發(fā)現程序中的錯誤并修正.根據國內外研發(fā)人員的經驗,軟件的調試時間一般能夠占到研發(fā)時間總量的一半以上[1]. 正所謂“工欲善其事,必先利其器”,集成開發(fā)環(huán)境便是軟件調試的“利器”[2]. “利器”的易用性及功能的全面性不僅僅會影響國產平臺下軟件調試的效率,同時也會在一定程度上影響國產軟硬件的規(guī)?;瘧眠M程.
Eclipse是一個開源的、可擴展的、用Java語言實現的開發(fā)平臺,就其本身而言,它不是一個一整塊的程序,而是一個包含了插件載入器、被數百個甚至更多的插件所包圍的小內核[3]. 該內核為插件的加載和運行提供了環(huán)境,每個插件則以結構化的方式在整體中提供服務. 因此,只需要通過增刪插件,就可以根據需求定制所需的調試環(huán)境[4].
CDT(C/C ++ Development Toolkit)是Eclipse平臺下一組用來開發(fā)、調試C/C++程序的插件,使用CDT插件可以開發(fā)、調試用C/C++編程語言實現的應用程序[5]. 但是以該插件為代表的傳統調試方式尚存在一些不足,例如:
(1) 對遠程調試等常見的調試方式支持有限,在發(fā)起調試時往往需要開發(fā)人員手動完成填寫IP地址和端口號、上傳待調試程序、啟動調試服務器等工作,調試準備工作繁瑣;
(2) 對多平臺編譯工具鏈的支持有限,當開發(fā)環(huán)境下存在多個編譯工具鏈時,需要開發(fā)人員手動選擇與該工具鏈對應的調試工具和動態(tài)鏈接庫的搜索路徑等,不支持一鍵式調試;
(3) 對錯誤定位的支持有限,開發(fā)人員為提高調試效率往往會設置斷點并讓一部分代碼自動執(zhí)行,在傳統的調試方式下如果這部分代碼在自動執(zhí)行的過程出現異常,則需要重新設置斷點并再次發(fā)起調試,嚴重遲滯了調試進度;
(4) 對國產軟硬件平臺的支持有限,社區(qū)開源的Eclipse集成開發(fā)環(huán)境僅對X86平臺提供支持,國產系統廠商默認提供的集成開發(fā)環(huán)境則綁定了各自的軟硬平臺,對其它國產平臺的支持力度不足,不具備通用性和易用性.
因此,針對國產平臺飛速發(fā)展的現狀和CDT插件的上述不足之處,本文面向龍芯、申威等國產處理器以及中標麒麟、深度、JARI-Works等國產操作系統,設計開發(fā)了一個支持國產平臺并提供智能跟蹤調試功能的功能插件,增強了國產平臺下的調試功能,大大提高了調試效率.
反向調試技術[6]是實現程序智能跟蹤與調試的基礎,只有具備了能在程序執(zhí)行的整個歷史中來回穿梭的能力,才有可能精確定位到程序故障發(fā)生的位置.
目前的主流調試器GDB除了提供傳統的單步執(zhí)行調試的方式以外,在GDB 7版本之后,也提供了反向調試的功能,能夠通過reverse-next、reverse-nexti等指令對程序指令執(zhí)行歷史進行保存與回溯[7]. GDB的反向調試功能原理如圖1所示.
圖1 GDB反向調試示意圖
在圖1中可以看到GDB對每條指令都加入了斷點(int 3),然后在斷點發(fā)生時差分保存被調試應用程序的寄存器與內存狀態(tài). 由于對每條指令都加入了斷點,并在斷點處保存程序的運行狀態(tài),因此可以保證最高的歷史調試精度. 但是,GDB實現反向調試功能的代價就是數據記錄將嚴重影響被調試程序的性能,極端情況下會使該程序的執(zhí)行速度下降數個數量級,同時將導致記錄數據占用較大的磁盤空間.
“萬物皆插件”是Eclipse平臺的基本設計理念.Eclipse的主要功能便是提供了一套明確定義了接口、類、方法的插件機制,使各種定制化的插件能夠無縫集成到該平臺中,方便了功能組件的研發(fā)[3].
插件(Plug-in)是該平臺中最小的功能單元,平臺下的每項功能都由一個或多個插件完成. 每個插件的行為由具體的代碼實現定義,依賴項和提供的服務則由MANIFEST.MF、plugin.xml等插件清單文件定義.這種架構保證了插件僅在需要的時候被Eclipse載入,可以減少平臺啟動時間和內存消耗. 圖2展示了Eclipse平臺架構的組織形式,可以看到該平臺提供的絕大多數功能(如Java開發(fā)、C/C++開發(fā)等)均以外圍插件(如JDT、CDT等)的形式予以實現.
在開發(fā)人員啟動Eclipse之后,平臺運行時系統(Platform Runtime)將掃描默認插件的存放目錄plugins和鏈接文件的存放目錄links,在注冊表中添加插件的相關信息,并緩存各個插件的數據信息. 在開發(fā)人員使用某個插件提供的功能服務時,該插件才會被平臺運行時系統激活并調入內存,最后在使用結束的某個合適時機被清理. 擴展點在Eclipse中作為一個松耦合的功能模塊廣泛使用,插件在插件清單中聲明擴展點,提供接口和相關類的最小集合供他人使用. 其他插件聲明該擴展點的擴展項,實現合適的接口,并引用提供的類或基于提供的類進行創(chuàng)建. CDT提供了許多擴展點使用戶可以添加自定義功能,以符合用戶的需求[8].
圖2 Eclipse平臺架構
在國產平臺上,由于GDB反向調試的架構特定性[9],以及單指令中斷跟蹤的方式[10],導致其移植工作量巨大,而且性能開銷極大,因此不適合在國產平臺進行直接移植使用. 有鑒于此,本文借鑒Visual Studio的IntelliTrace功能[11],提出了基于事件的反向調試的技術方案.
在基于事件的反向調試技術中,調試器不再維護一個連續(xù)執(zhí)行、連續(xù)變化的程序執(zhí)行歷史. 亦即不再提供單步后退、單步前進等功能,而僅在程序運行過程中對程序的運行狀態(tài)在關鍵點進行快照,提供對快照的管理以輔助開發(fā)人員定位程序錯誤,開發(fā)人員無法對快照進行單步后退或者單步前進以查看單條指令或單行源碼執(zhí)行前后的程序狀態(tài),而只能跳躍式地在離散的快照之間穿梭. 這些快照的管理包括快照的存儲、快照的載入、在快照中前進與后退,以及讀取快照存儲下來的程序狀態(tài),以確定程序的故障. 在功能上,這種方式類似于IntelliTrace的反向調試,其總體架構如圖3所示. 在圖3中,插件首先通過通信模塊和本地或遠程的調試服務器取得聯系,隨后實時獲取堆棧、系統調用、信號、事件等調試信息,最后通過UI視圖和GDB、CDT等后臺工具插件以圖形化的方式向開發(fā)人員顯示調試信息.
在Linux操作系統下,應用程序與系統環(huán)境之間的交互主要是通過系統調用與信號,它們均會以超出應用程序指令預期的方式改變程序的行為. 其中信號對應了大部分程序運行時的錯誤,包括最常見的段錯誤. 因此,記錄下相應的事件,即可對相當部分難以調試的軟件故障進行回放調試,從而協助快速定位與解決故障. 基于事件的反向調試技術支持的事件如表1所示.
圖3 智能跟蹤調試工具總體架構
表1 支持的事件列表
監(jiān)聽程序的系統調用與信號,可以通過ptrace的PTRACE_SYSCALL以及PTRACE_GETSIGINFO等功能實現. 一旦發(fā)生了對應的系統調用與信號,調試器即可獲取對應的系統調用與信號的信息,并同時獲得被調試程序的狀態(tài),包括寄存器、堆、各個線程的調用棧與線程局部存儲等信息,并將其保存下來. 針對X11事件的監(jiān)控,由于XServer從硬件那里接收到所有輸入事件都會通知XRecord,因此當需要監(jiān)控X事件時,調試器只需把對應的代碼掛到XRecord循環(huán)中. 只有系統一有輸入事件產生,XRecord接著就通過事件循環(huán)告訴調試器,調試器再利用實時截獲到的輸入事件進行處理. 對于d-bus事件的監(jiān)控,由于所有的消息接口都來源于libdbus,調試器只要對d-bus收發(fā)函數通過PRELOAD的方式接管,即可對被調試程序的dbus事件進行跟蹤調試.
在發(fā)生特定事件,收到信號和發(fā)起系統調用的時候,只有記錄完整的堆棧信息才能對程序進行回溯. 考慮到性能開銷,本文設置了兩種記錄模式:
1) 轉儲模式:每個事件點發(fā)生的時候自動暫停所有運行的線程,記錄所有線程的調用棧信息,同時使用minidump方式轉儲指定的內存數據,記錄完成后自動恢復所有線程(用戶基本感受不到程序被暫停過). 由于每次轉儲都有進程切換,并且記錄的內存數據跟程序的復雜性和設置的轉儲粒度相關,程序越復雜或轉儲粒度越細致,記錄的內存數據就越多,會導致被調試程序運行變慢. 但是優(yōu)點是記錄了大量的內存數據,在回放的時候可以查看內存數據(具體跟轉儲粒度有關).本文轉儲模式特點如表2所示.
2) 快速模式:每個事件點只記錄當前發(fā)生事件的線程的調用棧里面每個棧幀的函數的參數、函數內已知局部變量的值、errno等全局變量、用戶態(tài)所有寄存器值等,不轉儲內存數據. 優(yōu)點是記錄過程沒有線程切換,速度快. 缺點是不能查看應用程序的全局變量的值和當前發(fā)生事件點時刻的其它線程的調用棧信息.
表2 本文轉儲模式與coredump對比
在轉儲階段,調試器跟蹤被調試應用程序記錄的跟蹤文件是調試器自定義的格式,GDB無法識別. 此外,調試器還要保證記錄階段和回放階段程序映射的內存地址空間是同一個地址范圍. 原生的GDB無法支持該功能,因此還需要在GDB基礎上擴展.
本文依據GDB Remote Serial Protocol實現一個RSP服務器[12,13],在RSP服務器里面啟動記錄的進程,用戶就可以用GDB連接到該服務器來進行回放調試.該方法節(jié)省了開發(fā)調試器的時間,同時也可以讓熟悉GDB的用戶快速熟悉反向調試的用法,而且方便與集成開發(fā)環(huán)境里的插件進行集成.
可視化調試插件包含以下4個部分:工程管理模塊、遠程通信模塊、調試調度模塊和后臺調試器. 工程管理模塊負責獲取當前待調試工程的具體信息,包括工具鏈類型、調試類型等. 如果工程采用的是交叉調試等模式時,遠程通信模塊會被調用,自動建立與目標機之間的通信連接,并收發(fā)本機和目標機之間的通信報文. 通信發(fā)起模塊對Eclipse平臺和CDT插件提供的API進行改寫,根據工程管理模塊提供的數據重新配置調試信息,并調用后臺調試器發(fā)起調試. 各模塊的關系如圖4所示.
圖4 智能調試插件模塊關系圖
開發(fā)人員首先需要在界面上點擊“調試”菜單項或命令按鈕以激活智能調試插件并進入調試流程,此時插件會自動讀取工程的配置信息,并根據工程的類型(本地或遠程)選擇不同的調試操作,如圖5所示.
對于本地調試,插件會自動讀取當前的軟硬件平臺信息,并選擇與之匹配的調試工具. 隨后,插件會再去尋找待調試工具,同時搜索調試所需的動態(tài)庫、相關聯的工程,并設置環(huán)境變量. 待所有準備工作就緒后,啟動調試. 整體流程如圖6所示.
遠程調試的流程與本地調試基本類似,但在進行準備工作之前需要與目標機建立網絡連接,并將待調試的程序通過網絡傳送至指定位置,隨后再進行搜索動態(tài)庫、配置環(huán)境變量等工作. 整體流程如圖7所示.
通過上述步驟處理,智能調試插件支持一鍵進入調試界面,將手動配置等操作屏蔽掉,可以顯著提高軟件的開發(fā)效率.
圖5 智能調試插件工作流程圖
圖6 本地調試流程圖
圖7 遠程調試流程圖
現通過Eclipse CDT原始插件和智能調試插件二者操作流程的對比以驗證智能調試插件方案的實施效果,測試環(huán)境配置見表3. 實驗結果表明本文設計的智能調試插件具備方案可行、簡便易用等特性.
使用傳統的調試插件時,首先需要在一個對話框中選擇調試類型,如“gdb/mi”、“gdbserver”和“remote gdb/mi”等. 對于“gdb/mi”這樣的本地調試,一般可以正常發(fā)起調試,但如果應用程序用到的動態(tài)鏈接庫不在/usr/lib、/usr/lib64、/usr/local/lib等常見的搜索路徑下時,則需要手動在調試配置項中指定搜索路徑. 倘若該工程依賴的多個動態(tài)鏈接庫分布在不同位置,則需要重復該工作若干次,較為繁瑣. 對于“gdbserver”、“remote gdb/mi”等遠程調試,除了本地調試的上述問題之外,還需要先將生成的二進制程序通過網絡發(fā)送到目標機,再在目標機端以指定參數啟動GDB Server,最后在CDT插件提供的界面中填入IP地址、端口號等信息并發(fā)起調試. 上述過程操作均需開發(fā)人員手動操作,過程較為繁復. 同時,傳統的CDT插件對國產平臺下的調試功能缺乏支持.
表3 智能調試插件測試配置
智能跟蹤調試技術和與之對應的功能插件從流程上簡化了用戶的調試操作,其為數不多的人工操作步驟中的絕大多數也可以通過簡單的鼠標點擊完成. 與傳統的CDT調試相比,該技術的優(yōu)點見表4.
表4 調試技術對比
在Eclipse平臺下發(fā)起調試只需兩步操作:(1) 右鍵點擊工程; (2) 依次選擇“Debug As”、“JARI-Works C/C++ Application”菜單項,如果工程信息與調試插件存儲的信息不匹配則會彈出提示窗口,此時根據具體情形用鼠標點擊對應的按鈕即可. 對應的界面如圖8所示,其中包含了A、B、C、D等若干個視圖. 其中,視圖A為項目管理視圖,通過工程名稱的后綴可以看到各工程的類型(應用工程、動態(tài)鏈接庫工程、驅動工程等)和工具鏈信息(本地開發(fā)工具鏈、龍芯系列工具鏈、飛騰工具鏈等); 視圖B為代碼編輯視圖,顯示當前調試的代碼行,通過Alt+快捷鍵的形式可以控制調試方向,如F6為正向單步調試的快捷鍵,則Alt+F6為反向單步調試,每個代碼行改變的變量在視圖E中高亮顯示,打印信息則在視圖D中顯示; 視圖C為遠程連接視圖,當前集成開發(fā)環(huán)境保存的遠程連接均在此顯示,本地調試時此視圖無用,遠程調試時調試插件會根據工程信息自動建立調試連接,并在此視圖中予以顯示.
圖8 調試界面示意圖
本文闡述了反向調試的基本原理和Eclipse的插件體系架構,針對國產平臺性能較低的瓶頸,設計了基于事件的反向調試方案,通過對程序運行過程中關鍵點進行快照,結合高性能的轉儲模塊,為國產平臺提供易用的智能調試解決方案.
通過設計Eclipse下基于事件的反向調試插件,可以方便地進行故障定位,從而提高調試效率,但還存在如下不足:(1) 轉儲時可設置粒度不足,尤其針對性能較弱的國產處理器平臺; (2) 調試視圖設計較為簡單,未全面反應調試時程序狀態(tài); (3) 反向調試目前只支持Alt+快捷鍵的方式操作,在工具欄中缺少按鈕控件,不能全面覆蓋開發(fā)人員的使用習慣; (4) 隨著國產平臺的推陳出新,本技術覆蓋的工具鏈仍需繼續(xù)擴充. 這些問題將在后續(xù)工作中嘗試改進.