前言:
對(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é)合lex 及 yacc將OC或任意的語(yǔ)言編譯成C代碼去執(zhí)行,從而實(shí)現(xiàn)hotfix。