C語(yǔ)言的runtime -- libffi

前言:

對(duì)于C語(yǔ)言來(lái)說(shuō),我們?cè)诤瘮?shù)調(diào)用前,需要明確的告訴編譯器這個(gè)函數(shù)的參數(shù)和返回值類(lèi)型是什么,函數(shù)才能正常執(zhí)行。

如此說(shuō)來(lái)動(dòng)態(tài)的調(diào)用一個(gè)C函數(shù)是不可能實(shí)現(xiàn)的。

因?yàn)槲覀冊(cè)诰幾g前,就要將遵循調(diào)用規(guī)則的函數(shù)調(diào)用寫(xiě)在需要調(diào)用的地方,然后通過(guò)編譯器編譯生成對(duì)應(yīng)的匯編代碼,將相應(yīng)的棧和寄存器狀態(tài)準(zhǔn)備好。

如果想在運(yùn)行時(shí)動(dòng)態(tài)去調(diào)用的話,將沒(méi)有人為我們做這一系列的處理。

所以我們需要解決這個(gè)問(wèn)題:當(dāng)我們?cè)谶\(yùn)行時(shí)動(dòng)態(tài)調(diào)用一個(gè)函數(shù)時(shí),自己要先將相應(yīng)棧和寄存器狀態(tài)準(zhǔn)備好,然后生成相應(yīng)的匯編指令。而 <mark>libffi</mark> 庫(kù)正好替我們解決了此難題。


什么是FFI?

FFI(Foreign Function Interface)允許以一種語(yǔ)言編寫(xiě)的代碼調(diào)用另一種語(yǔ)言的代碼,而libffi庫(kù)提供了最底層的、與架構(gòu)相關(guān)的、完整的FFI。libffi的作用就相當(dāng)于編譯器,它為多種調(diào)用規(guī)則提供了一系列高級(jí)語(yǔ)言編程接口,然后通過(guò)相應(yīng)接口完成函數(shù)調(diào)用,底層會(huì)根據(jù)對(duì)應(yīng)的規(guī)則,完成數(shù)據(jù)準(zhǔn)備,生成相應(yīng)的匯編指令代碼。

FFI 的核心API

(1)函數(shù)原型結(jié)構(gòu)體

typedef struct {
  ffi_abi abi;
  unsigned nargs;
  ffi_type **arg_types;
  ffi_type *rtype;
  unsigned bytes;
  unsigned flags;
#ifdef FFI_EXTRA_CIF_FIELDS
  FFI_EXTRA_CIF_FIELDS;
#endif
} ffi_cif;

(2)封裝函數(shù)原型

/* 封裝函數(shù)原型
        ffi_prep_cif returns a libffi status code, of type ffi_status. 
        This will be either FFI_OK if everything worked properly; 
        FFI_BAD_TYPEDEF if one of the ffi_type objects is incorrect; 
        or FFI_BAD_ABI if the abi parameter is invalid.
    */
        ffi_status ffi_prep_cif(ffi_cif *cif,
                    ffi_abi abi,                  //abi is the ABI to use; normally FFI_DEFAULT_ABI is what you want. Multiple ABIs for more information.
                    unsigned int nargs,           //nargs is the number of arguments that this function accepts. ‘libffi’ does not yet handle varargs functions; see Missing Features for more information.
                    ffi_type *rtype,              //rtype is a pointer to an ffi_type structure that describes the return type of the function. See Types.
                    ffi_type **atypes);           //argtypes is a vector of ffi_type pointers. argtypes must have nargs elements. If nargs is 0, this argument is ignored.

(3)函數(shù)對(duì)象的回調(diào)結(jié)構(gòu)體

typedef struct {
#if 0
  void *trampoline_table;
  void *trampoline_table_entry;
#else
  char tramp[FFI_TRAMPOLINE_SIZE];
#endif
  ffi_cif   *cif;
  void     (*fun)(ffi_cif*,void*,void**,void*);
  void      *user_data;
} ffi_closure

(4)函數(shù)回調(diào)對(duì)象的創(chuàng)建

int (* blockImp)(char *);  //聲明一個(gè)函數(shù)指針
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), &bound_puts);

(5)關(guān)聯(lián)函數(shù)原型與回調(diào)函數(shù)

ffi_status ffi_prep_closure_loc (ffi_closure *closure,  //閉包,一個(gè)ffi_closure對(duì)象
   ffi_cif *cif,  //函數(shù)原型
   void (*fun) (ffi_cif *cif, void *ret, void **args, void*user_data), //函數(shù)實(shí)體
   void *user_data, //函數(shù)上下文,函數(shù)實(shí)體實(shí)參
   void *codeloc)   //函數(shù)指針,指向函數(shù)實(shí)體

將函數(shù)的參數(shù)等數(shù)據(jù)信息與回調(diào)對(duì)象_closure關(guān)聯(lián)起來(lái),當(dāng)程序調(diào)用到此函數(shù)時(shí),也會(huì)執(zhí)行此回調(diào)地址的代碼,同時(shí)將獲得此函數(shù)的所有參數(shù)

調(diào)用:ffi_prep_closure_loc(closure, &cif, calCircleArea,stdout, bound_calCircleArea)

參數(shù)解析:

Prepare a closure function.

參數(shù) closure is the address of a ffi_closure object; this is the writable address returned by ffi_closure_alloc.

參數(shù) cif is the ffi_cif describing the function parameters.

參數(shù) user_data is an arbitrary datum that is passed, uninterpreted, to your closure function.

參數(shù) codeloc is the executable address returned by ffi_closure_alloc.

函數(shù)實(shí)體 fun is the function which will be called when the closure is invoked. It is called with the arguments:

函數(shù)實(shí)體參數(shù) cif
The ffi_cif passed to ffi_prep_closure_loc. 
函數(shù)實(shí)體參數(shù) ret
A pointer to the memory used for the function's return value. fun must fill this, unless the function is declared as returning void. 
函數(shù)實(shí)體參數(shù) args
A vector of pointers to memory holding the arguments to the function. 
函數(shù)實(shí)體參數(shù) user_data
The same user_data that was passed to ffi_prep_closure_loc.
ffi_prep_closure_loc will return FFI_OK if everything went ok, and something else on error.

After calling ffi_prep_closure_loc, you can cast codeloc to the appropriate pointer-to-function type.

You may see old code referring to ffi_prep_closure. This function is deprecated, as it cannot handle the need for separate writable and executable addresses.

(6)釋放函數(shù)回調(diào)

 ffi_closure_free(closure);   //釋放閉包

如何動(dòng)態(tài)調(diào)用 C 函數(shù)

(1)步驟:

1. 準(zhǔn)備一個(gè)函數(shù)實(shí)體 
2. 聲明一個(gè)函數(shù)指針 
3. 根據(jù)函數(shù)參數(shù)個(gè)數(shù)/參數(shù)及返回值類(lèi)型生成一個(gè)函數(shù)原型 
4. 創(chuàng)建一個(gè)ffi_closure對(duì)象,并用其將函數(shù)原型、函數(shù)實(shí)體、函數(shù)上下文、函數(shù)指針關(guān)聯(lián)起來(lái) 
5. 釋放closure

(2)示例代碼:

#include <stdio.h>
#include "libffi/ffi.h"
// 函數(shù)實(shí)體
void calCircleArea(ffi_cif *cif,
                  float *ret,
                  void *args[],
                  FILE *stream) {
    float pi = 3.14;
    float r = **(float **)args[0];
    float area = pi * r * r;
    *ret = area;
    printf("我是那個(gè)要被動(dòng)態(tài)調(diào)用的函數(shù)\n area:%.2f\n *ret = %.2f",area,*ret);
}


int main(int argc, const char * argv[]) {

    ///函數(shù)原型
    ffi_cif cif;
    ///參數(shù)
    ffi_type *args[1];
    ///回調(diào)閉包
    ffi_closure *closure;
    ///聲明一個(gè)函數(shù)指針,通過(guò)此指針動(dòng)態(tài)調(diào)用已準(zhǔn)備好的函數(shù)
    float (*bound_calCircleArea)(float *);
    float rc = 0;
    
    /* Allocate closure and bound_calCircleArea */  //創(chuàng)建closure
    closure = ffi_closure_alloc(sizeof(ffi_closure), &bound_calCircleArea);
    
    if (closure) {
        /* Initialize the argument info vectors */
        args[0] = &ffi_type_pointer;
        /* Initialize the cif */  //生成函數(shù)原型 &ffi_type_float:返回值類(lèi)型
        if (ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_float, args) == FFI_OK) {
            /* Initialize the closure, setting stream to stdout */
                // 通過(guò) ffi_closure 把 函數(shù)原型_cifPtr / 函數(shù)實(shí)體JPBlockInterpreter / 上下文對(duì)象self / 函數(shù)指針blockImp 關(guān)聯(lián)起來(lái)
            if (ffi_prep_closure_loc(closure, &cif, calCircleArea,stdout, bound_calCircleArea) == FFI_OK) {
                    float r = 10.0;
                    //當(dāng)執(zhí)行了bound_calCircleArea函數(shù)時(shí),獲得所有輸入?yún)?shù), 后續(xù)將執(zhí)行calCircleArea。
                    //動(dòng)態(tài)調(diào)用calCircleArea
                    rc = bound_calCircleArea(&r);
                    printf("rc = %.2f\n",rc);
                }
            }
        }
    /* Deallocate both closure, and bound_calCircleArea */
    ffi_closure_free(closure);   //釋放閉包
    return 0;
}

由上可知:如果我們利用好ffi_prep_closure_loc 的第四個(gè)參數(shù) user_data,用其傳入我們想要的函數(shù)實(shí)現(xiàn),將函數(shù)實(shí)體變成一個(gè)通用的函數(shù)實(shí)體,然后將函數(shù)指針改為void*,通過(guò)結(jié)構(gòu)體創(chuàng)建一個(gè)block保存函數(shù)指針并返回,那么我們就可以實(shí)現(xiàn)JS調(diào)用含有任意類(lèi)型block參數(shù)的OC方法了。

總結(jié)

根據(jù)以上的思想:

我們可以將 ffi_closure 關(guān)聯(lián)的指針替換原方法的IMP,

當(dāng)對(duì)象收到該方法的消息時(shí) objc_msgSend(id self, SEL sel, ...) ,

將最終執(zhí)行自定義函數(shù) void ffifunction(fficif *cif, void *ret, void **args, void *userdata) 。

而實(shí)現(xiàn)這一切的主要工作是:設(shè)計(jì)可行的結(jié)構(gòu),存儲(chǔ)類(lèi)的多個(gè)hook信息;

根據(jù)包含不同參數(shù)的方法和切面block,生成包含匹配 ffi_type 的cif;

替換類(lèi)某個(gè)方法的實(shí)現(xiàn)為 ffi_closure 關(guān)聯(lián)的imp,記錄hook;

在 ffi_function 里,根據(jù)獲得的參數(shù),動(dòng)態(tài)調(diào)用原始imp和block。

后續(xù)我們可以利用這個(gè)庫(kù),結(jié)合lexyacc將OC或任意的語(yǔ)言編譯成C代碼去執(zhí)行,從而實(shí)現(xiàn)hotfix。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容