深入iOS系統(tǒng)底層之程序中的匯編代碼

合抱之木,生于毫末;九層之臺(tái),起于壘土萌壳;千里之行,始于足下亦镶。--(老子·道德經(jīng) )

對(duì)于一個(gè)閉源系統(tǒng)來(lái)說(shuō)如果想研究某些邏輯的內(nèi)部實(shí)現(xiàn)就需要對(duì)匯編語(yǔ)言進(jìn)行掌握和了解、對(duì)于某些需要高性能實(shí)現(xiàn)的邏輯來(lái)說(shuō)用匯編語(yǔ)言實(shí)現(xiàn)可能是最好的選擇袱瓮、對(duì)于某些邏輯來(lái)說(shuō)可能只能用匯編來(lái)實(shí)現(xiàn)缤骨。以最后一個(gè)能力來(lái)說(shuō):當(dāng)我們要實(shí)現(xiàn)一個(gè)HOOK所有OC方法調(diào)用的邏輯時(shí)欠啤,因?yàn)镠OOK的方法不能破壞原有函數(shù)的參數(shù)棧邑商,而且還需要在適當(dāng)?shù)臅r(shí)候調(diào)用原始的函數(shù)而不關(guān)注原始函數(shù)的入?yún)r(shí)就只能選擇用匯編語(yǔ)言來(lái)實(shí)現(xiàn)帘瞭。

查看程序的匯編代碼

其實(shí)更多的時(shí)候我們不要求去編寫(xiě)一段匯編代碼或者機(jī)器指令者甲,而是如果能夠讀懂簡(jiǎn)單的匯編代碼就能窺探一些系統(tǒng)底層的實(shí)現(xiàn)邏輯和原理。當(dāng)然市面上也有很多的反匯編的工具軟件能夠?qū)R編代碼轉(zhuǎn)化為高級(jí)語(yǔ)言的偽代碼蝎毡,缺點(diǎn)就是這些工具大多是靜態(tài)分析工具以及反匯編出來(lái)的代碼不一定完全正確萍桌,有時(shí)候我們可能更加希望在運(yùn)行時(shí)去調(diào)試或者分析一些問(wèn)題,這樣能夠閱讀匯編代碼的話效果會(huì)更好一些瘫里。

查看匯編代碼的三種方法

Xcode提供了三種查看程序匯編代碼的方式:

  1. 在程序運(yùn)行時(shí)的斷點(diǎn)處可以通過(guò)Debug菜單->Debug Workflow->Always Show Disassembly來(lái)切換匯編代碼模式和高級(jí)語(yǔ)言模式。
  2. 通過(guò)快捷鍵 alt + command + \ 可以對(duì)某個(gè)系統(tǒng)函數(shù)或者第三方庫(kù)函數(shù)或者類的方法設(shè)置符號(hào)斷點(diǎn)荡碾,這樣當(dāng)程序出現(xiàn)相應(yīng)的函數(shù)或者方法調(diào)用時(shí)就會(huì)切換到匯編代碼模式谨读。你可以通過(guò)這種方式來(lái)閱讀和了解函數(shù)或者方法的實(shí)現(xiàn)。
  3. 如果你想查看某個(gè)高級(jí)語(yǔ)言文件生成的偽匯編代碼時(shí)坛吁,你需要在對(duì)應(yīng)的文件處通過(guò)Product菜單->Perform Action->Assemble "xxxxx" 來(lái)查看這個(gè)文件生成的偽匯編代碼劳殖。當(dāng)你在模擬器模式下所看到的就是x64系統(tǒng)下的匯編代碼,當(dāng)你在設(shè)備模式下時(shí)所看到的就是arm系統(tǒng)下的匯編代碼拨脉。

clang命令的簡(jiǎn)單介紹

通過(guò)上述的第三種方式查看生成的匯編代碼的方式其實(shí)是通過(guò)clang命令完成的哆姻。clang是一個(gè)C/C++/Objective-C語(yǔ)言的編譯器,它包含了預(yù)處理玫膀、語(yǔ)法分析矛缨、優(yōu)化、代碼生成帖旨、匯編裝配箕昭、鏈接等功能。我們通過(guò)菜單來(lái)進(jìn)行的構(gòu)建程序的操作其實(shí)內(nèi)部實(shí)現(xiàn)都是借助clang來(lái)完成的解阅。你可以在命令終端中鍵入man clang來(lái)查看這個(gè)命令的所有參數(shù)和使用介紹落竹,你還可以在Xcode工程中使用command + 9快捷鍵就可以看到你每次構(gòu)建工程的詳細(xì)流程,這里面有對(duì)程序使用clang命令的進(jìn)行編譯和鏈接的具體實(shí)踐货抄。

程序編譯鏈接命令流程圖

可以看出無(wú)論是源代碼編譯還是程序鏈接都是用clang命令來(lái)實(shí)現(xiàn)的述召,不要被命令中大量的編譯鏈接選項(xiàng)所嚇倒朱转,其實(shí)這些參數(shù)都是我們?cè)诳梢暬墓こ痰?code>Build Settings里面設(shè)置的

要想了解完整的編譯選項(xiàng)的設(shè)置和意義可以參考:https://pewpewthespells.com/blog/buildsettings.html

我們只介紹clang命令的幾個(gè)主要的參數(shù)選項(xiàng):


  clang  [-arch <arm|arm64|x86_64>] [-x <objective-c|objective-c++|c|c++|assembler-with-cpp>] [-L<庫(kù)路徑>] [-I<頭文件路徑>] [-F<框架頭文件路徑>] [-isysroot 系統(tǒng)SDK路徑] [-fobjc-arc | -fno-objc-arc] [-lxxx] [-framework XXX] [-Xlinker option] [-Xlinker value] [-E 源代碼文件] [-rewrite-objc 源代碼文件] [-c 源代碼文件] [-S 源代碼文件] [-filelist LinkFileList文件] [-o 輸出文件]  

1.常規(guī)參數(shù)

?-arch <arm|arm64|x86_64|i386>: 生成的代碼的體系結(jié)構(gòu),四選一积暖。
?-x <objective-c|objective-c++|c|c++|assembler-with-cpp: 指定編譯的文件的語(yǔ)言肋拔,五選一,默認(rèn)為objective-c呀酸。這個(gè)選項(xiàng)用在編譯階段凉蜂。
?-I<頭文件路徑>: 指定#import或者#include .h文件的搜索路徑。
?-L<庫(kù)路徑>: 指定鏈接時(shí)的動(dòng)態(tài)庫(kù)或者靜態(tài)庫(kù)文件的搜索路徑性誉。這個(gè)選項(xiàng)用在鏈接階段窿吩。
?-F<框架頭文件路徑>: 指定#import一個(gè)框架庫(kù)時(shí)的頭文件搜索路徑。
?-isysroot 系統(tǒng)SDK路徑: 指定程序使用的系統(tǒng)框架SDK的路徑错览。比如:
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.1.sdk
表明使用真機(jī)版的iOS12.1版本的SDK來(lái)編譯或者鏈接當(dāng)前程序纫雁。
?-fobjc-arc | -fno-objc-arc: 表明當(dāng)前程序是使用arc編譯還是mrc來(lái)編譯。
?-lxxx: 只在鏈接時(shí)使用倾哺,表明將名字為libxxx的庫(kù)鏈接到程序中來(lái)轧邪。
?-framework XXX: 只在鏈接時(shí)使用,表明將名字為XXX的framework庫(kù)鏈接到程序中來(lái)羞海。
?-Xlinker option -Xlinker value: 設(shè)置鏈接的選項(xiàng)忌愚,這里必須要成對(duì)出現(xiàn),其意義表示: option = value却邓。

2.預(yù)處理

?-E 源代碼文件 -o 輸出文件: 對(duì)源代碼進(jìn)行預(yù)處理硕糊。也就是將所有#include和#import的頭文件展開(kāi)、將所有宏定義展開(kāi)腊徙、將所有枚舉值轉(zhuǎn)化為常量值的處理简十。你可以借助Product菜單->Perform Action->Preprocess "xxxxx"來(lái)查看一個(gè)源代碼文件的預(yù)處理結(jié)果。

3.生成C++代碼

?-rewrite-objc 源代碼文件: 將OC代碼轉(zhuǎn)化為對(duì)應(yīng)的C++語(yǔ)言實(shí)現(xiàn)撬腾。并在源代碼文件的當(dāng)前目錄下生成一個(gè)對(duì)應(yīng)的后綴為.cpp的C++代碼螟蝙。你可以通過(guò)這種方法來(lái)詳細(xì)了解arc的實(shí)現(xiàn)原理、block的實(shí)現(xiàn)以及調(diào)用原理民傻、各種OC關(guān)鍵字的實(shí)現(xiàn)邏輯原理胰默、OC類屬性和方法的實(shí)現(xiàn)邏輯、類方法的定義以及runtime的機(jī)制等等邏輯饰潜。因此用這個(gè)參數(shù)可以幫助我們窺探很多iOS系統(tǒng)的秘密初坠。在使用這個(gè)命令時(shí)可能會(huì)遇到一個(gè)常見(jiàn)的錯(cuò)誤:

In file included from xxxx.m:9:
xxxx.h:9:29: fatal error: module 'UIKit' not found
#pragma clang module import UIKit /* clang -E: implicit import for #import <UIKit/UIKit.h> */
                     ~~~~~~~^~~~~
1 warning and 1 error generated.

這個(gè)主要是因?yàn)檎也坏较到y(tǒng)SDK的路徑文件所致,因此可以帶上-isysroot參數(shù)來(lái)同時(shí)指定系統(tǒng)SDK路徑彭雾。下面就是一個(gè)使用的示例:


clang -rewrite-objc -arch arm64  -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.1.sdk xxxx.m

這里的-isysroot后面的路徑要確保是對(duì)應(yīng)系統(tǒng)SDK的路徑碟刺,同時(shí)-arch中的值要和路徑中的SDK要是相同的結(jié)構(gòu)體系爽柒。

4.生成匯編代碼

?-S 源代碼文件 -o 輸出文件: 要將某個(gè)源代碼文件生成匯編代碼時(shí)需要在 -S 參數(shù)后面指定源代碼文件者填。而-o 后面的輸出文件就是對(duì)應(yīng)的匯編代碼文件,一般這個(gè)輸出文件以.s為擴(kuò)展名占哟。這里要注意同時(shí)使用-arch參數(shù)指定輸出的體系架構(gòu)心墅。

5.編譯

?-c 源代碼文件 -o 輸出文件:要編譯某個(gè)源代碼文件時(shí)使用這兩個(gè)參數(shù)選項(xiàng)榨乎,其中-c后面跟著的是要編譯的源代碼文件铐姚,而-o后面輸出的是.o為擴(kuò)展名的目標(biāo)文件隐绵。

6.鏈接

?-filelist LinkFileList文件 -o 輸出文件: 執(zhí)行鏈接時(shí)要把所有目標(biāo).o文件作為輸入?yún)?shù),但是為了管理方便可以將這些.o文件的路徑保存到一個(gè)擴(kuò)展名為.LinkFileList的文件中悍手,然后再使用-filelist 參數(shù)后面跟隨對(duì)應(yīng)的.LinkFileList文件來(lái)指定目標(biāo)文件集合诡延。而-o后面的輸出文件就是對(duì)應(yīng)的可執(zhí)行程序文件肆良。

工程中引入?yún)R編代碼

你也可以在xcode工程中直接引入?yún)R編代碼或者使用匯編代碼來(lái)編寫(xiě)程序和函數(shù)惹恃,添加匯編文件的方法是:File菜單->New->File...->在列表中選擇:Assembly File即可巫糙。一般情況下匯編代碼都是以.s為擴(kuò)展名,生成的文件是一個(gè)空文件恳不,然后你就可以在文件里面編寫(xiě)對(duì)應(yīng)的匯編代碼了神妹。系統(tǒng)也支持在匯編代碼中設(shè)置斷點(diǎn)進(jìn)行調(diào)試鸵荠。因?yàn)閕OS系統(tǒng)支持多種體系結(jié)構(gòu)哨坪,所以可以在匯編代碼中使用幾個(gè)宏來(lái)區(qū)分代碼是x86_64的還是arm或者arm64的, 就比如下面的代碼:

//你可以像高級(jí)語(yǔ)言一樣通過(guò)#include引入頭文件当编。
#include <xxx.h>

//arm體系
#ifdef __arm__

//指令和數(shù)據(jù)定義

//arm64體系
#elif __arm64__

//指令和數(shù)據(jù)定義

//x86 32位體系
#elif __i386__

//指令和數(shù)據(jù)定義

//x86_64位體系
#elif __x86_64__

//指令和數(shù)據(jù)定義

//其他體系
#else

#endif

當(dāng)你在項(xiàng)目中添加了一個(gè)匯編文件時(shí),就需要掌握和了解匯編代碼的編寫(xiě)。關(guān)于匯編指令的詳細(xì)描述由于太過(guò)龐大這里就不介紹了,這里主要介紹一些常用的匯編關(guān)鍵字,以便幫助大家能更好的閱讀和編寫(xiě)程序谁帕。

常見(jiàn)的匯編語(yǔ)法

在Xcode中無(wú)論是AT&T還是arm匯編語(yǔ)言的關(guān)鍵字都以.開(kāi)頭峡继。編寫(xiě)匯編代碼主要就是數(shù)據(jù)的定義以及代碼指令。一個(gè)匯編語(yǔ)言文件中還可以使用和C語(yǔ)言類似的文件引入以及各種預(yù)編譯指令匈挖,還可以引用高級(jí)語(yǔ)言中定義的變量和符號(hào)以及函數(shù)碾牌。

1.注釋

匯編指令中注釋和C/C++/OC相同。arm體系下的匯編代碼特有的行注釋是代碼后面的 ;號(hào)注釋儡循,而x86_64體系下的匯編代碼的特有的行注釋是##舶吗。

2.節(jié)

無(wú)論是指令還是數(shù)據(jù)管理的單位都是節(jié)(Section)。因?yàn)樵趇OS系統(tǒng)的mach-o文件格式中的數(shù)據(jù)和指令的存儲(chǔ)都是以段(Segment)和節(jié)為單位劃分的择膝。任何代碼和數(shù)據(jù)總是在某個(gè)節(jié)內(nèi)被定義誓琼。每個(gè)節(jié)都?xì)w屬于某個(gè)段,每個(gè)節(jié)有一個(gè)唯一的名字肴捉。節(jié)定義的關(guān)鍵字和語(yǔ)法如下:

.section <段名>,<節(jié)名>,<節(jié)屬性>

相同的段名和節(jié)名可以出現(xiàn)在多出腹侣,數(shù)據(jù)和代碼都是定義在由.section指定的節(jié)下開(kāi)始,并結(jié)束于下一個(gè)節(jié)的定義開(kāi)始處齿穗。系統(tǒng)最終在生成代碼時(shí)會(huì)將相同的段名和節(jié)名的內(nèi)容統(tǒng)一匯總到一起存儲(chǔ)傲隶。一般情況下所有的指令代碼都是在__TEXT段下的節(jié)中被定義,而數(shù)據(jù)定義則是在__DATA段下的節(jié)中被定義窃页。如果匯編代碼中不指定節(jié)名則數(shù)據(jù)和代碼默認(rèn)是在__TEXT,__text下跺株。系統(tǒng)還提供了兩個(gè)簡(jiǎn)化代碼段和數(shù)據(jù)段的節(jié)定義關(guān)鍵字。

//代碼段的定義脖卖,等價(jià)于 .section __TEXT,__text
.text

//數(shù)據(jù)段的定義乒省,等價(jià)于 .section __DATA,__data
.data

在反匯編代碼中的節(jié)定義中除了指定名稱外你還會(huì)看到一些比如:regular,pure_instructions,no_dead_strip,cstring_literals等等節(jié)定義的屬性。這些屬性所代表的意義和mach-o文件格式中的結(jié)構(gòu)體struct section_64中的flags字段所表示的意義一致胚嘲。flags可設(shè)置的值就是<mach-o/loader.h>中那些以S_開(kāi)頭的宏定義值作儿。

3.標(biāo)簽和符號(hào)

標(biāo)簽是一個(gè)可被理解的地址偏移表示,是一個(gè)地址的別名馋劈。使用標(biāo)簽的目標(biāo)是為了讓程序代碼更具有可讀性。標(biāo)簽定義后可以在其他指令中引用晾嘶,也可以在數(shù)據(jù)變量中被引用妓雾。標(biāo)簽的定義規(guī)則為:

標(biāo)簽名1:
//代碼和數(shù)據(jù)
標(biāo)簽名2:
//代碼和數(shù)據(jù)

標(biāo)簽可以看成是一個(gè)文件中的局部指針變量,對(duì)于數(shù)據(jù)段中定義的標(biāo)簽通常用來(lái)當(dāng)做訪問(wèn)變量的地址垒迂,而對(duì)于代碼段中定義的標(biāo)簽通常用來(lái)做指令跳轉(zhuǎn)用械姻。比如下面的代碼:

//x86_64中的代碼
.data
AGE:    //標(biāo)簽的定義處
.long 13

.text
LAB1:    //標(biāo)簽的定義處
mov AGE(%rip), %rax     //標(biāo)簽的使用處
jmp LAB1                         //標(biāo)簽的使用處

有的時(shí)候還可以定義方向標(biāo)簽,方向標(biāo)簽只能是數(shù)字机断,然后可以在使用這些方向標(biāo)簽時(shí)楷拳,在方向標(biāo)簽后面帶一個(gè)b表明跳轉(zhuǎn)到當(dāng)前指令前面定義的某個(gè)最近的方向標(biāo)簽绣夺,而方向標(biāo)簽后面帶一個(gè)f表明跳轉(zhuǎn)到當(dāng)前指令后面定義的某個(gè)最近的方向標(biāo)簽。就比如下面演示的代碼:

//x86_64中的演示代碼欢揖,這里面定義了方向標(biāo)簽陶耍,同時(shí)也有如何跳轉(zhuǎn)到這些方向標(biāo)簽的使用方法。
.text
mov %rax, %rax
1:                //a
mov %rax, %rax
2:                //b
mov %rax, %rax
2:                //c
mov %rax, %rax
jmp 2b   //跳轉(zhuǎn)到c處
jmp 1b   //跳轉(zhuǎn)到a處
jmp 1f   //跳轉(zhuǎn)到d處

1:                //d
mov %rax, %rax

標(biāo)簽只是文件內(nèi)地址偏移的別名她混,只能在定義的文件內(nèi)部引用烈钞。要想讓這個(gè)標(biāo)簽被外部引用和訪問(wèn)就需要將標(biāo)簽聲明為符號(hào)。高級(jí)語(yǔ)言文件中定義的能被外部訪問(wèn)的函數(shù)和全局變量其實(shí)都是一個(gè)符號(hào)坤按,不管是函數(shù)地址還是全局變量的內(nèi)存地址毯欣,其實(shí)都是一個(gè)地址位置,而地址的別名則是可以用標(biāo)簽表示臭脓,因此要想將一個(gè)標(biāo)簽定義為外部可訪問(wèn)酗钞,就需要將標(biāo)簽名聲明為符號(hào)。就如高級(jí)語(yǔ)言中的靜態(tài)函數(shù)和靜態(tài)變量以及全局函數(shù)和全局變量一樣来累,匯編語(yǔ)言中的符號(hào)聲明也有兩種:

//對(duì)外可見(jiàn)的全局符號(hào)砚作,可以被外部程序引用和訪問(wèn)。
.global  全局符號(hào)名
全局符號(hào)名:

//私有外部符號(hào)佃扼,只在程序內(nèi)可引用和訪問(wèn)偎巢。
.private_extern  私有外部符號(hào)名
私有外部符號(hào)名:

符號(hào)名要和標(biāo)簽名匹配。因?yàn)镃語(yǔ)言的函數(shù)名稱以及全局變量等符號(hào)在編譯時(shí)生成的符號(hào)前面添加一個(gè)下劃線_兼耀。所以在高級(jí)語(yǔ)言中的名稱對(duì)應(yīng)的真實(shí)符號(hào)都是帶一個(gè)下劃線前綴的压昼,因此一般情況下我們?cè)趨R編語(yǔ)言中聲明的符號(hào)和標(biāo)簽名最好帶一個(gè)下劃線。并且在其他高級(jí)語(yǔ)言的聲明中不要使用這個(gè)下化線瘤运,就比如下面的例子:

//xxx.s

//在數(shù)據(jù)段中定義一個(gè)全局變量符號(hào)_testSymbol窍霞。
.data
.global _testSymbol
_testSymbol:
.int 10

.............................................
//xxx.m

//高級(jí)語(yǔ)言中聲明使用這個(gè)符號(hào)。
extern int testSymbol;

int main(int argc, char *argv[])
{
   printf("testSymbol = %d",testSymbol);
   return 0;
}


同時(shí)在匯編代碼中引用高級(jí)語(yǔ)言定義的符號(hào)時(shí)拯坟,也要多帶上一個(gè)下劃線前綴但金。

4.對(duì)齊

因?yàn)閮?nèi)存尋址訪問(wèn)的一些特性,要求我們的某些代碼或者數(shù)據(jù)的存放地址必須是某個(gè)數(shù)字的倍數(shù)郁季,也就是所謂的對(duì)齊冷溃。設(shè)置對(duì)齊的關(guān)鍵字如下:

//表明此處的地址是(2^3)8的倍數(shù)。這里面p2align貌似和align所表達(dá)的意義相似梦裂,不知道為什么會(huì)有兩個(gè)關(guān)鍵字似枕。
.align 3
.p2align 3

5.宏定義

匯編語(yǔ)言也可以和C語(yǔ)言一樣使用宏定義,來(lái)做一些代碼復(fù)用處理年柠。宏定義的語(yǔ)法如下:

//宏的開(kāi)始
.macro 宏名稱

//這里面可以編寫(xiě)任何其他的匯編代碼和關(guān)鍵字
// 宏可以帶參數(shù)凿歼,宏內(nèi)使用參數(shù)總是從$0開(kāi)始。
//宏的結(jié)束
.endmacro

在使用定義的宏時(shí)就直接在相應(yīng)的地方插入宏的名字即可,如果宏有參數(shù)則參數(shù)跟在宏名稱后面并且參數(shù)之間以逗號(hào)分隔答憔。下面就是一個(gè)宏定義和使用的例子:

//宏定義
.macro Test

mov x0, $0
mov x1, $1

.endmacro

//宏使用
Test 10,20

6.數(shù)據(jù)的定義

數(shù)據(jù)的定義類似C語(yǔ)言中變量的定義味赃,匯編代碼中也支持多種類型的數(shù)據(jù)定義。定義一個(gè)數(shù)據(jù)的語(yǔ)法如下:

.<數(shù)據(jù)類型>  值

一共有如下的數(shù)據(jù)類型:

類型 描述 舉例
.byte 單個(gè)字節(jié) .byte 0x10
.long 長(zhǎng)整型4字節(jié) .long 0x10
.quad 4倍類型虐拓,8字節(jié)長(zhǎng)度 .quad 0x10
.asciz 以0結(jié)尾的字符串 .asciz "Hello world!"
.ascii 不以0結(jié)尾的字符串 .ascii "Hello world!"
.space 空字節(jié)數(shù)心俗,后面跟數(shù)量 .space 4
.short 短整型2字節(jié) .short 0x10

數(shù)據(jù)類型的值可以是一個(gè)常量也可是一個(gè)表達(dá)式,也可以是一個(gè)標(biāo)簽符號(hào)侯嘀。如果我們想給某個(gè)數(shù)據(jù)定義指定一個(gè)類似于變量的名稱另凌,則可以和標(biāo)簽來(lái)結(jié)合。比如:

name:
.asciz "歐陽(yáng)大哥"
age:
.long 13
nickname:
.quad name   //這里的昵稱變量是一個(gè)指針表明和name是相同的戒幔。

如果要想在代碼塊中訪問(wèn)上面定義了標(biāo)簽名的變量吠谢,則可以采用如下指令:

//x86體系的指令訪問(wèn)符號(hào)變量
leaq name(%rip), %rax
movl age(%rip), %ebx
movq nickname(%rip), %rcx

//arm64體系的指令訪問(wèn)符號(hào)變量
adrp x0, name@PAGE
add x0, x0, name@PAGEOFF
adrp x1, age@PAGE
add x1, x1, age@PAGEOFF
ldr x1, [x1]
adrp x2, nickname@PAGE
add x2, x2, nickname@PAGEOFF

7.函數(shù)的定義

匯編語(yǔ)言中并沒(méi)有專門(mén)用于函數(shù)定義的關(guān)鍵字,匯編語(yǔ)言中只有代碼塊的定義诗茎,所有可執(zhí)行的代碼塊都存放在代碼段中工坊。所謂函數(shù)調(diào)用其實(shí)就是調(diào)用函數(shù)代碼對(duì)應(yīng)的首地址。因此對(duì)于文件內(nèi)的函數(shù)調(diào)用其實(shí)可以借助標(biāo)簽來(lái)完成敢订,而其他文件對(duì)函數(shù)的調(diào)用則可以借助符號(hào)來(lái)完成王污。對(duì)于函數(shù)中的參數(shù)部分的處理則是按照函數(shù)調(diào)用參數(shù)傳遞的ABI規(guī)則來(lái)指定,具體詳情可以參考我的深入iOS系統(tǒng)底層之CPU寄存器介紹中的介紹楚午。

下面就是一個(gè)求兩個(gè)參數(shù)和的加法函數(shù)在x86_64位體系結(jié)構(gòu)下的實(shí)現(xiàn):

//x86_64位下的函數(shù)實(shí)現(xiàn)
.text
.global _add
.align 3
_add:
movq  %rdi,%rbx
movq  %rsi,%rax
addq  %rbx,%rax
ret
LExit_add:

8.指令的編寫(xiě)

關(guān)于在匯編語(yǔ)言中編寫(xiě)指令這里就不贅述了昭齐,否則一本書(shū)也說(shuō)不完,大家可以參考相關(guān)的匯編代碼的書(shū)籍即可矾柜,最好的方法是閱讀CPU體系結(jié)構(gòu)手冊(cè):

9.偽條件語(yǔ)句

匯編語(yǔ)言有相應(yīng)的進(jìn)行比較和跳轉(zhuǎn)的指令阱驾,但是我們?nèi)匀豢梢越柚鷤螚l件語(yǔ)句來(lái)使得我們的代碼更加具有可讀性。偽條件語(yǔ)句的語(yǔ)法如下:

.if 邏輯表達(dá)式
.elseif 邏輯表達(dá)式
.else
.endif

10.CFI: 調(diào)用框架指令

這部分偽指令以.cfi開(kāi)頭怪蔑。主要用來(lái)記錄函數(shù)的幀棧信息和用于異常處理里覆。具體的指令介紹請(qǐng)參考:https://blog.csdn.net/permike/article/details/41550991。這篇文章介紹的已經(jīng)很詳細(xì)了缆瓣。關(guān)于函數(shù)幀棧信息和異常的實(shí)現(xiàn)原理我會(huì)在后續(xù)的文章中繼續(xù)介紹喧枷。

引用匯編代碼文件中的符號(hào)

因?yàn)閰R編代碼源文件沒(méi)有所謂的.h頭文件聲明。所以當(dāng)你在其他文件中要想使用匯編語(yǔ)言中定義的函數(shù)或者全局變量時(shí)弓坞,可以在你的源代碼文件的頂部進(jìn)行符號(hào)使用的聲明:

//xxxxx.m

//函數(shù)聲明
extern void 不帶下劃線的函數(shù)符號(hào)(參數(shù)列表);

//變量使用聲明
extern 類型 不帶下劃線的變量符號(hào);

在高級(jí)語(yǔ)言中嵌入?yún)R編代碼

我們還可以在高級(jí)語(yǔ)言中嵌入?yún)R編代碼隧甚,嵌入的主要目的是為了優(yōu)化代碼的性能,還有一些高級(jí)語(yǔ)言完成不了能力比如獲取當(dāng)前執(zhí)行指令的地址以及讀取一些狀態(tài)寄存器和特殊寄存器的值渡冻,還有一些場(chǎng)景甚至可以用匯編代碼來(lái)解決高級(jí)語(yǔ)言需要用鎖來(lái)解決的多線程的問(wèn)題等等呻逆。具體的嵌入方法和規(guī)則我這里就偷一下懶,直接訪問(wèn)這個(gè)鏈接:

https://blog.csdn.net/pbymw8iwm/article/details/8227839

就可以很清楚的知道嵌入的規(guī)則了菩帝,這篇文章已經(jīng)介紹得很仔細(xì)了。下面我將舉3個(gè)具體的例子:

  • 高級(jí)語(yǔ)言的變量作為嵌入?yún)R編代碼的輸入輸出
//計(jì)算兩個(gè)數(shù)相加
long add(long a, long b)
{
    long c = 0;
#if __arm64__
     __asm__(
             "ldr x11, %1\n"
             "ldr x12, %2\n"
             "add %0, x11, x12\n"
             :"=r"(c)
             :"m"(a),"m"(b)
             );
    
#elif __x86_64__
    
    __asm__(
            "movq %1,%%rdi\n"
            "movq %2,%%rsi\n"
            "addq %%rdi,%%rsi\n"
            "movq %%rsi,%0\n"
            :"=r"(c)
            :"m"(a),"m"(b)
            );
    
#else
        c = a + b;
#endif
    
    return c;
}

  • 系統(tǒng)的特殊寄存器的值輸出給高級(jí)語(yǔ)言的變量
//打印當(dāng)前指令的地址以及當(dāng)前線程ID
void foo()
{
    unsigned long pc = 0;
    unsigned long threadid = 0;
    
#if __arm64__

      //arm64限制了直接讀寫(xiě)PC寄存器的方式,而是改動(dòng)相對(duì)偏移
      //TPIDRRO_EL0是指內(nèi)核中的線程ID呼奢,用專門(mén)的指令mrs來(lái)讀取
      __asm__(
              "adr x0, #0\n"
              "stur x0, %0\n"
              "mrs %1,TPIDRRO_EL0\n"
              :"=m"(pc),"=r"(threadid)
              );
    
#elif __x86_64__
    //x86體系的CPU沒(méi)有專門(mén)的寄存器保存線程ID
    __asm__(
            "leaq (%%rip), %%rdi\n"
            "movq %%rdi, %0\n"
            :"=m"(pc)
            );
#else
    NSAssert(0, @"oops!");
#endif
    
   
    NSLog(@"pc=%ld, threadid=%ld",pc, threadid);
    
}

  • 無(wú)鎖多線程變量訪問(wèn)
    假設(shè)程序中定義了兩個(gè)變量x和y宜雀,現(xiàn)在A線程負(fù)責(zé)讀取這兩個(gè)變量的值進(jìn)行處理,而B(niǎo)線程則負(fù)責(zé)寫(xiě)入這兩個(gè)變量的最新值握础,這兩個(gè)變量具有關(guān)聯(lián)系辐董,必須同時(shí)寫(xiě)入和讀取。如果是用高級(jí)語(yǔ)言來(lái)實(shí)現(xiàn)為了保證同步則需要在兩個(gè)線程的讀寫(xiě)兩個(gè)變量的地方進(jìn)行加鎖處理禀综。而在arm體系結(jié)構(gòu)下則可以借助ldp,stp兩個(gè)條指令來(lái)實(shí)現(xiàn)指令級(jí)別上的原子操作简烘,因?yàn)闊o(wú)需加鎖從而達(dá)到最佳的性能。
//假設(shè)x,y變量保存在全局變量critical數(shù)組中定枷。
long critical[2];

void read(long *px, long *py)
{
#if __arm64__
    __asm__(
            "ldp x9, x10, %2\n"
            "stur x9,%0\n"
            "stur x10,%1\n"
            :"=m"(*px),"=m"(*py):"m"(critical)
           );  
#else
    //其他體系結(jié)構(gòu)在讀取時(shí)必須要加鎖處理孤澎。
    *px = critical[0];
    *py = critical[1];
#endif
}

void write(long x, long y)
{
#if __arm64__
    __asm__(
            "stp %1, %2, %0":"=m"(critical):"r"(x),"r"(y)
           );
#else
    //其他體系結(jié)構(gòu)在寫(xiě)入兩個(gè)變量時(shí)必須要加鎖處理。
    critical[0] = x;
    critical[1] = y;
#endif
}



??【返回目錄


歡迎大家訪問(wèn)我的github地址簡(jiǎn)書(shū)地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末欠窒,一起剝皮案震驚了整個(gè)濱河市覆旭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岖妄,老刑警劉巖型将,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異荐虐,居然都是意外死亡七兜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)福扬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)腕铸,“玉大人,你說(shuō)我怎么就攤上這事忧换√窆撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵亚茬,是天一觀的道長(zhǎng)酪耳。 經(jīng)常有香客問(wèn)我,道長(zhǎng)刹缝,這世上最難降的妖魔是什么碗暗? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮梢夯,結(jié)果婚禮上言疗,老公的妹妹穿的比我還像新娘。我一直安慰自己颂砸,他們只是感情好噪奄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布死姚。 她就那樣靜靜地躺著,像睡著了一般勤篮。 火紅的嫁衣襯著肌膚如雪都毒。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天碰缔,我揣著相機(jī)與錄音账劲,去河邊找鬼。 笑死金抡,一個(gè)胖子當(dāng)著我的面吹牛瀑焦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梗肝,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼榛瓮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了统捶?” 一聲冷哼從身側(cè)響起榆芦,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喘鸟,沒(méi)想到半個(gè)月后匆绣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡什黑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年崎淳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愕把。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拣凹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恨豁,到底是詐尸還是另有隱情嚣镜,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布橘蜜,位于F島的核電站菊匿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏计福。R本人自食惡果不足惜跌捆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望象颖。 院中可真熱鬧佩厚,春花似錦、人聲如沸说订。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至闺鲸,卻和暖如春筋讨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摸恍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赤屋,地道東北人立镶。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像类早,于是被迫代替她去往敵國(guó)和親媚媒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容