Unity下Bug修復神器InjectFix正式對外開源

InjectFix是騰訊最新對外開源的Unity代碼邏輯熱修復方案,可實如今Unity線上客戶端內,不用迭代新版本,就能快速修復遊戲的線上bug。html

先說幾個亮點:
一、直接在Unity工程上修改C#便可更新;老項目無需修改原有代碼便可使用;
二、更符合蘋果熱更新條款;
三、每一個遊戲一份私有補丁格式,安全更有保障。ios

InjectFix經騰訊內部多個項目應用反饋十分良好,不只能解決線上bug,還能夠有效的提升平常開發效率,下面咱們聊下這項目的前世此生。安全

熱更方案大亂鬥
全部支持ios的熱更方案都有個共同點:更新後代碼都是解析執行。若是按其更新前是否解析執行,能夠分爲兩大類:
一類是某些模塊甚至整個遊戲,都一直解析執行。這是最傳統的方式,目前市面上全部主流方案(xLua,slua,tolua,ILRuntime,jsb等等)都支持這種方式。這種方式的特色:
一、或多或少都會有些侵入性:ILRuntime解析執行C#編譯後的程序集,在這些方案裏頭侵入性可能最小,但也須要對代碼重構,把要更新的邏輯拆到單獨程序集。各類非C#的腳本侵入性最大,一個已經完成的純C#項目要用意味着重寫。
ps:也有一種思路是經過一個C#轉XX腳本工具來實現C#編碼,解析執行,但若是你是一個已有項目想這麼轉一下,大機率是失敗的,除非你一開始就在用這方式在開發,碰到坑就避開,由於這類方案每每不是完整支持所有語法,支持的語法也不必定能徹底一致。
二、基於性能,實現便利性等的考慮,通常遊戲有些地方要以原生的方式跑,這些原生跑的代碼出了bug這種方式是無能爲力的。
三、若是使用的腳本是動態類型語言,還會帶來代碼維護困難的問題。
四、優勢是能夠新增功能,有的遊戲甚至能夠作到一次下載,後續不用整包更新。但蘋果條款分析的章節能夠看到,這也不必定是好事。
另一類是以原生方式跑,若是有bug,把邏輯重定向到新的,解析執行的邏輯。這種方式的特色:
一、侵入性低,後期項目也可使用。
二、正常邏輯是原生方式運行,有問題只是局部切換到解析執行,因此性能比較好。
三、會致使代碼段增大,增大正比於注入的類的數量。
四、這種方式每每難以新增功能。
第二種方式是接下來討論的重點,方便起見,咱們稱之爲「熱修復」,熱修復最先的成熟方案是xLua提供,通過兩年來的使用已經逐漸被接受,tolua#後來也加入了這功能,也有一些網友基於ILRuntime作了熱修復功能。app

InjectFix是什麼?
InjectFix就是一個熱修復的實現。那它和其它熱修復方案又有什麼不一樣呢?
設想這麼個場景,咱們有一個一千行代碼的函數,其中有一行有問題,咱們須要修復它。
若是用xLua,須要用lua去從新實現一遍這個函數,工做量大。而基於ILRuntime的熱修復,因爲其補丁是另外一個程序集,它沒法直接訪問原類的私有成員,因此那999行正常代碼通常也不能直接使用,須要作較多修改。
而InjectFix不須要用lua,也不須要像ILRuntime熱修復那樣另外建一個工程把那一千行邏輯重實現。只須要在Unity原工程直接改掉這行代碼,而後標註這函數要更新便可。
不只如此,InjectFix還有其它優點:
運行時很是小巧,僅100K左右,比各lua方案,ILRuntime都要小不少,並且不依賴第三方庫,純C#實現。
支持每一個遊戲生成一份本身私有的補丁格式,私有的指令定義。這樣相比通用的lua原代碼,lua字節碼,clr程序集都更安全些。
支持Assembly-CSharp.dll以外的dll的修復。
免代碼生成,更乾淨。
它也有缺點,不支持新增類,也不支持在已有類新增字段,修bug仍是夠用的,但難以經過熱更爲遊戲增長新功能。InjectFix就一個純粹的修bug工具而已。ide

黑科技
因爲InjectFix支持重複加載補丁,新加載補丁會自動覆蓋上一個,這特性能夠用來實現真機代碼邏輯實時修改。
(嵌入視頻,視頻地址:https://v.qq.com/x/page/v0924...函數

蘋果政策合規性
各熱更方案羣的問的頻率最高的問題之一:這方案會不會致使我遊戲蘋果審覈不經過。
讓咱們看看蘋果的熱更新條款:工具

clipboard.png

能夠看到最新條款容許下載代碼解析執行,但前提是不能經過新增特性和功能來把程序改得(和審覈時相比)面目全非。再看看一般被拒時的理由中的Guideline 2.5.2裏的一句:Your app, extension, or linked framework appears to contain code designed explicitly with the capability to change your app’s behavior or functionality after App Review approval。
有「新增特性和功能」能力的熱更新方案的尷尬之處在於有「改得面目全非」的能力。而InjectFix從它提供的能力(只能修改已有函數)來看,並不具有「新增特性和功能」的能力,這原本是弱點,放在這裏卻成爲合規性的保證了。性能

基本原理
InjectFix項目的研發挺曲折的。InjectFix和xLua是同一個做者,也是本文筆者,當時xLua開源後,不斷有人提但願提供個C#轉lua的工具,而深刻研究以爲實現個il虛擬機工做量還更小,這樣還能避免lua的一些gc問題。
決定要作il虛擬機後,也曾想過直接使用ILRuntime,評估後以爲不太符合咱們的使用場景:ILRuntime並不能實現和原生代碼的函數級別配合,這是咱們能實現原工程直接改Bug的關鍵;ILRuntime運行時部分依賴cecil,除了資源佔用大以外,還容易和unity自帶或者某些插件的cecil衝突;加載的是標準的程序集在安全性方面也比較堪憂。雖然說這些均可以改,但修改的工做量也挺大的,還不如本身寫一個。
InjectFix實現bug修復主要靠這兩部分:虛擬機負責新邏輯的解析執行;注入代碼負責把調用重定向到虛擬機;下面咱們結合最簡單的例子介紹下這兩部分。ui

虛擬機
關鍵部分用幾行僞碼就能夠描述清楚:
clipboard.pngthis

導讀
一、pc指向的是函數的第一條指令;
二、argumentBase指向的是第一個參數;
三、while+switch一條條指令往下執行,具體指令的操做在case那;

argumentBase指向的是求值棧該函數的棧幀,棧幀是這麼安排的:

clipboard.png

先放參數(若是有的話),再放本地變量(若是有的話),接着是臨時區域,當函數返回時彈掉全部東西,若是有返回值就放到棧頂(函數執行前參數0的位置)。

用以下一個靜態方法來演示下虛擬機怎麼運行:
public static float Add(float a, float b)
{

return a - b;

}
這函數編譯後是這四條指令
clipboard.png
Add函數的執行過程
一、指令1把參數0 Push到棧頂;
二、指令2把參數1 Push到棧頂;
三、指令3把兩個棧頂元素彈出(Pop)並相加,結果Push到棧頂;
四、指令4把棧頂拷貝到參數0的位置,清理棧,退出循環,Execute函數執行結束。

代碼注入
上面的Add函數注入後是這樣的
public static float Add(float a, float b)
{

if (WrappersManagerImpl.IsPatched(92))
{
    return WrappersManagerImpl.GetPatch(92).__Gen_Wrap_25(a, b);
}
return a - b;

}
比較簡單,發現這函數有patch的話,就重定向到虛擬機。
而__Gen_Wrap_25是個適配器函數,賦值把參數壓棧,調用虛擬機的Execute函數,並把結果返回。__Gen_Wrap_25的實現以下:
public float __Gen_Wrap_25(float P0, float P1)
{

Call call = Call.Begin();
call.PushSingle(P0);
call.PushSingle(P1);
this.virtualMachine.Execute(this.methodId, ref call, 2, 0);
return call.GetSingle(0);

}

PS:咱們的例子僅有三種指令,和這幾條指令無關的代碼所有簡化了,真正複雜得多,有興趣能夠看源碼瞭解。

關於開源
閉門造車很難作出好項目。須要用心聆聽,根據反饋不斷的改進本身。而開源,可以聽到更多的聲音,也能更好的改進這個項目。

總結下InjectFix使用簡單,小巧,合規且安全。即便你不打算用它來更新線上版本,只要你程序有原生部分,接入也能必定程度上提升開發效率,沒什麼拒絕它的理由,是吧?

相關文章
相關標籤/搜索