分享一个跟符号链接相关的Crash,一个小问题,但是开发没有相关意识,甚至也没自测就上线了。虽然听着是有点离谱,但确实发生了也挺合(li)理(pu)的。
某次应用灰度期间出现了一个Crash,编译链接正常,运行时Crash:
1 | SIGABRT: |
Crash原因比较明确,就是运行时符号缺失,dyld接管后直接abort了。
Crash行的源码很简单,伪代码如下:1
2
3
4
5
6
7- (NSError *)handleResult:(NSInterger)code {
// ..
// KKMagicalManager.mm:626
NSError *error = KKMagicalMakeError(code, @"");
return error;
}
搜相关函数,其实是有实现的:1
2
3
4
5
6
7
8
9
10
11
12// KKType.h
NSError *KKMagicalMakeError(NSInteger code, NSString *fmt, ...) NS_FORMAT_FUNCTION(2, 3);
// KKType.m
NSError *_KKMagicalMakeError(NSInteger code, NSString *fmt, ...) {
va_list args;
va_start(args, fmt);
NSString *message = [[NSString alloc] initWithFormat:fmt arguments:args];
va_end(args);
return [NSError errorWithDomain:@"abc" code:code userInfo:@{}];
}
灰度版本该函数确实有改动,原实现伪代码如下:1
2
3
4
5
6
7
8
9// KKType.h
static NSError *_KKMagicalMakeError(NSInteger code, NSString *fmt, ...) {
va_list args;
va_start(args, fmt);
NSString *message = [[NSString alloc] initWithFormat:fmt arguments:args];
va_end(args);
return [NSError errorWithDomain:@"abc" code:code userInfo:@{}];
}
看出是什么问题没,运行期怎么就找不到符号了呢?
查看MachO的符号,如下1
2
3
4
5
6
7
8% nm App | grep KKMagicalVoiceMakeError
0000000000f18fd8 T _KKMagicalVoiceMakeError
0000000007c0a068 t _KKMagicalVoiceMakeError.island
U __Z23KKMagicalVoiceMakeErrorlP8NSStringz
0000000007c6c9bc t __Z23KKMagicalVoiceMakeErrorlP8NSStringz.island
jason@JASONZXCHEN-MB2 QQKSong.app % echo __Z23KKMagicalVoiceMakeErrorlP8NSStringz | c++filt
KKMagicalVoiceMakeError(long, NSString*, ...)
jason@JASONZXCHEN-MB2 QQKSong.app %
明显_KSMagicalVoiceMakeError符号是存在的!那导致Crash的找不到的符号是什么呢?
很容易发现,上面nm输出中,确实有一个未定义的符号:
U __Z23KKMagicalVoiceMakeErrorlP8NSStringz
这个是经过name mangling的符号,demangle后是KKMagicalVoiceMakeError(long, NSString*, ...)。这个跟函数声明都是一致的,看着好像也都没什么毛病。
等等,你肯定会想到了:
1、这里一个函数,为什么会生成了两个符号?
2、为什么发生了name mangling?
3、历史版本为什么没问题?
我们回头看灰度版本修改的实现:1
2
3
4
5
6
7
8
9
10
11
12// KKType.h
NSError *KKMagicalMakeError(NSInteger code, NSString *fmt, ...) NS_FORMAT_FUNCTION(2, 3);
// KKType.m
NSError *_KKMagicalMakeError(NSInteger code, NSString *fmt, ...) {
va_list args;
va_start(args, fmt);
NSString *message = [[NSString alloc] initWithFormat:fmt arguments:args];
va_end(args);
return [NSError errorWithDomain:@"abc" code:code userInfo:@{}];
}
name mangling是C++的典型的特性,主要是为了支持函数重载、命名空间等特性。而C通常不会发生name mangling。因此可以推测那就是这个函数是以C++的规则进行了编译。
经过简单确认,可以发现,_KKMagicalMakeError这个函数有多处调用,有部分调用入口是.mm的文件中。这样很容易实锤了。回答上面的问题。
首先,因为.mm文件中通过引入KKType.h文件引用了该函数,因此编译时,.h声明的函数以C++的规则进行了编译,也就发生了name mangling;而KKType.m中函数_KKMagicalMakeError却是以C的规则进行编译,因此最终生成了两个符号。.h声明的符号实际上并没有对应的实现,因此对应符号在链接后成了一个Undefined的符号。
而历史实现,直接在.h文件中将该函数声明为static并实现,因此符号是统一的。
修改方案就很简单,使用 extern “C”,这也是C/C++混编经常使用的、用来指定相关函数要以C的方式进行编译。1
2
3
4
5
6
7
8
9#if __cplusplus
extern "C" {
#endif
NSError *_KSMagicalVoiceError(NSInteger code, NSString *fmt, ...) NS_FORMAT_FUNCTION(2, 3);
#if __cplusplus
} //Extern C
#endif
評論