應用程序熱補丁(三):完整的設計與實現

前言

在前兩篇文章介紹了應用程序熱補丁的關鍵技術:

  • 修復運行時進程的函數

  • 載入熱補丁到進程中

  • 自動生成熱補丁等等

這些是組成應用程序熱補丁技術框架的關鍵部分,但是在生產環境中使用熱補丁技術還需要考慮適應現代軟體的屬性、熱補丁的安全性、以及在運營中對熱補丁的管理等等。

通過介紹UCloud應用程序熱補丁框架的設計理念和框架中各個組件,我們會解決以下實踐中遇到的問題:

  • 熱補丁的管理(載入、卸載、激活、回滾熱補丁等)

  • 打入熱補丁時的安全檢查(簡單說是什麼時候打入熱補丁是安全的)

  • 熱補丁對多線程的支持等等。

應用程序熱補丁的意義

介紹UCloud應用程序熱補丁框架之前,首先介紹一下我們為什麼研發和使用熱補丁技術。

目前主流的熱補丁技術,例如Ksplice、kpatch、kGraft、以及後來的livepatch等都是特別針對Linux內核的熱補丁技術,可以在不重啟系統的情況下,修復內核缺陷。我們一般稱為內核熱補丁。

UCloud使用了內核熱補丁修復了若干內核問題,避免了重啟系統導致的服務中斷,保證了操作系統本身的可用性。

在此基礎上,我們的下一個目標提高是核心組件中的單點的可用性。例如虛擬化的核心組件QEMU,雖然作為單點程序運行,但是可用性的要求和內核是一致的。雖然QEMU本身支持在線遷移,可以遷移客戶的虛擬機到新版本的QEMU上,但是遷移本身比較笨重。在遷移過程中會牽扯多個模塊,例如網路、存儲等,同時遷移時間和虛擬機的downtime、break time在運營上也會帶來挑戰。

對比QEMU通過在線遷移升級,使用熱補丁修復極快,並且對虛擬機周邊環境沒有依賴,可以對用戶的虛擬機做到靜默升級。由於熱補丁本身的天然屬性,熱補丁更適用於代碼改動較小的修復(例如安全漏洞),而在線遷移升級比較適用於大版本的升級。

在UCloud我們通過熱補丁修復了若干次QEMU的缺陷和安全漏洞,極大提高了可用性和安全性。因此我們認為對於代碼改動較小的問題時,熱補丁是一個完美的解決方案。

為什麼要自研應用程序熱補丁技術?答案也很簡單,我們無法找到一個實用並且易用的應用程序熱補丁技術,同時也由於我們已經在內核熱補丁領域的具有一定的積累,所以決定敢為人先、自研應用程序熱補丁技術。

設計理念

提出需求

介紹設計理念之前,首先應該提出應用程序熱補丁在UCloud雲平台的需求:

  • 應用程序熱補丁的適用場景和內核熱補丁是一致的,目的是修復缺陷,而不是增加功能和升級版本。所以應用程序熱補丁必須允許函數級別上的修改(不論是本地函數還是全局函數)。

  • 應用程序熱補丁必須是安全的,也就是打入熱補丁的前後進程的狀態必須一致,熱補丁只會操作修改的函數,不會影響進程的正常運行。

  • 應用程序熱補丁必須支持雲平台環境中現代軟體具有的特性,比如說Linux x86_64、多線程等等。

  • 熱補丁必須由工具來構建,也必須要由工具來管理(載入卸載等)。

  • 熱補丁必須同時支持回滾,同時支持一個進程多次熱補丁修復。

  • 降低熱補丁運營的難度。

我們針對這些需求,設計出如下的應用程序熱補丁框架。

設計思路

  • 支持修複函數級別的、並且可以自動化生成熱補丁的工具。

  • 支持多線程、熱補丁安全檢查、多熱補丁狀態管理的熱補丁載入工具。

  • 運行中的應用程序應記錄熱補丁的信息和狀態,可供外部工具查詢。

或者簡單來說,我們要做到,拿到源碼和patch就能通過工具自動生成熱補丁,熱補丁可以安全的打入運行的多線程應用程序中(不會引起程序的錯亂和崩潰),並且支持打入多個熱補丁。打入後的熱補丁可以被回滾取消,可以查詢當前應用程序中熱補丁的狀態和信息。

框架組件

這個框架的設計我們通過以下組件實現:

  • Creator

負責通過patch和源碼自動化生成熱補丁。

支持函數級別修復,不論本地函數還是全局函數。

  • Loader

負責載入熱補丁到目標進程中,也負責管理熱補丁(類似於客戶端程序)。

目標進程支持Linux x86_64、多線程等。

支持熱補丁安全檢查。

支持對熱補丁狀態的操作(例如載入、卸載、激活、回滾查詢等等)。

  • Core Runtime

負責記錄多個熱補丁的狀態和信息,同時提供熱補丁通用操作。

作為熱補丁模塊的通用運行時框架被Loader載入到目標進程中。

Loader對熱補丁狀態的操作最終由Core Runtime在目標進程空間中執行。

  • 熱補丁(補丁本身)

負責提供修復後的替換代碼和額外信息。

被Loader載入到目標進程中,註冊自己的信息到Core Runtime

在激活後使用自身包含的替換代碼代替有問題的函數。

組件之間協作如下圖所示,Creator工具根據程序源碼和patch生成熱補丁模塊,然後Loader將熱補丁模塊載入到目標進程Process的地址空間里,最後熱補丁和通用運行時Core Runtime一起完成熱修復。

實現方法

接下來分別講各個組件的實現:

Creator

基於對多種內核熱補丁技術的理解,我們認為應用程序的熱補丁也是可以通過工具自動生成的。雖然相比內核,應用程序的格式更加複雜、編譯鏈接的過程也更不固定,但是自動生成熱補丁應該是可行的。

我們知道,編譯源代碼之後會生成目標文件,將單個或多個目標文件鏈接可以生成可執行文件。目標文件和可執行文件都是ELF格式(Executable and Linkable Format)。ELF是一種標準且通用的文件格式,Linux上的可執行文件、目標文件、庫、core dump都是ELF。

Creator工具根據ELF標準的格式,解析修復前後的目標文件,找到前後不同的函數,提取出差異(包括改變和新增的函數),連同差異本身的屬性信息,生成一個動態鏈接庫格式的熱補丁。如下圖所示:

之前的文章介紹過二進位比較生成熱補丁替換代碼,這裡不再贅述。

Loader

Loader工具作為一個客戶端程序,操作目標進程Process,包括熱補丁的載入、激活、回滾、卸載、查看等。如下所示:

Loader利用了ptrace調用。Loader通過ptrace可以對Process的內存、寄存器進行讀寫,改變Process的運行狀態,也可以捕獲Process的信號。這樣Loader可以停止Process的運行,並根據AMD64 ABI對內存和寄存器進行修改,使Process執行dlopen等函數,載入熱補丁到Process的地址空間中,或者執行其他熱補丁的操作。熱補丁被載入之後,在/proc/pid/maps文件中可以看到。

Loader如何利用ptrace載入熱補丁在之前的文章中有詳細描述,不再贅述。

Loader隨後會停止Process所有的線程,準備激活熱補丁,此時Loader需要進行一致性檢查,也就是查看熱補丁的激活對當前的所有線程來講是否安全。需要檢查的是熱補丁的需要替換的函數是否在線程當前函數調用棧上,如果在調用棧上,說明現在是不安全的,激活熱補丁不能保證一致性。

在停止所有線程的時候,首先需要得到全部的線程信息,可以通過libthread_db庫與進程中libc進行交互得到線程的信息,也可以通過/proc/pid/tasks/目錄從內核中直接得到線程的信息。在停止所有線程之後,需要再次獲取所有線程信息,查看是否有新增線程,如果有需要停止新增線程。重複以上動作直到沒有新線程出現。

Core Runtime

在一個進程的生命周期中,可能需要多次熱補丁修復,同時多個熱補丁也會使用一些通用的功能,因此需要一個通用的核心模塊來提供通用功能,並且記錄進程中每個熱補丁的信息。這個通用模塊作為一個動態鏈接庫,我們叫做Core Runtime。

Loader在載入熱補丁時,首先需要載入Core Runtime到進程的地址空間,對進程而言,Core Runtime只需要載入一次。

在熱補丁被載入到進程的地址空間後,通過構造函數,首先向Core Runtime提供自己的信息,註冊到Core Runtime里,然後將熱補丁中差異函數的需要重定向的部分手動計算重定向。在激活熱補丁的時候,Core Runtime會根據熱補丁註冊時得到的信息,保存舊函數,並把舊函數入口位置替換成跳轉到新的函數的機器碼,完成熱修復。如下所示:

在回滾熱補丁的時候,Core Runtime把舊函數入口位置恢復,完成回滾。

Core Runtime同時可以管理多個熱補丁,以熱補丁的名字作為ID,區分不同的熱補丁,記錄必要的信息。

如下所示:

熱補丁(補丁本身)

這裡的熱補丁指的是狹義上的作為動態鏈接庫被Loader載入到目標進程Process中的熱補丁。

熱補丁由Creator產生,包含了替換代碼和一些動態信息(比如新舊函數的地址、大小、重定向信息等)。熱補丁被載入後,包含的函數和變數就存在於目標進程的地址空間中。熱補丁激活以後,所有對老函數的訪問,都會重定向到熱補丁地址範圍內的新函數。

如下所示:

總結

Creator、Loader、Core Runtime、熱補丁這四者構成了UCloud熱補丁技術框架,這四個組件相輔相成,互相協作完成熱補丁。

Creator負責生成熱補丁,Loader負責熱補丁的進程外管理(包括載入、卸載、激活、回滾熱補丁等),Core Runtime負責熱補丁的進程內管理(記錄熱補丁、備份舊函數、恢復舊函數等)。雖然是四個組件,但是都必須遵守同一個熱補丁規格標準,這樣才能共同完成熱補丁的工作。

通過這個框架,極大降低了我們製作熱補丁、打入熱補丁和運營熱補丁的難度。

例如,一個QEMU安全漏洞修復的流程可以簡化為:

  1. Creator通過QEMU源碼和漏洞修復patch生成熱補丁。

  2. 熱補丁被Loader打入正在運行的應用程序中(載入並且激活)。

  3. (可選)對運行中的應用程序查詢熱補丁的狀態和信息。

  4. (可選)對已經打入的熱補丁進行回滾和卸載。

值得指出的是,目前不是全部patch都可以自動生成熱補丁,原因是極少部分由於程序修改複雜,但是可以通過手動修改patch簡化代碼或者簡化邏輯做到可以自動生成熱補丁。大約90%的patch在無需修改的情況下都能自動生成熱補丁。

在一些特定場景下,我們通過第一篇文章(《應用程序熱補丁(一):幾行代碼構造免重啟修復補丁》)中介紹的熱補丁技術手動編寫熱補丁即可,無需使用複雜的自動生成熱補丁技術。

另外,目前UCloud應用程序熱補丁技術支持Linux C語言程序,但對於其他編譯型語言解決思路基本一致(例如C++等)。

在UCloud,我們利用應用程序熱補丁修復了若干緊急安全漏洞和缺陷,在關鍵時刻迅速解決問題,相比於傳統的軟體升級方式,解決問題更加及時。

希望通過一系列的文章填補目前應用程序熱補丁的空白部分,使更多人了解熱補丁的技術原理,讓熱補丁技術給更多人帶來更多的價值。

本文由『UCloud內核團隊』提供。作者:王超

——————

相關閱讀推薦:

如何用幾行代碼打造應用程序熱補丁?(二)

如何用幾行代碼打造應用程序熱補丁?(一)

機器學習進階筆記之十 | 那些TensorFlow上好玩的黑科技

機器學習進階筆記之九 | 利用TensorFlow搞定「倒字驗證碼」

機器學習進階筆記之八 | TensorFlow與中文手寫漢字識別

福利時間

如果你想親自上手,在雲上部署體驗以上技術實踐過程,大U為大家爭取到了100元 UCloud雲服務代金券,夠大家免費使用1個月的1核/2G/20G數據盤雲主機。

立即 註冊UCloud,在活動/邀請碼一欄填入:zhihu-ucloud,即可獲得代金券。

有需求的同學快去領代金券吧,有問題請添加UCloud運營小妹個人微信號:Surdur進行諮詢。

「UCloud機構號」將獨家分享雲計算領域的技術洞見、行業資訊以及一切你想知道的相關訊息。歡迎提問&求關注 o(*////▽////*)q~

以上。


推薦閱讀:

到底是誰的bug?聊聊模塊化
極光日報 第 171 期 | 2017 / 5 / 8
爆炸,解體,入侵,你想得到的你想不到的大BUG們
少女前線為什麼會有這麼多BUG?這對少女前線以後的發展有影響嗎?
蘋果電腦有哪些漏洞或不便之處?

TAG:云计算 | 应用程序Application | Bug |