王維晟
(國家計算機網絡應急技術處理協調中心,北京 100029)
專用網卡往往插在一臺服務器上,它在硬件上接收前端數據分發設備的數據,根據負載均衡原理可以將數據發送到后臺服務器的多個線程,使服務器的每個線程都能對數據同時進行處理,同時通過專用網卡的軟件系統可以提供數據包的預處理功能。專用網卡在網絡拓撲中的位置如圖1所示。

圖1 專用網卡在網絡拓撲中的位置
研究發現,熱補丁可以保證當前業務不中斷的前提下,對軟件故障進行調試和修復。針對熱補丁的研究,主要是基于VXWORKS 操作系統下的熱補丁,而專用網卡運行硬件環境是Intel 64位處理器,軟件是基于Linux 操作系統(Centos 7.2),并按功能封裝成不同動態庫。文中介紹了熱補丁的相關技術,并詳細論述了動態庫熱補丁的實現方案,最后通過一個工程案例表明,專用網卡中使用動態庫熱補丁,能大大提高軟件故障修復的效率。
改變動態庫中某個函數的控制流程,可以使用LD_PRELOAD 提前加載動態庫;可以修改PLT/GOT 表;可以仿DPDK 構造constructor 屬性。本文采用最直接的JMP 跳轉指令來實現改變控制流。
動態庫熱補丁的核心原理如圖2所示,先給當前進程打上動態庫libpatch.so 這一補丁文件,其中libpatch.so 文件實現了fun1函數,假設main 函數中調用fun0函數,fun0函數定義在動態庫libtest0.so 中,我們通過改變fun0函數代碼入口處的匯編指令,使其通過JMP 跳轉指令,直接跳轉到補丁文件中的fun1處執行。

圖2 動態庫熱補丁核心原理
在真實的補丁文件libpatch.so 中,我們實現的是fun1函數,然后通過給進程打補丁,用補丁文件中的fun1 代替進程中的fun1。我們把當前進程稱為遠端環境,把打補丁的進程稱為本端環境。動態庫熱補丁技術中涉及如下幾個關鍵技術:獲取遠端環境上的函數的地址;執行遠端環境上的的函數;本端補丁文件中符號的重定向。
1.1.1 獲取遠端環境上的函數的地址
前文提到,可以通過PTRACE 系統調用對進程進程跟蹤,另外程序還可以通過dlopen 函數憑空加載一個動態庫,并通過dlsym獲取符號地址。令人遺憾的是,dlopen函數定義在libdl.so中,而專用網卡軟件Linux 環境甚至都沒有libdl.so 這一動態庫的存在。不過值得慶幸的是,幾乎每一個進程都會使用libc.so 這一動態庫,該動態庫中有一套__libc 打頭的dlopen 函數。
每一個動態庫中符號,在文件的重定向信息中都存在一個符號地址偏移,當加載到內存,會給每個動態庫分配一個基地址,可以通過cat/proc/pid/maps 命令查詢,所以動態庫中符號地址就等于動態庫基地址加上重定向信息中符號地址偏移。根據這一原理,可以計算出__libc_dlopen_mode 函數在遠端環境下的地址,如圖3所示。

圖3 遠端函數地址計算原理
1.1.2 執行遠端環境上的函數
要想執行遠端環境上的函數,僅僅知道遠端函數的地址是不夠的,還需要構造遠端函數參數,以及把函數參數傳遞給遠端。
在遠端環境打開補丁文件時,需要將補丁文件路徑作為參數傳遞給__libc_dlopen_mode 函數,但是因為字符串是保存在.rodata節中的,而遠端環境肯定沒有補丁文件路徑這一字符串,所以無法進行字符串重定位。這時需要在遠端調用mmap 申請一塊遠端內存,將函數參數字符拷貝到這塊遠端內存中。同理,解決補丁文件中包含的所有字符串重定向問題時,都需要進行先mmap 后拷貝操作。
在Intel 64位處理下,當函數的參數小于六個時,函數參數使用寄存器保存,參數從左到右依次使用edi,esi,edx,ecx,r8d 和r9d 寄存器,當函數的參數大于六個時,多出的參數使用棧保存。根據Intel 64位處理器棧是從高地址往低地址生長的,在執行遠端函數時,需要取遠端進程棧的最小地址開始的一片棧空間,供補丁函數內的局部變量以及補丁函數參數大于六個時使用。
1.1.3 本端補丁文件中符號的重定向
補丁文件中的函數,不可避免可能會調用遠端進程中的函數或者全局變量,這兩種類型的未定義符號需要完成重定向操作,根據PLT 表和GOT 表相關原理,需要修改補丁文件未定義符號在GOT 表中存放符號地址。如果補丁文件中未定義符號屬于函數,且該函數在遠端環境的靜態庫中實現,那么需要向GOT 表該符號地址中寫入靜態庫函數地址;如果補丁文件中未定義符號屬于函數,且函數在遠端環境動態態庫中實現,那么需要向GOT 表該符號地址中寫入動態庫基地址加上該符號在動態庫的偏移;如果補丁文件中未定義符號屬于全局變量,且該變量在遠端環境動態庫中定義,鑒于dlopen 可以自動修復這些符號的重定向問題,無需手動重定向;如果補丁文件中未定義符號屬于全局變量,且該變量在遠端環境靜態庫中實現,因為dlopen 對靜態全局變量無法延后重定向,所以軟件中建議使用GET 或SET 函數對全局變量取值或賦值。
結合對PTRACE 相關原理和動態庫熱補丁關鍵技術的研究,動態庫熱補丁軟件流程圖如圖4所示。首先解析補丁文件,根據ELF 文件格式特點,提取補丁文件中需要打補丁的函數名,以及需要解決重定向問題的未定義符號,然后通過PTRACE 機制,對補丁文件中未定義符號進行重定向修復,最后需要對遠端進程中與補丁函數同名函數實施JMP 指令跳轉。

圖4 動態庫熱補丁軟件流程圖
針對補丁文件ELF 解析部分做細致分析,其流程圖如圖5所示。首先需要解析出補丁文件中.rela.plt 節、.dynstr 節、.dynsym節、.symtab 節和.strtab 節相關信息,然后在這些節中提取到補丁函數和未定義符號,最后需要訪問遠端進程,遍歷程序頭PHDR,提取補丁文件中函數和未定義符號對應的地址,便于后續做重定向和函數跳轉。

圖5 補丁文件ELF解析
某公司專用網卡設備運行進程包含動態庫libtest.so,該動態庫中有一個發包接口中inac_alloc_pkt 函數沒對入參做合法性檢測,導致系統發包時會出現異常。
采用熱補丁方式對故障進行修復,首先編寫patch.c 文件,重寫inac_alloc_pkt 接口,新增對參數合法性檢測。根據本文的理論,我們編寫代碼制作了自己的二進制補丁工具do_patch,該工具使用方法是“do_patch[待打補丁的進程pid 號]”。接著我們把libpatch.so 和do_patch 文件拷貝到專用網卡所在的Linux 環境的app 目錄下,利用ps 命令查看當當前環境網口發包進程pid 為13456,然后執行補丁命令,最終可以看到在沒有更換版本的前提下,遠端環境上故障得到修復,提高了故障修復的效率。
本文對動態庫熱補丁相關的關鍵技術和實現做了詳細的闡述,并結合實際工程案例論證了該方案的可行性。熱補丁技術不但給故障修復提供了一種手段,而且可以大大減少軟件開發成本。基于本文Intel 64位處理和Linux 操作系統的動態庫熱補丁方案,對于其他平臺的熱補丁研究也有一定的參考價值。