RFC-0069:ELF 執行器中的標準 I/O

RFC-0069:ELF Runner 中的標準 I/O
狀態已接受
區域
  • 元件架構
說明

ELF 元件的機制,將 stdout 和 stderr 串流轉送至 LogSink 服務。

變更
作者
審查人員
提交日期 (年/月)2021-02-02
審查日期 (年/月)2021-02-17

摘要

推出兩個新旗標 forward_stdout_toforward_stderr_to,針對想在啟動時接收 stdout 和/或 stderr 串流的元件,控制啟用行為。啟用後,串流將會由 LogSink 服務支援。

提振精神

元件管理員啟動後,其 stdout 和 stderr 串流會繫結至 debuglog,因為在啟動程序的這個階段沒有任何替代項目。舉例來說, Archivist 提供 LogSink 服務的元件本身是由元件管理員啟動。

直到最近,元件管理員將所有元件的 stdout 和 stderr 串流重新導向至核心的偵錯記錄檔。具體做法是複製元件管理員自己的 stdout 和 stderr 控制代碼,分別繫結至偵錯記錄檔 (如上所述)。不過,這仍存在一些問題。 首先,使用者模式元件不應寫入偵錯記錄檔,因為這是保留給核心用途和高權限使用者模式元件的空間,而大部分的使用者模式元件都應改為寫入 LogSink 服務。其次,提供兩個傳出串流,但未明確選擇加入,違反了最低權限原則

目前,元件管理員並未完全支援這項功能,因此我們在解決先前存在的兩個缺點時,希望將其復原。我們建議您在 ELF 執行器中新增標記,以明確選擇加入,而非間接將 stdout 和/或 stderr 授予所有元件。

元件架構團隊正在從 appmgr (元件 v1) 遷移至元件架構 (Components v2) 進行長時間的遷移作業。這項工作的主要專案之一,就是遷移網路堆疊團隊擁有的所有元件。stdout/stderr 支援是遷移所有這些元件的必備條件。之所以要求這項要求,是因為網路堆疊元件是以 Go 編寫而成。Go 程式與以 C++ 或 Rust 編寫的程式不同,會在執行階段環境中執行,而在設定期間發送錯誤給 stderr,開發人員撰寫的程式就會開始執行。由於錯誤會在程式的進入點之前記錄,因此 Go 元件的作者無法將 stdout 和 stderr 控點繫結至記錄服務。為解決這個問題,我們可以在 Go 執行階段建立分支及修改 Go 執行階段,納入必要的記錄初始化,然後才開始執行使用者編寫的 Go 程式。本課程將為我們帶來重大的技術負擔 也就是維持 Go 分支或者,元件管理員還可以在元件 (以及 Go 執行階段) 啟動之前,將 stdout 和 stderr 處理常式繫結至記錄服務,藉此允許 Fuchsia 的記錄服務擷取錯誤訊息。

更廣泛來說,我們想要減輕上述 v1 至 v2 遷移的負擔。目前,appmgr 提供 stdout 和 stderr 處理給所有 v1 元件,並將其轉送至 debuglog。因此,合理假設 Fuchsia 中的許多開發人員依賴這項功能。在 2021 年遷移至更多元件的同時,我們也應讓開發人員維持仰賴的 stdout 和 stderr 支援。

相關規定

幕後記錄服務必須是 LogSink,而非偵錯記錄檔。我們必須改用 LogSink 的原因有很多。首先,如上所述, debuglog 是供核心使用,其次,偵錯記錄會使用小型 (128 KB) 的共用環形緩衝區處理所有程序,並以 FIFO 為基礎輪替訊息。Archivist 會定期排除這些訊息,並將其轉送至 LogSink。不過,如果在 Archivist 將偵錯記錄緩衝區讀取到 LogSink 前,就先推出偵錯記錄緩衝區,則這類緩衝區就須承擔「遺失」的責任。針對 stdout 和 stderr 記錄服務使用 LogSink 不僅可避免訊息遺失的機率,還會降低使用 debuglog 的其他元件和程序捨棄訊息的可能性。此外,目前還沒有機制可以追蹤緩衝區的旋轉速度比清空的快幅度。第三, debuglog 不支援嚴重性等級 (例如 DEBUG、INFO 等)這一點非常重要,因為我們需要區分 stdout 訊息和 stderr 訊息。關於記錄,我們只能將每個輸出串流對應至特定的嚴重性等級。

設計

本提案會在 ELF 元件 forward_stdout_toforward_stderr_toprogram 結構定義中導入兩個新的列舉值。ELF 元件的列舉為選用項目,預設為 none。如果不是,ELF 執行元件就不會將 stdoutstderr 控制代碼繫結至 LogSink 服務 (目前行為),系統會繼續忽略這些串流。將這些值設為 log 時,ELF 執行元件會建立通訊端,用於擷取 stdoutstderr 串流的輸出內容。接著,再將讀取的位元組轉送至 LogSink 服務。若為 stdout,則會傳送 INFO 訊息;如為 stderr,則會傳送 WARN 訊息。訊息會以換行符號分隔,且每一行都會以不可分割的訊息分割至 LogSink 服務。LogSink 服務的訊息大小上限為 32 KB,因此我們會將位元組串流緩衝區限制為 30 KB (允許空間訊息中繼資料存在一些空間)。超過限制的位元組數會捨棄,且只有部分訊息會傳送至 LogSink 服務。這可引發有趣的極端案例,其中部分程式碼點可在 30 KB 邊界分割。在此情況下,系統不會進行特殊處理作業,程式碼點的前半部會依照原樣解碼。所有輸入位元組都會使用 String::from_utf8_lossy 解碼為 UTF-8。根據這個函式的 API,所有無效的 UTF-8 序列都會替換為 U+FFFD REPLACEMENT CHARACTER

{
    program: {
        "runner": "elf",
        "forward_stdout_to": "log",
        "forward_stderr_to": "log",
    }
}

實作

由於這項功能將僅限於 ELF 執行元件,因此實作所需的變更幅度相當小。根據我們的預測,導入這項變更所需的 CL 不會超過 2 個。

效能

由於程式碼只會傳送至 LogSink,因此效能成本低廉。這項功能會產生最明顯的負擔,就是剖析位元組串流並分割新行。不過,由於記錄是一項不規則的作業,而位元組串流本身「通常」短,因此成本相對來說相當低。更重要的是,這只會影響明確選擇採用此行為的元件。因此,若發生效能問題,我們只需在這些元件中將 forward_stdout_toforward_stderr_to 設為 none,就能快速解決問題,並暫時改回正方形,直到解決基礎的效能問題為止。

安全性考量

Archivist 已設有歸因和流量控制的機制,因此行為不當的元件無法藉由濫發 stdout 的方式拒絕服務。因此無須額外調整。但請注意,這項功能提供的機制可將任意位元組放入另一個程序的位址空間 (從元件到 Archivist)。如果緩衝區太大,這或許會造成問題,但我們已提到上述緩解措施。此外,由於此實作最少會處理輸入串流,因此可降低向上擴充權限的風險。既然這是個複雜的剖析器,錯誤和安全漏洞的可能性就會增加。

隱私權注意事項

LogSink 後端和所有 LogSink 用戶端都符合隱私權規定,會將記錄歸因至其來源,且已有充分的 PII 破壞機制。因此無須額外調整。

測試

除了單元測試之外,我們也會新增整合測試,確保記錄寫入 Archivist。這些整合測試會以所有支援的語言編寫:C/C++、Rust 和 Go。由於 ELF 執行元件不會處理 Dart 元件執行作業,因此 Dart 不會進行測試。

說明文件

這個標記會在 Components v2 文件的 ELF 執行器部分記錄。

缺點、替代方案和未知

編號控點

我們嘗試了增加編號的控點,從程序架構到元件架構,藉此支援這項功能。雖然我們決定不採用這項工具,但資訊清單檔案的大致設計如下:

{
    program: {
        "runner": "elf",
        "handles": ["STDOUT", "STDERR"]
    }
}

ELF 執行元件會讀取常數字串,並在內部將其對應至適當的編號控點。最後,我們之所以做出這個決定,是因為我們不知道其他編號控點的立即用途。此外,如果我們找到其他應在 ELF 執行元件的 program 架構中設定的編號控制代碼,就可以巧妙更新資訊清單檔案語法和 ELF 執行元件實作項目。

元件管理服務

此外,我們也發現針對編號控制代碼推出了新的架構層級能力。資訊清單檔案的概略設計如下:

{
    use: [
        {
            "handle": "stdout-to-log",
            "from": "framework",
            "number": "STDOUT",
        },
        {
            "handle": "stderr-to-log",
            "from": "framework",
            "number": "STDERR",
        }
    ]
}

基於上述有關編號控點的原因,此方法被視為不理想,因為在元件管理員層級導入這項功能,會引發元件管理員關於 POSIX 相容性的新問題。例如,是否所有執行者都必須實作這個項目?執行不是使用 stdout/stderr 的執行元件 (例如「web」執行元件器),看起來會是什麼樣子?因此,我們決定在 POSIX 相容性問題之外,將 POSIX 相容性問題當做獨立的工作流,不在這個 RFC 的涵蓋範圍內。

全新元件簡介

我們也嘗試實作翻譯層,此部分可以剖析 stdout/stderr 位元組串流,並將其轉送至 LogSink 服務,這是由元件架構團隊擁有和管理的新元件。然而經過多次討論後,我們決定這種方法會不夠直觀,因為我們必須找出記錄訊息時保留記錄歸因的方法。Archivist 會利用事件能力取得元件來源資訊 (例如路徑名稱),而如果使用中層元件,這些資訊都會遺失。

FDIO

此外,我們也研究了使用 fdio ( Fuchsia 的 POSIX 相容程式庫) 來實作這項功能。也就是說,建立一種新類型可識別 stdout/stderr 檔案描述元,並在 fdio 內部 (在 fdio 內) 將輸出內容重新導向至 LogSink。然而,經過多次討論後,系統決定不要修改 Fdio,因為這會導致實作更加困難。我們發現,在極端案例中,您無法使用 LogSink 轉寄器在 fdio 中實作 POSIX 相容性。此外,採用 fdio 為基礎的實作會產生更多不確定性,並會產生重複的工作。或者,如上文所述,如果使用通訊端,則會是符合 POSIX 規範的「立即可用」。