從C的偽代碼到匯編-動(dòng)手實(shí)現(xiàn)objc-msgSend_第1頁(yè)
從C的偽代碼到匯編-動(dòng)手實(shí)現(xiàn)objc-msgSend_第2頁(yè)
從C的偽代碼到匯編-動(dòng)手實(shí)現(xiàn)objc-msgSend_第3頁(yè)
從C的偽代碼到匯編-動(dòng)手實(shí)現(xiàn)objc-msgSend_第4頁(yè)
從C的偽代碼到匯編-動(dòng)手實(shí)現(xiàn)objc-msgSend_第5頁(yè)
已閱讀5頁(yè),還剩13頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

從C的偽代碼到匯編-動(dòng)手實(shí)現(xiàn)objc-msgSend網(wǎng)址:edu.51CTO.com從C的偽代碼到匯編,動(dòng)手實(shí)現(xiàn)objc_msgSendobjc_msgSend函數(shù)支撐了我們使用Objective-C實(shí)現(xiàn)的一切。GwynneRaskind,F(xiàn)ridayQ&A的讀者,建議我談?wù)刼bjc_msgSend的內(nèi)部實(shí)現(xiàn)。要理解某件事還有比自己動(dòng)手實(shí)現(xiàn)一次更好的方法嗎?咱們來(lái)自己動(dòng)手實(shí)現(xiàn)一個(gè)objc_msgSend。Tramapoline!Trampopoline!(蹦床)當(dāng)你寫(xiě)了一個(gè)發(fā)送Objective-C消息的方法:[obj

message]

編譯器會(huì)生成一個(gè)objc_msgSend調(diào)用:objc_msgSend(obj,

@selector(message));

之后objc_msgSend會(huì)負(fù)責(zé)轉(zhuǎn)發(fā)這個(gè)消息。它都做了什么?它會(huì)查找合適的函數(shù)指針或者IMP,然后調(diào)用,最后跳轉(zhuǎn)。任何傳給objc_msgSend的參數(shù),最終都會(huì)成為IMP的參數(shù)。IMP的返回值成為了最開(kāi)始被調(diào)用的方法的返回值。Class

c

=

object_getClass(self);

IMP

imp

=

class_getMethodImplementation(c,

_cmd);

return

imp(self,

_cmd,

...);

}

這有點(diǎn)過(guò)于簡(jiǎn)單。事實(shí)上會(huì)有一個(gè)方法緩存來(lái)提升查找速度,像這樣:id

objc_msgSend(id

self,

SEL

_cmd,

...)

{

Class

c

=

object_getClass(self);

IMP

imp

=

cache_lookup(c,

_cmd);

if(!imp)

imp

=

class_getMethodImplementation(c,

_cmd);

return

imp(self,

_cmd,

...);

}

通常為了速度,cache_lookup使用inline函數(shù)實(shí)現(xiàn)。匯編在Apple版的runtime中,為了最大化速度,整個(gè)函數(shù)是使用匯編實(shí)現(xiàn)的。在Objective-C中每次發(fā)送消息都會(huì)調(diào)用objc_msgSend,在一個(gè)應(yīng)用中最簡(jiǎn)單的動(dòng)作都會(huì)有成千或者上百萬(wàn)的消息。為了讓事情更簡(jiǎn)單,我自己的實(shí)現(xiàn)中會(huì)盡可能少的使用匯編,使用獨(dú)立的C函數(shù)抽象復(fù)雜度。匯編代碼會(huì)實(shí)現(xiàn)下面的功能:id

objc_msgSend(id

self,

SEL

_cmd,

...)

{

IMP

imp

=

GetImplementation(self,

_cmd);

imp(self,

_cmd,

...);

}

GetImplementation

可以用更可讀的方式工作。

匯編代碼需要:1.把所有潛在的參數(shù)存儲(chǔ)在安全的地方,確保GetImplementation不會(huì)覆蓋它們。2.調(diào)用GetImplementation。3.把返回值保存在某處。4.恢復(fù)所有的參數(shù)值。5.跳轉(zhuǎn)到GetImplementation返回的IMP。讓我們開(kāi)始吧!這里我會(huì)嘗試使用x86-64匯編,這樣可以很方便的在Mac上工作。這些概念也可以應(yīng)用于i386或者ARM。這個(gè)函數(shù)會(huì)保存在獨(dú)立的文件中,叫做msgsend-asm.s。這個(gè)文件可以像源文件那樣傳遞給編譯器,然后會(huì)被編譯并鏈接到程序中。第一件事要做的是聲明全局的符號(hào)(globalsymbol)。因?yàn)橐恍o(wú)聊的歷史原因,C函數(shù)的globalsymbol會(huì)在名字前有個(gè)下劃線(xiàn):.globl

_objc_msgSend

_objc_msgSend:

編譯器會(huì)很高興的鏈接最近可使用的(nearestavailable)objc_msgSend。簡(jiǎn)單的鏈接這個(gè)到一個(gè)測(cè)試app已經(jīng)可以讓[objmessage]表達(dá)式使用我們自己的代碼而不是蘋(píng)果的runtime,這樣可以相當(dāng)方便的測(cè)試我們的代碼確保它可以工作。整型數(shù)和指針參數(shù)會(huì)被傳入寄存器%rsi,%rdi,%rdx,%rcx,%r8和%r9。其他類(lèi)型的參數(shù)會(huì)被傳進(jìn)棧(stack)中。這個(gè)函數(shù)最先做的事情是把這六個(gè)寄存器中的值保存在棧中,這樣它們可以在之后被恢復(fù):pushq

%rsi

pushq

%rdi

pushq

%rdx

pushq

%rcx

pushq

%r8

pushq

%r9

除了這些寄存器,寄存器%rax扮演了一個(gè)隱藏的參數(shù)。它用于變參的調(diào)用,并保存?zhèn)魅氲南蛄考拇嫫鳎╲ectorregisters)的數(shù)量,用于被調(diào)用的函數(shù)可以正確的準(zhǔn)備變參列表。以防目標(biāo)函數(shù)是個(gè)變參的方法,我同樣也保存了這個(gè)寄存器中的值:pushq

%rax

為了完整性,用于傳入浮點(diǎn)類(lèi)型參數(shù)的寄存器%xmm也應(yīng)該被保存。但是,要是我能確保GetImplementation不會(huì)傳入任何的浮點(diǎn)數(shù),我就可以忽略掉它們,這樣我就可以讓代碼更簡(jiǎn)潔。接著,對(duì)齊棧。MacOSX要求一個(gè)函數(shù)調(diào)用棧需要對(duì)齊16字節(jié)邊界。上面的代碼已經(jīng)是棧對(duì)齊的,但是還是需要顯式手動(dòng)處理下,這樣可以確保所有都是對(duì)齊的,就不用擔(dān)心動(dòng)態(tài)調(diào)用函數(shù)時(shí)會(huì)崩潰。要對(duì)齊棧,在保存%r12的原始值到棧中后,我把當(dāng)前的棧指針保存到了%r12中。%r12是隨便選的,任何保存的調(diào)用者寄存器(caller-savedregister)都可以。重要的是在調(diào)用完GetImplementation后這些值仍然存在。然后我把棧指針按位與(and)上-0x10,這樣可以清除棧底的四位:pushq

%r12

mov

%rsp,

%r12

andq

$-0x10,

%rsp

現(xiàn)在棧指針是對(duì)齊的了。這樣可以安全的避開(kāi)上面(above)保存的寄存器,因?yàn)闂J窍蛳略鲩L(zhǎng)的,這種對(duì)齊的方法會(huì)讓它更向下(moveitfurtherdown)。是時(shí)候該調(diào)用GetImplementation了。它接收兩個(gè)參數(shù),self和_cmd。調(diào)用習(xí)慣是把這兩個(gè)參數(shù)分別保存到%rsi和%rdi中。然而傳入objc_msgSend中時(shí)就是那樣了,它們沒(méi)有被移動(dòng)過(guò),所以不需要改變它們。所有要做的事情實(shí)際上是調(diào)用GetImplementation,方法名前面也要有一個(gè)下劃線(xiàn):callq

_GetImplementation

整型數(shù)和指針類(lèi)型的返回值保存在%rax中,這就是找到返回的IMP的地方。因?yàn)?rax需要被恢復(fù)到初始的狀態(tài),返回的IMP需要被移動(dòng)到別的地方。我隨便選了個(gè)%r11。mov

%rax,

%r11

現(xiàn)在是時(shí)候該恢復(fù)原樣了。首先要恢復(fù)之前保存在%r12中的棧指針,然后恢復(fù)舊的%r12的值:mov

%r12,

%rsp

popq

%r12

然后按壓入棧的相反順序恢復(fù)寄存器的值:popq

%rax

popq

%r9

popq

%r8

popq

%rcx

popq

%rdx

popq

%rdi

popq

%rsi

現(xiàn)在一切都已經(jīng)準(zhǔn)備好了。參數(shù)寄存器(argumentregisters)都恢復(fù)到了之前的樣子。目標(biāo)函數(shù)需要的參數(shù)都在合適的位置了。IMP在寄存器%r11中,現(xiàn)在要做的是跳轉(zhuǎn)到那里:jmp

*%r11

就這樣!不需要其他的匯編代碼了。jump把控制權(quán)交給了方法實(shí)現(xiàn)。從代碼的角度看,就好像發(fā)送消息者直接調(diào)用的這個(gè)方法。之前的那些迂回的調(diào)用方法都消失了。當(dāng)方法返回,它會(huì)直接放回到objc_msgSend的調(diào)用處,不需要其他的操作。這個(gè)方法的返回值可以在合適的地方找到。非常規(guī)的返回值有一些細(xì)節(jié)需要注意。比如大的結(jié)構(gòu)體(不能用一個(gè)寄存器大小保存的返回值)。在x86-64,大的結(jié)構(gòu)體使用隱藏的第一個(gè)參數(shù)返回。當(dāng)你像這樣調(diào)用:NSRect

r

=

SomeFunc(a,

b,

c);

這個(gè)調(diào)用會(huì)被翻譯成這樣:NSRect

r;

SomeFunc(&r,

a,

b,

c);

用于返回值的內(nèi)存地址被傳入到%rdi中。因?yàn)閛bjc_msgSend期望%rdi和%rsi中包含self和_cmd,當(dāng)一個(gè)消息返回大的結(jié)構(gòu)體時(shí)不會(huì)起作用的。同樣的問(wèn)題存在于多個(gè)不同平臺(tái)上。runtime提供了objc_msgSend_stret用于返回結(jié)構(gòu)體,工作原理和objc_msgSend類(lèi)似,只是知道在%rsi中尋找self和在%rdx中尋找_cmd。相似的問(wèn)題發(fā)生在一些平臺(tái)上發(fā)送消息(messages)返回浮點(diǎn)類(lèi)型值。在這些平臺(tái)上,runtime提供了objc_msgSend_fpret(在x86-64,objc_msgSend_fpret2用于特別極端的情況)。方法查找讓我們繼續(xù)實(shí)現(xiàn)GetImplementation。上面的匯編蹦床意味著這些代碼可以用C實(shí)現(xiàn)。記得嗎,在真正的runtime中,這些代碼都是直接用匯編寫(xiě)的,是為了盡可能的保證最快的速度。這樣不僅可以更好的控制代碼,也可以避免重復(fù)像上面那樣保存并恢復(fù)寄存器的代碼。GetImplementation可以簡(jiǎn)單的調(diào)用class_getMethodImplementation實(shí)現(xiàn),混入Objective-Cruntime的實(shí)現(xiàn)。這有點(diǎn)無(wú)聊。真正的objc_msgSend為了最大化速度首先會(huì)查找類(lèi)的方法緩存。因?yàn)镚etImplementation想模仿objc_msgSend,所以它也會(huì)這么做。要是緩存中不包含給定的selector入口點(diǎn)(entry),它會(huì)繼續(xù)查找runtime(itfallbacktoqueryingtheruntime)。我們現(xiàn)在需要的是一些結(jié)構(gòu)體定義。方法緩存是類(lèi)(class)結(jié)構(gòu)體中的私有結(jié)構(gòu)體,為了得到它我們需要定義自己的版本。盡管是私有的,這些結(jié)構(gòu)體的定義還是可以通過(guò)蘋(píng)果的Objective-Cruntime開(kāi)源實(shí)現(xiàn)獲得(譯注:/tarballs/objc4/)。首先需要定義一個(gè)cacheentry:typedef

struct

{

SEL

name;

void

*unused;

IMP

imp;

}

cache_entry;

相當(dāng)簡(jiǎn)單。別問(wèn)我unused字段是干什么的,我也不知道它為什么在那。這是cache的全部定義:struct

objc_cache

{

uintptr_t

mask;

uintptr_t

occupied;

cache_entry

*buckets[1];

};

緩存使用hashtable(哈希表)實(shí)現(xiàn)。實(shí)現(xiàn)這個(gè)表是為了速度的考慮,其他無(wú)關(guān)的都簡(jiǎn)化了,所以它有點(diǎn)不一樣。表的大小永遠(yuǎn)都是2的冪。表格使用selector做索引,bucket是直接使用selector的值做索引,可能會(huì)通過(guò)移位去除不相關(guān)的低位(lowbits),并與mask執(zhí)行一個(gè)邏輯與(logicaland)。下面是一些宏,用于給定selector和mask時(shí)計(jì)算bucket的索引:#ifndef

__LP64__

#

define

CACHE_HASH(sel,

mask)

(((uintptr_t)(sel)>>2)

&

(mask))

#else

#

define

CACHE_HASH(sel,

mask)

(((unsigned

int)((uintptr_t)(sel)>>0))

&

(mask))

#endif

最后是類(lèi)的結(jié)構(gòu)體。這是Class指向的類(lèi)型:struct

class_t

{

struct

class_t

*isa;

struct

class_t

*superclass;

struct

objc_cache

*cache;

IMP

*vtable;

};

需要的結(jié)構(gòu)體都已經(jīng)有了,現(xiàn)在開(kāi)始實(shí)現(xiàn)GetImplementation吧:IMP

GetImplementation(id

self,

SEL

_cmd)

{

首先要做的是獲取對(duì)象的類(lèi)。真正的objc_msgSend通過(guò)類(lèi)似self->isa的方式獲取,但是它會(huì)使用官方的API實(shí)現(xiàn):Class

c

=

object_getClass(self);

因?yàn)槲蚁朐L(fǎng)問(wèn)最原始的形式,我會(huì)為指向class_t結(jié)構(gòu)體的指針執(zhí)行類(lèi)型轉(zhuǎn)換:struct

class_t

*classInternals

=

(struct

class_t

*)c;

現(xiàn)在該查找IMP了。首先我們把它初始為NULL。如果我們?cè)诰彺嬷姓业?,我們?huì)賦值為它。如果查找緩存后仍為NULL,我們會(huì)回退到速度較慢的方法:IMP

imp

=

NULL;

接著,獲取指向cache的指針:struct

objc_cache

*cache

=

classInternals->cache;

計(jì)算bucket的索引,獲取指向buckets數(shù)組的指針:uintptr_t

index

=

CACHE_HASH(_cmd,

cache->mask);

cache_entry

**buckets

=

cache->buckets;

然后,我們使用要找的selector查找緩存。runtime使用的是線(xiàn)性鏈(linearchaining),之后只是遍歷buckets子集直到找到需要的entry或者NULLentry:for(;

buckets[index]

!=

NULL;

index

=

(index

+

1)

&

cache->mask)

{

if(buckets[index]->name

==

_cmd)

{

imp

=

buckets[index]->imp;

break;

}

}

如果沒(méi)有找到entry,我們會(huì)調(diào)用runtime使用一種較慢的方法。在真正的objc_msgSend中,上面的所有代碼都是使用匯編實(shí)現(xiàn)的,這時(shí)候就該離開(kāi)匯編代碼調(diào)用runtime自己的方法了。一旦查找緩存后沒(méi)有找到需要的entry,期望快速發(fā)送消息的希望就要落空了。這時(shí)候獲取更快的速度就沒(méi)那么重要了,因?yàn)橐呀?jīng)注定會(huì)變慢,在一定程度上也極少的需要這么調(diào)用。因?yàn)檫@點(diǎn),放棄匯編代碼轉(zhuǎn)而使用更可維護(hù)的C也是可以接受的:if(imp

==

NULL)

imp

=

class_getMethodImplementation(c,

_cmd);

不管怎樣,IMP現(xiàn)在已經(jīng)獲取到了。如果它在緩存中,就會(huì)在那里找到它,否則它會(huì)通過(guò)runtime查找到。class_getMethodImplementation調(diào)用同樣會(huì)使用緩存,所以下次調(diào)用會(huì)更快。剩下的就是返回IMP:return

imp;

}

測(cè)試為了確保它能工作,我寫(xiě)了一個(gè)快速的測(cè)試程序:@interface

Test

:

NSObject

-

(void)none;

-

(void)param:

(int)x;

-

(void)params:

(int)a

:

(int)b

:

(int)c

:

(int)d

:

(int)e

:

(int)f

:

(int)g;

-

(int)retval;

@end

@implementation

Test

-

(id)init

{

fprintf(stderr,

"in

init

method,

self

is

%p\n",

self);

return

self;

}

-

(void)none

{

fprintf(stderr,

"in

none

method\n");

}

-

(void)param:

(int)x

{

fprintf(stderr,

"got

parameter

%d\n",

x);

}

-

(void)params:

(int)a

:

(int)b

:

(int)c

:

(int)d

:

(int)e

:

(int)f

:

(int)g

{

fprintf(stderr,

"got

params

%d

%d

%d

%d

%d

%d

%d\n",

a,

b,

c,

d,

e,

f,

g);

}

-

(int)ret

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論