王睿伯,吳振偉,張文喆,鄔會軍,張于舒晴,盧 凱
(國防科技大學計算機學院,湖南 長沙 410073)
訪存監控,即獲取程序訪問內存的行為,在系統軟件和體系結構研究中有著非常廣泛的應用。當前,實現訪存監控的主流技術手段包括代碼插樁(Instrumentation)和頁保護(Page Protection)2大類。
代碼插樁是一種通用的程序分析手段。代碼插樁在保證目標程序原始執行邏輯完整性的基礎上,在目標程序中插入一些額外的代碼片段用以收集程序的執行信息,如程序的控制流、數據流等。基于代碼插樁技術實現訪存監控,即通過插樁程序中的內存訪問指令監控程序的內存訪問行為。從插樁時機的角度看,代碼插樁技術可以分為靜態編譯插樁和動態二進制插樁2大類。編譯插樁是指在目標程序的編譯過程中注入插樁代碼,生成新的可執行程序[1]。動態二進制插樁則是在可執行程序運行期間動態注入插樁代碼。典型的動態二進制插樁工具,如Intel的Pin[2]、QEMU(Quick EMUlator)[3]、DynamoRIO[4]等,采用即時編譯 (Just-In-Time Compilation) 技術對目標二進制代碼執行動態重編譯,并在動態重編譯過程中實現代碼插樁。相比于編譯插樁,動態二進制插樁通常會引入數十倍于前者的性能開銷。然而,動態二進制插樁無需重新編譯目標程序,可以靈活適用于無法獲得目標程序源代碼的情形。此外,編譯插樁的監控目標通常局限于用戶態程序,而動態二進制插樁普遍適用于監控用戶態程序和操作系統內核。

Figure 1 Page table entry圖1 頁表項
頁保護是內存管理單元MMU(Memory Manag- ement Unit)在硬件層面提供的一種頁面訪問權限檢查功能[5-7]。軟件可以通過配置頁表項PTE(Page Table Entry)中的權限標志位實現頁面粒度的訪問權限控制。當用戶程序的訪存請求違背頁表項所規定的被訪問內存頁的訪問權限時,MMU會觸發頁錯誤。針對因訪存違例而觸發的頁錯誤,Linux內核會向觸發異常的進程發送段錯誤信號SIGSEGV(SIGnal:SEGmentation Violation)。基于頁保護實現內存寫訪問監控的基本原理是通過寫保護目標內存區域,即將其訪問權限配置為只讀,使被監控程序的寫請求觸發訪問權限違例,進而在自定義的SIGSEGV信號處理函數中獲取到被監控程序正在試圖寫入的內存地址。在記錄了被訪問到的內存頁地址后,SIGSEGV信號處理函數可通過mprotect系統調用解除對該內存頁的寫保護,使得信號處理函數返回后被中斷的寫訪問可以恢復執行。類似地,可以通過將目標內存區域的訪問權限設置為不可訪問,使得被監控程序對該區域的讀寫請求均觸發訪問權限違例異常,從而實現內存讀寫訪問監控。基于頁保護實現訪存監控的性能開銷,主要來自于因修改PTE的頁訪問權限標志位而引入的特權級切換和頁表緩存TLB(Translation Lookaside Buffer)刷新。
總體而言,編譯插樁引入的運行時開銷最低,但應用場景相對受限。動態二進制插樁應用范圍廣,但性能開銷顯著。頁保護相比于動態二進制插樁,運行時開銷大幅降低,但在編程靈活性和監控粒度方面仍有局限性[8]。本文的目標是:突破頁保護在編程靈活性方面的局限性,構建一種融合頁保護和編譯插樁優勢的高效細粒度訪存監控機制。
如圖1所示,分頁式內存管理模式下,頁表描述虛擬頁到物理頁的映射關系,并且存放頁的保護位,即訪問權限。每個進程擁有獨立的頁表。進程的頁表由操作系統創建和維護,供MMU硬件查詢實現虛實地址轉換和內存訪問權限檢查。如果進程違背頁表中所設置的權限訪問內存(如寫入只讀內存區域),MMU將觸發頁錯誤中斷。出現頁錯誤后,操作系統的頁錯誤處理例程通過讀取硬件寄存器(如x86處理器的CR2寄存器),獲得引起頁錯誤的內存地址,并向被中斷的進程發送SIGSEGV信號。進程收到SIGSEGV信號的缺省行為是終止執行并返回錯誤代碼。
基于頁保護的訪存監控機制,通過操作系統提供的內存保護系統調用(mprotect)接口,預先將目標虛擬內存區域的訪問權限設置為不可訪問或只讀,使得被監控程序對目標虛擬內存區域的讀寫訪問或寫訪問無法通過MMU的訪存權限檢查而被硬件中斷。與此同時,訪存監控機制通過向操作系統注冊SIGSEGV信號處理函數對因訪存違例而產生的SIGSEGV信號進行進一步處理。如圖 2所示,SIGSEGV信號處理函數首先從中斷棧中讀取引發頁錯誤的內存地址,即被監控程序正在訪問的地址。此外,SIGSEGV信號處理函數可以進一步通過保存在中斷棧中的錯誤代碼確定訪存類型是讀訪問還是寫訪問。在記錄被監控進程的訪存行為之后,SIGSEGV信號處理函數再次發起mprotect系統調用,解除被監控程序對相應虛擬內存頁的訪問限制,使被中斷的訪存操作在SIGSEGV信號處理函數返回后可以恢復正常執行。

Figure 2 Workflow of page protection-based memory access monitoring mechanism圖2 基于頁保護的訪存監控機制工作流程

Figure 3 Architecture of MPK圖3 MPK架構
基于PTE的頁保護機制中,用戶態程序需要陷入到內核態修改頁訪問權限,上下文切換開銷較大。此外,操作系統在修改PTE后需要執行開銷顯著的TLB flush操作以確保TLB的數據一致性。
MPK(Memory Protection Keys)提供了一種相對于操作頁表項更加輕量化且更加靈活的頁保護機制。MPK硬件擴展在每個處理器核心中增加1個每核私有的(core-private)寄存器PKRU (Protection Key rights Register for User pages)來描述頁訪問權限,支持軟件層在用戶態實現線程局部的頁訪問權限控制。如圖 3所示,MPK使用傳統PTE中的4個空閑位域存儲pkey(protection key)值,提供0~15共計16個pkey。擁有相同pkey的頁構成一個分組,共享PKRU中pkey所對應的內存頁訪問權限描述。具體而言,PKRU中為每個pkey維護WD(Write Disable)和AD(Access Disable)2個權限控制位。軟件可以將某pkey在PKRU中的WD或AD位置為1,使其對該pkey所指向頁分組的寫訪問或所有訪問觸發異常。由于PKRU為用戶態寄存器,用戶程序讀/寫PKRU寄存器只需要執行非特權態的RDPKRU/WRPKRU指令,而無需發起系統調用。
在使用MPK的情形下,MMU在執行權限檢查時,會同時檢查PTE中描述的訪問權限和PTE中的pkey在PKRU中對應的訪問權限,最終被允許的訪問權限為二者的交集。由于PKRU為處理器核心獨有,MPK支持線程局部的訪問權限控制。如圖 4所示,針對同一內存頁,Corea和Coreb可以同時擁有相互獨立的訪問權限。而PTE為所有線程所共享,修改PTE中的頁保護位將影響所有線程的訪問權限。
MPK硬件擴展的設計初衷是增強程序的訪存安全性。基于MPK所提供的輕量化內存訪問控制機制,已有大量研究工作圍繞敏感數據隔離保護[10]、unikernel內部內存隔離[11]、線程級訪存約束[12]等應用場景開展研究。本文將MPK提供的輕量化頁保護特性用于建立低開銷的訪存監控機制。

Figure 4 Memory access permissions control in MPK[9]圖4 MPK中的訪問權限檢查[9]
在基于頁保護的訪存監控中,被監控程序對同一內存頁的多次訪問中,只有第1次訪問會觸發頁錯誤中斷。在SIGSEGV信號處理函數通過mprotect系統調用解除內存頁訪問限制后,對該頁面的后續訪問將不會繼續觸發頁錯誤中斷,從而無法被頁保護機制監控到。為了實現能夠監控到每條訪存指令的細粒度訪存監控,需要在對被保護內存頁的第1次訪問結束后且對該內存頁的后續訪問未開始執行前的某個時機再次中斷被監控程序,并重新施加頁保護。對此,本文利用處理器的單步(Single-Stepping)調試模式實現了基于硬件頁保護的細粒度訪存監控。單步調試模式下,處理器每執行一條指令,便會觸發一次調試異常。操作系統的調試異常處理例程,會向觸發調試異常的程序發送SIGTRAP信號。在x86架構下,可以通過配置EFLAGS寄存器的TF(Trap Flag)標志位,控制處理器進入或退出單步執行模式。
在程序觸發調試異常陷入到內核態時,被中斷的程序執行上下文被保存在內核棧。內核在調用程序預先注冊的SIGTRAP信號處理函數時,會將被中斷程序的執行上下文拷貝到信號棧(Signal Stack)。由此,程序自定義的信號處理函數可以通過信號棧訪問到程序被中斷時的上下文信息。信號處理函數結束后,將執行sigreturn函數。sigreturn函數通過信號棧中保存的上下文信息,恢復被中斷程序的執行現場。
如圖5所示,本文通過向操作系統注冊自定義的SIGSEGV和SIGTRAP信號處理函數,并通過設置頁保護和處理器調試模式,使訪存指令在執行前和執行后分別觸發頁錯誤和調試異常,從而實現可以攔截到每一條訪存指令的細粒度訪存監控。具體而言,本文在SIGSEGV信號處理函數中,將信號棧中保存的TF標志位置為1。這樣一來,當SIGSEGV信號處理函數執行結束后,sigreturn函數使用信號棧中保存的上下文信息恢復被中斷程序的執行現場時會將處理器的TF標志位置為1,從而使處理器進入到單步執行模式。被中斷的訪存指令在單步執行模式下執行結束后,處理器將會觸發調試異常。接下來,操作系統的調試異常處理例程將向被監控程序發送SIGTRAP信號。被監控程序接收到SIGTRAP后,操作系統內核會進一步調用此前注冊的SIGTRAP信號處理函數。至此,通過對SIGSEGV和SIGTRAP信號的融合處理,本文實現了分別在一條訪存指令執行前和執行后這2個時間點中斷被監控程序的執行。在SIGTRAP信號處理函數中,本文對單步模式下被訪問到的虛擬內存頁重新施加頁保護,使得后續對該虛擬內存頁的訪問可以被持續監控,從而突破頁保護機制只支持頁粒度訪存監控的局限性。

Figure 5 Fine-grained page protection圖5 細粒度頁保護
在借助調試異常實現細粒度訪存監控之外,本文進一步利用MPK的輕量化特性,實現運行時開銷更低的內存訪問權限控制。具體而言,本文在程序初始階段,向操作系統內核申請一個內存保護鍵值pkey,并通過pkey_mprotect系統調用將pkey標記到擬監控內存區域的頁表項。擁有相同pkey的虛擬內存頁構成了一個內存頁分組,以下簡稱pkey分組。在完成pkey與虛擬頁之間的綁定后,本文通過執行WRPKRU指令修改PKRU寄存器中pkey對應的訪問權限描述位,限制被監控程序對pkey分組的訪問,即施加頁保護。WRPKRU是非特權態指令,執行開銷非常低。相較于通過mprotect系統調用修改頁表項,以配置PKRU寄存器的方式修改虛擬內存頁訪問權限,可以避免引入用戶態和內核態之間的上下文切換開銷。此外,每個處理器核心擁有獨立的PKRU寄存器,對PKRU寄存器內容的修改不需要在處理器核心之間同步。而修改頁表項則不得不引入開銷顯著的TLB flush操作,以實現處理器核之間的同步。綜上,本文借助MPK機制,實現了一種相較于傳統的mprotect更加輕量化的頁保護機制。
在針對pkey分組施加頁保護之后,被監控程序對pkey分組的訪問將使其觸發頁錯誤而被中斷。針對此情形,操作系統內核同樣會向被監控程序發送SIGSEGV信號,并將被訪問到的pkey分組對應的pkey值保存于信號棧。本文在SIGSEGV信號處理函數中,從信號棧中獲取到被監控程序正試圖訪問的pkey分組對應的pkey,并通過修改中斷現場保存的PKRU寄存器中pkey對應的訪問權限控制位,解除對pkey分組的訪問限制,使被監控程序可以恢復執行對pkey分組的訪問。類似地,本文通過在SIGSEGV信號處理函數中設置中斷現場保存的TF標志位,使被監控程序在完成對pkey分組的訪問權限之后觸發調試異常,從而實現在訪存指令執行結束后再次攔截被監控程序。SIGTRAP信號處理函數通過配置中斷現場保存的PKRU寄存器中pkey對應的訪問權限控制位,重新對pkey分組施加訪問限制,使被監控程序對pkey分組的后續訪問可以被持續監控到。
至此,本文基于對pkey分組訪問權限違例異常和處理器調試異常的融合處理,突破了MPK僅支持pkey分組粒度訪問權限控制的監控粒度局限性,實現了細粒度的訪存監控。此外,上述輕量化細粒度訪存監控機制僅需要占用1個pkey,沒有觸及MPK僅提供有限數量pkey的硬件資源局限性。
基于頁保護實現訪存監控,是以使被監控程序觸發訪問權限違例異常為基礎;而編譯器插樁則是在程序編譯期間面向訪存指令插入訪存監控邏輯。與基于頁保護監控訪存行為相比,編譯器插樁訪存指令引入的運行時開銷更低且可擴展性更好,具有更好的性能表現。但是,在監控能力方面,編譯器插樁具有一定的局限性。基于編譯器插樁實現訪存監控,需要重新編譯被監控程序的源碼,這使得編譯器插樁無法被用于非開源程序和第三方庫等。本文提出的輕量化細粒度訪存監控,可有效彌補編譯器插樁在監控能力方面的局限性。通過融合基于MPK的輕量化細粒度頁保護和編譯插樁,本文提出了一種混合式訪存監控機制,實現了對訪存監控性能和訪存監控能力的兼顧。
混合式訪存監控機制的目標場景是目標程序主體源碼可重編譯而目標程序所依賴的部分動態鏈接庫不可重編譯的情形,這種場景也是真實應用中普遍存在的情形。混合式訪存監控的核心思想是利用MPK的輕量化特性,實現對虛擬內存訪問權限的低開銷頻繁切換。具體而言,與細粒度頁保護機制類似,混合式訪存監控在程序初始化階段申請pkey并使用該pkey標記擬監控的虛擬內存區域構建pkey分組,并通過配置pkey對應的PKRU寄存器中的訪問權限控制位,實現對pkey分組的訪問約束。對于目標程序中支持源碼重編譯的部分,混合式訪存監控采用編譯器插樁訪存指令的方式注入訪存監控邏輯,并在訪存指令前后分別插入2條WRPKRU指令配置PKRU寄存器中pkey對應的訪問權限控制位,以解除和恢復對pkey分組的訪問約束,從而使被插樁到的訪存指令在程序運行階段不會觸發訪問權限違例異常,即實現頁保護免疫。由于WRPKRU操作非常輕量化(4.3節的實驗結果表明,WRPKRU操作引入的額外開銷不超過4%),上述針對單條訪存指令的細粒度頁保護免疫過程得以有效兼顧編譯器插樁的性能優勢。同時,對于被監控程序中不支持重編譯的訪存指令,其對pkey分組的訪問會因觸發訪問權限違例異常而被監控到。
至此,本文通過基于MPK的細粒度頁保護免疫,實現了兼顧編譯器插樁性能優勢和頁保護功能優勢的混合式訪存監控機制。
本文在一個Linux服務器(內核版本5.4.0)上開展了實驗測評。該服務器有2個20核Intel?Xeon?Gold 6230 2.10 GHz處理器。DRAM大小是64 GB (每個socket 32 GB)。本文使用的測試程序由clang-6.0.1編譯,編譯插樁工具基于LLVM 6.0.1實現。所有實驗結果都是10次運行結果的平均值。
本文在內存對象緩存系統Memcached[13]中,分別實現了以下幾種訪存監控機制:(1)本文提出的細粒度頁保護機制PP(Page Protection);(2)本文提出的基于MPK處理器擴展的輕量化細粒度頁保護機制(MPK);(3)本文提出的輕量化細粒度頁保護與編譯器插樁相融合的混合式訪存監控機制(MPK-CI);(4)僅插入WRPKRU操作而不執行訪存監控的對比組(WRPKRU)。其中,WRPKRU對比組不具備訪存監控功能,主要用于驗證WRPKRU操作的輕量化特性。
本文使用Memtier[14]測試程序生成輸入數據,對上述集成了訪存監控功能的Memcached變體實現進行壓力測試。測試過程中,Mmcached運行4個服務器工作線程,Memtier運行16個工作線程,每個工作線程模擬50個客戶端,每個客戶端發起10 000個針對Memcached服務端的讀寫請求操作(共計800萬次請求)。其中,Memtier觸發的讀寫請求操作中,Set操作和Get操作的比例為1∶10,數據對象的大小為32 B。實驗過程中,使用綁核工具numactl分別將Memcached進程和Memtier進程綁定在CPU 0和CPU 1上。
本文記錄了在不同Memcached變體實現作為服務端的情形下,Memtier完成800萬次讀寫請求操作所需的時間,并以缺省Memcached實現(不監控訪存行為)作為服務端的運行時間為基準,對實驗結果進行歸一化處理。
本文通過寫保護Memcached內置slab分配器所分配的內存頁,監控Memcached對動態內存對象的訪存行為。圖 6展示了本文對比測評的訪存監控機制相對于缺省Memcached實現的相對時間開銷。其中,PP引入了相對缺省Memcached實現5.07倍的時間開銷。得益于用戶空間內存訪問權限控制的輕量化特性,輕量化細粒度頁保護的相對時間開銷下降至3.43,相比于PP的減少了約32%。MPK-CI分組展示了使用編譯器插樁監控Memcached程序源碼樹下items.c源文件中的訪存操作而基于輕量化細粒度頁保護機制監控Memcached程序中其他訪存行為的實驗結果。 MPK-CI的相對時間開銷約為2.1。WRPKRU分組只在缺省Memcached實現的所有訪存操作前后插入WRPKRU操作而不執行訪存監控,該分組的實驗結果表明,在Memcached中頻繁執行WRPKRU引入的額外開銷低于4%。
本文基于Intel MPK硬件擴展提出了一種輕量化且細粒度的訪存監控機制,相比于傳統方法,降低了超過30%的性能開銷。與此同時,本文工作突破了傳統頁保護機制僅支持頁粒度訪存監控的局限性,基于硬件頁保護機制實現了字節粒度的訪存監控。此外,本文提出的輕量化細粒度頁保護訪存監控機制能夠與編譯插樁方法高效融合,有效彌補了編譯器插樁無法覆蓋程序中不支持重編譯部分的局限性。