誰偷了我的熱更新?Mono,JIT,IOS

來源:互聯網
上載者:User

標籤:

前言

由於匹夫本人是做遊戲開發工作的,所以平時也會加一些玩家的群。而一些困擾玩家的問題,同樣也困擾著我們這些手機遊戲開發人員。這不最近匹夫看自己加的一些群,常常會有人問為啥這個遊戲一更新就要重新下載,而不能遊戲內更新呢?作為遊戲開發人員,或者說Unity3D程式猿,我們都清楚Unity3D不支援熱更新,甚至於在IOS平台上產生新的代碼都會導致遊戲報錯崩潰(匹夫之所以在此處強調產生新的代碼這幾個字,就是提醒各位不要混淆Reflection.Emit和反射)。但我們是否和普通的玩家一樣,看到的僅僅是“不能”的現象,而不瞭解“不能”背後的原因呢?那今天小匹夫就拋磚引玉,寫寫自己對這個問題的想法~~聊聊到底是誰偷了玩家的熱更新。

從一個常見的報錯說起

不知道各位看官中的U3D程式猿在開發IOS版本的時候是否也曾經碰到過這樣的報錯:

ExecutionEngineException: Attempting to JIT compile method ‘XXXX‘ while running with --aot-only.

這個報錯的意思很明確,說的也很具體,翻譯成中文的大意就是在使用--aot-only這個選項的前提下,又試圖去使用JIT編譯器編譯XXX方法。

那麼不知道是否會有看官覺得這個問題興許是程式跑在IOS平台上時,不小心犯了IOS的忌諱,使用了JIT(假設此時我們還不知道為何使用JIT是IOS的忌諱)去動態編譯代碼導致的IOS的報錯呢?

答案是否定的。

又或者更進一步,看到“ExecutionEngineException”,似乎和IOS平台的異常沒什麼太大的關聯,那就把責任定位在Unity3D的引擎上好了。一定是遊戲引擎此時不支援JIT編譯了。

也不全對,不過離真相很近了。

各位想想,能涉及到編譯的被懷疑的對象還能有誰呢?

好了,不賣關子了。這個異常其實是Mono的異常。換言之,Unity3D使用了Mono來編譯,所以Unity3D的嫌疑被排除。而IOS並沒有因為產生或者運行動態產生的程式碼而報錯,換言之這個異常發生在觸發IOS異常之前,所以說Mono在IOS平台上進行JIT編譯之前就先一步讓程式崩潰了。

說到這裡,就繞不過Mono是如何編譯代碼這個話題了。如果我們去Mono的託管頁面看它的源碼,就可以簡單對它的目錄結構做一個簡單的分析,匹夫就簡單總結一下Mono編譯部分的目錄結構:

docs 關於mono運行時的文檔,在這裡你可以看到例如編譯的說明文檔,還有小匹夫很看重的Mono運行時的API列表
data 一些Mono運行時的設定檔
mono Mono運行時的核心,也是本文關於Mono部分的焦點,簡單介紹一下它的幾個比較重要的子目錄
    metadata 實現了處理metadata的邏輯
    mini JIT編譯器(重點)
    dis 可執行CIL代碼的反編譯器
    cil CIL指令的XML配置,在這裡你可以看到CIL的指令都是什麼
    arch 不同體繫結構的特定部分。
mcs C#源碼編譯器(C#---->CIL)
  mcs    
    mcs 源碼編譯器
    jay 剖析器的產生程式

好啦,具體到咱們要聊的JIT編譯,我們需要看的就是mono目錄下的mini檔案夾中的檔案了,這個檔案夾中的.c檔案們實現了JIT編譯。

這個目錄的結構截個圖都截不全,因為檔案太多:

不過這裡小匹夫想來一個倒敘,也就是先直接定位這個報錯“ExecutionEngineException: Attempting to JIT compile method ‘XXXX‘ while running with --aot-only.”的位置,然後再探明它究竟是如何被觸發的。

這樣,我們就來到了mono的JIT編譯器目錄mini下的mini.c檔案。這裡就是JIT的邏輯實現。而那段報錯呢?在mini.c檔案中是這樣處理的:

if (mono_aot_only) {    char *fullname = mono_method_full_name (method, TRUE);    char *msg = g_strdup_printf ("Attempting to JIT compile method ‘%s‘ while     running with --aot-only. See http://docs.xamarin.com/ios/about/limitations for more information.\n", fullname);    *jit_ex = mono_get_exception_execution_engine (msg);    g_free (fullname);    g_free (msg);    return NULL;}

mono_aot_only?沒錯,只要我們設定mono的編譯模式為full-aot(比如打IOS安裝包的時候),則在運行時試圖使用JIT編譯時間,mono自身的JIT編譯器就會禁止這種行為進而報告這個異常。JIT編譯的過程根本還沒開始,就被自己扼殺了。

那麼JIT究竟是什麼洪水猛獸?為何IOS這麼忌諱它呢?那就不得不聊聊JIT本尊了。

美麗的JIT因何美麗

名如其特點,JIT——just in time,即時編譯。

什嗎?這就是匹夫你要告訴大傢伙的?這不是人人都知道的嘛?而且網上一搜也全都是JIT=just in time了事。好吧好吧,匹夫知錯啦。那就認真的定義一下JIT:

一個程式在它啟動並執行時候建立並且運行了全新的代碼,而並非那些最初作為這個程式的一部分儲存在硬碟上的固有的代碼。就叫JIT。

幾個點:

  1. 程式需要運行
  2. 產生的程式碼是新的代碼,並非作為原始程式的一部分被存在磁碟上的那些代碼
  3. 不光產生代碼,還要運行。

需要提醒的是第三點,也就是JIT不光是產生新的代碼,它還會運行新產生的程式碼。之後我們會就這個話題展開。不過在之前匹夫還是要解釋一下,為何稱JIT是美麗的。

舉個例子:

比如你某一天突然穿越成為了一個優秀的學者(好吧好吧,這個貌似不是必須要穿越),現在要去一個語言不通的國家做一系列講座。面對語言不通的窘境,如何才不出醜呢?

匹夫有三條方案:

  1. 在家的時候僱人把所有的講稿全部翻譯一遍。這是最省事的做法,但卻缺乏靈活性。比如臨時有更好的話題或者點子,也只能恨自己沒有好好學外語了。
  2. 雇一個翻譯和你一起出發,你說啥他就翻譯成啥。這樣就不存在靈活性的問題,因為完全是同步的。不過缺點同樣明顯,翻譯要翻譯很多話,包括你重複說的話。所以需要的時間要遠遠高於方案1。
  3. 雇一個翻譯和你一起出發,但不是你說啥他就翻譯啥,而是記錄翻譯過的話,遇到曾經翻譯過的就不會再翻譯了。你自己就可以根據之前的翻譯記錄和別人交流了。

看完這三條方案,各位看官心中更喜歡哪個呢?

匹夫個人的答案是方案3,因為這便是JIT的道。所以說JIT的美麗,就在於即保留了對代碼最佳化的靈活性,也兼具對熱點代碼進行重複利用的功能。

類比一下JIT的過程

JIT這麼好,那它是如何?既產生新代碼,又能運行新代碼的呢?

編譯器如何產生代碼很多文章都有涉及,匹夫就不多在此著墨了。下面我就著重和各位聊聊,如何運行新產生的程式碼。

首先我們要知道產生的所謂機器碼到底是神馬東西。一行看上去只是處理幾個數位代碼,蘊含著的就是機器碼。

unsigned char[] macCode = {0x48, 0x8b, 0x07};

macCode對應的彙編指令就是:

mov    (%rdi),%rax

其實可以看出機器碼就是位元流,所以將它載入進記憶體並不困難。而問題是應該如何執行。

好啦。下面我們就類比一下執行新產生的機器碼的過程。假設JIT已經為我們編譯出了新的機器碼,是一個求和函數的機器碼:

long add(long num) {  return num + 1;}//對應的機器碼
0x48, 0x83, 0xc0, 0x01, 0xc3

首先,動態在記憶體上建立函數之前,我們需要在記憶體上分配空間。具體到類比動態建立函數,其實就是將對應的機器碼映射到記憶體空間中。這裡我們使用c語言做實驗,利用mmap函數來實現這一點。

標頭檔 #include <unistd.h>    #include <sys/mman.h>
定義函數 void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize)
函數說明 mmap()用來將某個檔案內容映射到記憶體中,對該記憶體地區的存取即是直接對該檔案內容的讀寫。

 

 因為我們想要把已經是位元流的“求和函數”在記憶體中建立出來,同時還要運行它。所以mmap有幾個參數需要注意一下。

代表映射地區的保護方式,有下列組合:

    PROT_EXEC  映射地區可被執行;
    PROT_READ  映射地區可被讀取;
    PROT_WRITE  映射地區可被寫入;

#include<stdio.h>                                                                                            
#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/mman.h>//分配記憶體void* create_space(size_t size) { void* ptr = mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); return ptr;}

這樣我們就獲得了一塊分配給我們存放代碼的空間。下一步就是實現一個方法將機器碼,也就是位元流拷貝到分配給我們的那塊空間上去。使用memcpy即可。

//在記憶體中建立函數void copy_code_2_space(unsigned char* m) {    unsigned char macCode[] = {        0x48, 0x83, 0xc0, 0x01,        c3     };    memcpy(m, macCode, sizeof(macCode));}

然後我們在寫一個main函數來處理整個邏輯:

#include<stdio.h>                                                                                            #include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/mman.h>//分配記憶體void* create_space(size_t size) {    void* ptr = mmap(0, size,            PROT_READ | PROT_WRITE | PROT_EXEC,            MAP_PRIVATE | MAP_ANON,            -1, 0);       return ptr;}//在記憶體中建立函數void copy_code_2_space(unsigned char* addr) {    unsigned char macCode[] = {        0x48, 0x83, 0xc0, 0x01,        0xc3     };    memcpy(addr, macCode, sizeof(macCode));}//main 聲明一個函數指標TestFun用來指向我們的求和函數在記憶體中的地址int main(int argc, char** argv) {                                                                                                  const size_t SIZE = 1024;    typedef long (*TestFun)(long);    void* addr = create_space(SIZE);    copy_code_2_space(addr);    TestFun test = addr;    int result = test(1);    printf("result = %d\n", result);     return 0;}

編譯並且運行看一下結果:

//編譯gcc testFun.c//運行./a.out 1 

留給我們的難題

OK,到此為止,一切都很順利。這個例子類比了動態代碼在記憶體上的產生,和之後的運行。似乎沒有什麼問題呀?可不知道各位是否忽略了一個前提?那就是我們為這塊地區設定的保護模式可是:可讀,可寫,可執行檔啊!如果沒有記憶體可讀寫可執行檔許可權,我們的實驗還能成功嗎?

讓我們把create_space函數中的“可執行”PROT_EXEC許可權去掉,看看結果會是怎樣的一番景象。

修改代碼,同時將剛才產生的可執行檔a.out刪除重建運行。

rm a.outvim testFun.cgcc testFun.c./a.out 1

結果。。。報錯了!

小結論

所以,IOS並非把JIT禁止了。或者換個句式講,IOS封了記憶體(或者堆)的可執行許可權,相當於變相的封鎖了JIT這種編譯方式。原因呢?且聽下回分解~~~~~誰偷了我的熱更新?IOS和安全性漏洞的賭注

 

如果各位看官覺得文章寫得還好,那麼就容小匹夫跪求各位給點個“推薦”,謝啦~

裝模作樣的 聲明一下:本博文章若非特殊註明皆為原創,若需轉載請保留原文連結(http://www.cnblogs.com/murongxiaopifu/p/4278947.html )及作者資訊慕容小匹夫

誰偷了我的熱更新?Mono,JIT,IOS

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.