02.29 UnicornVM-全平臺全架構正式發佈


UnicornVM-全平臺全架構正式發佈

這個月初,我們發佈了適用於Android armeabi-v7a的UnicornVM,月中陸陸續續發佈了適用於Android arm64-v8a、iOS arm64的版本。這三個版本原計劃是3、4、5月逐步發佈的,但是特殊時期我們需要響應國家號召居家不出門不添亂,所以咱們只有老實居家使出洪荒之力加油搞開發寫代碼了。結果就是,我們整整提前了2個月完成UnicornVM的測試和發佈,可喜可賀。

今天,我們修復了arm64版本若干崩潰的問題,也達到了比較好的穩定性,所以arm32/arm64兩個架構、Android/iOS兩個平臺目前都處於穩定版本的狀態。

從年前1月份提出UnicornVM這個新產品概念到現在已經有2個月的時間了,可能很多朋友還不是很瞭解這個框架到底有什麼作用,可以拿來幹嘛?那麼,今天這篇文章,我們將詳細介紹一下它的結構和用法。

首先從字面UnicornVM上理解,拆成兩部分Unicorn、VM。其中Unicorn是由越南大佬Nguyen Anh Quynh開發的虛擬CPU框架,底層基於Qemu,而VM是指Virtual Machine也即虛擬機。虛擬機一般有兩種解釋,一種是指系統虛擬機,比如VMWare、VirtualBox,一種是指軟件虛擬機,比如Python解釋器,此處我們的UnicornVM指第二種情況。那麼UnicornVM與Unicorn區別在哪裡呢?

我們都知道,有意義的程序要正常運行起來,需要代碼、數據兩部分。把代碼、數據這兩者統一起來看其實它們都是內存,即便是存放在文件系統裡面的數據也是需要加載到內存裡面才能訪問。但是由於Unicorn是基於Qemu開發而來的,因此它自帶虛擬內存管理。所以我們想要正常讓它模擬執行函數代碼時,還需要手動處理內存映射的問題,這為我們在某些場景使用帶來了各種不方便。

所以,我們可以直接讓Unicorn與程序的運行內存交互,而拋棄它的Qemu虛擬內存嗎?這其實就是UnicornVM解決的核心問題。你只需要丟一個合法的函數地址給它,然後它就可以達到像直接執行這個函數一樣的效果。同時,由於這是一個虛擬機架構,所以我們可以在指令級別控制這個函數的執行過程,你可以理解成我們把arm/arm64指令集腳本化了,而UnicornVM就是這個腳本化系統的解釋器,就像Python解釋器、JavaScript解釋器執行它們各自的字節碼一樣。

目前,UnicornVM框架導出了2個API以及若干數據結構,如下:

<code>// run function 'fn' on our VCPU with 'ctx'// return value is x0VCAPI long vc_run_interp(const void *fn, const vc_context_t *ctx);/<code>
<code>// make a wrapper for function 'fn' with 'usrctx','callback'// return value is a new function pointer which will run under our VCPU// you can replace this pointer to target's function pointer// like C++-Vtable/Script-Native-Bridge// if return null, you should check errnoVCAPI const void *vc_make_callee(const void *fn, void *usrctx,                                 fn_vc_callback_t callback);/<code>

vc_run_interp表示直接將函數跑在虛擬機解釋器裡面,第一個參數fn代表要執行的函數地址,第二個參數ctx是執行這個函數時候由調用者提供的上下文,包括寄存器參數、回調函數等信息。

vc_make_callee表示對指定的函數生成一個包裝函數,而這個包裝函數就跑在虛擬機解釋器裡面,第一個參數fn代表要執行的函數地址,第二個參數usrctx是調用者自定義的上下文,第三個參數callback是解釋器的回調函數。

我們重點看一下解釋器回調相關的結構,它的定義如下:

<code>// callback prototyptypedef vc_callback_return_t (*fn_vc_callback_t)(vc_callback_args_t *args);/<code>
<code>// callback return typetypedef enum vc_callback_return_t {  cbret_continue,    // let interp continue  cbret_processed,   // already processed by callback implementation  cbret_recursive,   // interp this function recursively  cbret_directcall,  // call this function directly} vc_callback_return_t;/<code>
<code>// opcode type for callback argstypedef enum vc_optype_t {  vcop_read,    // memory read  vcop_write,   // memory write  vcop_call,    // function call  vcop_return,  // function return  vcop_svc,     // arch syscall} vc_optype_t;// callback argstypedef struct vc_callback_args_t {  // your own context passed for vc_run_interp/vc_make_callee  const void *usrctx;  // arm64 execution context  vc_arm64ctx_t *arm64ctx;  // current opcode  vc_optype_t op;  union {    // for vcop_read/vcop_write    struct {      const void *src;      void *dst;      int byte;    } rw;    // for vcop_call    struct {      const void *callee;    } call;    // for vcop_return    struct {      const void *hitaddr;  // which address hit return    } ret;    // for vcop_svc    struct {      int sysno;  // syscall number    } svc;  } info;} vc_callback_args_t;/<code>

它的作用是虛擬機解釋器在執行到內存讀寫、函數調用/返回、系統調用等指令時,調用由用戶指定的這個回調,從而達到用戶可控的目的。回調函數的返回值用於告訴虛擬機解釋器該如何處理本條指令,比如遞歸調用、忽略、默認處理等,如果調用者已經處理了這條指令,則可以返回cbret_processed。我們看一下sample裡面的代碼片段,如下:

<code>// run interpretee directlyint vrun_print_message(const char *reason, const char **argv, FILE *cblogfp, fn_vc_callback_t cb) {  vc_context_t ctx;  memset(&ctx, 0, sizeof(ctx));  ctx.usrctx = cblogfp;  ctx.callback = cb;  ctx.regctx.r[0].p = reason;  ctx.regctx.r[1].p = (void *)argv;  return (int)(long)vc_run_interp((void *)print_message, &ctx);}/<code>

這是sample裡面的例子代碼,將函數print_message直接跑在解釋器下面。

<code>// run interpretee with a wrapperint wrapper_print_message(const char *reason, const char **argv, FILE *cblogfp,                          fn_vc_callback_t cb) {  const void *fnptr = vc_make_callee((void *)print_message, cblogfp, cb);  return ((int (*)(const char *, const char **))fnptr)(reason, argv);}/<code>

這是sample裡面的例子代碼,將函數print_message生成一個代理函數指針,然後直接調用這個指針,代理函數將會執行在虛擬機解釋器裡面。

好了,看到這裡,我想朋友們已經學會並理解了UnicornVM的核心理念和功能,如果你在使用它的過程中有任何疑問、建議、問題,歡迎反饋,麼麼噠。


分享到:


相關文章: