Zircon 程式載入和動態連結

在 Zircon 中,核心不會直接參與正常的程式載入作業。 不過,核心會提供 也就是建立的使用者空間程式 虛擬記憶體物件程序虛擬記憶體位址地區執行緒

ELF 和系統 ABI

標準的 Zircon 使用者空間環境採用執行檔與可連結格式 (ELF) ,而且會提供動態連結器和 以 ELF 為基礎的 C/C++ 執行環境。Zircon 程序可以 僅透過 Zircon vDSO 使用系統呼叫,方法為 核心提供的 ELF 格式,並使用 C/C++ 呼叫慣例 對於 ELF 型系統而言相當常見

使用者空間程式碼 (視適用功能而定) 可使用系統呼叫來 直接建立程序並載入程式 使用 ELF,但 Zircon 針對機器程式碼的標準 ABI 使用 ELF,如此處所述。

背景:傳統 ELF 程式載入

ELF 之前為 與 Unix System V 第 4 版推出後成為通用標準 適用於大部分 Unix 系統的可執行檔案格式。在這些系統中 核心使用 POSIX 整合程式載入與檔案系統存取權 execve API。這些系統載入 ELF 程式的方式有些差異,但 依循這個模式:

  1. 核心會依名稱載入檔案,並檢查其為 ELF 或 系統支援的其他檔案類型這裡有 #! 指令碼 處理完成,且 (如果存在) 的非 ELF 格式支援。
  2. 核心會根據其 PT_LOAD 程式對應 ELF 映像檔 標題。若是 ET_EXEC 檔案,會將程式區段置於下列位置: 在 p_vaddr 中指定的記憶體內超過固定位址。針對 ET_DYN 檔案,系統會選擇程式的第一個 已載入 PT_LOAD,然後根據 p_vaddr相對於第一個區隔的 p_vaddr。通常是 基本位址為隨機選擇 (ASLR)。
  3. 如果有 PT_INTERP 節目標頭,則其內容 (範圍 p_offsetp_filesz 提供的 ELF 檔案中的位元組) 做為檔案名稱,尋找另一個名為 ELF 解譯器的 ELF 檔案。 須為 ET_DYN 檔案。核心載入方式和 載入執行檔,但一律會在其自行選擇的位置。 翻譯程式通常是具有名稱的 ELF 動態連結器 例如 /lib/ld.so.1/lib/ld-linux.so.2,但核心會載入 輸入任何檔案名稱
  4. 核心會設定堆疊,並為初始執行緒註冊。 啟動與電腦在所選進入點位址執行的執行緒。

    • 進入點是 ELF 檔案標頭中的 e_entry 值。 根據基準地址調整如果有 PT_INTERP,項目 就是解譯器,而非主要執行檔
    • 註冊和堆疊內容有組合層級通訊協定 核心為程式設定接收其引數 環境字串和實用值的輔助向量。時間 存在 PT_INTERP,其中包含基準地址、進入點 和來自主要執行檔 ELF 的程式標題表格位址 標題。動態連接器會根據這項資訊找出 執行檔在記憶體中的 ELF 動態連結中繼資料,及其運作方式。 動態連結啟動完成後,動態連結器會跳到 主要執行檔的進入點位址。

Zircon 程式載入是這項傳統獲得靈感的推手,但 有差異。傳統載入模式的關鍵原因 這是因為動態連結器的 隨機選擇的基準地址不得與固定地址重疊 由 ET_EXEC 執行檔使用。Zircon 不支援 固定地址程式載入 (ELF ET_EXEC 檔案) 中,僅 位置獨立執行檔或 PIE,也就是 ELF ET_DYN 檔案。

檔案系統不屬於 Zircon API 較低層的一部分。 根據 VMO 和處理序間通訊 (IPC) 來載入程式 透過管道使用的通訊協定。

程式載入要求的開頭是:

  • 含有執行檔 (ZX_RIGHT_READ 和 需要 ZX_RIGHT_EXECUTE 權限)
  • 引數字串清單 (在 C/C++ 程式中變成 argv[])
  • 環境字串清單 (在 C/C++ 程式中變成 environ[])
  • 初始處理常式清單,每個處理常式 帳號代碼輸入資訊

系統會處理下列三種檔案類型:

開頭為 #! 的指令碼檔案

檔案的第一行開頭為 #!,且不得超過 127 個字元長。#! 之後的第一個非空白字詞是 指令碼口譯名稱。以後如果有任何內容 組合成為指令碼解譯器引數

  • 系統會在原始引數前面加上指令碼解譯器名稱 清單 (會變成 argv[0])。
  • 如果有指令碼解譯器引數,則會插入該引數的 解譯器名稱與原始引數清單 (變成 argv[1], 原始 argv[0] 變成 argv[2])。
  • 程式載入器會透過下列方式查詢指令碼解譯器名稱: 載入器服務來取得新的 VMO。
  • 使用 所有經過修改的引數清單都會保留下來VMO 帳號代碼 就會關閉原始執行檔只有指令碼口譯 可取得原始 argv[0] 字串以供搭配使用,而非原始 VMO。 巢狀結構設有上限 (目前 5 個),限制了 允許在程式載入失敗之前重新啟動。

沒有 PT_INTERP 的 ELF ET_DYN 檔案

  • 系統會為前 PT_LOAD 個區隔隨機選擇基本位址 然後根據基本地址,在每個 PT_LOAD 路段中對應。 方法是建立 VMAR, 從第一個區隔的第一頁到最後一個區段 最後一個區隔的網頁。
  • VMO 會在另一個隨機位址建立並對應,用於保存堆疊 初始執行緒中的值如果有 PT_GNU_STACK 節目標題 具有非零的 p_memsz,用來決定堆疊大小 (四捨五入) 最多整頁)。否則,系統會使用合理的預設堆疊大小。
  • vDSO 會對應至程序 (另一個包含 ELF 映像檔的 VMO),也包含隨機的基本位址。
  • 系統會在 zx_thread_create() 程序中建立新的執行緒。
  • 系統會建立新的管道,稱為 Bootstrap 頻道。程式載入器寫入這個管道訊息 processargs 通訊協定格式的檔案。這個 Bootstrap 訊息包含引數和環境字串,以及 來自原始要求的初始控點因此這份清單會逐漸擴大 帳號代碼:

    • 新的程序本身
    • 其根 VMAR
    • 初始執行緒
    • 涵蓋執行檔載入位置的 VMAR
    • 剛為堆疊建立的 VMO
    • (選用) 預設工作,因此新的 可能會建立更多
    • (可選擇 vDSO VMO) 讓新程序 它就會建立系統呼叫

    接著,程式載入器會關閉管道的結尾。

  • 初始執行緒會透過 zx_process_start() 系統呼叫啟動:

    • entry 會將新執行緒的電腦從執行檔的e_entry ELF 標頭,根據基本地址調整。
    • stack 會將新執行緒的堆疊指標設為 堆疊對應。
    • arg1 會將控制代碼轉移至啟動管道 第一個引數登錄到 C ABI 中。
    • arg2 會將 vDSO 的基本位址傳遞至第二個引數 。

    因此,程式進入點可以寫成 C 函式:

    noreturn void _start(zx_handle_t bootstrap_channel, uintptr_t vdso_base);
    

含有 PT_INTERP 的 ELF ET_DYN 檔案

在這個例子中,程式載入器不會直接使用 並在讀取 PT_INTERP 標頭後,再擷取 ELF 執行檔。相反地 使用 PT_INTERP 內容做為 ELF 解譯器的名稱。這個 名稱用於向載入器服務 取得新的 VMO,其中含有 ELF 解譯器,這是另一個 ELF ET_DYN 檔案。接著,系統會在載入 VMO 時載入 VMO,而不是主要執行檔 VMO。上述啟動條件如上所述,但差異如下:

  • 額外訊息 processargs 通訊協定中的 啟動頻道 (位於主要 Bootstrap 訊息前面)。 ELF 解譯器應使用這個載入器 Bootstrap 訊息 讓它可以正常運作 但會保留第二個啟動程序 並將 Bootstrap 管道帳號代碼移交給我們 主要程式的進入點載入器 Bootstrap 訊息 僅包含程式載入器新增的必要帳號,不含 主要 Bootstrap 訊息內的完整集合,加上以下程式碼:

    • 主要 ELF 執行檔的原始 VMO 處理常式
    • 載入器服務的管道處理常式

      這些物件可讓 ELF 解譯器自行載入 並透過載入器服務取得 以便載入共用程式庫的額外 VMO。系統也會顯示這則訊息 包含引數和環境字串,讓 ELF 翻譯器會在記錄訊息中使用 argv[0],並檢查 環境變數,例如 LD_DEBUG

  • 系統會忽略 PT_GNU_STACK 節目標頭。相反地 載入器會選擇最小的堆疊大小 包含載入器啟動訊息,外加一些呼吸室 用於呼叫影格的 ELF 解譯器啟動程式碼。這個 「呼吸房」來源中的大小為 PTHREAD_STACK_MIN, 如果啟動後訊息大小不大,整個堆疊就是 但動態連結器的導入程序 有足夠的空間進行工作動態連結器應讀取 主要執行檔的 PT_GNU_STACK,並切換至合理的堆疊 移至主要可執行檔項目前的一般使用大小 點。

,瞭解如何調查及移除這項存取權。

processargs 通訊協定

<zircon/processargs.h> 定義 是透過 Bootstrap 頻道傳送的開機訊息通訊協定, 程式載入器。程序啟動時,系統會控制這個程序 啟動管道,且可存取系統呼叫 vDSO。這項程序只有一個帳號代碼, 只能查看全域系統資訊及各自的記憶體 透過 Bootstrap 管道取得資訊和控點

processargs 通訊協定是一種單向通訊協定,用於傳送 自行啟動管道。新程序絕不會寫回 頻道。程式載入器通常會傳送訊息,然後關閉 管道結尾處後再推出新程序這些 訊息必須傳達新程序所需的一切資訊,但 接收及解碼這種格式訊息的程式碼 則適合在受到限制的環境下使用堆積分配方式是無法實現的 可能沒有圖書館的設施。

詳情請參閱標頭檔案 訊息格式的詳細資料。他預期這個臨時的通訊協定 最終會替換為正式的 IDL 型通訊協定,不過 格式保持簡單,只要以簡單的手寫方式解碼即可 再也不是件繁重乏味的工作

Bootstrap 訊息會顯示:

  • 初始帳號代碼清單
  • 對應至每個帳號代碼的 32 位元帳號代碼資訊
  • 帳號代碼資訊項目可參照的名稱字串清單
  • 引數字串清單 (在 C/C++ 程式中變成 argv[])
  • 環境字串清單 (在 C/C++ 程式中變成 environ[])

處理資訊項目

帳號代碼有許多用途,以帳號代碼輸入類型表示:

這些大部分只會透過程式載入器傳遞 不必知道 目標對象 目的

載入器服務

在動態連結系統中,可執行檔指的是 執行階段其他檔案,其中包含共用程式庫和外掛程式。 動態連結器是以 ELF 解譯器形式載入, 有權存取所有額外檔案 動態連結必須被主要程式的進入點控制。

Zircon 的所有標準使用者空間都使用動態連結 是由 userboot 載入的第一個程序。裝置驅動程式和 檔案系統是由透過這種方式載入的使用者空間程式實作。以下內容 無法以較高層級的抽象層定義程式載入 例如檔案系統模式 為 傳統系統的做法。 而是僅以 VMO 和 一種以管道為基礎的簡易通訊協定。

這項載入器服務通訊協定是動態連結器取得 VMO 的方式 代表必須在共用程式庫中載入的額外檔案。

這是簡單的遠端程序呼叫 (RPC) 通訊協定, <zircon/processargs.h>。 傳送載入程式服務的程式碼 並在動態連接器啟動期間接收他們的回覆。 無法存取非重大的圖書館設施

ELF 解譯器在自家的載入器服務中收到其管道代碼 processargs 啟動訊息 (可透過帳號代碼資訊項目識別) PA_HND(PA_LDSVC_LOADER, 0)。所有要求都是同步的 RPC 使用 zx_channel_call()。要求和回覆皆以 zx_loader_svc_msg_t 標頭;有些包含額外資料有些則包含 VMO 控制代碼請求運算碼為:

  • LOADER_SVC_OP_LOAD_SCRIPT_INTERP字串 ->VMO 帳號代碼

    程式載入器會將指令碼解譯器名稱#! 指令碼,並取回 VMO 以取代 VMO 建立指令碼

  • LOADER_SVC_OP_LOAD_OBJECT字串 ->VMO 帳號代碼

    動態連結器會傳送物件名稱 (共用資料庫或 外掛程式),然後取回包含該檔案的 VMO 控制代碼。

  • LOADER_SVC_OP_CONFIG字串 ->reply ignored

    動態連結器傳送字串,以識別其載入設定。 這麼做將會影響之後 LOADER_SVC_OP_LOAD_OBJECT 請求會決定要為 名字

  • LOADER_SVC_OP_DEBUG_PRINT字串 ->reply ignored

    這是一個簡易的臨時記錄工具,主要用來 動態連接器和早期計畫啟動問題。很方便 因為早期啟動程式碼使用載入器服務,但未啟用 可以使用許多其他帳號代碼或複雜設施這將 取而代之的是一些易於使用的記錄功能 無法透過載入器服務傳送

  • LOADER_SVC_OP_LOAD_DEBUG_CONFIG字串 ->VMO 帳號代碼

    這項功能適用於開發人員導向的功能,因此可能無法 通常可在實際工作環境中運作

    程式執行階段會傳送字串,命名為偵錯設定 然後讓 VMO 從 VM 讀取設定資料 Sanitizer 執行階段會使用此功能,允許將大型選項文字儲存在 檔案,而不要直接在環境字串中傳送。

  • LOADER_SVC_OP_PUBLISH_DATA_SINKstringVMO handle ->reply ignored

    這項功能適用於開發人員導向的功能,因此可能無法 通常可在實際工作環境中運作

    程式執行階段傳送一個命名資料接收器的字串,並傳輸其他移轉作業 並專門用來發布該 VMO 的專屬控制代碼資料接收器 字串用於識別資料類型,VMO 的物件名稱可以 明確識別這個 VMO 中的資料集用戶端必須 將唯一的控制代碼轉移至 VMO,防止 VMO 會在使用者不知情的情況下調整其大小,但可能還是 對應並繼續寫入資料至 VMO。程式碼檢測 執行階段都會使用這個字串來提供大型的二進位追蹤記錄結果。

Zircon 的標準 ELF 動態連接器

上述的 ELF 慣例,以及 processargs載入器服務通訊協定 用於載入程式的 ABI。程式可以使用任何 符合基本 ELF 格式慣例的機器程式碼執行檔。 即可使用 vDSO 系統呼叫

ABI、processargs 資料和偵測到的載入器服務設施 。程式接收的帳號代碼與資料的確切詳細資料 這些通訊協定取決於較高層級的程式環境。Zircon 系統程序使用會導入基本 ELF 動態的 ELF 解譯器 以及載入器服務的簡易實作方式。

Zircon 的標準 C 程式庫和動態連結器 統一導入: 來自 musl。它會藉由 PT_INTERP 字串 ld.so.1。會使用 DT_NEEDED 字串命名。 共用程式庫做為載入器服務 物件名稱。

簡易載入器服務可將要求對應至檔案系統存取權:

  • 指令碼解譯器偵錯設定名稱的開頭必須為 / 並做為絕對檔案名稱使用
  • 資料接收器名稱在 /tmp 中成為子目錄,每個 VMO 「已發布」會變成包含 VMO 物件名稱的子目錄檔案
  • object 名稱會作為系統 lib/ 目錄中的檔案搜尋。
  • load configuration 字串取得的子目錄名稱。 後面可視需要加上 ! 字元。該名稱位於 系統會搜尋 lib/ 系統目錄,然後才搜尋 lib/ 本身。 如果包含 ! 後置字串,系統「只會」搜尋這些子目錄。 舉例來說,清理工具執行階段會使用 asan,因為該檢測作業 與未檢測的程式庫程式碼相容,但 dfsan!,因為 這項檢測作業要求程序中的所有程式碼都必須 能夠正常控制。

採用 LLVM AddressSanitizer 識別為 PT_INTERP 字串 asan/ld.so.1。這個版本 載入共用程式庫前的 load configuration 字串 asan。 執行 SanitizerCoverage時 會使 VMO 發布至資料接收器名稱 sancov,並使用 VMO 名稱,包含程序 KOID。