三味猪屋

iOS包大小瘦身方案(二)- 基于clang插件iOS瘦身实践

clang作为LLVM提供的编译器前端,可将用户的源代码(C/C++/Objective-C)编译成语言/目标设备无关的IR(Intermediate Representation)实现。其可提供良好的插件支持,容许用户在编译时,运行额外的自定义动作。
本篇是基于clang的插件特性,编写一个clang插件作为一个源代码级别的分析工具(或编译器)生成各种中间文件。编译完成后,还需编写一个工具去分析所有包含源码的方法(包括用户编写,以及引入的第三方库源代码),检查这些方法中哪些最终可被程序主入口调用,剩余即是疑似无用代码。从而分析出源代码中那些确定无用的代码,便可以有效去除无用的代码从而减少包大小。
LLVM工程包含了一组模块化,可复用的编辑器和工具链:

1
2
3
4
5
6
7
8
9
10
11
12
13
LLVM Core:包含一个现在的源代码/目标设备无关的优化器,一集一个针对很多主流(甚至于一些非主流)的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内置的链接器


原文链接

1、clang源码并编译安装
查看clang源码并编译安装
2、编写clang插件
要实现自定义的Clang插件(以C++为例),应按照以下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.自定义继承自
clang::PluginASTAction(基于consumer的AST前端Action抽象基类)
clang::ASTConsumer(用于客户读取AST的抽象基类),
clang::RecursiveASTVisitor(前序或后续地深度优先搜索整个 AST,并访问每一个节点的基类)等基类,
2.根据自身需要重载
PluginASTAction::CreateASTConsumer,
PluginASTAction::ParseArgs,
ASTConsumer::HandleTranslationUnit
RecursiveASTVisitor::VisitDecl
RecursiveASTVisitor::VisitStmt
等方法,实现自定义的分析逻辑。
3.注册插件
static FrontendPluginRegistry::Add<MyPlugin> X("my-plugin-name", "my-plugin-description");
4.编译时载入插件
clang++ *** -Xclang -load -Xclang path-of-your-plugin.dylib -Xclang -add-plugin -Xclang your-pluginName -Xclang -plugin-arg-your-pluginName -Xclang your-pluginName-param

编写clang插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/AST/RecursiveASTVisitor.h"
using namespace clang;
using namespace std;
using namespace llvm;
namespace ClangPlugin
{
class ClangPluginASTVisitor : public RecursiveASTVisitor<ClangPluginASTVisitor>
{
private:
ASTContext *context;
public:
void setContext(ASTContext &context){
this->context = &context;
}
bool VisitDecl(Decl *decl){
if(isa<ObjCImplDecl>(decl)){
ObjCImplDecl *interDecl = (ObjCImplDecl*)decl;
cout<<"[KWLM]Class Implementation Found:"<<interDecl->getNameAsString()<<endl;
}
return true;
}
};
class ClangPluginASTConsumer : public ASTConsumer
{
private:
ClangPluginASTVisitor visitor;
void HandleTranslationUnit(ASTContext &context){
visitor.setContext(context);
visitor.TraverseDecl(context.getTranslationUnitDecl());
}
};
class ClangPluginASTAction : public PluginASTAction
{
public:
unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,StringRef InFile){
return unique_ptr<ClangPluginASTConsumer>(new ClangPluginASTConsumer);
}
bool ParseArgs(const CompilerInstance &CI, const std::vector<std::string>& args){
return true;
}
};
}
static clang::FrontendPluginRegistry::Add<ClangPlugin::ClangPluginASTAction>X("ClangPlugin", "ClangPlugin");

编译生成插件(dylib):

1
clang -std=c++11 -stdlib=libc++ -L/opt/local/lib -L/opt/llvm/llvm_build/lib -I/opt/llvm/llvm_build/tools/clang/include -I/opt/llvm/llvm_build/include -I/opt/llvm/llvm/tools/clang/include -I/opt/llvm/llvm/include -dynamiclib -Wl,-headerpad_max_install_names -lclang -lclangFrontend -lclangAST -lclangAnalysis -lclangBasic -lclangCodeGen -lclangDriver -lclangFrontendTool -lclangLex -lclangParse -lclangSema -lclangEdit -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lLLVMX86CodeGen -lLLVMX86AsmParser -lLLVMX86Disassembler -lLLVMExecutionEngine -lLLVMAsmPrinter -lLLVMSelectionDAG -lLLVMX86AsmPrinter -lLLVMX86Info -lLLVMMCParser -lLLVMCodeGen -lLLVMX86Utils -lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -lLLVMAnalysis -lLLVMTarget -lLLVMCore -lLLVMMC -lLLVMSupport -lLLVMBitReader -lLLVMOption -lLLVMProfileData -lpthread -lcurses -lz -lstdc++ -fPIC -fno-common -Woverloaded-virtual -Wcast-qual -fno-strict-aliasing -pedantic -Wno-long-long -Wall -Wno-unused-parameter -Wwrite-strings -fno-rtti -fPIC your-clang-plugin-source.cpp -o your-clang-plugin-name.dylib

使用clang编译oc文件(载入生成的your-clang-plugin-name.dylib):

1
/opt/llvm/llvm_build/bin/clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator10.0.sdk -I/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 -mios-version-min=8.0 -Xclang -load -Xclang ~/Desktop/your-clang-plugin-name.dylib -Xclang -add-plugin -Xclang your-clang-plugin-name -c ./ocClsDemo.m

Xcode集成Clang插件:
要在Xcode中使用Clang插件,需要Hack Xcode.app。
XcodeHacking.zip
通过command-line方式或者手动拷贝的方式将XcodeHack拷贝到Xcode目录中,如果Specifications文件不存在则手动创建一个。

1
2
sudo mv HackedClang.xcplugin `xcode-select -print- path`/../PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins
sudo mv HackedBuildSystem.xcspec `xcode-select -print- path`/../Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications



之后在Xcode->Target-Build Settings->Build Options->Compiler for C/C++/Objective-C选择Clang LLVM Trunk即可使得Xcode使用1生成的的Clang来编译。至于其他的命令行参数,均可以通过Xcode设置完成。

3、clang插件在Xcode工程的配置:
clang插件示例工程
a、首先通过Xcode将自定义clang插件编译成动态库(dylib)。


b、其次编译生成分析工具XcodeCodeAnalyser


c、设置工程
使用用户编译的Clang载入ClangPlugin去编译并生成各种中间文件。编译完成之后使用Xcode提供的post_build_action_shell机制调用分析工具,生成最终结果。


参考:
http://railsware.com/blog/2014/02/28/creation-and-using-clang-plugin-with-xcode/
http://blog.mrriddler.com/2017/02/24/Clang插件-Sherlock/
https://lmsgsendnilself.github.io/blog/2017/02/28/clangtojszi-yuan/
http://kangwang1988.github.io/tech/2016/11/01/validate-ios-api-using-clang-plugin.html
https://github.com/kangwang1988/XcodeValidAPI
http://www.cocoachina.com/ios/20151211/14562.html
http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=2651112096&idx=1&sn=ce8fccce7d5f70e30c078e63e8ea0d15&scene=21#wechat_redirect