1. 程式人生 > >趣探 Mach-O:載入過程

趣探 Mach-O:載入過程

這是Mach-O系列的第二篇,趣探 Mach-O:檔案格式分析是本文的一個基礎

我們都知道 Mach-OOS X 系統的可執行檔案,說到可執行檔案肯定離不開程序。在 Linux 中,我們會通過 Fork()來新建立子程序,然後執行映象通過exec()來替換為另一個可執行程式,至於為什麼這麼做,解釋如下

原因闡述:這是基於作業系統方面的分析。程序可以通過fork()系統呼叫來建立子程序,子程序得到與父程序地址空間相同的(但是獨立的)一份拷貝,包括文字、資料和bss段、堆以及使用者棧等,但是新執行緒只會複製呼叫fork的執行緒。所有父程序中別的執行緒,到了子程序中都是突然“蒸發”掉的。

我們線上程問題中經常會提到鎖,每個鎖都有一個持有者(最後一次lock

它的執行緒)。為了效能,鎖物件會因為fork複製到子程序中,但是子程序只複製呼叫fork的執行緒,很可能並不擁有鎖持有者執行緒,那麼就沒有辦法解開鎖,導致死鎖問題、記憶體洩漏

避免死鎖的方法:在子執行緒中馬上呼叫exec函式,一個程序一旦呼叫exec類函式,它本身就”死亡”了,系統把程式碼段替換成新的程式的程式碼,廢棄原有的資料段和堆疊段,併為新程式分配新的資料段與堆疊段,唯一留下的,就是程序號,也就是說,對系統而言,還是同一個程序,不過已經是另一個程式了。

綜上所述,我們在使用者態會通過exec*系列函式來載入一個可執行檔案,同時exec*都只是對系統呼叫execve的封裝,那我們載入Mach-O

的流程,就從execve說起。Mach-O有多種檔案型別,比如MH_DYLIB檔案、MH_BUNDLE檔案、MH_EXECUTE檔案(這些需要dyld動態載入),MH_OBJECT(核心載入)等。所以一個程序往往不是隻需要核心載入器就可以完成載入的,需要dyld來進行動態載入配合。考慮核心載入和dyld載入兩種情況,就會有如下流程圖

11852671-5f1c67158b6c5c5b

execve

這個函式只是直接呼叫 __mac_execve(),對於原始碼內部實現細節,可以看XNU的原始碼

__mac_execve()

原始碼可以參考:bsd/kern/kern_exec.c

主要是為載入映象進行資料的初始化,以及資源相關的操作,在其內部會執行exec_activate_image()

,映象載入的工作都是由它完成的

123456789101112 int__mac_execve(proc_tp,struct__mac_execve_args *uap,int32_t *retval){structimage_params *imgp;// 初始化imgp資料.......exec_activate_image(imgp);}

exec_activate_image

原始碼可以參考:bsd/kern/kern_exec.c

主要是拷貝可執行檔案到記憶體中,並根據不同的可執行檔案型別選擇不同的載入函式,所有的映象的載入要麼終止在一個錯誤上,要麼最終完成載入映象。在OS X中專門處理可執行檔案格式的程式叫execsw映象載入器

12852671-081a4a14eddd6124

OS X有三種可執行檔案,mach-oexec_mach_imgact處理,fat binaryexec_fat_imgact處理,interpreter(直譯器)由exec_shell_imgact處理

exec_mach_imgact

原始碼可以參考:bsd/kern/kern_exec.c

主要是用來對Mach-O做檢測,會檢測Mach-O頭部,解析其架構、檢查imgp等內容,並拒絕接受DylibBundle這樣的檔案,這些檔案會由dyld負責載入

然後把Mach-O對映到記憶體中去,呼叫load_machfile()

load_machfile

原始碼可以參考:bsd/kern/mach_loader.c

load_machfile會載入Mach-O中的各種load monmand命令。在其內部會禁止資料段執行,防止溢位漏洞攻擊,還會設定地址空間佈局隨機化(ASLR),還有一些對映的調整。

真正負責對載入命令解析的是parse_machfile()

parse_machfile

原始碼可以參考:bsd/kern/mach_loader.c

parse_machfile會根據load_command的種類選擇不同的函式來載入,內部是一個Switch語句來實現的

常見的命令有LC_SEGMENT_64LC_LOAD_DYLINKERLC_CODE_SIGNATURELC_UUID等,更多命令可以檢視Mach-O檔案格式,也可以查詢這篇文章-趣探 Mach-O:檔案格式分析

對於命令的載入會進行多次掃描,當掃描三次之後,並且存在dylinker_command命令時,會執行 load_dylinker(),啟動動態連結器 (dyld)

動態連結過程

動態連結也有區分,一種是載入主程式(很多部落格裡這麼寫),是由load commands指定的dylib,以靜態的方式存放在二進位制檔案裡,一種是由DYLD_INSERT_LIBRARIES動態指定。下面這種就是提前指定在二進位制檔案中的動態庫,下面的闡述主要是站在前者的角度,對於動態指定的後期再研究

13852671-1abd5daa82e06fc5

你可以在 load 方法處打一個斷點看一下,通過檢視呼叫棧可以發現:

123456 0+[XXObject load]1call_class_loads()2call_load_methods3load_images4dyld::notifySingle(dyld_image_states,ImageLoader const*)11_dyld_start

_dyld_start甚是耀眼,覺得是dyld的入口,然後就去看dyld原始碼,全域性搜了一下_dyld_start,就發現註釋,於是順著註釋往下閱讀

原始碼可以參考:dyld/src/dyld.cpp

14852671-ab8ef5b05acbb274

核心會載入dyld並呼叫dyld_start方法,隨後dyld_start會呼叫_main(),在_main函式中對資料進行一通初始化之後,就會呼叫instantiateFromLoadedImage函式初始化ImageLoader例項

12 // instantiate ImageLoader for main executablesMainExecutable=instantiateFromLoadedImage(mainExecutableMH,mainExecutableSlide,sExecPath);

instantiateFromLoadedImage

12345678910111213 // The kernel maps in main executable before dyld gets control.  We need to // make an ImageLoader* for the already mapped in main executable.staticImageLoader*instantiateFromLoadedImage(constmacho_header*mh,uintptr_t slide,constchar*path){// try mach-o loaderif(isCompatibleMachO((constuint8_t*)mh,path)){ImageLoader*image=ImageLoaderMachO::instantiateMainExecutable(mh,slide,path,gLinkContext);addImage(image);returnimage;}throw"main executable not a known format";}

instantiateFromLoadedImage這個函式內的程式碼比較容易理解,檢測Mach-O是否合法,合法的話就初始化ImageLoader例項,然後將其加入到一個全域性的管理ImageLoader的陣列中去

isCompatibleMachO會對Mach-O頭部的一些資訊與當前平臺進行比較,判斷其合法性。

ImageLoader

12345678910111213 //// ImageLoader is an abstract base class.  To support loading a particular executable// file format, you make a concrete subclass of ImageLoader.//// For each executable file (dynamic shared object) in use, an ImageLoader is instantiated.//// The ImageLoader base class does the work of linking together images, but it knows nothing// about any particular file format.////classImageLoader{}

註釋可得,ImageLoader是一個抽象基類,每一個動態載入的可執行檔案都會初始化一個ImageLoader例項

instantiateMainExecutable

原始碼可以參考:dyld/src/ImageLoaderMachO.cpp

1234567891011121314151617181920 // create image for main executableImageLoader*ImageLoaderMachO::instantiateMainExecutable(constmacho_header*mh,uintptr_t slide,constchar*path,constLinkContext&context){boolcompressed;unsignedintsegCount;unsignedintlibCount;constlinkedit_data_command*codeSigCmd;constencryption_info_command*encryptCmd;sniffLoadCommands(mh,path,false,&compressed,&segCount,&libCount,context,&codeSigCmd,&encryptCmd);// instantiate concrete class based on content of load commandsif(compressed)returnImageLoaderMachOCompressed::instantiateMainExecutable(mh,slide,path,segCount,libCount,context);else#if SUPPORT_CLASSIC_MACHOreturnImageLoaderMachOClassic::instantiateMainExecutable(mh,slide,path,segCount,libCount,context);#elsethrow"missing LC_DYLD_INFO load command";#endif}

可以這裡會有一個Bool型別的compressed作為判斷,然後返回不同的例項。

這兩種例項都是做什麼?

ImageLoaderMachOCompressedImageLoaderMachOClassic均繼承於ImageLoaderMachOImageLoaderMachO 繼承於ImageLoader

sniffLoadCommands會對Mach-Oclassic還是compressed的做一個判斷

instantiateMainExecutable是對ImageLoaderMachOCompressedImageLoaderMachOClassic做初始化,並載入load comond命令,之中呼叫過程也比較簡單,可以參考網路的這個圖片,比較清晰,具體內容可以看原始碼

15852671-760dedf0ba9800e9

至此,主二進位制檔案的載入就結束,然後會連結各種DYLD_INSERT_LIBRARIES動態庫,以上就是非常粗略的瞭解了下載入Mach-O的過程,後期有需要再深入研究

寫的比較倉促,難免有錯誤的地方,望包涵並告知

參考連結