Clang是llvm的编译器前端,非常适合进行源码分析.目前开源的oclint就是基于clang进行的代码静态检查.工作中遇到了一些问题需要进行代码分析,所以学习了插件的开发流程.既然开发插件就要有合适的IDE,Mac上最合适的无疑是xcode了.本文将讲述如何使用xcode开发clang插件,在此之前请先了解clang的相关知识.
一、搭建环境
1.获取Clang源码
由于是要使用到Xcode中,因此最好还是从苹果官网中获取LLVM的源码,目前版本是Xcode8.1下的LLVM的源码
https://opensource.apple.com/tarballs/clang/clang-800.0.42.1.tar.gz
PS:
a.查看xcode对应的clang版本 https://trac.macports.org/wiki/XcodeVersionInfo
b.官网查询对应发布版本 https://opensource.apple.com/source/clang/
这不只是Clang部分的源码,其中LLVM主要的子项目包括:
LLVM Core: 包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的CPU的汇编代码生成支持。
Clang: 一个C/C++/Objective-C编译器,致力于提供令人惊讶的快速编译,极其有用的错误和警告信息,提供一个可用于构建很棒的源代码级别的工具.
dragonegg gcc插件,可将GCC的优化和代码生成器替换为LLVM的相应工具。
LLDB: 基于LLVM提供的库和Clang构建的优秀的本地调试器。 libc++、libc++
ABI 符合标准的,高性能的C++标准库实现,以及对C++11的完整支持。compiler-rt: 针对”__fixunsdfdi”和其他目标机器上没有一个核心IR(intermediate
representation)对应的短原生指令序列时,提供高度调优过的底层代码生成支持。OpenMP: Clang中对多平台并行编程的runtime支持。 vmkit: 基于LLVM的Java和.NET虚拟机实现
polly :支持高级别的循环和数据本地化优化支持的LLVM框架。 libclc: OpenCL(开放运算语言)标准库的实现
klee: 基于LLVM编译基础设施的符号化虚拟机 SAFECode: 内存安全的C/C++编译器
lld :clang/llvm内置的链接器
2.编译插件
下载源码完成后解压目录,接下来就是要做编译LLVM的工作了。
找到 [解压llvm目录]/src/tools/clang/examples 目录,在里面新建一个目录如MyPlugin。然后修改example目录的CMakeLists.txt文件,添加一项:
add_subdirectory(MyPlugin)
然后进入创建的MyPlugin目录,创建三个文件,分别是:
CMakeList.txt
MyPlugin.cpp
MyPlugin.exports
然后在新建的CMakeList.txt中加入下面内容:
# If we don't need RTTI or EH, there's no reason to export anything
# from the plugin.
if( NOT MSVC ) # MSVC mangles symbols differently
if( NOT LLVM_REQUIRES_RTTI )
if( NOT LLVM_REQUIRES_EH )
set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/MyPlugins.exports)
endif()
endif()
endif()
add_llvm_loadable_module(MyPlugin MyPlugin.cpp)
if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
target_link_libraries(MyPlugin ${cmake_2_8_12_PRIVATE}
clangAST
clangBasic
clangFrontend
LLVMSupport
)
endif()
然后在插件文件MyPlugin.cpp中,添加下面的内容:
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"
using namespace clang;
namespace
{
class MyPluginConsumer : public ASTConsumer
{
CompilerInstance &Instance;
std::set<std::string> ParsedTemplates;
public:
MyPluginConsumer(CompilerInstance &Instance,
std::set<std::string> ParsedTemplates)
: Instance(Instance), ParsedTemplates(ParsedTemplates) {}
};
class MyPluginASTAction : public PluginASTAction
{
std::set<std::string> ParsedTemplates;
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
llvm::StringRef) override
{
return llvm::make_unique<MobCodeConsumer>(CI, ParsedTemplates);
}
bool ParseArgs(const CompilerInstance &CI,
const std::vector<std::string> &args) override {
DiagnosticsEngine &D = CI.getDiagnostics();
D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
“My plugin Started..."));
return true;
}
};
}
static clang::FrontendPluginRegistry::Add<MyPluginASTAction>
X("MyPlugin", “My plugin");
上面的代码中主要先看看MyPluginASTAction的ParseArgs方法,这是一个插件的入口函数,在这个方法里面调用了一个叫DiagnosticsEngine对象的Report方法,这段代码的主要功能是向编译器报告一个错误,而错误的描述就是“My plugin Started…”,下面会有具体的演示效果。关于其它部分的代码现在可以暂时不用理会,后续的章节会进行详细的说明。
现在先回到解压llvm目录的根目录,首先来对这些源码生成一个Xcode工程,源码项目的编译是由cmake管理(关于cmake详细资料请参考:cmake官方教程),因此生成Xcode工程非常方便。执行下面的shell命令:
cd 解压llvm目录
mkdir build && cd build
cmake -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64 -DLLVM_TARGETS_TO_BUILD=host -DLLVM_INCLUDE_TESTS=OFF -DCLANG_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON -DLIBCXX_INCLUDE_TESTS=OFF -DCOMPILER_RT_INCLUDE_TESTS=OFF -DCOMPILER_RT_ENABLE_IOS=OFF ../src
等待执行,提示成功后即可看到目录下多了一个build目录,点进去就可以看到一个Xcode的工程文件。双击打开项目,然后执行All_BUILD的scheme等待完成即可(这里面会有一个compiler_rt的编译报错,表示无法编译compiler_rt,由于这块不涉及插件编写所以可以暂时忽略)。
接着选择MyPlugin的scheme继续buid,编译成功后会在Debug/lib目录中多出一个叫MyPlugin.dylib文件,该文件便是我们要在xcode中用的插件。
PS:项目中可以看到刚才我们创建的cpp文件
3. 添加一个简单的插件项目到自己的项目
创建一个自己的工程项目,这里我用的是testPlugin
打开要使用插件的Xcode项目,在build settings一栏中对Other C Flags一项进行编辑,调整为:
-Xclang -load -Xclang /llvm/build/Debug/lib/MyPlugin.dylib -Xclang -add-plugin -Xclang MyPlugin
注:最后一项-Xclang MyPlugin中的MyPlugin为插件名字,一定要是自己设置的插件名称,否则无法调用插件。
由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误,如下图所示:
不一致的Clang版本错误
为了解决这个问题需要调整Xcode中使用的Clang编译器,将默认的编译器改为我们自己编译出来的编译器。具体的方法是在build settings中再添加两项自定义项(Editor-> Add build settings -> Add User-Defined Setting):
CC = / [解压llvm目录]/build/Debug/bin/clang
CXX =/ [解压llvm目录]/build/Debug/bin/clang++
目的用于指定Xcode的编译器从之前默认的,改为自定义的Clang编译器(注:CC和CXX中需要指定为你编译出来的Clang所在的绝对路径)。
Common + B 编译则可以看到一个插件输出的错误提示。
Good job!!
该错误正是我们D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
输出的错误。
“My plugin Started..."));
至此我们成功的在自己的项目中使用了自己编译的clang插件。
摘录:
https://my.oschina.net/vimfung/blog/866109
https://github.com/LiuShulong/SLClangTutorial/blob/master/%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E4%BD%A0%E7%BC%96%E5%86%99clang%E6%8F%92%E4%BB%B6%E5%92%8Clibtool.md
http://kangwang1988.github.io/tech/2016/10/31/check-code-style-using-clang-plugin.html