李峰峰博客

热修复之 MangoFix 实现原理

2022-10-02

一、基本使用

MangoFix 是 iOS 中另一个热修复 SDK,其实现原理与 JSPatch 不同。MangoFix 定义了一套与 OC 相似语法规则,使用该规则编写热修复代码,并使用 Lex、Yacc 实现词法分析器、语法分析器处理热修复代码生成抽象语法树 AST 并解释执行,再利用 Runtime 实现方法的替换、新增等,以达到热修复的目的。

例如,想使用 MangoFix 热修复修改 ViewController 中的 testMethodWithStr: 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface ViewController : UIViewController

- (void)testMethodWithStr:(NSString *)str;

@end


@implementation ViewController

- (void)testMethodWithStr:(NSString *)str {

}

@end

热修复代码文件(demo.mg)中热修复代码如下:

1
2
3
4
5
6
7
8
9
class ViewController:UIViewController {

- (void)testMethodWithStr:(NSString *)str {
// 变量定义
NSString *text = @"Example Text";
self.resultView.text = text;
}

}

加载热修复代码文件:

1
2
3
4
5
MFContext *context = [[MFContext alloc] init];

NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"mg"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[context evalMangoScriptWithSourceString:script];

二、实现原理

无论是 JSPatch 还是 MangoFix,热修复代码本质上都是一段代码字符串,要实现热修复,最关键的一步就是如何让这段代码字符串被 iOS 系统(或 APP)理解并执行。

JSPatch 利用了 iOS 系统提供的 JavaScriptCore 的 API,通过使用 JS 编写热修复代码,利用 JavaScriptCore 去执行 JS 代码(字符串),最后利用 Runtime 去实现方法的替换,达到了热修复的目的。

而 MangoFix 则是自定义了一套语言,这套语言仅可用于 iOS 的热修复,所以可以被称为 DSL(Domain-Specific Language,领域特定语言)。为了使 MangoFix 的这套自定义的语言编写的可以被执行,就需要将 MangoFix 热修复代码字符串构建成一个逻辑能被 iOS 系统(或 APP)识别、执行和遍历的抽象语法树 AST。

而要构建 AST,就必须实现对应的词法分析器、语法分析器。MangoFix 则利用 Lex、Yacc 分别实现词法分析器、语法分析器,最终使热修复代码字符串得以被执行,最后再利用 Runtime 实现对原方法的替换。

简而言之,MangoFix 自定义了一套解释型语言用于编写热修复代码,再利用 Lex、Yacc实现解释器并执行热修复代码,最后再利用 Runtime 实现对原方法的替换,达到热修复的目的。

还以下面这段热修复代码为例看下 MangoFix 实现原理:

1
2
3
4
5
6
7
8
9
class ViewController:UIViewController {

- (void)testMethodWithStr:(NSString *)str {
// 变量定义
NSString *text = @"Example Text";
self.resultView.text = text;
}

}

MangoFix 加载执行热修复代码,主要是通过下述方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)evalMangoScriptWithAES128Data:(NSData *)scriptData {
@autoreleasepool {
// 将加密的脚本(热修复代码字符串),解密成原始代码字符串
NSData *mangoFixData = [scriptData AES128ParmDecryptWithKey:_key iv:_iv];
NSString *mangoFixString = [[NSString alloc] initWithData:mangoFixData encoding:NSUTF8StringEncoding];
if (!mangoFixString.length) {
NSLog(@"[MangoFix] [ERROR] : AES128(ECBMode) decrypt error!");
return;
}
// 设置解释器
mf_set_current_compile_util(self.interpreter);
// 添加内置函数到解释器
mf_add_built_in(self.interpreter);
// 编译(解释)热修复代码:词法解析、语法解析,生成抽象语法树 AST
[self.interpreter compileSourceWithString:mangoFixString];
// 置空解释器
mf_set_current_compile_util(nil);
// 解释并执行热修复代码:语义分析、解释执行
mf_interpret(self.interpreter);
}
}

这里主要关注 compileSourceWithString:mf_interpret() 两个方法。

compileSourceWithString: 方法实现如下:

1
2
3
4
5
6
7
8
9
10
- (void)compileSourceWithString:(NSString *)source {
extern void mf_set_source_string(char const *source);
mf_set_source_string([source UTF8String]);
extern void yyrestart (FILE * input_file );
extern int yyparse(void);
yyrestart(NULL); /* 每次解析前,重置yylex */
if (yyparse()) {
return;
}
}

可以看到该方法主要逻辑如下:

  • 设置词法分析源字符串为热修复代码字符串
  • 重置词法分析器
  • 执行语法分析函数 yyparse()

其中,yyparse() 函数是语法分析器核心函数,是 Xcode 根据 Yacc 文件(MangoFix/Compiler/lex_yacc/mango.y)自动编译生成的。

关于 Lex 词法分析器、Yacc 语法分析器相关内容,可以看我的另一篇博客:《编译原理之 Lex & Yacc》

前面提到的热修复示例代码:

1
2
3
4
5
6
7
8
9
class ViewController:UIViewController {

- (void)testMethodWithStr:(NSString *)str {
// 变量定义
NSString *text = @"Example Text";
self.resultView.text = text;
}

}

在词法分析、语法分析时,就需要匹配出代码字符串中的类、父类、方法名、方法实现等内容。

以类信息匹配为例,Yacc 文件(mango.y)中定义的类匹配文法规则如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class_definition: annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LC
{
NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1;
NSString *name = (__bridge_transfer NSString *)$3;
NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5;
NSString *superName = (__bridge_transfer NSString *)$6;
mf_start_class_definition(annotationList, name, superAnnotationList, superName,nil);
}
RC
{
MFClassDefinition *classDefinition = mf_end_class_definition(nil);
$$ = (__bridge_retained void *)classDefinition;
}
| annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LC
{
NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1;
NSString *name = (__bridge_transfer NSString *)$3;
NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5;
NSString *superName = (__bridge_transfer NSString *)$6;
mf_start_class_definition(annotationList, name, superAnnotationList, superName,nil);
}
member_definition_list RC
{
NSArray *members = (__bridge_transfer NSArray *)$9;
MFClassDefinition *classDefinition = mf_end_class_definition(members);
$$ = (__bridge_retained void *)classDefinition;
}
| annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LT protocol_list GT LC
{
NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1;
NSString *name = (__bridge_transfer NSString *)$3;
NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5;
NSString *superName = (__bridge_transfer NSString *)$6;
NSArray *protocolNames = (__bridge_transfer NSArray *)$8;
mf_start_class_definition(annotationList, name, superAnnotationList, superName, protocolNames);
}
RC
{
MFClassDefinition *classDefinition = mf_end_class_definition(nil);
$$ = (__bridge_retained void *)classDefinition;
}
| annotation_list CLASS IDENTIFIER COLON annotation_list IDENTIFIER LT protocol_list GT LC
{
NSArray<MFAnnotation *> *annotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$1;
NSString *name = (__bridge_transfer NSString *)$3;
NSArray<MFAnnotation *> *superAnnotationList = (__bridge_transfer NSArray<MFAnnotation *> *)$5;
NSString *superName = (__bridge_transfer NSString *)$6;
NSArray *protocolNames = (__bridge_transfer NSArray *)$8;
mf_start_class_definition(annotationList, name, superAnnotationList, superName, protocolNames);
}
member_definition_list RC
{
NSArray *members = (__bridge_transfer NSArray *)$12;
MFClassDefinition *classDefinition = mf_end_class_definition(members);
$$ = (__bridge_retained void *)classDefinition;
}
;

上述主要逻辑如下:

  • 词法分析器输出词法单元 tokens
  • 语法分析器使用 tokens 按照类文法规则匹配类信息
  • 匹配成功后,获取类、父类等信息,调用 mf_start_class_definition() 函数
  • 匹配结束后,调用 mf_end_class_definition() 函数

mf_start_class_definition()mf_end_class_definition() 函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void mf_start_class_definition(NSArray<MFAnnotation *> *annotationList, NSString *name, NSArray<MFAnnotation *> *superAnnotationList, NSString *superName, NSArray<NSString *> *protocolNames){
MFInterpreter *interpreter = mf_get_current_compile_util();
MFClassDefinition *classDefinition = [[MFClassDefinition alloc] init];
classDefinition.lineNumber = interpreter.currentLineNumber; // mango.l 中赋值
classDefinition.annotationList = annotationList;
classDefinition.superAnnotationList = superAnnotationList;
if (classDefinition.swiftModuleAnnotation) {
classDefinition.name = [NSString stringWithFormat:@"%s.%@", classDefinition.swiftModuleAnnotation.expr.cstringValue, name];
[[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:classDefinition.name alias:name];
} else {
NSString *swiftClassName = [[MFSwfitClassNameAlisTable shareInstance] swiftClassNameByAlias:name];
if (swiftClassName) {
classDefinition.name = swiftClassName;
} else {
classDefinition.name = name;
}
}

if (classDefinition.superSwiftModuleAnnotation) {
classDefinition.superName = [NSString stringWithFormat:@"%s.%@", classDefinition.superSwiftModuleAnnotation.expr.cstringValue, superName];
[[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:classDefinition.superName alias:superName];
} else {
NSString *swiftSuperClassName = [[MFSwfitClassNameAlisTable shareInstance] swiftClassNameByAlias:superName];
if (swiftSuperClassName) {
classDefinition.superName = swiftSuperClassName;
} else {
classDefinition.superName = superName;
}
}
classDefinition.protocolNames = protocolNames;
interpreter.currentClassDefinition = classDefinition;
}


MFClassDefinition *mf_end_class_definition(NSArray<MFMemberDefinition *> *members){
MFInterpreter *interpreter = mf_get_current_compile_util();
MFClassDefinition *classDefinition = interpreter.currentClassDefinition;
NSMutableArray<MFPropertyDefinition *> *propertyDefinition = [NSMutableArray array];
NSMutableArray<MFMethodDefinition *> *classMethods = [NSMutableArray array];
NSMutableArray<MFMethodDefinition *> *instanceMethods = [NSMutableArray array];
for (MFMemberDefinition *memberDefinition in members) {
memberDefinition.classDefinition = classDefinition;
if ([memberDefinition isKindOfClass:[MFPropertyDefinition class]]) {
[propertyDefinition addObject:(MFPropertyDefinition *)memberDefinition];
}else if ([memberDefinition isKindOfClass:[MFMethodDefinition class]]){
MFMethodDefinition *methodDefinition = (MFMethodDefinition *)memberDefinition;
if (methodDefinition.classMethod) {
[classMethods addObject:methodDefinition];
}else{
[instanceMethods addObject:methodDefinition];
}
}
}
classDefinition.properties = propertyDefinition;
classDefinition.classMethods = classMethods;
classDefinition.instanceMethods = instanceMethods;
interpreter.currentClassDefinition = nil;
return classDefinition;
}

mf_start_class_definition() 主要逻辑如下:

  • 获取当前的解释器实例
  • 创建一个新的 MFClassDefinition 对象,并初始化其属性
  • 将解释器当前行号赋值给类定义对象的 lineNumber 属性(用于后续的异常信息中)
  • MFClassDefinition 对象保存到解释器的 currentClassDefinition 属性中

mf_end_class_definition() 主要逻辑如下:

  • 获取当前解释器实例
  • 获取解释器的 currentClassDefinition
  • 将从热修复代码中匹配到的属性、类方法、实例方法分别保存到 currentClassDefinitionpropertiesclassMethodsinstanceMethods

除了上述类信息匹配之外,还有对应的方法信息、方法实现等信息的匹配,匹配完成后,就生成了对应的 AST,而上述的 MFClassDefinition 则是 AST 的一个节点,其中就保存了热修复代码中的类、方法、属性等信息。

生成的 AST 之后,就开始执行 mf_interpret() 函数,mf_interpret() 函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
void mf_interpret(MFInterpreter *interpreter){
for (__kindof NSObject *top in interpreter.topList) {
if ([top isKindOfClass:[MFStatement class]]) {
execute_statement(interpreter, interpreter.topScope, top);
}else if ([top isKindOfClass:[MFStructDeclare class]]){
add_struct_declare(interpreter,top);
}else if ([top isKindOfClass:[MFClassDefinition class]]){
define_class(interpreter, top);
fix_class(interpreter,top);
}
}
}

在前述的示例中,创建的 AST 的 top 节点是 MFClassDefinition ,所以此处会调用 define_class()fix_class() 函数。

define_class() 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
static void define_class(MFInterpreter *interpreter, MFClassDefinition *classDefinition){
if (classDefinition.annotationIfExprResult == AnnotationIfExprResultNoComputed) {
MFExpression *annotationIfConditionExpr = classDefinition.ifAnnotation.expr;
if (annotationIfConditionExpr) {
MFValue *value = mf_eval_expression(interpreter, interpreter.topScope, annotationIfConditionExpr);
classDefinition.annotationIfExprResult = value.isSubtantial ? AnnotationIfExprResultTrue : AnnotationIfExprResultFalse;
if (!value.isSubtantial) {
return;
}
}else{
classDefinition.annotationIfExprResult = AnnotationIfExprResultTrue;
}
}


if (classDefinition.annotationIfExprResult != AnnotationIfExprResultTrue) {
return;
}

// 获取类名对应的 Class 对象
Class clazz = NSClassFromString(classDefinition.name);
if (!clazz) {
// 类不存在,则动态创建类
// 获取父类
NSString *superClassName = classDefinition.superName;
Class superClass = NSClassFromString(superClassName);

// 如果父类不存在且有 Swift 模块注解
if (!superClass) {
if (classDefinition.swiftModuleAnnotation && !classDefinition.superSwiftModuleAnnotation) {
NSString *sueprClassFullName = [NSString stringWithFormat:@"%s.%@", classDefinition.swiftModuleAnnotation.expr.cstringValue, superClassName];
superClass = NSClassFromString(sueprClassFullName);
if (superClass) {
[[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:sueprClassFullName alias:superClassName];
classDefinition.superName = sueprClassFullName;
}
}
}

// 如果父类仍然不存在,则递归
if (!superClass) {
define_class(interpreter, interpreter.classDefinitionDic[superClassName]);
superClass = NSClassFromString(superClassName);
}

if (!superClass && classDefinition.swiftModuleAnnotation && !classDefinition.superSwiftModuleAnnotation) {
NSString *sueprClassFullName = [NSString stringWithFormat:@"%s.%@", classDefinition.swiftModuleAnnotation.expr.cstringValue, superClassName];
define_class(interpreter, interpreter.classDefinitionDic[sueprClassFullName]);
superClass = NSClassFromString(sueprClassFullName);
if (superClass) {
[[MFSwfitClassNameAlisTable shareInstance] addSwiftClassNmae:sueprClassFullName alias:superClassName];
classDefinition.superName = sueprClassFullName;
}
}

// 如果父类仍然不存在,则抛出错误
if (!superClass) {
mf_throw_error(classDefinition.lineNumber, MFRuntimeErrorNotFoundSuperClass, @"not found super class: %@",superClassName);
return;
}

// 分配并注册新的类
clazz = objc_allocateClassPair(superClass, classDefinition.name.UTF8String, 0);
objc_registerClassPair(clazz);

}

// 将生成的类对象存储在类定义中
classDefinition.clazz = clazz;

}

define_class() 主要逻辑如下:

  • 获取类名对应的 Class 对象,判断 Class 对象是否已存在。
    • 如果不存在,则动态创建 Class
    • 如果已经存在,直接保存到 classDefinitionclazz 属性中。

fix_class() 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
static void fix_class(MFInterpreter *interpreter, MFClassDefinition *classDefinition){
Class clazz = classDefinition.clazz;
for (MFPropertyDefinition *prop in classDefinition.properties) {
replace_prop(interpreter,clazz, prop);
}

for (MFMethodDefinition *classMethod in classDefinition.classMethods) {
replace_method(interpreter, clazz, classMethod);
}

for (MFMethodDefinition *instanceMethod in classDefinition.instanceMethods) {
replace_method(interpreter, clazz, instanceMethod);
}
}



static void replace_method(MFInterpreter *interpreter, Class clazz, MFMethodDefinition *method){

// 略 ......

NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:typeEncoding.UTF8String];
unsigned int argCount = (unsigned int)[sig numberOfArguments];
void *imp = NULL;

// ffi_cif(Call Interface)用于描述函数调用的接口,包括返回类型、参数类型等。
ffi_cif *cif = malloc(sizeof(ffi_cif));//不可以free

// 创建一个可以调用的函数闭包
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), (void **)&imp);
ffi_type *returnType = mf_ffi_type_with_type_encoding(sig.methodReturnType);

// 根据方法签名设置函数的返回类型和参数类型
ffi_type **args = malloc(sizeof(ffi_type *) * argCount);
for (int i = 0 ; i < argCount; i++) {
args[i] = mf_ffi_type_with_type_encoding([sig getArgumentTypeAtIndex:i]);
}

// 准备 ffi_cif 以便用于函数调用,并将闭包与具体的函数实现绑定
if(ffi_prep_cif(cif, FFI_DEFAULT_ABI, argCount, returnType, args) == FFI_OK)
{
NSDictionary *userInfo = nil;
if (method.classDefinition) {
userInfo = @{@"class":c2, @"typeEncoding":[typeEncoding copy], @"classDefinition" : method.classDefinition};
} else {
userInfo = @{@"class":c2, @"typeEncoding":[typeEncoding copy]};
}
CFTypeRef cfuserInfo = (__bridge_retained CFTypeRef)userInfo;
// 将闭包与具体的函数实现 replaceIMP 绑定,并传递 userInfo 作为上下文
ffi_prep_closure_loc(closure, cif, replaceIMP, (void *)cfuserInfo, imp);
}
class_replaceMethod(c2, sel, imp, typeEncoding.UTF8String);
}


static void replaceIMP(ffi_cif *cif, void *ret, void **args, void *userdata){

// 略 ......

NSMutableArray<MFValue *> *argValues = [NSMutableArray array];
NSUInteger numberOfArguments = [methodSignature numberOfArguments];
for (NSUInteger i = 0; i < numberOfArguments; i++) {
MFValue *argValue;
const char *type = [methodSignature getArgumentTypeAtIndex:i];
if (strcmp(type, "@?") == 0) {
id block = (__bridge id)(*(void **)args[i]);
block = [block copy];
argValue = [MFValue valueInstanceWithObject:block];
}else{
void *arg = args[i];
argValue = [[MFValue alloc] initWithCValuePointer:arg typeEncoding:[methodSignature getArgumentTypeAtIndex:i] bridgeTransfer:NO];
}

[argValues addObject:argValue];
}
// 执行热修复中的方法实现
__autoreleasing MFValue *retValue = mf_call_mf_function(inter, classScope, method.functionDefinition, argValues);
[retValue assignToCValuePointer:ret typeEncoding:[methodSignature methodReturnType]];
}

fix_class() 主要逻辑如下:

  • 创建 imp,用于关联热修复代码中方法实现
  • 利用 libffi 将 replaceIMP 函数与 imp 进行关联
  • 利用 Runtime 使用 imp 替换 OC 原方法实现
    • 完成替换后,原方法执行,实际将会执行到 replaceIMP 函数
    • replaceIMP 函数中,会传递方法参数并执行热修复代码中具体方法逻辑

关于 libffi:
libffi(Foreign Function Interface Library)是一个开源库(libffi),它提供了最底层的接口,在不确定参数个数和类型的情况下,根据相应规则,完成所需数据的准备,生成相应汇编指令的代码来完成函数调用。

libffi 允许程序在运行时调用未知的函数,主要用于实现跨语言调用和动态代码生成,它在运行时生成调用代码,而不需要在编译时知道函数的签名。libffi 在很多编程语言的实现中都有应用,例如 Python 的 ctypes 模块、Java 的 JNI(Java Native Interface)、和 Ruby 的 Fiddle 模块等。

总结上述逻辑,MangoFix 实现方法替换的流程可用下图表示: