MCJIT 設計與實作¶
簡介¶
本文檔描述了 MCJIT 執行引擎和 RuntimeDyld 組件的內部運作方式。它旨在作為實作的高階概述,展示物件在整個程式碼產生和動態載入過程中的流程和互動。
引擎建立¶
在大多數情況下,會使用 EngineBuilder 物件來建立 MCJIT 執行引擎的實例。EngineBuilder 接受 llvm::Module 物件作為其建構子的引數。然後,客戶端可以設定各種選項,我們控制這些選項稍後會傳遞給 MCJIT 引擎,包括選擇 MCJIT 作為要建立的引擎類型。特別值得注意的是 EngineBuilder::setMCJITMemoryManager 函數。如果客戶端此時未明確建立記憶體管理器,則在實例化 MCJIT 引擎時,將會建立預設記憶體管理器(特別是 SectionMemoryManager)。
一旦設定了選項,客戶端就會呼叫 EngineBuilder::create 來建立 MCJIT 引擎的實例。如果客戶端未使用此函數採用 TargetMachine 作為參數的形式,則將根據與用於建立 EngineBuilder 的 Module 相關聯的目标三元组建立新的 TargetMachine。

EngineBuilder::create 將呼叫靜態 MCJIT::createJIT 函數,傳入其指向模組、記憶體管理器和目標機器物件的指標,所有這些指標後續都將由 MCJIT 物件擁有。
MCJIT 類別有一個成員變數 Dyld,其中包含 RuntimeDyld 包裝器類別的實例。此成員將用於 MCJIT 和實際 RuntimeDyldImpl 物件之間的通訊,該物件在載入物件時建立。

建立後,MCJIT 保留指向從 EngineBuilder 接收的 Module 物件的指標,但它不會立即為此模組產生程式碼。程式碼產生會延遲到明確呼叫 MCJIT::finalizeObject 方法,或呼叫需要已產生程式碼的函數(例如 MCJIT::getPointerToFunction)時才會進行。
程式碼產生¶
當如上所述觸發程式碼產生時,MCJIT 將首先嘗試從其 ObjectCache 成員中檢索物件映像(如果已設定)。如果無法檢索快取的物件映像,MCJIT 將呼叫其 emitObject 方法。MCJIT::emitObject 使用本地 PassManager 實例並建立新的 ObjectBufferStream 實例,在對其建立的 Module 呼叫 PassManager::run 之前,這兩個實例都會傳遞給 TargetMachine::addPassesToEmitMC。

PassManager::run 呼叫會導致 MC 程式碼產生機制將完整的可重定位二進制物件映像(取決於目標,格式為 ELF 或 MachO)發送到 ObjectBufferStream 物件中,然後刷新該物件以完成該過程。如果正在使用 ObjectCache,則映像將在此處傳遞到 ObjectCache。
此時,ObjectBufferStream 包含原始物件映像。在程式碼可以執行之前,必須將此映像中的程式碼和資料區段載入到合適的記憶體中,必須套用重定位,並且必須完成記憶體權限和程式碼快取失效(如果需要)。
物件載入¶
一旦獲得物件映像(透過程式碼產生或從 ObjectCache 檢索),它就會傳遞到 RuntimeDyld 以進行載入。RuntimeDyld 包裝器類別檢查物件以確定其檔案格式,並建立 RuntimeDyldELF 或 RuntimeDyldMachO(兩者都派生自 RuntimeDyldImpl 基底類別)的實例,並呼叫 RuntimeDyldImpl::loadObject 方法來執行實際載入。

RuntimeDyldImpl::loadObject 首先從其接收的 ObjectBuffer 建立 ObjectImage 實例。ObjectImage 包裝了 ObjectFile 類別,是一個輔助類別,用於解析二進制物件映像,並提供對格式特定標頭中包含的資訊的訪問權,包括區段、符號和重定位資訊。
然後,RuntimeDyldImpl::loadObject 迭代映像中的符號。收集有關通用符號的資訊以供稍後使用。對於每個函數或資料符號,相關的區段都會載入到記憶體中,並且該符號儲存在符號表映射資料結構中。迭代完成後,會為通用符號發出一個區段。
接下來,RuntimeDyldImpl::loadObject 迭代物件映像中的區段,並且對於每個區段,迭代該區段的重定位。對於每個重定位,它都會呼叫格式特定的 processRelocationRef 方法,該方法將檢查重定位並將其儲存在兩個資料結構之一中:基於區段的重定位列表映射和外部符號重定位映射。

當 RuntimeDyldImpl::loadObject 返回時,物件的所有程式碼和資料區段都將載入到記憶體管理器分配的記憶體中,並且重定位資訊將已準備好,但重定位尚未套用,並且產生的程式碼仍未準備好執行。
[目前(截至 2013 年 8 月)MCJIT 引擎將在 loadObject 完成時立即套用重定位。但是,這不應該發生。由於程式碼可能是為遠端目標產生的,因此在套用重定位之前,應讓客戶端有機會重新映射區段地址。可以多次套用重定位,但在要重新映射地址的情況下,第一次套用是徒勞的。]
地址重新映射¶
在產生初始程式碼之後且在呼叫 finalizeObject 之前,客戶端可以隨時重新映射物件中區段的地址。通常這樣做是因為程式碼是為外部進程產生的,並且正在映射到該進程的地址空間中。客戶端透過呼叫 MCJIT::mapSectionAddress 來重新映射區段地址。這應該在區段記憶體複製到其新位置之前發生。
當呼叫 MCJIT::mapSectionAddress 時,MCJIT 會將呼叫傳遞給 RuntimeDyldImpl(透過其 Dyld 成員)。RuntimeDyldImpl 將新地址儲存在內部資料結構中,但此時不會更新程式碼,因為其他區段可能會更改。
當客戶端完成重新映射區段地址時,它將呼叫 MCJIT::finalizeObject 以完成重新映射過程。
最終準備¶
當呼叫 MCJIT::finalizeObject 時,MCJIT 會呼叫 RuntimeDyld::resolveRelocations。此函數將嘗試定位任何外部符號,然後套用物件的所有重定位。
外部符號透過呼叫記憶體管理器的 getPointerToNamedFunction 方法來解析。記憶體管理器將傳回目標地址空間中請求符號的地址。(注意,這可能不是主機進程中的有效指標。)然後,RuntimeDyld 將迭代它已儲存的與此符號相關聯的重定位列表,並調用 resolveRelocation 方法,該方法將透過格式特定的實作,將重定位套用到載入的區段記憶體。
接下來,RuntimeDyld::resolveRelocations 迭代區段列表,並且對於每個區段,迭代已儲存的引用該符號的重定位列表,並為此列表中的每個條目呼叫 resolveRelocation。此處的重定位列表是重定位列表,其中與重定位相關聯的符號位於與列表相關聯的區段中。這些位置中的每一個都將具有一個目標位置,將在其上套用重定位,該目標位置可能位於不同的區段中。

一旦如上所述套用了重定位,MCJIT 就會呼叫 RuntimeDyld::getEHFrameSection,如果傳回非零結果,則將區段資料傳遞給記憶體管理器的 registerEHFrames 方法。這允許記憶體管理器呼叫任何所需的目標特定函數,例如向偵錯器註冊 EH 框架資訊。
最後,MCJIT 呼叫記憶體管理器的 finalizeMemory 方法。在此方法中,記憶體管理器將在必要時使目標程式碼快取失效,並將最終權限套用到其為程式碼和資料記憶體分配的記憶體頁面。