HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

基于 LLVM 的源码级依赖分析方案的设计与实现

  • 2020-03-04
  • 本文字数:4086 字

    阅读完需:约 13 分钟

基于LLVM的源码级依赖分析方案的设计与实现

1. 导读

随着业务快速发展,移动客户端技术架构也从单一的工程配置,转向模块化、组件化、动态化方向发展。越来越多的业务模块被拆分成独立组件 bundle,进行独立开发、构建、测试、发布、运营,但这也面临着许多挑战:


  • 如何保证众多的独立组件 bundle 能够准确无误快速集成到主工程、打包、提测、发布审核?

  • 如果删除或更新某个独立组件 bundle,将会对剩余的哪些 bundle 有影响?

  • 架构或产品优化时,哪些独立组件 bundle 可以删除/下线?


这就需要确定这些独立组件 bundle 之间的依赖关系。

2. 依赖分析的定义

简单地说,通过某种技术手段获取到某个复杂系统中各个子系统之间相互关系,并将这种关系数据化、图像化处理的过程,即依赖分析。

3. 常见的依赖分析方案

3.1 基于 Cocoapods 的依赖包分析

Cocoapods 是 iOS 业界提供,开源的、事实上的依赖管理标准工具,其 Podfile.lock 及 podspec 文件中均有显式的记录各个组件之间的依赖关系,因此只需要分析这些文件即可获取到依赖关系。

3.2 基于 #include 和 #import 头文件的依赖分析

众所周知,当某个源码文件 A 依赖另一个源码文件 B 时,必定会在 A 文件头部显式的添加上 #include 和 #importB。因此只需要扫描所有源码文件中的头文件引用关系即可获取到依赖关系。

3.3 基于 nm、otool 等命令行工具的符号依赖分析

nm 和 otool 常用于分析二进制文件中的符号信息,通过符号建立依赖关系。

3.4 三种符号依赖分析比较

三种方案各有优缺点:


方案优点缺点分析时机难度
Cocoapods简单直观,业内基础方案分析粒度大(以bundle为单位)编译前简单
头文件引用简单直观分析粒度中(以文件为单位),存在无效、循环依赖问题编译前简单
nm/otool简单直观分析粒度细(以符号为单位),编译混淆或优化(strip)的库查不到符号信息编译后简单


本文从编译原理角度,设计一种新的源码级别依赖分析方案。

4. 基于 LLVM 的依赖分析方案

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.


LLVM 项目是一系列分模块、可重用的编译工具链。它提供了一种代码编写良好的中间表示(IR),可以作为多种语言的后端,还可以提供与编程语言无关的优化和针对多种 CPU 架构的代码生成功能,举个例子来说明整个 LLVM 的编译过程:


// main.m#include <stdio.h>#define kPeer 3int main(int argc, const char * argv[]) {    int a = 1;    int b = 2;    int c = a + b + kPeer;    printf("%d",c);    return 0;}
// 执行命令 clang -ccc-print-phases main.m 输出0: input, "main.m", objective-c1: preprocessor, {0}, objective-c-cpp-output2: compiler, {1}, ir3: backend, {2}, assembler4: assembler, {3}, object5: linker, {4}, image6: bind-arch, "x86_64", {5}, image
复制代码


整体流程如图示:


4.1 预处理(Preprocessor)阶段

预处理包括:条件编译、源文件包含、宏替换、行控制、抛错、杂注和空指令。


clang-E main.m
复制代码

4.2 词法分析(Lexer)阶段

行词法分析:将预处理过的代码转化成一个个 Token,比如左括号、右括号、等于、字符串等等。


clang-fmodules-fsyntax-only-Xclang-dump-tokens main.m
复制代码

4.3 语法分析(AST)阶段

行语法分析:根据当前语言的语法,验证语法是否正确,并将所有节点组合成抽象语法树(AST)。


clang-fmodules-fsyntax-only-Xclang-ast-dump main.m
复制代码

4.4 中间代码(IR)生成阶段

CodeGen 负责将语法树从顶至下遍历,翻译成中间代码 IR,IR 是 LLVM Frontend 的输出,也是 LLVM Backerend 的输入,桥接前后端。


clang-S-fobjc-arc-emit-llvm main.m-o main.ll
复制代码

4.5 代码优化(Opt)阶段

例如 Xcode 中开启了 bitcode,那么苹果后台拿到的就是这种中间代码,苹果可以对 bitcode 做进一步的优化。


clang-emit-llvm-c main.m-o main.bc
复制代码

4.6 代码生成器(CodeGen)阶段

// 生成汇编代码clang-S-fobjc-arc main.m-o main.s
// 生成目标文件clang-fmodules-c main.m-o main.o
复制代码

4.7 链接成可执行文件

clang main.o-o main
复制代码


其中 IR 代码生成(CodeGen)阶段,会遍历整个 AST 语法树,在此处插桩记录下函数名 + 行号 + 文件路径 + 源码 hash 值等信息,即可生成依赖分析的元数据。

5. 如何进行 LLVM 插桩

针对 iOS 端的代码编译,LLVM 前端使用 Clang 编译器,要在中间代码(IR)阶段插桩即要进行 Clang Plugin 开发。

5.1 准备 Clang 开发工具链

可以选择自行编译的 Clang 开发工具链,如下操作:


#!/bin/shcd /optsudo mkdir llvmpushd llvm &&git clone -b release_80 git@github.com:llvm-mirror/llvm.git llvm &&git clone -b release_80 git@github.com:llvm-mirror/clang.git llvm/tools/clang &&git clone -b release_80 git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra &&git clone -b release_80 git@github.com:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt &&popd &&sudo mkdir -v llvm_build &&pushd llvm_build &&cmake -DCMAKE_INSTALL_PREFIX=/opt/llvm_release  \      -DLLVM_TARGETS_TO_BUILD="X86;ARM;Mips;AArch64;WebAssembly" \      -DCMAKE_BUILD_TYPE=Release                \      -DLLVM_ENABLE_FFI=ON                      \      -DLLVM_ENABLE_RTTI=ON                     \      -DLLVM_BUILD_TESTS=OFF                    \      -DLLVM_INCLUDE_TESTS=OFF                  \      -Wno-dev -G Ninja ../llvm                &&ninja && ninja install && popd
复制代码


也可以选择已编译好的 Clang 开发工具链,下载地址:http://releases.llvm.org/

5.2 编写 Clang 插件

Clang 插件实际上一个动态链接库,因此使用 Xcode 创建一个 dylib 工程,将编译器指定到准备好的 Clang 工具链上即可开始,如下图示:



Clang Plugin 通常的入口点是 FrontendAction。FrontendAction 是一个接口,它允许用户指定的 actions 作为编译的一部分来执行。为了在 AST clang 上运行工具,AST clang 提供了方便的接口 ASTFrontendAction,它负责执行 action。剩下的唯一部分是实现 CreateASTConsumer 方法,该方法为每个翻译单元返回一个 ASTConsumer。继承它们即可实现遍历 AST 语法树的功能:


功能
clang::RecursiveASTVisitor遍历AST语法树的抽象基类
clang::PluginASTAction基于consumer的AST前端Action抽象基类
clang::ASTConsumer读取AST的抽象基类


识别 AST 语法树中的类名、方法名、调用关系,需使用 AST 中的以下类:


功能
clang::ObjCInterfaceDecl记录Object-C类声明信息
clang::ObjCCategoryDecl记录Object-C扩展类名信息
clang::ObjCMethodDecl记录Object-C类方法声明信息
clang::ObjCImplDecl记录Object-C类方法实现声明信息
clang::ObjCImplementationDecl记录Object-C类方法实现信息
clang::ObjCPropertyDecl记录Object-C类的属性声明信息
clang::ObjCProtocolDecl记录Object-C协议声明信息
clang::ObjCMessageExpr记录Object-C表达式信息

5.3 加载 Clang 插件

在编译参数 Other C/C++ Flag 中添加


-Xclang -load -Xclang /opt/llvm_release/plugins/libXXXPlugin.dylib -Xclang -add-plugin -Xclang XXXPlugin
复制代码

5.4 举个例子

以下代码实现遍历 AST 语法树中的所有 C++类名,并打印出来的功能:


#include "clang/AST/ASTConsumer.h"#include "clang/AST/RecursiveASTVisitor.h"#include "clang/Frontend/CompilerInstance.h"#include "clang/Frontend/FrontendAction.h"#include "clang/Tooling/Tooling.h"
using namespace clang;
class FindNamedClassVisitor : public RecursiveASTVisitor<FindNamedClassVisitor> {public: explicit FindNamedClassVisitor(ASTContext *Context) : Context(Context) {}
bool VisitCXXRecordDecl(CXXRecordDecl *Declaration) { llvm::outs() << "Found class: " << Declaration->getNameAsString() << "\n"; return true; }
private: ASTContext *Context;};
class FindNamedClassConsumer : public clang::ASTConsumer {public: explicit FindNamedClassConsumer(ASTContext *Context) : Visitor(Context) {}
virtual void HandleTranslationUnit(clang::ASTContext &Context) { Visitor.TraverseDecl(Context.getTranslationUnitDecl()); }private: FindNamedClassVisitor Visitor;};
class FindNamedClassAction : public clang::ASTFrontendAction {public: virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer( clang::CompilerInstance &Compiler, llvm::StringRef InFile) { return std::unique_ptr<clang::ASTConsumer>( new FindNamedClassConsumer(&Compiler.getASTContext())); }};
复制代码


编译参数可使用 LLVM 为我们提供的 llvm-config 工具自动生成,执行


llvm-config --cxxflags --ldflags --system-libs --libs core
复制代码


其余额外依赖库自行根据功能添加。

6. 建立依赖关系元数据

通过加载定制化开发的 Clang Plugin,经过编译即可生成如下面格式的数据结构:


{    "+[GTMBase64 decodeBytes:length:]": {        "call": [            "+[GTMBase64 baseDecode:length:charset:requirePadding:]"        ],        "class": "GTMBase64",        "filename": "/Sources/Internal/Encode/GTMBase64.m",        "range": "11401-11553",        "sourceCode": "{return [self baseDecode:bytes length:length charset:kBase64DecodeChars requirePadding:YES];}"    }}
复制代码


其中:


key值描述
call标识调用链上的方法列表
class标识类名
filename标识编译单元文件名
range标识方法所在行号
sourceCode标识方法的实现源码


基于这些依赖元数据,经过后台系统加工处理,就可以准确地知道某个组件 bundle 与其他组件之间的关系,实现一套基于 LLVM 的依赖分析方案。

7. 小结

本文主要介绍了业内常见的依赖分析方案,并分享了一种基于 LLVM 的,从细粒度方法级别来实现依赖分析的方案,它能更准确反馈出各个独立组件 bundle 之间的关系,指导开发人员优化架构设计,可以应对未来“五独”技术进化带来的挑战。


2020-03-04 14:482578

评论

发布
暂无评论
发现更多内容

软件测试 | Appium架构介绍与环境配置

测吧(北京)科技有限公司

测试

怎么编写接口测试用例?

Liam

测试用例 如何编写测试 测试用例设计

Wallys|QCN9074/QCN6024/ 5G 802.11ax|Does QCN9074-5G module support W56?

Cindy-wallys

QCN9074 QCN6024

中国人寿业务稳定性保障:“1+1+N” 落地生产全链路压测

TakinTalks稳定性社区

软件测试 | adb命令的组成

测吧(北京)科技有限公司

测试

拜占庭将军问题和 Raft 共识算法讲解

京东科技开发者

算法 分布式系统 拜占庭将军问题 raft共识算法 日志同步

3dmax的常用功能和使用方法

Finovy Cloud

3D渲染 3DMAX

模块6 拆分电商系统为微服务

KING

ChatGPT 最好的替代品

图灵教育

机器学习 BERT ChatGPT

ChatGPT 最好的替代品

图灵社区

机器学习 Transformer BERT ChatGPT

软件测试 | App常见Bug解析

测吧(北京)科技有限公司

测试

HIFIVE音加加×筷子科技,内容创作如何再加“筷”一点

HIFIVE音加加

音乐 版权 视频创作 AI剪辑

OceanBase 4.0解读:兼顾高效与透明,我们对DDL的设计与思考

OceanBase 数据库

数据库 oceanbase

@所有人,OceanBase DevCon • 2023来啦!

OceanBase 数据库

数据库 oceanbase

iOS tableView添加group分组

刿刀

ios UITableView

ChatGPT搜索风暴

OneFlow

人工智能 深度学习

4道数学题,求解极狐GitLab CI 流水线|第2&3题:父子流水线 + 多项目流水线

极狐GitLab

ci DevOps pipeline 极狐GitLab 流水线

信通院发布“2023可信AI案例 知识计算应用优秀案例”, 百度知识中台三大案例入选

Geek_2d6073

Slurm集群调度策略详解(2)-回填调度

慕冰

Slurm 回填调度

设计模式第七讲-外观模式、适配器模式、模板方法模式详解

C++后台开发

设计模式 后端开发 Linux服务器开发 适配器模式 C++开发

软件测试 | 录制Appium测试用例

测吧(北京)科技有限公司

测试

软件测试 | 元素定位方式与隐式等待

测吧(北京)科技有限公司

测试

袋鼠云产品功能更新报告04期丨2023年首次,产品升级“狂飙”

袋鼠云数栈

故障案例:MySQL唯一索引有重复值,官方却说This is not a bug

GreatSQL

基于 Flink 的小米数据集成实践

Apache Flink

大数据 flink 实时计算

机器学习洞察 | 一文带你“讲透” JAX

亚马逊云科技 (Amazon Web Services)

【2.10-2.17】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动 优质创作周报

理论+实践,揭秘昇腾CANN算子开发

华为云开发者联盟

人工智能 AI 华为云 企业号 2 月 PK 榜 华为云开发者联盟

谈JVM参数GC线程数ParallelGCThreads合理性设置

京东科技开发者

线程 JVM cpu GC线程 ParallelGCThreads

可靠、安全、稳定,开源高质量项目 | 亚马逊的开源文化

亚马逊云科技 (Amazon Web Services)

实现“无感知”的网络运维,新华社融媒体流量分析平台的创新之路

智维数据

大数据 防火墙 数据可视化 智能运维 运维安全

基于LLVM的源码级依赖分析方案的设计与实现_文化 & 方法_高德技术_InfoQ精选文章