張紅
摘要:在嵌入式系統(tǒng)調(diào)試環(huán)境下,需將大量的結(jié)構(gòu)體變量輸出到診斷軟件,進(jìn)行解析與呈現(xiàn),而結(jié)構(gòu)體數(shù)量龐大,且容易變化。在軟件快速迭代開發(fā)階段,迫切需要使結(jié)構(gòu)體解析過(guò)程自動(dòng)化。最關(guān)鍵的一步,是實(shí)現(xiàn)結(jié)構(gòu)體定義數(shù)據(jù)庫(kù)的提取。此文主要研究基于Clang編譯器,實(shí)現(xiàn)從前端編譯結(jié)構(gòu)體定義文件生成的抽象語(yǔ)法樹中提取結(jié)構(gòu)體定義信息。實(shí)驗(yàn)結(jié)果表明,該方法能準(zhǔn)確的實(shí)現(xiàn)從結(jié)構(gòu)體定義文件提取結(jié)構(gòu)體定義XML數(shù)據(jù)庫(kù)。
關(guān)鍵詞: Clang編譯器;抽象語(yǔ)法樹;信息提??;結(jié)構(gòu)體定義
中圖分類號(hào):TP393 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):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ā)過(guò)程中,為了快速分析軟件運(yùn)行過(guò)程,定位問(wèn)題,將系統(tǒng)運(yùn)行中的各類診斷信息輸出到診斷軟件解析,而大量的診斷信息是基于結(jié)構(gòu)體類型,在嵌入式系統(tǒng)開發(fā)前期,采用手工編寫解析結(jié)構(gòu)體的函數(shù)來(lái)實(shí)現(xiàn)。但結(jié)構(gòu)體定義在開發(fā)調(diào)試過(guò)程中會(huì)經(jīng)常發(fā)生變更,結(jié)構(gòu)體解析庫(kù)就需要同步更新維護(hù),隨著系統(tǒng)工程模塊化程度提高,規(guī)模也越來(lái)越大,涉及的人員越來(lái)越多,結(jié)構(gòu)體定義與解析庫(kù)之間更新不同步的問(wèn)題越來(lái)越頻繁,維護(hù)成本越來(lái)越高,嚴(yán)重影響了軟件開發(fā)迭代進(jìn)度。
本文在開源編譯框架LLVM的前端編譯器Clang的基礎(chǔ)上,通過(guò)開發(fā)一個(gè)Clang前端插件,實(shí)現(xiàn)從抽象語(yǔ)法樹AST(Abstract Syntax Tree)中進(jìn)行結(jié)構(gòu)體數(shù)據(jù)庫(kù)提取。相比于手工編寫解析函數(shù),將繁重的開發(fā)和維護(hù)工作量降到0,大大提高了工作效率。
本文第二節(jié)介紹Clang 前端插件的編寫、編譯與執(zhí)行方法;給出結(jié)構(gòu)體數(shù)據(jù)庫(kù)提取插件的實(shí)現(xiàn)方法;第三節(jié)對(duì)本文進(jìn)行總結(jié)。
2 相關(guān)工作
2.1 Clang 前端插件開發(fā)介紹
Clang作為L(zhǎng)LVM開源編譯框架的一種前端編譯器,實(shí)現(xiàn)編譯過(guò)程中的詞法分析,語(yǔ)法分析,類型檢查,中間代碼生成。Clang對(duì)用戶進(jìn)行前端插件的開發(fā)提供了很好的支持,前端操作的切入點(diǎn)是抽象類FrontendAction,此接口支持在前端編譯過(guò)程中執(zhí)行插件定制的操作。AST消費(fèi)者的切入點(diǎn)是抽象類ASTConsumer,此接口支持對(duì)抽象語(yǔ)法樹的訪問(wèn)。
本文是研究在編譯過(guò)程中從抽象語(yǔ)法樹提取結(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)建抽象語(yǔ)法樹的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的抽象語(yǔ)法樹ASTStructConsumer類。
重載virtual bool HandleTopLevelDecl(DeclGroupRef DG),實(shí)現(xiàn)從抽象語(yǔ)法樹節(jié)點(diǎn)中提取所需信息。為了將分析AST過(guò)程中得到的信息組織成XML文件,可在此類的構(gòu)造函數(shù)中創(chuàng)建XML文件,并構(gòu)造初始的節(jié)點(diǎn)框架
3) 注冊(cè)插件
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,即可對(duì)插件進(jìn)行編譯。
2.1.3 執(zhí)行clang插件
$clang –cc1 –load StructFrontendAction.so –plugin my-plugin-name compileFile
Clang –cc1為編譯器,-load將加載所有注冊(cè)的插件,-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ù)類型,如對(duì)內(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,自定義的成員類型定義信息來(lái)自于TypedefDecl,而枚舉類型信息在EnumDecl節(jié)點(diǎn)。因此需要分析AST中的TranslationUnitDecl頂級(jí)節(jié)點(diǎn)下的所有TypedefDecl,EnumDecl和RecordDecl節(jié)點(diǎn)。每種節(jié)點(diǎn)類型的都是繼承自NamedDecl。
首先獲取Decl的名稱,可以通過(guò)NamedDecl的getNameAsString()實(shí)現(xiàn)。有些可能是匿名,獲取Decl名字為空,如“typedef { …}TypeA;”,這時(shí)可以通過(guò)getTypedefNameForAnonDecl()獲取匿名Decl的TypedefNameDecl,若該匿名對(duì)象的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ǔ)類型的對(duì)應(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)
分析出枚舉類型名字后,通過(guò)遍歷EnumDecl的枚舉向量來(lái)提取枚舉成員字符串與數(shù)值,將信息作為enums的子節(jié)點(diǎn)寫入XML文件:
3)RecordDecl節(jié)點(diǎn)
RecordDecl下除了struct類型還有其它類型,本文只關(guān)注RecordDecl下的struct類型,可以通過(guò)下isStruct()判斷。在結(jié)構(gòu)體成員不為空,即field_empty()為FALSE時(shí),通過(guò)訪問(wèn)ASTRecordLayout對(duì)象,getFieldCount()可以獲取結(jié)構(gòu)體成員個(gè)數(shù)FiledCount,getSize()可以獲取結(jié)構(gòu)體的大小。將信息作為structs的子節(jié)點(diǎn)存入XML文件:
通過(guò)遍歷RecordDecl::field_iterator來(lái)獲取結(jié)構(gòu)體成員變量信息,getFieldIndex()可以獲取成員變量索引值,getFieldOffset(fieldIndex)可以獲取索引值為fieldIndex的成員在結(jié)構(gòu)體中的偏移值,getName()可以獲取該成員的變量名,getType()可以獲取該成員的類型,成員變量的大小需要通過(guò)getASTContext()獲取ASTContext對(duì)象,進(jìn)而由getTypeSize(fieldType)獲取。將信息作為該struct節(jié)點(diǎn)的子節(jié)點(diǎn)存入XML文件:
分析成員變量類型時(shí),需對(duì)聯(lián)合體、指針、數(shù)組、位域類型信息作進(jìn)一步提取。
①對(duì)聯(lián)合體類型,可以通過(guò)isUnionType()來(lái)判斷,union節(jié)點(diǎn)對(duì)象為RecordDecl類型,通過(guò)ASTRecordLayout可以獲取union下的成員信息。
②對(duì)位域類型,可以通過(guò)isBitField()來(lái)判斷,需進(jìn)一步提取位寬信息,通過(guò) (*iter)->getBitWidthValue((*iter)->getASTContext() );來(lái)實(shí)現(xiàn),其中iter 為RecordDecl::field_iterator 。將位寬信息作為該成員變量field節(jié)點(diǎn)的子節(jié)點(diǎn)存入XML文件:
③對(duì)指針類型,可以通過(guò)isPointerType()來(lái)判斷,提取指針?biāo)割愋偷拿Q和原始類型信息。將信息作為field節(jié)點(diǎn)的子節(jié)點(diǎn)存入XML文件:
④對(duì)數(shù)組類型,可通過(guò)isArrayType()來(lái)判斷,需要獲取數(shù)組對(duì)象類型信息與數(shù)組的大小。通過(guò)getElementType()獲取數(shù)組成員類型,然后進(jìn)一步分析該類型的原始類型。結(jié)構(gòu)體成員中的數(shù)組均是定長(zhǎng)數(shù)組,即使作為變長(zhǎng)使用的零數(shù)組,在編譯階段也是作為定長(zhǎng)數(shù)組處理。 對(duì)于定長(zhǎng)數(shù)組ConstantArrayType可以通過(guò)getSize()獲取數(shù)組的大小。將數(shù)組類型成員信息存入XML文件:
對(duì)于多維數(shù)組,可以對(duì)數(shù)組成員進(jìn)行數(shù)組類型的遞歸分析。
3 總結(jié)
本文針對(duì)嵌入式系統(tǒng)中,對(duì)結(jié)構(gòu)體類型診斷信息解析維護(hù)工作量大的問(wèn)題,提出了采用基于clang編譯器的前端插件在編譯過(guò)程中,通過(guò)訪問(wèn)抽象語(yǔ)法樹節(jié)點(diǎn),提取結(jié)構(gòu)體定義信息數(shù)據(jù)庫(kù)的方法,按一定策略組織成結(jié)構(gòu)體定義的XML數(shù)據(jù)庫(kù)。本方法已應(yīng)用于嵌入式系統(tǒng)調(diào)試中,為結(jié)構(gòu)體自動(dòng)解析提供了基礎(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). 基于抽象語(yǔ)法樹的代碼靜態(tài)自動(dòng)測(cè)試方法研究[J]. 北京化工大學(xué)學(xué)報(bào):自然科學(xué)版,2007(S1):25-29.
[5] 章磊. Clang上的C/C++過(guò)程間分析和漏洞發(fā)掘[D].合肥:中國(guó)科學(xué)技術(shù)大學(xué),2009.
[6] 陳火旺.程序設(shè)計(jì)語(yǔ)言編譯原理[M]. 北京: 國(guó)防工業(yè)出版社, 2000.
[7] Kenneth C Louden.編譯原理及實(shí)踐[M]. 馮博琴,譯.北京: 機(jī)械工業(yè)出版社, 2000.
[8] 高艷玲. 編譯原理——C教學(xué)編譯器設(shè)計(jì)[J]. 電腦知識(shí)與技術(shù),2009(18):4932-4933.