写点什么

基于 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:482618

评论

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

CRISP-DM的应用与理解

天翼云开发者社区

数据挖掘 CRISP-DM

新手使用住宅代理有哪些常见误区?

IPIDEA全球HTTP

俄罗斯即将启动跨境加密支付试行计划:规避制裁与加密货币的未来

区块链软件开发推广运营

交易所开发 dapp开发 区块链开发 链游开发 NFT开发

Web3 游戏周报(8.25- 8.31)

Footprint Analytics

NocoBase 与百事通:为法律行业带来全新变革

NocoBase

低代码 无代码 客户案例

以MySQL为例,来看看maven-shade-plugin如何解决多版本驱动共存的问题?

EquatorCoco

MySQL 数据库

GPU虚拟化技术简介:实现高性能图形处理的灵活部署

天翼云开发者社区

云计算 gpu 虚拟化技术

算网资源调度关键技术

天翼云开发者社区

云计算 云服务

金九银十来了,你的简历写好了么?

不在线第一只蜗牛

程序员 前端

万界星空科技MES系统如何帮助企业降低成本

万界星空科技

工业互联网 制造业 生产管理系统 mes 智能制造业

利用淘宝商品评论API返回值进行竞品深度分析

代码忍者

Python自动复制Excel数据:将各行分别重复指定次数

快乐非自愿限量之名

Excel pyhton

与客户建立联系,成为企业战略决策的引领者

智达方通

全面预算管理 财务管理 客户关系

私有云服务器虚拟化超分比及资源扩容管理

天翼云开发者社区

云计算 服务器 虚拟化

ps2224最新版,Photoshop 2024(Win&Mac) 软件下载

你的猪会飞吗

PS2024破解 mac破解软件下载 ps2024中文

【解决方案】项目重构之如何使用 MySQL 替换原来的 MongoDB

EquatorCoco

MySQL 数据库 架构

软件测试学习笔记丨Linux与SQL实战

测试人

Linux 软件测试 自动化测试 测试开发

万界星空科技机械加工行业MES解决方案

万界星空科技

mes 万界星空科技 制造业工厂 机械加工 机械加工mes

NFTScan | 08.26~09.02 NFT 市场热点汇总

NFT Research

NFT\ NFTScan

云原生主键模型:高效、弹性,省钱又省心

StarRocks

数据库 大数据 存算分离 存算分离架构 湖仓一体

AI真“卷出天际”!我国发布全球首个月球专业大模型;0代码可做游戏,谷歌发布世界首个AI游戏引擎|AI日报

可信AI进展

量化现货合约跟单交易所平台系统搭建开发构建

V\TG【ch3nguang】

系统开发 量化现货合约跟单交易所

【论文速读】| 基于大语言模型智能体对文本到图像模型进行越狱

云起无垠

通义灵码使用安装教程,3分钟快速上手体验

阿里云云效

阿里云 云原生 通义灵码

中小型企业如何开发体育直播平台:低成本高效启动指南

软件开发-梦幻运营部

HBlock应用实践与性能测试:解锁老旧服务器存储潜力,打造高效稳定的存储集群

Geek_2dc40b

HBlock 老旧服务器盘活

通义灵码使用安装教程,3分钟快速上手体验

阿里巴巴云原生

阿里云 云原生 通义灵码

8款国内外免费AI生成视频工具对比实测!我们真的可以做到“一人搞定一部影视作品“吗?

可信AI进展

AI测评

回顾 Oracle 在 MySQL 8.0 中的管理工作

爱可生开源社区

MySQL oracle MySQL 8.0

蚂蚁知识图谱管理系统研究成果获国际顶会认可,KGFabric 论文入选 VLDB 2024

可信AI进展

Swap/dApp去中心化交易所系统开发搭建

V\TG【ch3nguang】

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