张红
摘要:在嵌入式系统调试环境下,需将大量的结构体变量输出到诊断软件,进行解析与呈现,而结构体数量庞大,且容易变化。在软件快速迭代开发阶段,迫切需要使结构体解析过程自动化。最关键的一步,是实现结构体定义数据库的提取。此文主要研究基于Clang编译器,实现从前端编译结构体定义文件生成的抽象语法树中提取结构体定义信息。实验结果表明,该方法能准确的实现从结构体定义文件提取结构体定义XML数据库。
关键词: Clang编译器;抽象语法树;信息提取;结构体定义
中图分类号:TP393 文献标识码: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 概述
在嵌入式软件开发过程中,为了快速分析软件运行过程,定位问题,将系统运行中的各类诊断信息输出到诊断软件解析,而大量的诊断信息是基于结构体类型,在嵌入式系统开发前期,采用手工编写解析结构体的函数来实现。但结构体定义在开发调试过程中会经常发生变更,结构体解析库就需要同步更新维护,随着系统工程模块化程度提高,规模也越来越大,涉及的人员越来越多,结构体定义与解析库之间更新不同步的问题越来越频繁,维护成本越来越高,严重影响了软件开发迭代进度。
本文在开源编译框架LLVM的前端编译器Clang的基础上,通过开发一个Clang前端插件,实现从抽象语法树AST(Abstract Syntax Tree)中进行结构体数据库提取。相比于手工编写解析函数,将繁重的开发和维护工作量降到0,大大提高了工作效率。
本文第二节介绍Clang 前端插件的编写、编译与执行方法;给出结构体数据库提取插件的实现方法;第三节对本文進行总结。
2 相关工作
2.1 Clang 前端插件开发介绍
Clang作为LLVM开源编译框架的一种前端编译器,实现编译过程中的词法分析,语法分析,类型检查,中间代码生成。Clang对用户进行前端插件的开发提供了很好的支持,前端操作的切入点是抽象类FrontendAction,此接口支持在前端编译过程中执行插件定制的操作。AST消费者的切入点是抽象类ASTConsumer,此接口支持对抽象语法树的访问。
本文是研究在编译过程中从抽象语法树提取结构体定义相关的信息,面向AST消费者前端操作的抽象接口类为FrontendAction的子类ASTFrontendAction,插件中前端操作基类选择ASTFrontendAction的子类PluginASTAction。自定义的AST消费者基类选择ASTConsumer。
2.1.1 编写Clang插件
1) 定义继承自PluginASTAction的自定义类StructFrontendAction。重载三个成员函数:
①用于创建抽象语法树的Consumer类。
ASTConsumer *CreateASTConsumer(CompilerInstance &CI, llvm::StringRef);
②用于分析此插件执行命令传入的参数。
bool ParseArgs(const CompilerInstance &CI,const std::vector
③用于打印输出此插件执行的help信息。
void PrintHelp(llvm::raw_ostream& ros);
2) 定义继承自ASTConsumer的抽象语法树ASTStructConsumer类。
重载virtual bool HandleTopLevelDecl(DeclGroupRef DG),实现从抽象语法树节点中提取所需信息。为了将分析AST过程中得到的信息组织成XML文件,可在此类的构造函数中创建XML文件,并构造初始的节点框架
3) 注册插件
static FrontendPluginRegistry::Add
2.1.2 编译Clang插件
参考开源代码Clang/Examples下的插件示例将编译环境配置好后,进入build目录执行:make clang,编译完成后,进入build/tools/clang/example下的插件编译目录,执行make,即可对插件进行编译。
2.1.3 执行clang插件
$clang –cc1 –load StructFrontendAction.so –plugin my-plugin-name compileFile
Clang –cc1为编译器,-load将加载所有注册的插件,-plugin指定加载特定的插件,若要将参数传递到插件里,可以使用-plugin-arg-
2.2 结构体信息提取的实现
结构体定义信息包括结构体名称、大小和成员个数,结构体成员变量名称、类型名称,成员变量类型的基础类型,成员变量在结构体中的偏移值,成员变量的大小。
结构体的成员的类型分以下六类:1)基本的系统内置(builtin)数据类型,如unsigned char,unsigned short,int等;2)重定义(typedef)数据类型,如对内置数据类型、结构体类型、枚举类型的重定义;3)指针;4)数组;5)联合体;6)位域。
以ASTConsumer的接口函数HandleTopLevelDecl(DeclGroupRef DG)为入口点,从AST的顶层节点TranslationUnitDecl开始, 该节点下的子节点类型有TypedefDecl,EnumDecl,RecordDecl,FunctionDec。结构体定义属于RecordDecl,自定义的成员类型定义信息来自于TypedefDecl,而枚举类型信息在EnumDecl节点。因此需要分析AST中的TranslationUnitDecl顶级节点下的所有TypedefDecl,EnumDecl和RecordDecl节点。每种节点类型的都是继承自NamedDecl。
首先获取Decl的名称,可以通过NamedDecl的getNameAsString()实现。有些可能是匿名,获取Decl名字为空,如“typedef { …}TypeA;”,这时可以通过getTypedefNameForAnonDecl()获取匿名Decl的TypedefNameDecl,若该匿名对象的TypedefNameDecl为空,该Decl将不可能作为结构体成员类型,可以忽略。
下面依次介绍TypedefDecl、EnumDecl,和RecordDecl节点信息的提取。
1)TypedefDecl节点
如“typedef A B;”,获取到的Decl的名称是B,此时需要分析出B的原始类型名称,原始类型可以是内置数据类型,或自定义结构体类型或枚举类型等,提取出新类型与基础类型的对应关系。当A不属于基础类型,将继续分析A的基础类型,直至找到基础类型C作为B的基础类型。
将提取到的新类型名称与基础类型名称的信息作为typedefs的子节點存入XML文件:
2)EnumDecl 节点
分析出枚举类型名字后,通过遍历EnumDecl的枚举向量来提取枚举成员字符串与数值,将信息作为enums的子节点写入XML文件:
3)RecordDecl节点
RecordDecl下除了struct类型还有其它类型,本文只关注RecordDecl下的struct类型,可以通过下isStruct()判断。在结构体成员不为空,即field_empty()为FALSE时,通过访问ASTRecordLayout对象,getFieldCount()可以获取结构体成员个数FiledCount,getSize()可以获取结构体的大小。将信息作为structs的子节点存入XML文件:
通过遍历RecordDecl::field_iterator来获取结构体成员变量信息,getFieldIndex()可以获取成员变量索引值,getFieldOffset(fieldIndex)可以获取索引值为fieldIndex的成员在结构体中的偏移值,getName()可以获取该成员的变量名,getType()可以获取该成员的类型,成员变量的大小需要通过getASTContext()获取ASTContext对象,进而由getTypeSize(fieldType)获取。将信息作为该struct节点的子节点存入XML文件:
分析成员变量类型时,需对联合体、指针、数组、位域类型信息作进一步提取。
①对联合体类型,可以通过isUnionType()来判断,union节点对象为RecordDecl类型,通过ASTRecordLayout可以获取union下的成员信息。
②对位域类型,可以通过isBitField()来判断,需进一步提取位宽信息,通过 (*iter)->getBitWidthValue((*iter)->getASTContext() );来实现,其中iter 为RecordDecl::field_iterator 。将位宽信息作为该成员变量field节点的子节点存入XML文件:
③对指针类型,可以通过isPointerType()来判断,提取指针所指类型的名称和原始类型信息。将信息作为field节点的子节点存入XML文件:
④对数组类型,可通过isArrayType()来判断,需要获取数组对象类型信息与数组的大小。通过getElementType()获取数组成员类型,然后进一步分析该类型的原始类型。结构体成员中的数组均是定长数组,即使作为变长使用的零数组,在编译阶段也是作为定长数组处理。 对于定长数组ConstantArrayType可以通过getSize()获取数组的大小。将数组类型成员信息存入XML文件:
对于多维数组,可以对数组成员进行数组类型的递归分析。
3 总结
本文针对嵌入式系统中,对结构体类型诊断信息解析维护工作量大的问题,提出了采用基于clang编译器的前端插件在编译过程中,通过访问抽象语法树节点,提取结构体定义信息数据库的方法,按一定策略组织成结构体定义的XML数据库。本方法已应用于嵌入式系统调试中,为结构体自动解析提供了基础,大大提高了软件迭代开发效率。
参考文献:
[1] LLVM[EB/OL]. http://www.llvm.org .
[2] Clang[EB/OL]. http://clang.llvm.org .
[3] 周睿. 基于Clang編译器的程序结构分析器设计[J]. 计算机时代,2016(10):54-56.
[4] 高传平,谈利群,宫云战. 基于抽象语法树的代码静态自动测试方法研究[J]. 北京化工大学学报:自然科学版,2007(S1):25-29.
[5] 章磊. Clang上的C/C++过程间分析和漏洞发掘[D].合肥:中国科学技术大学,2009.
[6] 陈火旺.程序设计语言编译原理[M]. 北京: 国防工业出版社, 2000.
[7] Kenneth C Louden.编译原理及实践[M]. 冯博琴,译.北京: 机械工业出版社, 2000.
[8] 高艳玲. 编译原理——C教学编译器设计[J]. 电脑知识与技术,2009(18):4932-4933.