Android 10 包含 Android 即時鎖定守護程式 (llkd
),旨在擷取及減輕核心死結。llkd
元件提供預設的獨立實作,但您也可以將 llkd
程式碼直接整合到其他服務中,做為主要迴圈的一部分或獨立執行緒。
偵測情境
llkd
有兩種偵測情境:持續性 D 或 Z 狀態,以及持續性堆疊簽章。
持續性 D 或 Z 狀態
如果執行緒處於 D (不可中斷的休眠) 或 Z (殭屍) 狀態,且沒有超過 ro.llk.timeout_ms or ro.llk.[D|Z].timeout_ms
的向前進度,llkd
就會終止程序 (或父項程序)。如果後續掃描顯示相同的程序持續存在,llkd
會確認活鎖狀態,並以可提供該狀態最詳細錯誤報告的方式讓核心進入恐慌狀態。
llkd
包含自我監控犬,在 llkd
鎖定時鬧鐘會發出鬧鐘;監控計時器的預期時間會兩倍,且每 ro.llk_sample_ms
取樣一次。
持續性堆疊簽章
針對使用者偵錯版本,llkd
可以使用永久堆疊簽章檢查功能偵測核心即時鎖定。如果 Z 以外狀態的執行緒含有持續列出的 ro.llk.stack
核心符號,且回報時間長於 ro.llk.timeout_ms
或 ro.llk.stack.timeout_ms
,llkd
就會終止程序 (即使有向前排程進度)。如果後續掃描顯示相同程序仍然存在,llkd
會確認即時鎖定條件和恐慌,進而為條件提供最詳細的錯誤報告。
當存在即時鎖定條件時,lldk
檢查會持續進行,並在 Linux 的 /proc/pid/stack
檔案中尋找組合字串 symbol+0x
或 symbol.cfi+0x
。符號清單位於 ro.llk.stack
中,預設為以逗號分隔的 cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
清單。
符號應很少出現,且壽命應夠短,以便在典型系統中,在 ro.llk.stack.timeout_ms
的逾時期間內,函式只會在樣本中出現一次 (每 ro.llk.check_ms
就會出現一次樣本)。由於缺乏 ABA 保護機制,這是避免誤觸發的唯一方法。符號函式必須顯示在呼叫可能發生爭用的鎖定函式下方。如果鎖定在符號函式下方或內部,則符號會顯示在所有受影響的程序中,而非只顯示在導致鎖定的程序中。
涵蓋範圍
llkd
的預設實作不會監控 init
、[kthreadd]
或 [kthreadd]
產生的項目。llkd
涵蓋 [kthreadd]
產生的執行緒:
- 驅動程式不得處於永久的 D 狀態。
或
- 驅動程式必須具備機制,以便在執行緒遭到外部終止時復原。例如,使用
wait_event_interruptible()
而非wait_event()
。
如果符合上述任一條件,llkd
拒絕清單即可調整為涵蓋核心元件。堆疊符號檢查會涉及額外的程序拒絕清單,以防止服務封鎖 ptrace
作業時違反安全政策。
Android 屬性
llkd
會回應多個 Android 屬性 (如下所列)。
- 名為
prop_ms
的屬性以毫秒為單位。 - 使用半形逗號 (,) 分隔清單的屬性會使用開頭分隔符來保留預設項目,然後分別使用可選加號 (+) 和減號 (-) 前置字串來新增或減去項目。對於這些清單,字串
false
與空清單同義,空白或缺少的項目會採用指定的預設值。
ro.config.low_ram
裝置的記憶體配置有限。
ro.debuggable
裝置已針對 userdebug 或 eng 版本進行設定。
ro.llk.sysrq_t
如果屬性是 eng
,則預設值為 ro.config.low_ram
或 ro.debuggable
。如果是 true
,則會傾印所有執行緒 (sysrq t
)。
ro.llk.enable
允許啟用即時鎖定守護程序。預設值為 false
。
llk.enable
針對工程師版本進行評估。預設值為 ro.llk.enable
。
ro.khungtask.enable
允許啟用 [khungtask]
Daemon。預設值為 false
。
khungtask.enable
針對工程師版本進行評估。預設值為 ro.khungtask.enable
。
ro.llk.mlockall
啟用呼叫 mlockall()
。預設值為 false
。
ro.khungtask.timeout
時間上限為 [khungtask]
。預設值為 12 分鐘。
ro.llk.timeout_ms
時間上限為 D 或 Z。預設值為 10 分鐘。將這個值加倍,即可為 llkd
設定鬧鐘監控器。
ro.llk.D.timeout_ms
D 時間上限。預設值為 ro.llk.timeout_ms
。
ro.llk.Z.timeout_ms
Z 時間上限。預設值為 ro.llk.timeout_ms
。
ro.llk.stack.timeout_ms
檢查持續堆疊符號的最大時間限制。預設值為 ro.llk.timeout_ms
。僅在 userdebug 或 eng 版本中啟用。
ro.llk.check_ms
D 或 Z 的執行緒範例。預設值為兩分鐘。
ro.llk.stack
檢查核心堆疊符號,如果持續存在,可能表示子系統已鎖定。預設為以逗號分隔的核心符號清單。cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable
檢查作業不會執行 ABA 的順向排程,除非在 ro.llk.stack.timeout_ms
期間每隔 ro.llk_check_ms
執行一次輪詢,因此堆疊符號應極為罕見且短暫 (符號不太可能會持續顯示在堆疊的所有樣本中)。檢查堆疊展開中是否有 symbol+0x
或 symbol.cfi+0x
的配對項目。僅適用於 userdebug 或 eng 版本;使用者版本的安全性問題會導致權限受限,無法進行這項檢查。
ro.llk.blacklist.process
llkd
不會監控指定的程序。預設值為 0,1,2
(kernel
、init
和 [kthreadd]
) 加上程序名稱 init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/get_nprocs-1]
。程序可以是 comm
、cmdline
或 pid
參照。自動化的預設值可以大於目前的資源大小上限 92。
ro.llk.blacklist.parent
llkd
不會監控具有指定父項的程序。預設值為 0,2,adbd&[setsid]
(kernel
、[kthreadd]
和 adbd
僅適用於殭屍 setsid
)。And (&) 分隔符指定只在與目標子程序搭配使用時才忽略父項。之所以選擇 AND 符號,是因為它從不是程序名稱的一部分,所以已選取了,不過殼層中的 setprop
需要逸出或加上引號,但通常指定的 init rc
檔案不會有這個問題。父項或目標程序可以是 comm
、cmdline
或 pid
參照。
ro.llk.blacklist.uid
llkd
不會監控符合指定 UID 的程序。以半形逗號分隔的 UIS 編號或名稱清單。預設值為空白或 false
。
ro.llk.blacklist.process.stack
llkd
不會監控指定的程序子集,以便取得即時鎖定堆疊簽章。預設為處理程序名稱 init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd
。防止與封鎖 ptrace
的程序相關的 sepolicy 違規 (因為無法檢查這些程序)。僅在 userdebug 和 eng 版本中啟用。如要進一步瞭解建構類型,請參閱「建構 Android」。
架構相關問題
- 屬性長度上限為 92 個半形字元 (不過,如果來源中
include/llkd.h
檔案中定義的預設值,則會忽略這項限制)。 - 內建的
[khungtask]
守護程序過於通用,且會在驅動程式程式碼中觸發,因為驅動程式程式碼會過度停留在 D 狀態。切換至 S 後,系統就能終止工作 (並視需要由驅動程式復活)。
程式庫介面 (選用)
您可以選擇使用 libllkd
元件的下列 C 介面,將 llkd
納入其他特權守護程序:
#include "llkd.h"
bool llkInit(const char* threadname) /* return true if enabled */
unsigned llkCheckMillseconds(void) /* ms to sleep for next check */
如果提供執行緒名稱,系統會自動產生執行緒,否則呼叫端必須在主迴圈中呼叫 llkCheckMilliseconds
。這個函式會傳回下次預期呼叫此處理常式前的時間長度。