日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2025/3/8 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 11纯代码 oc xcode_iOS代码染色原理及技术实践 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

背景

隨著業(yè)務(wù)的迅速發(fā)展,業(yè)務(wù)代碼邏輯的復(fù)雜度增加。QA測試的質(zhì)量對于產(chǎn)品上線后的穩(wěn)定性更加重要。一般QA測試的工作流程分為兩大項(xiàng):自動(dòng)化測試和人工測試。這兩種測試后都需要得到代碼覆蓋率。自動(dòng)化測試的覆蓋率,在雙端都有比較成熟的方案。?

本文著重介紹人工測試過程中,怎么得到對應(yīng)的代碼覆蓋率。涉及到的技術(shù)主要是代碼染色。以下會(huì)先介紹整體的工作流程,再對涉及到的技術(shù)一一闡述。

染色流程

流程圖中涉及到了雙端的關(guān)鍵節(jié)點(diǎn)以及技術(shù)點(diǎn)。我們重點(diǎn)介紹編譯階段。

· 編譯階段:生成染色包 (對IR文件插樁)

需要在編譯中增加編譯選項(xiàng),編譯后會(huì)為每個(gè)可執(zhí)行文件生成對應(yīng)的 .gcno文件。

· 運(yùn)行階段:生成二進(jìn)制覆蓋率文件。

在測試代碼中調(diào)用覆蓋率分發(fā)函數(shù),會(huì)生成對應(yīng)的 .gcda文件。

· 解析階段:將二進(jìn)制覆蓋率文件可視化。

編譯階段

在上文可以看出,編譯階段最核心的操作是對IR文件進(jìn)行插樁。

什么是IR文件?插樁邏輯是什么?我們往下看。

語言處理系統(tǒng)

一個(gè)完整的語言處理系統(tǒng)中,從源程序到可執(zhí)行的機(jī)器代碼,如下圖所示,歷經(jīng)幾個(gè)重要模塊。而我們上文提到的IR文件,是編譯器模塊中的產(chǎn)物,插樁處理也是在這個(gè)模塊中進(jìn)行。這里重點(diǎn)討論下編譯器。

編譯器

說起編譯器,我們了解到的傳統(tǒng)編譯器架構(gòu)分為前端、優(yōu)化器和后端。

傳統(tǒng)編譯器的劣勢是:前端和后端沒有完全分離,耦合在了一起,因而如果要支持一門新的語言或硬件平臺(tái),需要做大量的工作。一種更加靈活,適應(yīng)性更好的編譯器套件應(yīng)運(yùn)而生——LLVM.

LLVM

官網(wǎng):http://www.aosabook.org/en/llvm.html

LLVM是一個(gè)開源的,模塊化和可重用的編譯器和工具鏈技術(shù)的集合,或者說是一個(gè)編譯器套件。

可以使用LLVM來編譯Kotlin,Ruby,Python,Haskell,Java,D,PHP,Pure,Lua和許多其他語言。

LLVM核心庫還提供一個(gè)優(yōu)化器,對流行的CPU做代碼生成支持。

LLVM同時(shí)支持AOT預(yù)先編譯和JIT即時(shí)編譯。

2012年,LLVM獲得美國計(jì)算機(jī)協(xié)會(huì)ACM的軟件系統(tǒng)大獎(jiǎng),和UNIX,WWW,TCP/IP,Tex,JAVA等齊名。

LLVM和傳統(tǒng)編譯器最大的不同點(diǎn)在于,前端輸入的任何語言,在經(jīng)過編譯器前端處理后,生成的中間碼都是IR格式的。接下來看下LLVM架構(gòu)下的巨大優(yōu)勢,iOS&MacOS平臺(tái)的編譯器。

iOS&MacOS平臺(tái)編譯器

iOS、MacOS平臺(tái)開發(fā)用的IDE:Xcode。在 Xcode 5版本前使用的是GCC編譯器,在 Xcode 5中將GCC徹底拋棄,替換為LLVM 。LLVM包含了編譯器前端、優(yōu)化器和編譯器后端三大模塊。

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

如下圖所示,能看出LLVM的優(yōu)勢,對于一門新的編程語言,只需要提供對應(yīng)的編譯前端,生成IR。就可以完成整個(gè)新語言的處理。

聊過了IR文件在整個(gè)語言處理過程中的位置,下面我們看下IR文件生成邏輯以及插樁相關(guān)的邏輯。這不得不提到Clang。

Clang

Clang是LLVM的子項(xiàng)目,是C、C++和Objective-C的編譯器。Clang在整個(gè)Objective-C編譯過程中扮演了編譯器前端的角色,同時(shí)也參與到了Swift編譯過程中的Objective-C API映射階段。

Clang的特點(diǎn)是編譯速度快,模塊化,代碼簡單易懂,診斷信息可讀性強(qiáng),占用內(nèi)存小以及容易擴(kuò)展和重用等。

Clang的主要功能是輸出代碼對應(yīng)的抽象語法樹(AST),針對用戶發(fā)生的編譯錯(cuò)誤準(zhǔn)確地給出建議,并將代碼編譯成LLVM IR。

以Xcode為例,Clang編譯Objective-C代碼的速度是Xcode 5版本前使用的GCC的3倍,其生成的AST所耗用掉的內(nèi)存僅僅是GCC的五分之一左右。

關(guān)于iOS項(xiàng)目可以使用對應(yīng)的命令獲取,本文不作詳細(xì)介紹。

關(guān)于編譯器前端的主要工作項(xiàng),感興趣的讀者閱讀《編譯原理》——龍書。

介紹完了IR的“生成器”。接下來我們詳細(xì)介紹IR文件。

LLVM IR

LLVM Intermediate Representation。LLVM的中間代碼,是編譯器前端的輸出,和編譯器后端的輸入。是連接編譯器前端與LLVM后端的一個(gè)橋梁。

通常常見的文件格式為ll 和bt 。做過iOS開發(fā)的讀者應(yīng)該了解bitcode。bt就是編譯器開啟bitcode后的一種中間代碼格式。

IR提供了獨(dú)立于任何特定機(jī)器架構(gòu)的源語,因此它是LLVM優(yōu)化和進(jìn)行代碼生成的關(guān)鍵,也是LLVM有別于其他編譯器的最大特點(diǎn)。LLVM的核心功能都是圍繞IR建立的。

通常中間代碼的表示形式分為:語法樹(syntax tree)、三地址指令序列。為了更好的了解IR文件。這里介紹下三地址指令。

三地址指令

也可以稱為三地址代碼。之所以被稱為三地址指令,是源于它的指令形式:x = y op z ,其中op是一個(gè)二目運(yùn)算符,y和z是運(yùn)算分量的地址,x是運(yùn)算結(jié)果的存放地址。三地址指令最多只執(zhí)行一個(gè)運(yùn)算,通常是計(jì)算,比較或者分支跳轉(zhuǎn)運(yùn)算。

三地址代碼拆分了多運(yùn)算符算術(shù)表達(dá)式以及控制流語句的嵌套結(jié)構(gòu),所以適用于目標(biāo)代碼的生成和優(yōu)化。

//像 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是編譯器產(chǎn)生的臨時(shí)名字。

但是程序運(yùn)行過程中,每個(gè)模塊并不是完全獨(dú)立的。存在著模塊間的跳轉(zhuǎn)。這些被翻譯出的三地址指令,又被組合成另一種便于理解的形式——BB塊。

基本塊

基本塊(Basic Block)是滿足下列條件的最大的連續(xù)三地址指令序列:

· 控制流只能從基本塊中的第一個(gè)指令進(jìn)入該塊。

· 除了基本塊的最后一個(gè)指令,控制流在離開基本塊之前不會(huì)停機(jī)或者跳轉(zhuǎn)。

· 只要基本塊中的第一個(gè)指令被執(zhí)行,那么基本塊中的所有指令都會(huì)得到執(zhí)行

其中中間代碼指令序列生成BB塊的算法如下:

· 確定中間代碼序列中哪些指令是首指令

  • 中間代碼的第一個(gè)三地址指令是一個(gè)首指令。
  • 任意一個(gè)條件或無條件轉(zhuǎn)移指令之后的目標(biāo)指令是一個(gè)首指令。
  • 緊跟在一個(gè)條件或無條件轉(zhuǎn)移指令之后的指令是一個(gè)首指令。

· 每個(gè)首指令對應(yīng)的基本塊包括了從它自己開始,直到下一個(gè)首指令(不含)或者中間代碼的結(jié)尾指令之間的所有指令。

舉例:

i = 1 //第一個(gè)三地址指令,所以作為首指令 j = 1 //第11行,跳轉(zhuǎn)語句的目標(biāo)指令。所以作為首指令 t1 = 10*i t2 = t1+j t3 = 8*t2 t4 = t3-88 a[t4] = 0.0 j = j+1 if j<=10 goto (3) //本身作為跳轉(zhuǎn)指令,所以是首指令 i = i+1 if i<=10 goto (2) //本身作為跳轉(zhuǎn)指令,所以是首指令 i = 1 t5 = i – 1 //第17行,跳轉(zhuǎn)語句的目標(biāo)指令。所以是首指令 t6 = 88*t5 a[t6] = 1.0 i = i+1 if i<=10 goto (13)//本身作為跳轉(zhuǎn)指令,所以是首指令//把一個(gè)10x10的矩陣設(shè)置成單位矩陣中的中間代碼 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; }

對應(yīng)被劃分的BB塊:

在了解了BB塊之后。我們距離怎么對IR文件進(jìn)行插樁的真相已經(jīng)越來越近了,下面我們來看下最后一個(gè)最重要的環(huán)節(jié)。

流圖

當(dāng)將一個(gè)中間代碼程序劃分成為基本塊之后,我們用一個(gè)流圖來表示它們之間的控制流。流圖(flow graph)的結(jié)點(diǎn)就是這些基本塊。流圖就是通常的圖,它可以用任何適合表示圖的數(shù)據(jù)結(jié)構(gòu)來表示。

從基本塊B到基本塊C之間有一條邊當(dāng)且僅當(dāng)基本塊C的第一個(gè)指令緊跟在B的最后一個(gè)指令之后執(zhí)行。存在這樣一條邊的原因有兩種:

· 有一個(gè)從B的結(jié)尾跳轉(zhuǎn)到C的開頭的條件或無條件跳轉(zhuǎn)語句。

· 按照原來的三地址語句序列中的順序,C緊跟在B之后,且B的結(jié)尾不存在無條件跳轉(zhuǎn)語句。

我們說B是C的前驅(qū)(predecessor), 而C是B的一個(gè)后繼(successor)。

通常會(huì)增加兩個(gè)分部稱為入口(entry)和出口(exit)的結(jié)點(diǎn)。它們不和任何可執(zhí)行的中間指令對應(yīng)。從入口到流圖的第一個(gè)可執(zhí)行結(jié)點(diǎn)有一條邊(edges)。從任何包含了可能是程序的最后執(zhí)行指令的基本塊到出口有一條邊。如果程序的最后指令不是一個(gè)無條件轉(zhuǎn)移指令,那么包含了程序的最后一條指令的基本塊是出口結(jié)點(diǎn)的一個(gè)前驅(qū)。但任何包含了跳轉(zhuǎn)到程序之外的跳轉(zhuǎn)指令的基本塊也是出口結(jié)點(diǎn)的前驅(qū)。

其中B0-B7是BB塊。E0-E7是邊(edges)

插樁邏輯

覆蓋率計(jì)數(shù)指令的插入會(huì)進(jìn)行兩次循環(huán),外層循環(huán)遍歷編譯單元中的函數(shù),內(nèi)層循環(huán)遍歷函數(shù)的基本塊。函數(shù)遍歷用來向gcno文件中寫入函數(shù)位置信息。

一個(gè)函數(shù)中基本塊的插樁方法如下:

· 統(tǒng)計(jì)所有BB的后繼數(shù)n,創(chuàng)建和后繼數(shù)大小相同的數(shù)組ctr[n]。

· 以后繼數(shù)編號(hào)為序號(hào)將執(zhí)行次數(shù)依次記錄在 ctr[i] 位置,對于多后繼情況根據(jù)條件判斷插入。

根據(jù)生成流圖的規(guī)則,可以很容易得到樁點(diǎn)位置,[]處就是插入的樁點(diǎn)序號(hào)。

關(guān)于工程配置可以參考GCOV的官網(wǎng):

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

下面簡單介紹下gcov,gcno,gcda這三個(gè)gcc家族的關(guān)鍵成員。

GCOV

GCOV是一個(gè)GNU的本地覆蓋測試工具, 伴隨GCC發(fā)布,配合GCC共同實(shí)現(xiàn)對C或者C++文件的語句覆蓋和分支覆蓋測試。是一個(gè)命令行方式的控制臺(tái)程序。需要工具鏈的支持。

GCNO

利用Clang分別生成源文件的AST和IR文件,對比發(fā)現(xiàn),AST中不存在計(jì)數(shù)指令,而IR中存在用來記錄執(zhí)行次數(shù)的代碼。

覆蓋率映射關(guān)系生成源碼是LLVM的一個(gè)Pass,用來向IR中插入計(jì)數(shù)代碼并生成.gcno文件(關(guān)聯(lián)計(jì)數(shù)指令和源文件)。

上圖右側(cè)。即為gcno的可視化格式。

本質(zhì)上gcno是二進(jìn)制內(nèi)容。需要借助gcov工具(gcov -dump xxx.gcno)將文件轉(zhuǎn)換為這種可視的格式。

其中每個(gè)字段的含義

· 函數(shù)所在文件的絕對路徑(如上圖紅框所示)。

· Block :0-7 代表BB文件的編號(hào)。

· Counter為插樁后生成的存儲(chǔ)執(zhí)行次數(shù)的字段。

· Source Edges是前繼。

· Destination是后繼。

· Lines是指令在代碼文件中行數(shù)。

GCDA

gcda是由加了-fprofile-arcs編譯參數(shù)的編譯后的文件運(yùn)行所產(chǎn)生的,它包含了弧跳變的次數(shù)和其他的概要信息。

借助gcov工具可以查看gcda文件的大致內(nèi)容:

gcda文件已經(jīng)是一個(gè)包括了函數(shù)執(zhí)行情況的文件。剩余的工作就是將執(zhí)行情況更加可視化,和源碼進(jìn)行匹配。

了解了三個(gè)gc的重要成員。借助一些前端工具,我們就可以得到一份詳細(xì)的覆蓋率報(bào)告了。關(guān)于前端工具,大家可以自行搜索。

最后附上覆蓋率的一個(gè)報(bào)告片段

技術(shù)擴(kuò)展

了解上述基礎(chǔ)知識(shí)后,我們更加容易理解LLVM中的架構(gòu)及各個(gè)模塊的功能。我們可以在插樁過程中,修改原有的插樁邏輯。我們可以編寫XCode編譯器插件。總之,借助LLVM的源碼及我們了解到的知識(shí)。在一個(gè)語言的任意處理階段,我們都可以對其進(jìn)行定制,甚至我們可以創(chuàng)造一個(gè)自己的專屬語言。

源碼參考:

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

總結(jié)

以上是生活随笔為你收集整理的11纯代码 oc xcode_iOS代码染色原理及技术实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。