王維晟
(國(guó)家計(jì)算機(jī)網(wǎng)絡(luò)應(yīng)急技術(shù)處理協(xié)調(diào)中心,北京 100029)
專(zhuān)用網(wǎng)卡往往插在一臺(tái)服務(wù)器上,它在硬件上接收前端數(shù)據(jù)分發(fā)設(shè)備的數(shù)據(jù),根據(jù)負(fù)載均衡原理可以將數(shù)據(jù)發(fā)送到后臺(tái)服務(wù)器的多個(gè)線程,使服務(wù)器的每個(gè)線程都能對(duì)數(shù)據(jù)同時(shí)進(jìn)行處理,同時(shí)通過(guò)專(zhuān)用網(wǎng)卡的軟件系統(tǒng)可以提供數(shù)據(jù)包的預(yù)處理功能。專(zhuān)用網(wǎng)卡在網(wǎng)絡(luò)拓?fù)渲械奈恢萌鐖D1所示。
圖1 專(zhuān)用網(wǎng)卡在網(wǎng)絡(luò)拓?fù)渲械奈恢?/p>
研究發(fā)現(xiàn),熱補(bǔ)丁可以保證當(dāng)前業(yè)務(wù)不中斷的前提下,對(duì)軟件故障進(jìn)行調(diào)試和修復(fù)。針對(duì)熱補(bǔ)丁的研究,主要是基于VXWORKS 操作系統(tǒng)下的熱補(bǔ)丁,而專(zhuān)用網(wǎng)卡運(yùn)行硬件環(huán)境是Intel 64位處理器,軟件是基于Linux 操作系統(tǒng)(Centos 7.2),并按功能封裝成不同動(dòng)態(tài)庫(kù)。文中介紹了熱補(bǔ)丁的相關(guān)技術(shù),并詳細(xì)論述了動(dòng)態(tài)庫(kù)熱補(bǔ)丁的實(shí)現(xiàn)方案,最后通過(guò)一個(gè)工程案例表明,專(zhuān)用網(wǎng)卡中使用動(dòng)態(tài)庫(kù)熱補(bǔ)丁,能大大提高軟件故障修復(fù)的效率。
改變動(dòng)態(tài)庫(kù)中某個(gè)函數(shù)的控制流程,可以使用LD_PRELOAD 提前加載動(dòng)態(tài)庫(kù);可以修改PLT/GOT 表;可以仿DPDK 構(gòu)造constructor 屬性。本文采用最直接的JMP 跳轉(zhuǎn)指令來(lái)實(shí)現(xiàn)改變控制流。
動(dòng)態(tài)庫(kù)熱補(bǔ)丁的核心原理如圖2所示,先給當(dāng)前進(jìn)程打上動(dòng)態(tài)庫(kù)libpatch.so 這一補(bǔ)丁文件,其中l(wèi)ibpatch.so 文件實(shí)現(xiàn)了fun1函數(shù),假設(shè)main 函數(shù)中調(diào)用fun0函數(shù),fun0函數(shù)定義在動(dòng)態(tài)庫(kù)libtest0.so 中,我們通過(guò)改變fun0函數(shù)代碼入口處的匯編指令,使其通過(guò)JMP 跳轉(zhuǎn)指令,直接跳轉(zhuǎn)到補(bǔ)丁文件中的fun1處執(zhí)行。
圖2 動(dòng)態(tài)庫(kù)熱補(bǔ)丁核心原理
在真實(shí)的補(bǔ)丁文件libpatch.so 中,我們實(shí)現(xiàn)的是fun1函數(shù),然后通過(guò)給進(jìn)程打補(bǔ)丁,用補(bǔ)丁文件中的fun1 代替進(jìn)程中的fun1。我們把當(dāng)前進(jìn)程稱(chēng)為遠(yuǎn)端環(huán)境,把打補(bǔ)丁的進(jìn)程稱(chēng)為本端環(huán)境。動(dòng)態(tài)庫(kù)熱補(bǔ)丁技術(shù)中涉及如下幾個(gè)關(guān)鍵技術(shù):獲取遠(yuǎn)端環(huán)境上的函數(shù)的地址;執(zhí)行遠(yuǎn)端環(huán)境上的的函數(shù);本端補(bǔ)丁文件中符號(hào)的重定向。
1.1.1 獲取遠(yuǎn)端環(huán)境上的函數(shù)的地址
前文提到,可以通過(guò)PTRACE 系統(tǒng)調(diào)用對(duì)進(jìn)程進(jìn)程跟蹤,另外程序還可以通過(guò)dlopen 函數(shù)憑空加載一個(gè)動(dòng)態(tài)庫(kù),并通過(guò)dlsym獲取符號(hào)地址。令人遺憾的是,dlopen函數(shù)定義在libdl.so中,而專(zhuān)用網(wǎng)卡軟件Linux 環(huán)境甚至都沒(méi)有l(wèi)ibdl.so 這一動(dòng)態(tài)庫(kù)的存在。不過(guò)值得慶幸的是,幾乎每一個(gè)進(jìn)程都會(huì)使用libc.so 這一動(dòng)態(tài)庫(kù),該動(dòng)態(tài)庫(kù)中有一套__libc 打頭的dlopen 函數(shù)。
每一個(gè)動(dòng)態(tài)庫(kù)中符號(hào),在文件的重定向信息中都存在一個(gè)符號(hào)地址偏移,當(dāng)加載到內(nèi)存,會(huì)給每個(gè)動(dòng)態(tài)庫(kù)分配一個(gè)基地址,可以通過(guò)cat/proc/pid/maps 命令查詢(xún),所以動(dòng)態(tài)庫(kù)中符號(hào)地址就等于動(dòng)態(tài)庫(kù)基地址加上重定向信息中符號(hào)地址偏移。根據(jù)這一原理,可以計(jì)算出__libc_dlopen_mode 函數(shù)在遠(yuǎn)端環(huán)境下的地址,如圖3所示。
圖3 遠(yuǎn)端函數(shù)地址計(jì)算原理
1.1.2 執(zhí)行遠(yuǎn)端環(huán)境上的函數(shù)
要想執(zhí)行遠(yuǎn)端環(huán)境上的函數(shù),僅僅知道遠(yuǎn)端函數(shù)的地址是不夠的,還需要構(gòu)造遠(yuǎn)端函數(shù)參數(shù),以及把函數(shù)參數(shù)傳遞給遠(yuǎn)端。
在遠(yuǎn)端環(huán)境打開(kāi)補(bǔ)丁文件時(shí),需要將補(bǔ)丁文件路徑作為參數(shù)傳遞給__libc_dlopen_mode 函數(shù),但是因?yàn)樽址潜4嬖?rodata節(jié)中的,而遠(yuǎn)端環(huán)境肯定沒(méi)有補(bǔ)丁文件路徑這一字符串,所以無(wú)法進(jìn)行字符串重定位。這時(shí)需要在遠(yuǎn)端調(diào)用mmap 申請(qǐng)一塊遠(yuǎn)端內(nèi)存,將函數(shù)參數(shù)字符拷貝到這塊遠(yuǎn)端內(nèi)存中。同理,解決補(bǔ)丁文件中包含的所有字符串重定向問(wèn)題時(shí),都需要進(jìn)行先mmap 后拷貝操作。
在Intel 64位處理下,當(dāng)函數(shù)的參數(shù)小于六個(gè)時(shí),函數(shù)參數(shù)使用寄存器保存,參數(shù)從左到右依次使用edi,esi,edx,ecx,r8d 和r9d 寄存器,當(dāng)函數(shù)的參數(shù)大于六個(gè)時(shí),多出的參數(shù)使用棧保存。根據(jù)Intel 64位處理器棧是從高地址往低地址生長(zhǎng)的,在執(zhí)行遠(yuǎn)端函數(shù)時(shí),需要取遠(yuǎn)端進(jìn)程棧的最小地址開(kāi)始的一片棧空間,供補(bǔ)丁函數(shù)內(nèi)的局部變量以及補(bǔ)丁函數(shù)參數(shù)大于六個(gè)時(shí)使用。
1.1.3 本端補(bǔ)丁文件中符號(hào)的重定向
補(bǔ)丁文件中的函數(shù),不可避免可能會(huì)調(diào)用遠(yuǎn)端進(jìn)程中的函數(shù)或者全局變量,這兩種類(lèi)型的未定義符號(hào)需要完成重定向操作,根據(jù)PLT 表和GOT 表相關(guān)原理,需要修改補(bǔ)丁文件未定義符號(hào)在GOT 表中存放符號(hào)地址。如果補(bǔ)丁文件中未定義符號(hào)屬于函數(shù),且該函數(shù)在遠(yuǎn)端環(huán)境的靜態(tài)庫(kù)中實(shí)現(xiàn),那么需要向GOT 表該符號(hào)地址中寫(xiě)入靜態(tài)庫(kù)函數(shù)地址;如果補(bǔ)丁文件中未定義符號(hào)屬于函數(shù),且函數(shù)在遠(yuǎn)端環(huán)境動(dòng)態(tài)態(tài)庫(kù)中實(shí)現(xiàn),那么需要向GOT 表該符號(hào)地址中寫(xiě)入動(dòng)態(tài)庫(kù)基地址加上該符號(hào)在動(dòng)態(tài)庫(kù)的偏移;如果補(bǔ)丁文件中未定義符號(hào)屬于全局變量,且該變量在遠(yuǎn)端環(huán)境動(dòng)態(tài)庫(kù)中定義,鑒于dlopen 可以自動(dòng)修復(fù)這些符號(hào)的重定向問(wèn)題,無(wú)需手動(dòng)重定向;如果補(bǔ)丁文件中未定義符號(hào)屬于全局變量,且該變量在遠(yuǎn)端環(huán)境靜態(tài)庫(kù)中實(shí)現(xiàn),因?yàn)閐lopen 對(duì)靜態(tài)全局變量無(wú)法延后重定向,所以軟件中建議使用GET 或SET 函數(shù)對(duì)全局變量取值或賦值。
結(jié)合對(duì)PTRACE 相關(guān)原理和動(dòng)態(tài)庫(kù)熱補(bǔ)丁關(guān)鍵技術(shù)的研究,動(dòng)態(tài)庫(kù)熱補(bǔ)丁軟件流程圖如圖4所示。首先解析補(bǔ)丁文件,根據(jù)ELF 文件格式特點(diǎn),提取補(bǔ)丁文件中需要打補(bǔ)丁的函數(shù)名,以及需要解決重定向問(wèn)題的未定義符號(hào),然后通過(guò)PTRACE 機(jī)制,對(duì)補(bǔ)丁文件中未定義符號(hào)進(jìn)行重定向修復(fù),最后需要對(duì)遠(yuǎn)端進(jìn)程中與補(bǔ)丁函數(shù)同名函數(shù)實(shí)施JMP 指令跳轉(zhuǎn)。
圖4 動(dòng)態(tài)庫(kù)熱補(bǔ)丁軟件流程圖
針對(duì)補(bǔ)丁文件ELF 解析部分做細(xì)致分析,其流程圖如圖5所示。首先需要解析出補(bǔ)丁文件中.rela.plt 節(jié)、.dynstr 節(jié)、.dynsym節(jié)、.symtab 節(jié)和.strtab 節(jié)相關(guān)信息,然后在這些節(jié)中提取到補(bǔ)丁函數(shù)和未定義符號(hào),最后需要訪問(wèn)遠(yuǎn)端進(jìn)程,遍歷程序頭PHDR,提取補(bǔ)丁文件中函數(shù)和未定義符號(hào)對(duì)應(yīng)的地址,便于后續(xù)做重定向和函數(shù)跳轉(zhuǎn)。
圖5 補(bǔ)丁文件ELF解析
某公司專(zhuān)用網(wǎng)卡設(shè)備運(yùn)行進(jìn)程包含動(dòng)態(tài)庫(kù)libtest.so,該動(dòng)態(tài)庫(kù)中有一個(gè)發(fā)包接口中inac_alloc_pkt 函數(shù)沒(méi)對(duì)入?yún)⒆龊戏ㄐ詸z測(cè),導(dǎo)致系統(tǒng)發(fā)包時(shí)會(huì)出現(xiàn)異常。
采用熱補(bǔ)丁方式對(duì)故障進(jìn)行修復(fù),首先編寫(xiě)patch.c 文件,重寫(xiě)inac_alloc_pkt 接口,新增對(duì)參數(shù)合法性檢測(cè)。根據(jù)本文的理論,我們編寫(xiě)代碼制作了自己的二進(jìn)制補(bǔ)丁工具do_patch,該工具使用方法是“do_patch[待打補(bǔ)丁的進(jìn)程pid 號(hào)]”。接著我們把libpatch.so 和do_patch 文件拷貝到專(zhuān)用網(wǎng)卡所在的Linux 環(huán)境的app 目錄下,利用ps 命令查看當(dāng)當(dāng)前環(huán)境網(wǎng)口發(fā)包進(jìn)程pid 為13456,然后執(zhí)行補(bǔ)丁命令,最終可以看到在沒(méi)有更換版本的前提下,遠(yuǎn)端環(huán)境上故障得到修復(fù),提高了故障修復(fù)的效率。
本文對(duì)動(dòng)態(tài)庫(kù)熱補(bǔ)丁相關(guān)的關(guān)鍵技術(shù)和實(shí)現(xiàn)做了詳細(xì)的闡述,并結(jié)合實(shí)際工程案例論證了該方案的可行性。熱補(bǔ)丁技術(shù)不但給故障修復(fù)提供了一種手段,而且可以大大減少軟件開(kāi)發(fā)成本。基于本文Intel 64位處理和Linux 操作系統(tǒng)的動(dòng)態(tài)庫(kù)熱補(bǔ)丁方案,對(duì)于其他平臺(tái)的熱補(bǔ)丁研究也有一定的參考價(jià)值。