11纯代码 oc xcode_iOS代码染色原理及技术实践

背景

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

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

染色流程

57a5d60393f7d79c7a4ba209a518d48f.png

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

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

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

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

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

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

编译阶段

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

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

语言处理系统

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

cc9a276b48bec5d0a116f03d40162330.png

编译器

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

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

a8947d8819e414a4b89834c230a2d31e.png

iOS&MacOS平台编译器

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

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

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

7c55f1792805a1279a91a51001245626.png

聊过了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*z
t2=x+t1//源码:do i = i + 1; while(a[i] < 10); 被翻译成如下的三地址指令
i = i + 1
t1 = a[i]
if t1 < 10 goto 6
其中t1,t2是编译器产生的临时名字。

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

基本块

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

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

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

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

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

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

  • 中间代码的第一个三地址指令是一个首指令。
  • 任意一个条件或无条件转移指令之后的目标指令是一个首指令。
  • 紧跟在一个条件或无条件转移指令之后的指令是一个首指令。

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

举例:

i = 1 //第一个三地址指令,所以作为首指令
j = 1 //第11行,跳转语句的目标指令。所以作为首指令
t1 = 10*i
t2 = t1+j
t3 = 8*t2
t4 = t3-88
a[t4] = 0.0
j = j+1
if j<=10 goto (3) //本身作为跳转指令,所以是首指令
i = i+1
if i<=10 goto (2) //本身作为跳转指令,所以是首指令
i = 1
t5 = i – 1 //第17行,跳转语句的目标指令。所以是首指令
t6 = 88*t5
a[t6] = 1.0
i = i+1
if 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块:

168d55b2a7c91dc772990dce57a70b76.png

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

流图

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

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

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

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

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

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

8a02f3135d2d99ad9245727c8f7a74bf.png

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

插桩逻辑

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

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

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

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

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

eb27ae78f499a661dc35381cf8a6aa45.png

关于工程配置可以参考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文件(关联计数指令和源文件)。

cc784077cd0606e4eaadf4f35c2ffc33.png

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

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

其中每个字段的含义

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

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

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

· Source Edges是前继。

· Destination是后继。

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

GCDA

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

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

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

44cfd21ccbc51708a1f9990320524829.png

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

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

c75d0f13f01ab2e5b4b289c135784814.png

技术扩展

了解上述基础知识后,我们更加容易理解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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/529571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

删除了几个月的照片能找回么_手机删除照片怎么恢复正常?自动修复,一看就会...

手机删除照片怎么恢复正常&#xff1f;都说鱼的记忆只有七秒钟&#xff0c;那你的记忆是多久呢&#xff1f;曾经手机拍照留念的照片&#xff0c;是否由于一些微不足道的事情&#xff0c;冲动之下删掉了!想要还原删除掉的手机照片&#xff0c;首先要找对照片恢复器&#xff1a;手…

并发测试mysql_Jmeter性能测试系列——结果分析与报告输出

场景运行结束后&#xff0c;需针对测试结果进行性能分析。通常而言&#xff0c;Jmeter性能测试结果分析可从性能测试指标达成方面着手&#xff0c;然后再分析测试过程中出现的异常情况&#xff0c;逐一判断是否存在性能风险。1.用户登陆并发测试结果分析获取测试指标提取阶段获…

怎样取消连续包月自动续费_苹果手机连续包月会员怎么取消 设置iPhone解除应用自动续费...

[闽南网]相信我们都有在用苹果手机下载软件&#xff0c;但是有时候一个不小心就被开通了包月续费&#xff0c;这个的话会长期进行扣费&#xff0c;要是不想开通的话可以进行取消&#xff0c;不知道的话可以查看下面的教程。取消收费项目是可以从电脑端和手机端两个渠道来进行&a…

配置snmp_多种设备基于 SNMP 协议的敏感信息泄露漏洞数据分析报告

作者&#xff1a;知道创宇404实验室1. 更新情况2. 事件概述SNMP协议[1]&#xff0c;即简单网络管理协议&#xff08;SNMP&#xff0c;Simple Network Management Protocol&#xff09;&#xff0c;默认端口为 161/UDP&#xff0c;目前一共有3个版本&#xff1a;V1&#xff0c;V…

建立项目接口文档_分享:一步一个脚印,vue入门之使用mockjs搭建vue项目测试服务器...

在以前的文档中&#xff0c;我们构建了vue项目的整体架构&#xff0c;详见vue入门&#xff1a;vue项目架构设计起步&#xff0c;现在我们主要对其中的mock server 进行完善。一、概述前后端分离的项目优点之一就是可以前后端并行开发&#xff0c;互不影响。那么在后端接口没有完…

单机最大负载_电流互感器允许接入的实际最大二次负载(注电案例1865)

某国外水电站安装的水轮发电机组&#xff0c;单机额定容量为 120MW&#xff0c;发电机额定电压为 13.8kV&#xff0c;cosφ0.85。发电机、主变压器采用发变组单元接线&#xff0c;未装设发电机断路器&#xff0c;主变高压侧三相短路时流过发电机的最大短路电流为 19.6kA。发电机…

html video显示进度条_使用 tqdm 在 Python 应用中显示进度 | Linux 中国

如果你的程序需要一段时间才能显示结果&#xff0c;可通过显示它的进度来避免让用户感到沮丧。来源&#xff1a;https://linux.cn/article-12990-1.html作者&#xff1a;Moshe Zadka译者&#xff1a;geekpi&#xff08;本文字数&#xff1a;3093&#xff0c;阅读时长大约&#…

课题开题报告范文样本_成都汽车职业技术学校举行 2020年省、市、区课题开题报告会...

01为深入贯彻落实国务院、省、市、区关于深化教育教学改革的重要精神&#xff0c;充分发挥教育科研的先导作用&#xff0c;明晰课题研究的思路并提高课题研究的针对性&#xff0c;1月12日&#xff0c;成都汽车职业技术学校举行2020年省、市、区课题开题报告会。本次开题报告会邀…

安装mysql5 1步骤_Linux系统安装MySQL详细步骤(mysql-5.1等)

第一步、查找以前是否安装有mysql使用下面命令&#xff1a;rpm -qa|grep -i mysql如果显示有包则说明已安装mysql第二步、如果已安装&#xff0c;则需要删除已安装的数据库可按以下步骤删除数据库&#xff1a;删除包命令&#xff1a;rpm -e --nodeps 【包名】rpm -e --nodeps …

mysql分页查询关键_MySQL优化教程之超大分页查询

背景基本上只要是做后台开发&#xff0c;都会接触到分页这个需求或者功能吧。基本上大家都是会用MySQL的LIMIT来处理&#xff0c;而且我现在负责的项目也是这样写的。但是一旦数据量起来了&#xff0c;其实LIMIT的效率会极其的低&#xff0c;这一篇文章就来讲一下LIMIT子句优化…

mysql 分页 jdbc_JDBC调用MySQL分页存储过程实现(一)

DROP PROCEDURE IF EXISTS pro_pager;CREATE DEFINER root% PROCEDURE pro_pager(in p_pageNo int, /*当前页*/in p_perPageCnt int, /*每页记录数*/in p_sql VARCHAR(2000), /*查询sql语句*/out v_totalRowsCnt int, /*记录总条数*/out v_totalPageCnt int) /*记录总页数*/BE…

zabbix proxy mysql_zabbix proxy 配置

在监控大量服务器时&#xff0c;如果将所有的请求都发送到一个zabbix server上&#xff0c;将会对我们的zabbix server造成很大的压力&#xff0c;我们在规划多个区域或机房进行监控的时候&#xff0c;会考虑到使用zabbix proxy 来代理zabbix server 的部分功能。zabbix server…

mvc mysql linq_MVC3+Linq to sql 显示数据库中数据表的数据

1&#xff1a;首先创建asp.net mvc3应用程序 2&#xff1a;创建项目完成后 找到controllers文件鼠标右击选择添加控制器 3 为models文件夹添加一个linq to sql类文件&#xff0c;然后把数据库中的数据库复制进来。如截图操作 4&#xff1a;添加控制器好后会生成一个HomeControl…

mysql工作表格制作教程_Access制作复杂报表

何制作复杂报表利用excel输出复杂报表 在读这篇文章以前首先要提醒大家&#xff0c;Access 本身的报表也具有很强的实用性和强大的功能&#xff0c;只有当你发掘了其本身全部的功能却仍不能满足你对报表的特殊要求时才请使用 Excel 输出报表。很明显&#xff0c;使用 Excel 输出…

php+mysql投票代码_PHP+jQuery+MySql实现红蓝投票功能

本文是一篇综合知识应用类文章&#xff0c;需要您具备PHP、jQuery、MySQL以及html和css方面的基本知识。本文在《PHPMySqljQuery实现的“顶”和“踩”投票功能》一文基础上做了适当改进&#xff0c;共用了数据表&#xff0c;您可以先点击了解这篇文章。HTML我们需要在页面中展示…

numpy 最大值_第 85 天:NumPy 统计函数

数学统计在我们的程序当中特别是数据分析当中是必不可少的一部分&#xff0c;本文就来介绍一下 NumPy 常见的统计函数。最大值与最小值numpy.amin()用于计算数组中的元素沿指定轴的最小值。可以通过 axis 参数传入坐标轴来指定统计的轴&#xff0c;当指定 axis 时&#xff0c;a…

java中如何实现变量可配置_Java基础-如何配置环境变量

Java环境变量详细教程第一步、打开电脑环境变量设置窗口以Win10系统为例子。在桌面找到此电脑&#xff0c;右键此电脑— —>属性&#xff0c;点击属性— —>点击左侧高级系统设置点击高级系统设置点击环境变量第二步、新建JAVA_HOME点击系统变量中的新建,出现输入框&…

python三引号解析_[宜配屋]听图阁

和C语言一样&#xff0c;引号属于特殊功能字符&#xff0c;不能够像普通字符那样直接通过print打印&#xff0c;需要进行一些处理&#xff0c;比如说反斜杠转义等。这里介绍几种打印三引号的方法&#xff0c;希望对需要的朋友有用。1、第一中方法比较简单&#xff0c;直接使用三…

定时执行java程序_如何让Java程序定时运行

由于项目开发的需要&#xff0c;必须实现让一个Java程序定时运行。比如&#xff0c;我的项目中&#xff0c;有一个网络蜘蛛&#xff0c;需要从互联网上抓取数据&#xff0c;与其配合&#xff0c;有另一个程序来对新抓取的页面进行索引的创建&#xff0c;由于数据源更新频率不高…

卡法电子商务 java_javacard DES算法API使用示例

********** 2017年3月15日留言 ——关于java卡Applet系列csdn博文 *************貌似有不少人在看我写的几篇关于java卡applet的博文&#xff0c;也收到了一些评论指正博文错误&#xff0c;或者私信叫我发代码文件过去。在此需要说明的是&#xff0c;java卡applet的这几篇博文…