【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

iOS 代码染色原理及技术实践

  • 2020-09-08
  • 本文字数:4287 字

    阅读完需:约 14 分钟

iOS代码染色原理及技术实践

背景

随着业务的迅速发展,业务代码逻辑的复杂度增加。QA 测试的质量对于产品上线后的稳定性更加重要。一般 QA 测试的工作流程分为两大项:自动化测试和人工测试。这两种测试后都需要得到代码覆盖率。自动化测试的覆盖率,在双端都有比较成熟的方案。


本文着重介绍人工测试过程中,怎么得到对应的代码覆盖率。涉及到的技术主要是代码染色。以下会先介绍整体的工作流程,再对涉及到的技术一一阐述。

染色流程


流程图中涉及到了双端的关键节点以及技术点。我们重点介绍编译阶段。


  • 编译阶段:生成染色包 (对 IR 文件插桩)


需要在编译中增加编译选项,编译后会为每个可执行文件生成对应的 .gcno 文件。


  • 运行阶段:生成二进制覆盖率文件。


在测试代码中调用覆盖率分发函数,会生成对应的 .gcda 文件。


  • 解析阶段:将二进制覆盖率文件可视化。

编译阶段

在上文可以看出,编译阶段最核心的操作是对 IR 文件进行插桩。


什么是 IR 文件?插桩逻辑是什么?我们往下看。


语言处理系统


一个完整的语言处理系统中,从源程序到可执行的机器代码,如下图所示,历经几个重要模块。而我们上文提到的 IR 文件,是编译器模块中的产物,插桩处理也是在这个模块中进行。这里重点讨论下编译器。



编译器


说起编译器,我们了解到的传统编译器架构分为前端、优化器和后端。


传统编译器的劣势是:前端和后端没有完全分离,耦合在了一起,因而如果要支持一门新的语言或硬件平台,需要做大量的工作。一种更加灵活,适应性更好的编译器套件应运而生——LLVM.


LLVM


官网:http://www.aosabook.org/en/llvm.html


LLVM 是一个开源的,模块化和可重用的编译器和工具链技术的集合,或者说是一个编译器套件。


可以使用 LLVM 来编译 Kotlin,Ruby,Python,Haskell,Java,D,PHP,Pure,Lua 和许多其他语言。


LLVM 核心库还提供一个优化器,对流行的 CPU 做代码生成支持。


LLVM 同时支持 AOT 预先编译和 JIT 即时编译。


2012 年,LLVM 获得美国计算机协会 ACM 的软件系统大奖,和 UNIX,WWW,TCP/IP,Tex,JAVA 等齐名。


LLVM 和传统编译器最大的不同点在于,前端输入的任何语言,在经过编译器前端处理后,生成的中间码都是 IR 格式的。接下来看下 LLVM 架构下的巨大优势,iOS&MacOS 平台的编译器。



iOS&MacOS 平台编译器


iOS、MacOS 平台开发用的 IDE:Xcode。在 Xcode 5 版本前使用的是 GCC 编译器,在 Xcode 5 中将 GCC 彻底抛弃,替换为 LLVM 。LLVM 包含了编译器前端、优化器和编译器后端三大模块。


其中 Swift 除了在编译器前端和 Objective-C 稍有不同,其他模块都是相同的。


如下图所示,能看出 LLVM 的优势,对于一门新的编程语言,只需要提供对应的编译前端,生成 IR。就可以完成整个新语言的处理。



聊过了 IR 文件在整个语言处理过程中的位置,下面我们看下 IR 文件生成逻辑以及插桩相关的逻辑。这不得不提到 Clang。


Clang


Clang 是 LLVM 的子项目,是 C、C++和 Objective-C 的编译器。Clang 在整个 Objective-C 编译过程中扮演了编译器前端的角色,同时也参与到了 Swift 编译过程中的 Objective-C API 映射阶段。


Clang 的特点是编译速度快,模块化,代码简单易懂,诊断信息可读性强,占用内存小以及容易扩展和重用等。


Clang 的主要功能是输出代码对应的抽象语法树(AST),针对用户发生的编译错误准确地给出建议,并将代码编译成 LLVM IR。


以 Xcode 为例,Clang 编译 Objective-C 代码的速度是 Xcode 5 版本前使用的 GCC 的 3 倍,其生成的 AST 所耗用掉的内存仅仅是 GCC 的五分之一左右。


关于 iOS 项目可以使用对应的命令获取,本文不作详细介绍。


关于编译器前端的主要工作项,感兴趣的读者阅读《编译原理》——龙书。


介绍完了 IR 的“生成器”。接下来我们详细介绍 IR 文件。


LLVM IR


LLVM Intermediate Representation。LLVM 的中间代码,是编译器前端的输出,和编译器后端的输入。是连接编译器前端与 LLVM 后端的一个桥梁。


通常常见的文件格式为 ll 和 bt 。做过 iOS 开发的读者应该了解 bitcode。bt 就是编译器开启 bitcode 后的一种中间代码格式。


IR 提供了独立于任何特定机器架构的源语,因此它是 LLVM 优化和进行代码生成的关键,也是 LLVM 有别于其他编译器的最大特点。LLVM 的核心功能都是围绕 IR 建立的。


通常中间代码的表示形式分为:语法树(syntax tree)、三地址指令序列。为了更好的了解 IR 文件。这里介绍下三地址指令。


三地址指令


也可以称为三地址代码。之所以被称为三地址指令,是源于它的指令形式:x = y op z ,其中 op 是一个二目运算符,y 和 z 是运算分量的地址,x 是运算结果的存放地址。三地址指令最多只执行一个运算,通常是计算,比较或者分支跳转运算。


三地址代码拆分了多运算符算术表达式以及控制流语句的嵌套结构,所以适用于目标代码的生成和优化。


//像 x+y*z 这样的源代码被翻译成三地址指令序列:t1=y*zt2=x+t1
//源码:do i = i + 1; while(a[i] < 10); 被翻译成如下的三地址指令i = i + 1t1 = a[i]if t1 < 10 goto 6其中t1,t2是编译器产生的临时名字。
复制代码


但是程序运行过程中,每个模块并不是完全独立的。存在着模块间的跳转。这些被翻译出的三地址指令,又被组合成另一种便于理解的形式——BB 块。


基本块


基本块(Basic Block)是满足下列条件的最大的 连续三地址指令序列


  • 控制流只能从基本块中的第一个指令进入该块。

  • 除了基本块的最后一个指令,控制流在离开基本块之前不会停机或者跳转。

  • 只要基本块中的第一个指令被执行,那么基本块中的所有指令都会得到执行


其中中间代码指令序列生成 BB 块的算法如下:


  • 确定中间代码序列中哪些指令是首指令

  • 中间代码的第一个三地址指令是一个首指令。

  • 任意一个条件或无条件转移指令之后的目标指令是一个首指令。

  • 紧跟在一个条件或无条件转移指令之后的指令是一个首指令。

  • 每个首指令对应的基本块包括了从它自己开始,直到下一个首指令(不含)或者中间代码的结尾指令之间的所有指令。


举例:


i = 1 //第一个三地址指令,所以作为首指令j = 1 //第11行,跳转语句的目标指令。所以作为首指令t1 = 10*it2 = t1+jt3 = 8*t2t4 = t3-88a[t4] = 0.0j = j+1if j<=10 goto (3) //本身作为跳转指令,所以是首指令i = i+1if i<=10 goto (2) //本身作为跳转指令,所以是首指令i = 1t5 = i – 1 //第17行,跳转语句的目标指令。所以是首指令t6 = 88*t5a[t6] = 1.0i = i+1if i<=10 goto (13)//本身作为跳转指令,所以是首指令
//把一个10x10的矩阵设置成单位矩阵中的中间代码for(i=1;i<=10;i++){ for(j=1;j<=10;j++){ a[i,j] = 0.0; }}for(i=1;i<=10;i++){ a[i,j] = 1.0;}
复制代码


对应被划分的 BB 块:



在了解了 BB 块之后。我们距离怎么对 IR 文件进行插桩的真相已经越来越近了,下面我们来看下最后一个最重要的环节。

流图

当将一个中间代码程序划分成为基本块之后,我们用一个流图来表示它们之间的控制流。流图(flow graph)的结点就是这些基本块。流图就是通常的图,它可以用任何适合表示图的数据结构来表示。


从基本块 B 到基本块 C 之间有一条边当且仅当基本块 C 的第一个指令紧跟在 B 的最后一个指令之后执行。存在这样一条边的原因有两种:


  • 有一个从 B 的结尾跳转到 C 的开头的条件或无条件 跳转语句

  • 按照原来的三地址语句序列中的顺序,C 紧跟在 B 之后,且 B 的结尾不存在无条件跳转语句。


我们说 B 是 C 的前驱(predecessor), 而 C 是 B 的一个后继(successor)。


通常会增加两个分部称为 入口(entry)出口(exit) 的结点。它们不和任何可执行的中间指令对应。从入口到流图的第一个可执行结点有一条边(edges)。从任何包含了可能是程序的最后执行指令的基本块到出口有一条边。如果程序的最后指令不是一个无条件转移指令,那么包含了程序的最后一条指令的基本块是出口结点的一个前驱。但任何包含了跳转到程序之外的跳转指令的基本块也是出口结点的前驱。



其中 B0-B7 是 BB 块。E0-E7 是边(edges)


插桩逻辑


覆盖率计数指令的插入会进行两次循环,外层循环遍历编译单元中的函数,内层循环遍历函数的基本块。函数遍历用来向 gcno 文件中写入函数位置信息。


一个函数中基本块的插桩方法如下:


  • 统计所有 BB 的后继数 n,创建和后继数大小相同的数组 ctr[n]。

  • 以后继数编号为序号将执行次数依次记录在 ctr[i] 位置,对于多后继情况根据条件判断插入。


根据生成流图的规则,可以很容易得到桩点位置,[]处就是插入的桩点序号。



关于工程配置可以参考 GCOV 的官网:


https://gcc.gnu.org/onlinedocs/gcc/Gcov.html


下面简单介绍下 gcov,gcno,gcda 这三个 gcc 家族的关键成员。


GCOV


GCOV 是一个 GNU 的本地覆盖测试工具, 伴随 GCC 发布,配合 GCC 共同实现对 C 或者 C++文件的语句覆盖和分支覆盖测试。是一个命令行方式的控制台程序。需要工具链的支持。


GCNO


利用 Clang 分别生成源文件的 AST 和 IR 文件,对比发现,AST 中不存在计数指令,而 IR 中存在用来记录执行次数的代码。


覆盖率映射关系生成源码是 LLVM 的一个 Pass,用来向 IR 中插入计数代码并生成.gcno 文件(关联计数指令和源文件)。



上图右侧。即为 gcno 的可视化格式。


本质上 gcno 是二进制内容。需要借助 gcov 工具(gcov -dump xxx.gcno)将文件转换为这种可视的格式。


其中每个字段的含义


  • 函数所在文件的绝对路径(如上图红框所示)。

  • Block :0-7 代表 BB 文件的编号。

  • Counter 为插桩后生成的存储执行次数的字段。

  • Source Edges 是前继。

  • Destination 是后继。

  • Lines 是指令在代码文件中行数。


GCDA


gcda 是由加了-fprofile-arcs 编译参数的编译后的文件运行所产生的,它包含了弧跳变的次数和其他的概要信息。


借助 gcov 工具可以查看 gcda 文件的大致内容:


gcda 文件已经是一个包括了函数执行情况的文件。剩余的工作就是将执行情况更加可视化,和源码进行匹配。



了解了三个 gc 的重要成员。借助一些前端工具,我们就可以得到一份详细的覆盖率报告了。关于前端工具,大家可以自行搜索。


最后附上覆盖率的一个报告片段


技术扩展

了解上述基础知识后,我们更加容易理解 LLVM 中的架构及各个模块的功能。我们可以在插桩过程中,修改原有的插桩逻辑。我们可以编写 XCode 编译器插件。总之,借助 LLVM 的源码及我们了解到的知识。在一个语言的任意处理阶段,我们都可以对其进行定制,甚至我们可以创造一个自己的专属语言。


源码参考:


https://github.com/llvm-mirror/llvm/blob/release_70/lib/Transforms/Instrumentation/GCOVProfiling.cpp


https://llvm.org/doxygen/group__LLVMCCoreValueBasicBlock.html#ga444a4024b92a990e9ab311c336e74633


https://gcc.gnu.org/onlinedocs/gcc/Gcov.html


本文转载自公众号高德技术(ID:amap_tech)。


原文链接


iOS代码染色原理及技术实践


2020-09-08 10:042109

评论

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

Nautilus Chain 联合香港数码港举办 BIG DEMO DAY活动,释放何信号?

大瞿科技

深度剖析低代码平台

互联网工科生

低代码开发平台 JNPF

Macos多协议数据库管理工具:Navicat Premium 16 中文破解版 支持M1

iMac小白

第14期 | GPTSecurity周报

云起无垠

Spring高手之路15——掌握Spring事件监听器的内部逻辑与实现

砖业洋__

spring Spring 监听 ApplicationEventMulti Spring事件 PayloadApplicationEvent

官宣|Apache Flink 1.18 发布公告

Apache Flink

大数据 flink 实时计算

荣耀主题设计师邀请有礼

荣耀开发者服务平台

设计师 资源分享 安卓 ios 活动推荐

文心一言 VS 讯飞星火 VS chatgpt (122)-- 算法导论10.4 3题

福大大架构师每日一题

福大大架构师每日一题

LP丨IDO丨DeFi流动性质押挖矿系统制度开发设计

V\TG【ch3nguang】

iOS安全加固方法及实现

雪奈椰子

虚拟币比特币质押挖矿|算力挖矿模式系统搭建开发

V\TG【ch3nguang】

外贸网站流量下降可能是这五点原因造成的

九凌网络

DAPP去中心化流动性LP质押挖矿系统开发

V\TG【ch3nguang】

USDT质押挖矿(智能合约)系统开发(成熟技术)

V\TG【ch3nguang】

云质押挖矿项目系统开发DeFi模式/云算力挖矿系统开发技术讲解

V\TG【ch3nguang】

PRT普瑞缇ProTradex系统技术开发丨质押挖矿开发详情

V\TG【ch3nguang】

关于亚马逊 CodeWhisperer 的使用体验

亚马逊云科技 (Amazon Web Services)

人工智能 云上探索实验室 Amazon CodeWhisperer

Typora for Mac(Markdown文本编辑器) 1.7.6中文直装版

mac

Typora markdown编辑器 苹果mac Windows软件

最新Xmind for Mac破解版 附 破解安装教程「支持M1」

iMac小白

XMind下载 XMind2023 XMind Pro

区块链项目软件开发

西安链酷科技

区块链 dapp 去中心化 交易所 链游

外贸独立站谷歌SEO优化:提升网站排名和流量的关键要素

九凌网络

如何应用 NFTScan NFT API 在 Aptos 网络上开发 Web3 应用

NFT Research

NFT\ NFTScan nft工具

提高数据研究效率,优化成果分享及复用|ModelWhale 版本更新

ModelWhale

人工智能 低代码 数据服务 模型服务 数据引用

PDF编辑器推荐:Acrobat Pro DC 2023中文破解版 完美兼容M1

iMac小白

Acrobat Pro DC 2023 Adobe Acrobat Pro DC下载 Adobe Acrobat Pro DC破解

Codigger:在线协同开发革新

知者如C

010 Editor for Mac破解版 附 010 Editor注册码 完美兼容M1、M2

iMac小白

010 Editor for Mac 010 Editor下载 010 Editor破解版

外贸网站如何做好站内优化

九凌网络

API商品数据接口调用爬虫实战

Noah

OP 链 DAPP 智能合约质押挖矿系统开发(Python技术搭建)

V\TG【ch3nguang】

“草料二维码”插件登陆飞书多维表格,可批量生成二维码标签

草料二维码

二维码 飞书

扫盲低代码——基本原理

树上有只程序猿

软件开发 低代码 JNPF

iOS代码染色原理及技术实践_大前端_高德技术_InfoQ精选文章