摘 要:采用反射和開放編譯技術,設計并實現了一個面向對象程序靜態分析器,對開放編譯器OpenC++進行了擴展與改進。通過實驗研究表明,該程序靜態分析器可以得到全面的系統依賴信息,可以有效地輔助程序理解。
關鍵詞:面向對象;程序理解;逆向工程;反射;開放編譯;靜態分析
中圖分類號:TP311.5 文獻標志碼:A
文章編號:1001-3695(2008)09-2706-03
Static analyzer of objectoriented system based on open compiler
LIU Pengfei,HU Shengming,CHEN Ping,LIANG Ruoying
(School of Computer Science Technology, Xidian University, Xi’an 710071, China)
Abstract:This paper designed and implemented a static analyzer of objectoriented system based on the techniques of reflection and open compiler.The experiments show that the analyzer can get whole static dependency information and do well in assistant on program understanding.
Key words:objectoriented; program understanding; reverse engineering; reflection; open compiler; static analysis
隨著軟件技術的發展和社會需求的增長,軟件系統變得越來越復雜,更新速度也越來越快。軟件維護、遺產系統[1](legacy system)增多以及軟件的再工程問題均是軟件產業面臨的重要課題。以上這些問題的有效解決在很大程度上都依賴于程序理解。程序理解是軟件維護、遺留系統的現代化改造以及軟件的再工程等領域的重要技術。全面深入的靜態信息是程序理解和逆向工程的前提和關鍵。程序理解是一項相當復雜的工作。方法上需要將傳統的人工閱讀方法與分析工具或自動化轉換手段有機地融合,技術上則要求所研發的工具具有較強的壯健性,能處理應付多樣化的事務描述。從具體實現方式講,分為靜態分析工具和動態分析工具。動態方法雖然能清晰地體現對象交互的時序性,卻由于植入的信息量大,運行植入后的目標系統的低效成了該方法的瓶頸;其次,動態方法得不到完整的程序依賴信息,無法把握系統的整體結構。
傳統的靜態分析工具如Sniff+[2]、MOOSE[3]、ATT的CIA++[4,5](C++ information abstraction)等也能快速地得到程序依賴信息,但所得程序依賴缺少條件、循環、跳轉等精確的控制信息。本文根據C++語言的靜態類型特點,提出利用開放編譯器OpenC++對目標代碼進行深入的靜態分析,以得到目標程序完整的程序依賴信息,輔助程序理解。
1 反射和開放編譯
反射是關于并作用于計算系統本身的推理過
程,這一活動涉及訪問并可以部分地改變整個系統,從而影響自身的計算。反射作用的目的在于動態地修改計算系統的內部結構,使得計算更為有效[6]。反射的實現通常將系統細分為兩層,即基層和元層。應用的功能在基層實現。元層完成觀測、操縱基層的結構和行為的功能。面向對象系統通過引入元對象來實現反射。一個對象被和一個元對象聯系在一起時,這個元對象表示該對象的元信息[7]。
開放編譯思想是將編譯過程用元對象協議MOP(meta object protocol)向用戶開放,允許用戶自己通過編制實現元對象的程序片段來干預編譯過程,以達到特定的應用目的。MOP是在面向對象系統中實現反射技術的一種體系結構,是一種API接口,它開放了語言的頂層,允許用戶為了特殊需要去調整語言的設計和實現。按照實現的反射類型,MOP可以分為計算反射和結構反射兩大類;如果從反射的時機來看,可以區分為編譯時MOP和運行時MOP兩類;如果從元接口的類型來分,可以分為基于對象的MOP和基于類的MOP兩類。編譯時MOP是在編譯基層程序時調用元對象進行翻譯,所得到的新的基層程序將替代原有程序。元對象只存在于編譯時,在運行時并不存在。元層信息在運行時構造和存在的MOP稱為運行時MOP。相對于編譯時MOP,運行時MOP會帶來動態創建、查詢元對象的開銷,性能上的損失較大,且也無法利用編譯器在性能優化上的便利。本文使用OpenC++的MOP是實現結構反射的、基于類的、編譯時的MOP[8,9]。
2 靜態分析器的實現技術
2.1 OpenC++的工作原理
OpenC++編譯器的工作過程由三個階段組成:a)預處理, 從OpenC++到C++的源到源(source to source)轉換;b)標準C++編譯器的編譯;c)鏈接[10]。OpenC++ MOP是在第二階段控制轉換的接口,指明OpenC++的擴展特征如何轉換成普通的C++代碼,如圖1所示。
在OpenC++中,常規的C++程序稱為基級程序(baselevel program);通過MOP接口操縱基級程序元信息的程序稱為元級程序(metalevel program)。在圖1中,基級程序經C++預處理器處理后,被分割成許多小的代碼片段(codefragment)。這些代碼片段由類元對象轉換后,再重新組合成一個完整的C++程序。最后OpenC++將轉換后的C++程序傳遞給標準的C++編譯器,如GNU C++。這種過程給了用戶選擇后端C++編譯器的方便。
在OpenC++中,代碼片段由Ptree元對象以分析樹的形式表示。雖然元對象與C++中的對象類似,但它們只存在于編譯器,并且代表著基級程序的元狀態,因此稱它們為元對象,而不能簡單地稱為對象。
2.2 C++對靜態解析的支持
OpenC++沒有提供可直接獲取OO系統靜態信息的接口。分析OpenC++對源程序進行解析(parsing)和轉換(translation)的過程后發現:通過在其轉換路徑上的一些關鍵點上添加代碼,可以實現獲取靜態信息這一功能。
OpenC++讀入一個源文件后,逐段逐段地對其掃描。這里的段(fragment)是按聲明(declaration)區分的。OpenC++總共設置了七類聲明,即空聲明、TEMPLATE、METACLASS、EXTERN、NAMESPACE、USING和常規聲明。類定義、函數定義、非內聯方法的定義均屬于常規聲明的范疇。
經過詞法分析、語法分析后,OpenC++為每個段構造出相應的分析樹。接著,這棵分析樹在元對象的控制下,由根開始一層一層的、在元對象的控制下、按照后序的方式被轉換。整個過程如圖2所示。
如果利用OpenC++來獲取目標系統的動態信息,則可以在轉換時進行植入等自定義動作。由于只需要捕獲目標系統的靜態信息,無須對目標系統源碼進行轉換。這時的OpenC++和普通的 C++一樣,在需要轉換時提取目標代碼的靜態信息即可。
2.2.1 獲取函數/方法調用關系的實現
OpenC++為函數/方法調用提供了一個ClassWalker::TranslateFuncall()接口,允許開發者重載該方法以實現對方法調用語義的修改或擴充。下面以一個簡單的例子說明OpenC++處理一個函數/方法調用的過程:
class A{…};
void f(){…};
void main(){
A obj;(1)
obj.M(100);(2)
fn();(3)
}
main()的部分分析樹如圖3所示。圖3中A、B等表示非葉子節點;{“fn”,2}等表示葉子節點,其中第一個域表示字符串首地址,第二個域表示字符長度; PtreeBlock等表示某一特定類型的非葉子節點。
當元對象Walker(Walker子類ClassWalker的一個對象,用于遍歷分析樹)走到對應(3)的PtreeExprStatement節點下的PtreeFuncallExpr節點后,會調用ClassWalker::TranslateFuncall(…)進行轉換(通過Visitor設計模式)。由此可知ClassWalker::TranslateFuncall(…)是處理函數/方法調用的關鍵所在。
2.2.2 獲取循環、分支、跳轉信息的實現
由上可知獲取目標系統靜態信息的關鍵在于找到元對象對由目標系統源代碼所生成的分析樹進行轉換的關鍵點。OpenC++針對循環、分支、跳轉信息分別定義有元對象PtreeXXXStatement(For,While,If,Break,Do)。這些元對象的轉換由Walker::TranslateXXX(Ptree*) 負責,故此接口也就是獲取此類信息的關鍵點所在。
2.3 靜態信息抽取算法
從OpenC++的工作原理可知,OpenC++對目標程序構造相應的分析樹,然后利用元對象Walker或其子類對分析樹進行遍歷,因此OpenC++ MOP 只需在編譯時提供對C++中一些關鍵特性的控制能力即可,而不必在如何進行語法分析、如何構造語法樹這樣的細節上花費太多的精力。針對對象的每一個基本動作,如方法調用、數據讀寫、對象創建以及條件、循環、跳轉等控制信息,元對象都有對應的方法用于定制對象的這一種行為。其算法如下:
輸入:目標系統TS=(S1,S2,…,Sn)
輸出:XML格式的靜態信息文件SF
步驟:
foreach Si=rProgram() do//對程序聲明段進行分段分析
Ptree* root = rFunctionBody();
//分析函數體的定義體, root指向生成的分析樹AST的根節點
Walker w;
Ptree* root2=w.Translate(root);
//root2指向轉換后分析樹AST'的根節點
do//調用w.Translate(root)
root—>Translate(w);
do//調用w.Translate(w)
w.TranslateFunctionImplementation(root2);
do//調用w.TranslateFunctionImplementation()
foreach Ptree* node E AST do
case
PtreeFuncallExpr://函數調用節點
do//調用w.TranslateFuncall()
生成消息節點加入XML文件
od
PtreeForStatement:
PtreeWhileStatement:
PtreeBreakStatement:
PtreeDoStatement:
PtreeIfStatement:
do//調用w.TranslateIf()
if含有else
生成含有else的消息節點加入XML文件
else if 不含有else
生成不含有else的消息節點加入XML文件
od
PtreeXXX://其他類型XXX節點:
//調用父類的
//ClassWalker::TranslateXXX()
//Walker::TranslateXXX()
esac//End of case
od//End of foreach
od//End of 調用w.TranslateFunctionImp()
od//End of 調用w.Translate(w)
od//End of 調用w.Translate(root)
od//End of foreach
3 實驗研究
根據本文設計實現的面向對象程序靜態分析器已在本文開發的逆向工程的工具XDRE(XiDian reverse engineering)中得到應用。下面通過對一個例子程序的靜態信息提取,來測試該分析器的有效性。采用的用例是一個面向對象的停車場仿真系統。它是基于Windows平臺,使用VC++開發的基于MFC的工程。共有45個C++源文件(大小共102 KB),21個VC++類。
所得靜態信息文件存儲在CallNodeRelation.xml中,共得到函數/方法調用節點97個。限于篇幅下面僅列出包含條件的靜態信息片段。其中callnode_id表示所得依賴信息的序號。此靜態信息片段對應的代碼片斷如下:
void CCarGenerator::setHorizontalCarDistance(CCar* acar, CCar* another) {
//如果兩輛車均在水平方向
if((acar—>m_icurrentx) >= (another—>m_icurrentx)){another—>doWaiting();//后面水平車輛停下
}
else if((acar—>m_icurrentx) < (another—>m_icurrentx)) {
acar—>doWaiting();//后面水平車輛停下
}
}
從實驗結果可以看出,所得語句級別的程序依賴包括了帶有嵌套的條件、循環、跳轉等精確的控制信息。深入函數、方法內部,表現出了函數間的全面、深層次調用關系,而不僅僅是簡單函數依賴關系,為得到符合UML2.0的序列圖提供了足夠的信息。
4 結束語
靜態信息是程序理解和逆向工程的前提和關鍵。傳統的靜態分析缺少條件、循環、跳轉等控制信息,對程序的控制流描述不夠精確;動態分析恢復出的信息過少,且存在目標系統運行效率低下的瓶頸問題。基于上述問題,本文提出了基于反射和開放編譯技術的靜態分析方式,可以得到全面深入的靜態信息,有利于對目標程序的程序理解和逆向工程。由于沒有改寫底層的系統調用,沒有采用平臺相關的技術,使得本工具具有優秀的可移植性,只需重新編譯即可運行于Linux、Windows和Solaris等支持標準C/C++的系統。開放編譯技術的引入則增強了本分析器的自動化程度,無須修改源代碼,減輕了開發人員的負擔。
本文給出的程序靜態分析器以C++語言為基礎。隨著開放編譯技術應用面的擴展,許多主流的面向對象語言也都支持開放編譯,如Java語言,因此開放編譯的機制在面向對象程序靜態解析方面具有一定通用性。而利用開放編譯器技術也有助于軟件測試和度量方面的進一步研究[11]。
參考文獻:
[1]BENNETT K.Legacy systems:coping with success[J].IEEE Journal,1995,12(1):19-22.
[2]BELLAY B,GALL H.A comparison of four reverse engineering tools[C]//Proc of the 4th WCRE. Amsterdam:[s.n.],1997.
[3]RICHNER T,STEPHANE.Recovering highlevel views of objectoriented applications from static and dynamic information[C]//Proc of ICSM.Oxford:[s.n.],1999.
[4]CHEN Y F,GANSNER E R,KOUTSOFIOS E.A C++ data model supporting reachability analysis and dead code detection[C]//Proc of the 6th European Software Engineering Conference. Zurich:[s.n.],1998:682-694.
[5]GRASS J.Objectoriented design archeology with CIA++[J].Computing Systems,1992,5(1):5-67.
[6]陳平.反射結構和對象標志的研究[D].西安:西安電子科技大學,1991.
[7]ELIANE M A,ROSA C A.A fault injection approach based on reflective programming[C]//Proc of Dependable Systems and Networks. New York:[s.n.],2000.
[8]SHIGERU C.A metaobject protocol for C++[C]//Proc of OOPSLA. New York:[s.n.],1995.
[9]SHIGERU C.A stuty of compiletime metaobject protocol[D].Tokyo:Graduate School of Science,The University of Tokyo,1996.
[10]SHIGERU C.OpenC++ tutorial[EB/OL].(200110)[2002-05].http://www.csg.is.titech.ac.jp/~chiba/openc++.html.
[11]孫青巖, 陳平.基于開放編譯器的內存泄露檢測機制[J].計算機工程, 2004,30(20):42-44.