張紅
摘要:在嵌入式系統(tǒng)調(diào)試環(huán)境下,需將大量的結(jié)構(gòu)體變量輸出到診斷軟件,進(jìn)行解析與呈現(xiàn),而結(jié)構(gòu)體數(shù)量龐大,且容易變化。在軟件快速迭代開發(fā)階段,迫切需要使結(jié)構(gòu)體解析過程自動化。最關(guān)鍵的一步,是實(shí)現(xiàn)結(jié)構(gòu)體定義數(shù)據(jù)庫的提取。此文主要研究基于Clang編譯器,實(shí)現(xiàn)從前端編譯結(jié)構(gòu)體定義文件生成的抽象語法樹中提取結(jié)構(gòu)體定義信息。實(shí)驗(yàn)結(jié)果表明,該方法能準(zhǔn)確的實(shí)現(xiàn)從結(jié)構(gòu)體定義文件提取結(jié)構(gòu)體定義XML數(shù)據(jù)庫。
關(guān)鍵詞: Clang編譯器;抽象語法樹;信息提??;結(jié)構(gòu)體定義
中圖分類號:TP393 文獻(xiàn)標(biāo)識碼:A 文章編號:1009-3044(2017)06-0019-03
Abstract: In the debugging environment of the embedded system, the structure variables which are very large in quantity and mutable, should be output to the diagnostic software to be parsed and presented. In the rapidly iteration development phase, it is an urgent need to make the structure parsing process automation. It is the most crucial step to realize the extraction of structure definition database. This paper mainly studies how to extract structure definition information from the abstract syntax tree generated by the fronted compiler Clang. The experimental results show that this method can realize the structure XML database form the definition files accurately.
Key words:Clang compiler; abstract syntax tree (AST); information extraction; structure definition
1 概述
在嵌入式軟件開發(fā)過程中,為了快速分析軟件運(yùn)行過程,定位問題,將系統(tǒng)運(yùn)行中的各類診斷信息輸出到診斷軟件解析,而大量的診斷信息是基于結(jié)構(gòu)體類型,在嵌入式系統(tǒng)開發(fā)前期,采用手工編寫解析結(jié)構(gòu)體的函數(shù)來實(shí)現(xiàn)。但結(jié)構(gòu)體定義在開發(fā)調(diào)試過程中會經(jīng)常發(fā)生變更,結(jié)構(gòu)體解析庫就需要同步更新維護(hù),隨著系統(tǒng)工程模塊化程度提高,規(guī)模也越來越大,涉及的人員越來越多,結(jié)構(gòu)體定義與解析庫之間更新不同步的問題越來越頻繁,維護(hù)成本越來越高,嚴(yán)重影響了軟件開發(fā)迭代進(jìn)度。
本文在開源編譯框架LLVM的前端編譯器Clang的基礎(chǔ)上,通過開發(fā)一個(gè)Clang前端插件,實(shí)現(xiàn)從抽象語法樹AST(Abstract Syntax Tree)中進(jìn)行結(jié)構(gòu)體數(shù)據(jù)庫提取。相比于手工編寫解析函數(shù),將繁重的開發(fā)和維護(hù)工作量降到0,大大提高了工作效率。
本文第二節(jié)介紹Clang 前端插件的編寫、編譯與執(zhí)行方法;給出結(jié)構(gòu)體數(shù)據(jù)庫提取插件的實(shí)現(xiàn)方法;第三節(jié)對本文進(jìn)行總結(jié)。
2 相關(guān)工作
2.1 Clang 前端插件開發(fā)介紹
Clang作為LLVM開源編譯框架的一種前端編譯器,實(shí)現(xiàn)編譯過程中的詞法分析,語法分析,類型檢查,中間代碼生成。Clang對用戶進(jìn)行前端插件的開發(fā)提供了很好的支持,前端操作的切入點(diǎn)是抽象類FrontendAction,此接口支持在前端編譯過程中執(zhí)行插件定制的操作。AST消費(fèi)者的切入點(diǎn)是抽象類ASTConsumer,此接口支持對抽象語法樹的訪問。
本文是研究在編譯過程中從抽象語法樹提取結(jié)構(gòu)體定義相關(guān)的信息,面向AST消費(fèi)者前端操作的抽象接口類為FrontendAction的子類ASTFrontendAction,插件中前端操作基類選擇ASTFrontendAction的子類PluginASTAction。自定義的AST消費(fèi)者基類選擇ASTConsumer。
2.1.1 編寫Clang插件
1) 定義繼承自PluginASTAction的自定義類StructFrontendAction。重載三個(gè)成員函數(shù):
①用于創(chuàng)建抽象語法樹的Consumer類。
ASTConsumer *CreateASTConsumer(CompilerInstance &CI, llvm::StringRef);
②用于分析此插件執(zhí)行命令傳入的參數(shù)。
bool ParseArgs(const CompilerInstance &CI,const std::vector
③用于打印輸出此插件執(zhí)行的help信息。
void PrintHelp(llvm::raw_ostream& ros);
2) 定義繼承自ASTConsumer的抽象語法樹ASTStructConsumer類。
重載virtual bool HandleTopLevelDecl(DeclGroupRef DG),實(shí)現(xiàn)從抽象語法樹節(jié)點(diǎn)中提取所需信息。為了將分析AST過程中得到的信息組織成XML文件,可在此類的構(gòu)造函數(shù)中創(chuàng)建XML文件,并構(gòu)造初始的節(jié)點(diǎn)框架
3) 注冊插件
static FrontendPluginRegistry::Add
2.1.2 編譯Clang插件
參考開源代碼Clang/Examples下的插件示例將編譯環(huán)境配置好后,進(jìn)入build目錄執(zhí)行:make clang,編譯完成后,進(jìn)入build/tools/clang/example下的插件編譯目錄,執(zhí)行make,即可對插件進(jìn)行編譯。
2.1.3 執(zhí)行clang插件
$clang –cc1 –load StructFrontendAction.so –plugin my-plugin-name compileFile
Clang –cc1為編譯器,-load將加載所有注冊的插件,-plugin指定加載特定的插件,若要將參數(shù)傳遞到插件里,可以使用-plugin-arg-
2.2 結(jié)構(gòu)體信息提取的實(shí)現(xiàn)
結(jié)構(gòu)體定義信息包括結(jié)構(gòu)體名稱、大小和成員個(gè)數(shù),結(jié)構(gòu)體成員變量名稱、類型名稱,成員變量類型的基礎(chǔ)類型,成員變量在結(jié)構(gòu)體中的偏移值,成員變量的大小。
結(jié)構(gòu)體的成員的類型分以下六類:1)基本的系統(tǒng)內(nèi)置(builtin)數(shù)據(jù)類型,如unsigned char,unsigned short,int等;2)重定義(typedef)數(shù)據(jù)類型,如對內(nèi)置數(shù)據(jù)類型、結(jié)構(gòu)體類型、枚舉類型的重定義;3)指針;4)數(shù)組;5)聯(lián)合體;6)位域。
以ASTConsumer的接口函數(shù)HandleTopLevelDecl(DeclGroupRef DG)為入口點(diǎn),從AST的頂層節(jié)點(diǎn)TranslationUnitDecl開始, 該節(jié)點(diǎn)下的子節(jié)點(diǎn)類型有TypedefDecl,EnumDecl,RecordDecl,F(xiàn)unctionDec。結(jié)構(gòu)體定義屬于RecordDecl,自定義的成員類型定義信息來自于TypedefDecl,而枚舉類型信息在EnumDecl節(jié)點(diǎn)。因此需要分析AST中的TranslationUnitDecl頂級節(jié)點(diǎn)下的所有TypedefDecl,EnumDecl和RecordDecl節(jié)點(diǎn)。每種節(jié)點(diǎn)類型的都是繼承自NamedDecl。
首先獲取Decl的名稱,可以通過NamedDecl的getNameAsString()實(shí)現(xiàn)。有些可能是匿名,獲取Decl名字為空,如“typedef { …}TypeA;”,這時(shí)可以通過getTypedefNameForAnonDecl()獲取匿名Decl的TypedefNameDecl,若該匿名對象的TypedefNameDecl為空,該Decl將不可能作為結(jié)構(gòu)體成員類型,可以忽略。
下面依次介紹TypedefDecl、EnumDecl,和RecordDecl節(jié)點(diǎn)信息的提取。
1)TypedefDecl節(jié)點(diǎn)
如“typedef A B;”,獲取到的Decl的名稱是B,此時(shí)需要分析出B的原始類型名稱,原始類型可以是內(nèi)置數(shù)據(jù)類型,或自定義結(jié)構(gòu)體類型或枚舉類型等,提取出新類型與基礎(chǔ)類型的對應(yīng)關(guān)系。當(dāng)A不屬于基礎(chǔ)類型,將繼續(xù)分析A的基礎(chǔ)類型,直至找到基礎(chǔ)類型C作為B的基礎(chǔ)類型。
將提取到的新類型名稱與基礎(chǔ)類型名稱的信息作為typedefs的子節(jié)點(diǎn)存入XML文件:
2)EnumDecl 節(jié)點(diǎn)
分析出枚舉類型名字后,通過遍歷EnumDecl的枚舉向量來提取枚舉成員字符串與數(shù)值,將信息作為enums的子節(jié)點(diǎn)寫入XML文件:
3)RecordDecl節(jié)點(diǎn)
RecordDecl下除了struct類型還有其它類型,本文只關(guān)注RecordDecl下的struct類型,可以通過下isStruct()判斷。在結(jié)構(gòu)體成員不為空,即field_empty()為FALSE時(shí),通過訪問ASTRecordLayout對象,getFieldCount()可以獲取結(jié)構(gòu)體成員個(gè)數(shù)FiledCount,getSize()可以獲取結(jié)構(gòu)體的大小。將信息作為structs的子節(jié)點(diǎn)存入XML文件:
通過遍歷RecordDecl::field_iterator來獲取結(jié)構(gòu)體成員變量信息,getFieldIndex()可以獲取成員變量索引值,getFieldOffset(fieldIndex)可以獲取索引值為fieldIndex的成員在結(jié)構(gòu)體中的偏移值,getName()可以獲取該成員的變量名,getType()可以獲取該成員的類型,成員變量的大小需要通過getASTContext()獲取ASTContext對象,進(jìn)而由getTypeSize(fieldType)獲取。將信息作為該struct節(jié)點(diǎn)的子節(jié)點(diǎn)存入XML文件:
分析成員變量類型時(shí),需對聯(lián)合體、指針、數(shù)組、位域類型信息作進(jìn)一步提取。
①對聯(lián)合體類型,可以通過isUnionType()來判斷,union節(jié)點(diǎn)對象為RecordDecl類型,通過ASTRecordLayout可以獲取union下的成員信息。
②對位域類型,可以通過isBitField()來判斷,需進(jìn)一步提取位寬信息,通過 (*iter)->getBitWidthValue((*iter)->getASTContext() );來實(shí)現(xiàn),其中iter 為RecordDecl::field_iterator 。將位寬信息作為該成員變量field節(jié)點(diǎn)的子節(jié)點(diǎn)存入XML文件:
③對指針類型,可以通過isPointerType()來判斷,提取指針?biāo)割愋偷拿Q和原始類型信息。將信息作為field節(jié)點(diǎn)的子節(jié)點(diǎn)存入XML文件:
④對數(shù)組類型,可通過isArrayType()來判斷,需要獲取數(shù)組對象類型信息與數(shù)組的大小。通過getElementType()獲取數(shù)組成員類型,然后進(jìn)一步分析該類型的原始類型。結(jié)構(gòu)體成員中的數(shù)組均是定長數(shù)組,即使作為變長使用的零數(shù)組,在編譯階段也是作為定長數(shù)組處理。 對于定長數(shù)組ConstantArrayType可以通過getSize()獲取數(shù)組的大小。將數(shù)組類型成員信息存入XML文件:
對于多維數(shù)組,可以對數(shù)組成員進(jìn)行數(shù)組類型的遞歸分析。
3 總結(jié)
本文針對嵌入式系統(tǒng)中,對結(jié)構(gòu)體類型診斷信息解析維護(hù)工作量大的問題,提出了采用基于clang編譯器的前端插件在編譯過程中,通過訪問抽象語法樹節(jié)點(diǎn),提取結(jié)構(gòu)體定義信息數(shù)據(jù)庫的方法,按一定策略組織成結(jié)構(gòu)體定義的XML數(shù)據(jù)庫。本方法已應(yīng)用于嵌入式系統(tǒng)調(diào)試中,為結(jié)構(gòu)體自動解析提供了基礎(chǔ),大大提高了軟件迭代開發(fā)效率。
參考文獻(xiàn):
[1] LLVM[EB/OL]. http://www.llvm.org .
[2] Clang[EB/OL]. http://clang.llvm.org .
[3] 周睿. 基于Clang編譯器的程序結(jié)構(gòu)分析器設(shè)計(jì)[J]. 計(jì)算機(jī)時(shí)代,2016(10):54-56.
[4] 高傳平,談利群,宮云戰(zhàn). 基于抽象語法樹的代碼靜態(tài)自動測試方法研究[J]. 北京化工大學(xué)學(xué)報(bào):自然科學(xué)版,2007(S1):25-29.
[5] 章磊. Clang上的C/C++過程間分析和漏洞發(fā)掘[D].合肥:中國科學(xué)技術(shù)大學(xué),2009.
[6] 陳火旺.程序設(shè)計(jì)語言編譯原理[M]. 北京: 國防工業(yè)出版社, 2000.
[7] Kenneth C Louden.編譯原理及實(shí)踐[M]. 馮博琴,譯.北京: 機(jī)械工業(yè)出版社, 2000.
[8] 高艷玲. 編譯原理——C教學(xué)編譯器設(shè)計(jì)[J]. 電腦知識與技術(shù),2009(18):4932-4933.