前言
在前兩篇文章介紹了應(yīng)用程序熱補(bǔ)丁的關(guān)鍵技術(shù):
修復(fù)運(yùn)行時(shí)進(jìn)程的函數(shù)
加載熱補(bǔ)丁到進(jìn)程中
自動(dòng)生成熱補(bǔ)丁等等
這些是組成應(yīng)用程序熱補(bǔ)丁技術(shù)框架的關(guān)鍵部分柱查,但是在生產(chǎn)環(huán)境中使用熱補(bǔ)丁技術(shù)還需要考慮適應(yīng)現(xiàn)代軟件的屬性、熱補(bǔ)丁的安全性、以及在運(yùn)營(yíng)中對(duì)熱補(bǔ)丁的管理等等粤蝎。
通過(guò)介紹 UCloud 應(yīng)用程序熱補(bǔ)丁框架的設(shè)計(jì)理念和框架中各個(gè)組件窟扑,我們會(huì)解決以下實(shí)踐中遇到的問(wèn)題:
熱補(bǔ)丁的管理(加載喇颁、卸載、激活嚎货、回滾熱補(bǔ)丁等)
打入熱補(bǔ)丁時(shí)的安全檢查(簡(jiǎn)單說(shuō)是什么時(shí)候打入熱補(bǔ)丁是安全的)
熱補(bǔ)丁對(duì)多線程的支持等等橘霎。
應(yīng)用程序熱補(bǔ)丁的意義
介紹 UCloud 應(yīng)用程序熱補(bǔ)丁框架之前,首先介紹一下我們?yōu)槭裁囱邪l(fā)和使用熱補(bǔ)丁技術(shù)殖属。
目前主流的熱補(bǔ)丁技術(shù)姐叁,例如 Ksplice、kpatch、kGraft外潜、以及后來(lái)的 livepatch 等都是特別針對(duì) Linux 內(nèi)核的熱補(bǔ)丁技術(shù)谭溉,可以在不重啟系統(tǒng)的情況下,修復(fù)內(nèi)核缺陷橡卤。我們一般稱為內(nèi)核熱補(bǔ)丁扮念。
UCloud 使用了內(nèi)核熱補(bǔ)丁修復(fù)了若干內(nèi)核問(wèn)題,避免了重啟系統(tǒng)導(dǎo)致的服務(wù)中斷碧库,保證了操作系統(tǒng)本身的可用性柜与。
在此基礎(chǔ)上,我們的下一個(gè)目標(biāo)提高是核心組件中的單點(diǎn)的可用性嵌灰。例如虛擬化的核心組件 QEMU弄匕,雖然作為單點(diǎn)程序運(yùn)行,但是可用性的要求和內(nèi)核是一致的沽瞭。雖然 QEMU 本身支持在線遷移迁匠,可以遷移客戶的虛擬機(jī)到新版本的 QEMU 上,但是遷移本身比較笨重驹溃。在遷移過(guò)程中會(huì)牽扯多個(gè)模塊城丧,例如網(wǎng)絡(luò)、存儲(chǔ)等豌鹤,同時(shí)遷移時(shí)間和虛擬機(jī)的 downtime亡哄、break time 在運(yùn)營(yíng)上也會(huì)帶來(lái)挑戰(zhàn)。
對(duì)比 QEMU 通過(guò)在線遷移升級(jí)布疙,使用熱補(bǔ)丁修復(fù)極快蚊惯,并且對(duì)虛擬機(jī)周邊環(huán)境沒(méi)有依賴,可以對(duì)用戶的虛擬機(jī)做到靜默升級(jí)灵临。由于熱補(bǔ)丁本身的天然屬性截型,熱補(bǔ)丁更適用于代碼改動(dòng)較小的修復(fù)(例如安全漏洞),而在線遷移升級(jí)比較適用于大版本的升級(jí)儒溉。
在 UCloud 我們通過(guò)熱補(bǔ)丁修復(fù)了若干次 QEMU 的缺陷和安全漏洞宦焦,極大提高了可用性和安全性。因此我們認(rèn)為對(duì)于代碼改動(dòng)較小的問(wèn)題時(shí)睁搭,熱補(bǔ)丁是一個(gè)完美的解決方案。
為什么要自研應(yīng)用程序熱補(bǔ)丁技術(shù)舔痪?答案也很簡(jiǎn)單锌唾,我們無(wú)法找到一個(gè)實(shí)用并且易用的應(yīng)用程序熱補(bǔ)丁技術(shù)锄码,同時(shí)也由于我們已經(jīng)在內(nèi)核熱補(bǔ)丁領(lǐng)域的具有一定的積累夺英,所以決定敢為人先滋捶、自研應(yīng)用程序熱補(bǔ)丁技術(shù)。
設(shè)計(jì)理念
提出需求
介紹設(shè)計(jì)理念之前重窟,首先應(yīng)該提出應(yīng)用程序熱補(bǔ)丁在 UCloud 云平臺(tái)的需求:
應(yīng)用程序熱補(bǔ)丁的適用場(chǎng)景和內(nèi)核熱補(bǔ)丁是一致的载萌,目的是修復(fù)缺陷,而不是增加功能和升級(jí)版本扭仁。所以應(yīng)用程序熱補(bǔ)丁必須允許函數(shù)級(jí)別上的修改(不論是本地函數(shù)還是全局函數(shù))厅翔。
應(yīng)用程序熱補(bǔ)丁必須是安全的,也就是打入熱補(bǔ)丁的前后進(jìn)程的狀態(tài)必須一致熊泵,熱補(bǔ)丁只會(huì)操作修改的函數(shù)甸昏,不會(huì)影響進(jìn)程的正常運(yùn)行。
應(yīng)用程序熱補(bǔ)丁必須支持云平臺(tái)環(huán)境中現(xiàn)代軟件具有的特性怯邪,比如說(shuō) Linux x86_64花墩、多線程等等澄步。
熱補(bǔ)丁必須由工具來(lái)構(gòu)建村缸,也必須要由工具來(lái)管理(加載卸載等)。
熱補(bǔ)丁必須同時(shí)支持回滾仇箱,同時(shí)支持一個(gè)進(jìn)程多次熱補(bǔ)丁修復(fù)东羹。
降低熱補(bǔ)丁運(yùn)營(yíng)的難度。
我們針對(duì)這些需求属提,設(shè)計(jì)出如下的應(yīng)用程序熱補(bǔ)丁框架。
設(shè)計(jì)思路
支持修復(fù)函數(shù)級(jí)別的师坎、并且可以自動(dòng)化生成熱補(bǔ)丁的工具堪滨。
支持多線程、熱補(bǔ)丁安全檢查惶岭、多熱補(bǔ)丁狀態(tài)管理的熱補(bǔ)丁加載工具犯眠。
運(yùn)行中的應(yīng)用程序應(yīng)記錄熱補(bǔ)丁的信息和狀態(tài)筐咧,可供外部工具查詢。
或者簡(jiǎn)單來(lái)說(shuō)铺罢,我們要做到残炮,拿到源碼和 patch 就能通過(guò)工具自動(dòng)生成熱補(bǔ)丁,熱補(bǔ)丁可以安全的打入運(yùn)行的多線程應(yīng)用程序中(不會(huì)引起程序的錯(cuò)亂和崩潰)泉瞻,并且支持打入多個(gè)熱補(bǔ)丁苞冯。打入后的熱補(bǔ)丁可以被回滾取消舅锄,可以查詢當(dāng)前應(yīng)用程序中熱補(bǔ)丁的狀態(tài)和信息。
框架組件
這個(gè)框架的設(shè)計(jì)我們通過(guò)以下組件實(shí)現(xiàn):
- Creator
負(fù)責(zé)通過(guò) patch 和源碼自動(dòng)化生成熱補(bǔ)丁畴蹭。
支持函數(shù)級(jí)別修復(fù)鳍烁,不論本地函數(shù)還是全局函數(shù)老翘。
- Loader
負(fù)責(zé)加載熱補(bǔ)丁到目標(biāo)進(jìn)程中锻离,也負(fù)責(zé)管理熱補(bǔ)赌够场(類似于客戶端程序)傀履。
目標(biāo)進(jìn)程支持 Linux x86_64、多線程等碴犬。
支持熱補(bǔ)丁安全檢查梆暮。
支持對(duì)熱補(bǔ)丁狀態(tài)的操作(例如加載、卸載偿荷、激活唠椭、回滾查詢等等)贪嫂。
- Core Runtime
負(fù)責(zé)記錄多個(gè)熱補(bǔ)丁的狀態(tài)和信息,同時(shí)提供熱補(bǔ)丁通用操作斗塘。
作為熱補(bǔ)丁模塊的通用運(yùn)行時(shí)框架被 Loader 加載到目標(biāo)進(jìn)程中餐曹。
Loader 對(duì)熱補(bǔ)丁狀態(tài)的操作最終由 Core Runtime 在目標(biāo)進(jìn)程空間中執(zhí)行台猴。
- 熱補(bǔ)毒懔健(補(bǔ)丁本身)
負(fù)責(zé)提供修復(fù)后的替換代碼和額外信息宪彩。
被 Loader 加載到目標(biāo)進(jìn)程中,注冊(cè)自己的信息到 Core Runtime
在激活后使用自身包含的替換代碼代替有問(wèn)題的函數(shù)俊柔。
組件之間協(xié)作如下圖所示,Creator 工具根據(jù)程序源碼和 patch 生成熱補(bǔ)丁模塊物赶,然后 Loader 將熱補(bǔ)丁模塊加載到目標(biāo)進(jìn)程 Process 的地址空間里留晚,最后熱補(bǔ)丁和通用運(yùn)行時(shí) Core Runtime 一起完成熱修復(fù)。
實(shí)現(xiàn)方法
接下來(lái)分別講各個(gè)組件的實(shí)現(xiàn):
Creator
基于對(duì)多種內(nèi)核熱補(bǔ)丁技術(shù)的理解奖地,我們認(rèn)為應(yīng)用程序的熱補(bǔ)丁也是可以通過(guò)工具自動(dòng)生成的赋焕。雖然相比內(nèi)核隆判,應(yīng)用程序的格式更加復(fù)雜、編譯鏈接的過(guò)程也更不固定械筛,但是自動(dòng)生成熱補(bǔ)丁應(yīng)該是可行的飒炎。
我們知道郎汪,編譯源代碼之后會(huì)生成目標(biāo)文件,將單個(gè)或多個(gè)目標(biāo)文件鏈接可以生成可執(zhí)行文件抛计。目標(biāo)文件和可執(zhí)行文件都是 ELF 格式(Executable and Linkable Format)照筑。ELF 是一種標(biāo)準(zhǔn)且通用的文件格式,Linux 上的可執(zhí)行文件波俄、目標(biāo)文件懦铺、庫(kù)支鸡、core dump 都是 ELF趁窃。
Creator 工具根據(jù) ELF 標(biāo)準(zhǔn)的格式醒陆,解析修復(fù)前后的目標(biāo)文件叔汁,找到前后不同的函數(shù),提取出差異(包括改變和新增的函數(shù))码邻,連同差異本身的屬性信息像屋,生成一個(gè)動(dòng)態(tài)鏈接庫(kù)格式的熱補(bǔ)丁边篮。如下圖所示:
之前的文章介紹過(guò)二進(jìn)制比較生成熱補(bǔ)丁替換代碼戈轿,這里不再贅述思杯。
Loader
Loader 工具作為一個(gè)客戶端程序,操作目標(biāo)進(jìn)程 Process誊册,包括熱補(bǔ)丁的加載暖璧、激活澎办、回滾、卸載悍汛、查看等至会。如下所示:
Loader 利用了 ptrace 調(diào)用奉件。Loader 通過(guò) ptrace 可以對(duì) Process 的內(nèi)存县貌、寄存器進(jìn)行讀寫,改變 Process 的運(yùn)行狀態(tài)梧宫,也可以捕獲 Process 的信號(hào)摆碉。這樣 Loader 可以停止 Process 的運(yùn)行巷帝,并根據(jù) AMD64 ABI 對(duì)內(nèi)存和寄存器進(jìn)行修改,使 Process 執(zhí)行 dlopen 等函數(shù)驰徊,加載熱補(bǔ)丁到 Process 的地址空間中堕阔,或者執(zhí)行其他熱補(bǔ)丁的操作超陆。熱補(bǔ)丁被加載之后,在 / proc/pid/maps 文件中可以看到例驹。
Loader 如何利用 ptrace 加載熱補(bǔ)丁在之前的文章中有詳細(xì)描述退唠,不再贅述瞧预。
Loader 隨后會(huì)停止 Process 所有的線程,準(zhǔn)備激活熱補(bǔ)丁盆驹,此時(shí) Loader 需要進(jìn)行一致性檢查滩愁,也就是查看熱補(bǔ)丁的激活對(duì)當(dāng)前的所有線程來(lái)講是否安全。需要檢查的是熱補(bǔ)丁的需要替換的函數(shù)是否在線程當(dāng)前函數(shù)調(diào)用棧上廉丽,如果在調(diào)用棧上正压,說(shuō)明現(xiàn)在是不安全的,激活熱補(bǔ)丁不能保證一致性拓劝。
在停止所有線程的時(shí)候嘉裤,首先需要得到全部的線程信息价脾,可以通過(guò) libthread_db 庫(kù)與進(jìn)程中 libc 進(jìn)行交互得到線程的信息,也可以通過(guò) / proc/pid/tasks / 目錄從內(nèi)核中直接得到線程的信息犀变。在停止所有線程之后获枝,需要再次獲取所有線程信息骇笔,查看是否有新增線程,如果有需要停止新增線程懦傍。重復(fù)以上動(dòng)作直到?jīng)]有新線程出現(xiàn)粗俱。
Core Runtime
在一個(gè)進(jìn)程的生命周期中虚吟,可能需要多次熱補(bǔ)丁修復(fù),同時(shí)多個(gè)熱補(bǔ)丁也會(huì)使用一些通用的功能偏塞,因此需要一個(gè)通用的核心模塊來(lái)提供通用功能灸叼,并且記錄進(jìn)程中每個(gè)熱補(bǔ)丁的信息。這個(gè)通用模塊作為一個(gè)動(dòng)態(tài)鏈接庫(kù)慎冤,我們叫做 Core Runtime沧卢。
Loader 在加載熱補(bǔ)丁時(shí)但狭,首先需要加載 Core Runtime 到進(jìn)程的地址空間撬即,對(duì)進(jìn)程而言剥槐,Core Runtime 只需要加載一次。
在熱補(bǔ)丁被加載到進(jìn)程的地址空間后颅崩,通過(guò)構(gòu)造函數(shù)沿后,首先向 Core Runtime 提供自己的信息朽砰,注冊(cè)到 Core Runtime 里瞧柔,然后將熱補(bǔ)丁中差異函數(shù)的需要重定向的部分手動(dòng)計(jì)算重定向。在激活熱補(bǔ)丁的時(shí)候撼唾,Core Runtime 會(huì)根據(jù)熱補(bǔ)丁注冊(cè)時(shí)得到的信息备绽,保存舊函數(shù)肺素,并把舊函數(shù)入口位置替換成跳轉(zhuǎn)到新的函數(shù)的機(jī)器碼,完成熱修復(fù)猴伶。如下所示:
在回滾熱補(bǔ)丁的時(shí)候他挎,Core Runtime 把舊函數(shù)入口位置恢復(fù)办桨,完成回滾。
Core Runtime 同時(shí)可以管理多個(gè)熱補(bǔ)丁损姜,以熱補(bǔ)丁的名字作為 ID摧阅,區(qū)分不同的熱補(bǔ)丁绷蹲,記錄必要的信息。
如下所示:
熱補(bǔ)丁(補(bǔ)丁本身)
這里的熱補(bǔ)丁指的是狹義上的作為動(dòng)態(tài)鏈接庫(kù)被 Loader 加載到目標(biāo)進(jìn)程 Process 中的熱補(bǔ)丁苞俘。
熱補(bǔ)丁由 Creator 產(chǎn)生吃谣,包含了替換代碼和一些動(dòng)態(tài)信息(比如新舊函數(shù)的地址做裙、大小、重定向信息等)仔戈。熱補(bǔ)丁被加載后监徘,包含的函數(shù)和變量就存在于目標(biāo)進(jìn)程的地址空間中吧碾。熱補(bǔ)丁激活以后倦春,所有對(duì)老函數(shù)的訪問(wèn)落剪,都會(huì)重定向到熱補(bǔ)丁地址范圍內(nèi)的新函數(shù)忠怖。
如下所示:
總結(jié)
Creator凡泣、Loader问麸、Core Runtime钞翔、熱補(bǔ)丁這四者構(gòu)成了 UCloud 熱補(bǔ)丁技術(shù)框架布轿,這四個(gè)組件相輔相成来颤,互相協(xié)作完成熱補(bǔ)丁福铅。
Creator 負(fù)責(zé)生成熱補(bǔ)丁,Loader 負(fù)責(zé)熱補(bǔ)丁的進(jìn)程外管理(包括加載笆包、卸載庵佣、激活汛兜、回滾熱補(bǔ)丁等)粥谬,Core Runtime 負(fù)責(zé)熱補(bǔ)丁的進(jìn)程內(nèi)管理(記錄熱補(bǔ)丁、備份舊函數(shù)派哲、恢復(fù)舊函數(shù)等)哟玷。雖然是四個(gè)組件,但是都必須遵守同一個(gè)熱補(bǔ)丁規(guī)格標(biāo)準(zhǔn)椰苟,這樣才能共同完成熱補(bǔ)丁的工作树叽。
通過(guò)這個(gè)框架题诵,極大降低了我們制作熱補(bǔ)丁性锭、打入熱補(bǔ)丁和運(yùn)營(yíng)熱補(bǔ)丁的難度。
例如她奥,一個(gè) QEMU 安全漏洞修復(fù)的流程可以簡(jiǎn)化為:
Creator 通過(guò) QEMU 源碼和漏洞修復(fù) patch 生成熱補(bǔ)丁怎棱。
熱補(bǔ)丁被 Loader 打入正在運(yùn)行的應(yīng)用程序中(加載并且激活)拳恋。
(可選)對(duì)運(yùn)行中的應(yīng)用程序查詢熱補(bǔ)丁的狀態(tài)和信息。
(可選)對(duì)已經(jīng)打入的熱補(bǔ)丁進(jìn)行回滾和卸載隙赁。
值得指出的是鸳谜,目前不是全部 patch 都可以自動(dòng)生成熱補(bǔ)丁式廷,原因是極少部分由于程序修改復(fù)雜滑废,但是可以通過(guò)手動(dòng)修改 patch 簡(jiǎn)化代碼或者簡(jiǎn)化邏輯做到可以自動(dòng)生成熱補(bǔ)丁蠕趁。大約 90% 的 patch 在無(wú)需修改的情況下都能自動(dòng)生成熱補(bǔ)丁。
在一些特定場(chǎng)景下豁延,我們通過(guò)第一篇文章(《應(yīng)用程序熱補(bǔ)蛾几荨(一):幾行代碼構(gòu)造免重啟修復(fù)補(bǔ)丁》)中介紹的熱補(bǔ)丁技術(shù)手動(dòng)編寫熱補(bǔ)丁即可,無(wú)需使用復(fù)雜的自動(dòng)生成熱補(bǔ)丁技術(shù)诱咏。
另外苔可,目前 UCloud 應(yīng)用程序熱補(bǔ)丁技術(shù)支持 Linux C 語(yǔ)言程序,但對(duì)于其他編譯型語(yǔ)言解決思路基本一致(例如 C++ 等)袋狞。
在 UCloud焚辅,我們利用應(yīng)用程序熱補(bǔ)丁修復(fù)了若干緊急安全漏洞和缺陷,在關(guān)鍵時(shí)刻迅速解決問(wèn)題苟鸯,相比于傳統(tǒng)的軟件升級(jí)方式同蜻,解決問(wèn)題更加及時(shí)。
希望通過(guò)一系列的文章填補(bǔ)目前應(yīng)用程序熱補(bǔ)丁的空白部分早处,使更多人了解熱補(bǔ)丁的技術(shù)原理,讓熱補(bǔ)丁技術(shù)給更多人帶來(lái)更多的價(jià)值陕赃。