如何使用libffi庫實現(xiàn)OC方法替換和調(diào)用 OC 函數(shù)

最近在學(xué)習(xí)JS語言,走大前端的路子,學(xué)了兩天后,感覺要小試牛刀。突然想到JSPatch中JSPatch.js是JS寫的,正好之前有一些細(xì)節(jié)還沒完全了解,就決定重看了一遍,不看不知道,一看發(fā)現(xiàn)JSPatch里比我一年前看的升級了好多,并且被JPBlock的實現(xiàn)方式驚呆了~
結(jié)合之前逆向滴滴的DYC的實現(xiàn)方式,發(fā)現(xiàn)利用滴滴的JS腳本替換的函數(shù),函數(shù)有不同的地址(如果用JSPatch的實現(xiàn)方式,被替換的函數(shù)應(yīng)該有相同的函數(shù)地址~)。
然后就突發(fā)其想,如何動態(tài)的給OC函數(shù)綁定替換函數(shù),而不利用消息轉(zhuǎn)發(fā)的方式。
有了這么多的想法,就差實踐了...
由于之前完全不了解libffi,遂在github上搜索相關(guān)的技術(shù)資料,但是發(fā)現(xiàn)能參考的文檔比較少,不過還是發(fā)現(xiàn)了一個有100多stars的工程:
https://github.com/mikeash/MABlockClosure
原作者利用自定義的Block結(jié)構(gòu)體模擬OC的block結(jié)構(gòu),進(jìn)而獲取到block的函數(shù)簽名。
舉個例子:

id block = ^(int x, int y) { return x + y; };
    closure = [[MABlockClosure alloc] initWithBlock: block];
    int ret = ((int (*)(int, int))[closure fptr])(5, 10);
    NSLog(@"%d", ret);

block的函數(shù)簽名如下:
"i12@?0i4i8"
其中i12中的i代表返回值類型是int,12代碼這個block參數(shù)的總長度是12字節(jié)(不確定,感覺是這個意思),@?0標(biāo)識block的第一參數(shù)類型是Block類型(類似函調(diào)調(diào)用的self),后面的0代表了第一個參數(shù)從函數(shù)調(diào)用棧偏移0字節(jié)開始,i4代表了第二個參數(shù)是int類型,其中4代碼表了參數(shù)的起始位置從從函數(shù)調(diào)用棧偏移4字節(jié)開始,i8代表了第三個參數(shù)是int類型,其中8參數(shù)的起始位置從從函數(shù)調(diào)用棧偏移8字節(jié)開始。
分析出OC數(shù)據(jù)類型信息后(并沒有使用類型偏移信息~,只用到了類型信息),根據(jù)每種類型生成對應(yīng)的ffi_type,解析過程如下:

- (ffi_type *)_ffiArgForEncode: (const char *)str
{
    #define SINT(type) do { \
        if(str[0] == @encode(type)[0]) \
        { \
           if(sizeof(type) == 1) \
               return &ffi_type_sint8; \
           else if(sizeof(type) == 2) \
               return &ffi_type_sint16; \
           else if(sizeof(type) == 4) \
               return &ffi_type_sint32; \
           else if(sizeof(type) == 8) \
               return &ffi_type_sint64; \
           else \
           { \
               NSLog(@"Unknown size for type %s", #type); \
               abort(); \
           } \
        } \
    } while(0)
    
    #define UINT(type) do { \
        if(str[0] == @encode(type)[0]) \
        { \
           if(sizeof(type) == 1) \
               return &ffi_type_uint8; \
           else if(sizeof(type) == 2) \
               return &ffi_type_uint16; \
           else if(sizeof(type) == 4) \
               return &ffi_type_uint32; \
           else if(sizeof(type) == 8) \
               return &ffi_type_uint64; \
           else \
           { \
               NSLog(@"Unknown size for type %s", #type); \
               abort(); \
           } \
        } \
    } while(0)
    
    #define INT(type) do { \
        SINT(type); \
        UINT(unsigned type); \
    } while(0)
    
    #define COND(type, name) do { \
        if(str[0] == @encode(type)[0]) \
            return &ffi_type_ ## name; \
    } while(0)
    
    #define PTR(type) COND(type, pointer)
    
    #define STRUCT(structType, ...) do { \
        if(strncmp(str, @encode(structType), strlen(@encode(structType))) == 0) \
        { \
           ffi_type *elementsLocal[] = { __VA_ARGS__, NULL }; \
           ffi_type **elements = [self _allocate: sizeof(elementsLocal)]; \
           memcpy(elements, elementsLocal, sizeof(elementsLocal)); \
            \
           ffi_type *structType = [self _allocate: sizeof(*structType)]; \
           structType->type = FFI_TYPE_STRUCT; \
           structType->elements = elements; \
           return structType; \
        } \
    } while(0)
    
    SINT(_Bool);
    SINT(signed char);
    UINT(unsigned char);
    INT(short);
    INT(int);
    INT(long);
    INT(long long);
    
    PTR(id);
    PTR(Class);
    PTR(SEL);
    PTR(void *);
    PTR(char *);
    PTR(void (*)(void));
    
    COND(float, float);
    COND(double, double);
    
    COND(void, void);
    
    ffi_type *CGFloatFFI = sizeof(CGFloat) == sizeof(float) ? &ffi_type_float : &ffi_type_double;
    STRUCT(CGRect, CGFloatFFI, CGFloatFFI, CGFloatFFI, CGFloatFFI);
    STRUCT(CGPoint, CGFloatFFI, CGFloatFFI);
    STRUCT(CGSize, CGFloatFFI, CGFloatFFI);
    
#if !TARGET_OS_IPHONE
    STRUCT(NSRect, CGFloatFFI, CGFloatFFI, CGFloatFFI, CGFloatFFI);
    STRUCT(NSPoint, CGFloatFFI, CGFloatFFI);
    STRUCT(NSSize, CGFloatFFI, CGFloatFFI);
#endif
    
    NSLog(@"Unknown encode string %s", str);
    abort();
}

準(zhǔn)備就緒后

ffi_status status = ffi_prep_closure_loc(_closure, &_closureCIF, BlockClosure, self, _closureFptr);

根據(jù)類型信息(存儲在_closureCIF中),生成_closureFptr函數(shù)指針,函數(shù)指針的回調(diào)是BlockClosure方法。
基于以上的思路,既然OC 的 Block可以通過函數(shù)簽名實現(xiàn),那么OC中普通的函數(shù)也具有函數(shù)簽名,能否移植過去呢?帶著這種想法,我們準(zhǔn)備進(jìn)行進(jìn)一步的嘗試。
首先創(chuàng)建測試類MyObject

@interface MyObject : NSObject
- (CGRect)stringParam:(NSString *)str rectParam:(CGRect)rect charParam:(char)charValue intParam:(int)intValue pointParam:(CGPoint)point voidParam:(void *)voidP  endParam:(NSString *)end;
@end

@implementation MyObject
- (CGRect)stringParam:(NSString *)str rectParam:(CGRect)rect charParam:(char)charValue intParam:(int)intValue pointParam:(CGPoint)point voidParam:(void *)voidP  endParam:(NSString *)end{
    return CGRectMake(12, 13, 14, 15);
}
@end

編寫測試代碼:

Method method = class_getInstanceMethod(MyObject.class, @selector(stringParam:rectParam:charParam:intParam:pointParam:voidParam:endParam:));
    NSString *typeDescription = @((char *)method_getTypeEncoding(method));
    MABlockClosure *closure = [[MABlockClosure alloc] initWithSignature: typeDescription];
    class_replaceMethod(MyObject.class, @selector(stringParam:rectParam:charParam:intParam:pointParam:voidParam:endParam:), [closure fptr], [typeDescription UTF8String]);
    MyObject *object = [MyObject new];
    void *poiner = malloc(10);
    CGRect returnRect = [object stringParam:@"test1" rectParam:CGRectMake(12, 33, 54, 76)  charParam:'c' intParam:1986 pointParam:CGPointMake(-20, 25) voidParam:poiner endParam:@"hello world"];
    NSLog(@"returnRect 的值是%@",NSStringFromRect(returnRect));

首先,在MABlockClosure擴(kuò)展一個初始化方法,根據(jù)OC函數(shù)簽名初始化:

- (id)initWithSignature: (NSString *)signature {
    _allocations = [[NSMutableArray alloc] init];
    _signature = signature;
    _closure = AllocateClosure(&_closureFptr);
    [self _prepClosureCIF];
    [self _prepClosure];
    return self;
}

將BlockSig升級成FunctionSig

static const char *FunctionSig(id blockObj)
{
    if ([blockObj isKindOfClass:NSString.class]) {
        NSString *sign = blockObj;
        return [sign UTF8String];
    }
    
    
    struct Block *block = (void *)blockObj;
    struct BlockDescriptor *descriptor = block->descriptor;
    
    int copyDisposeFlag = 1 << 25;
    int signatureFlag = 1 << 30;
    
    assert(block->flags & signatureFlag);
    
    int index = 0;
    if(block->flags & copyDisposeFlag)
        index += 2;
    
    return descriptor->rest[index];
}

FunctionSig的作用是如果傳進(jìn)來的是字符串函數(shù)簽名,則直接使用,否則,解析block中的函數(shù)簽名。
重寫_prepClosureCIF函數(shù)

- (void)_prepClosureCIF
{
    _closureArgCount = [self _prepCIF: &_closureCIF withEncodeString: FunctionSig(_block?:_signature) skipArg: _block?YES:NO];
}

其中,skipArg是如果是block的類型,則忽略第一個參數(shù),如果是OC函數(shù),則不忽略第一個函數(shù)。(具體原因,需要進(jìn)一步研究...)
改造完畢后,我們嘗試在BlockClosure中獲取到OC中所有的參數(shù),并能設(shè)置返回值,就說明實現(xiàn)了目的。
我們看一下BlockClosure的實現(xiàn)

MABlockClosure *self = userdata;
    if (self->_signature.length > 0 ) {
        const char *str = [self->_signature UTF8String];
        int i = -1;
        while(str && *str)
        {
            const char *next = SizeAndAlignment(str, NULL, NULL, NULL);
            if(i >= 0)
                [self _ffiValueForEncode:str argumentPtr:args[i]];
            i++;
            str = next;
        }
        
        //!!!!!!!!這里先寫死,后面根據(jù)返回值的描述進(jìn)一步寫返回值的類型
        *(CGRect *)ret = CGRectMake(321, 123, 10, 10);
    }
    if (self->_block) {
        int count = self->_closureArgCount;
        void **innerArgs = malloc((count + 1) * sizeof(*innerArgs));
        innerArgs[0] = &self->_block;
        memcpy(innerArgs + 1, args, count * sizeof(*args));
        ffi_call(&self->_innerCIF, BlockImpl(self->_block), ret, innerArgs);
        free(innerArgs);
    }

第一個if語句是我加上去的,也是本文分析的重點。大致思路是根據(jù)獲取到函數(shù)簽名里的參數(shù)類型后,結(jié)合函數(shù)里傳遞進(jìn)來的參數(shù)數(shù)組,獲取參數(shù)值。因此核心部分在于分析 [self _ffiValueForEncode:str argumentPtr:args[i]]這個函數(shù)的實現(xiàn)。這個函數(shù),我具體的實現(xiàn)方式如下:

- (void *)_ffiValueForEncode: (const char *)str argumentPtr:(void**)argumentPtr
{
#define JP_BLOCK_PARAM_CASE(_typeString, _type, _selector) \
case _typeString: {                              \
_type returnValue = *(_type *)argumentPtr;                     \
param = [NSNumber _selector:returnValue];\
break; \
}
    
#define SINTV(_type,_selector) do { \
if(str[0] == @encode(_type)[0]) \
{ \
_type returnValue = *(_type *)argumentPtr; \
NSLog(@"參數(shù)值是%@",[NSNumber _selector:returnValue]);\
return [NSNumber _selector:returnValue]; \
} \
} while(0)

#define STRUCV(_type) do { \
if(strncmp(str, @encode(_type), strlen(@encode(_type))) == 0) \
{ \
_type returnValue = *(_type *)argumentPtr; \
NSLog(@"參數(shù)值是%@",NSStringFrom##_type(returnValue));\
return (NSString *)NSStringFrom##_type(returnValue); \
} \
} while(0)
    
#define PTROC(type) do { \
if(str[0] == @encode(type)[0]) \
{\
NSLog(@"OC對象參數(shù)值是%@",(__bridge id)(*(void**)argumentPtr));\
return (__bridge id)(*(void**)argumentPtr); \
}\
} while(0)


    

#define PTRC(type) do { \
if(str[0] == @encode(type)[0]) \
{\
NSLog(@"C指針地址是%p",*argumentPtr);\
return *argumentPtr; \
}\
} while(0)
    
    
    SINTV(_Bool, numberWithBool);
    SINTV(signed char, numberWithChar);
    SINTV(unsigned char, numberWithUnsignedChar);
    SINTV(short, numberWithShort);
    SINTV(int, numberWithInt);
    SINTV(long, numberWithLong);
    SINTV(long long, numberWithLongLong);
    
    PTROC(id);
    PTROC(Class);

    if(str[0] == @encode(SEL)[0]) {
        SEL returnValue = *(SEL *)argumentPtr;
        NSLog(@"OC對象參數(shù)值是%@",NSStringFromSelector(returnValue));\
        return NSStringFromSelector(returnValue); \
    }
    PTRC(void *);
    PTRC(char *);
    PTRC(void (*)(void));
    
    SINTV(float, numberWithFloat);
    SINTV(double, numberWithDouble);
    STRUCV(CGRect);
    STRUCV(CGPoint);
    STRUCV(CGSize);
    return (void *)0;
}

通過不同的類型,結(jié)合OC的打印方式,打印出參數(shù)的數(shù)值,本例中參數(shù)打印結(jié)果為:

BC46A297-5728-44F6-9A53-2C7CE779F8EA.png

因為只寫了一個測試方法,返回值目前是寫死的,見BlockClosure的注釋。
在控制臺打印的返回值如下:
returnRect 的值是{{321, 123}, {10, 10}}。
好了簡單分析到這里~
本文的Demo地址在:
https://github.com/springwinding/MABlockClosureEx

后面,我會將本文的實現(xiàn)方式移植到JSPatch中,重寫JPEngine中的
static void overrideMethod(Class cls, NSString *selectorName, JSValue *function, BOOL isClassMethod, const char *typeDescription)
和static void JPForwardInvocation(__unsafe_unretained id assignSlf, SEL selector, NSInvocation *invocation),看能否實現(xiàn)不通過消息轉(zhuǎn)發(fā),將OC的方法回掉給JS函數(shù)。


如何利用 libffi 調(diào)用 OC 函數(shù)
Demo 里新增調(diào)用的方式,沒有完善,只是提供一種調(diào)用思路,通過主動調(diào)用ffi_call 函數(shù),同時綁定 OC 的方法的 IMP 指針和調(diào)用參數(shù),詳見 Demo 中的例子,不多聊。
參考文獻(xiàn)如下:
http://www.cocoachina.com/ios/20161220/18400.html
https://github.com/bang590/JSPatch
http://www.atmark-techno.com/~yashi/libffi.html
https://github.com/mikeash/MABlockClosure

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

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

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