从一个常见的崩溃开始
昨天下班时候一朋友碰到一个下面的崩溃,但是全局断点却无法断到指定位置。鉴于不信邪的态度,决定尝试一番。然后将此事记录如下文。
1 | *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString hasPrefix:]: nil argument' |
崩溃表现
那老哥说他的APP运行之后,什么都不做,等待10s之后就会闪退,闪退的最重要信息是上面,栈结构上没有函数信息,只有地址。
打开全局断点,也无法直接断到指定的函数,这个信息我留了一张图,大致如下:(已脱敏)
先简单的分析一下
- 崩溃原因很明确:
-[__NSCFConstantString hasPrefix:]: nil argument
。 __NSCFConstantString
为NSString
类簇里面的一个类,常见的类似的还有很多。- (BOOL)hasPrefix:(NSString *)str;
中nil argument
,也就是str
为nil
。First throw call stack
下方均为地址,说明此函数应该在一个.a/.framework
中。APP运行之后,什么都不做,等待10s之后就会闪退
说明此崩溃为异步调用,至于是不是定时调用,这个真不好说。- 由于我技术有限,别的好像提炼不出有用信息了。。
复现一下崩溃
先来个简单的代码,验证一下 ** - (BOOL)hasPrefix:(NSString *)str;
中nil argument
,也就是str
为nil
。 **
1 |
|
运行一下,结果很意料之中,得出如下:
1 | Untitled.m:4:29: warning: null passed to a callee that requires a non-null argument [-Wnonnull] |
经过1 warning generated
之后,果断崩溃了,还有明确的崩溃时候栈结构。
好,问题确定了就是这个1 warning generated
造成的。改下就完事咯~~
寻找问题
尝试解除warning
远程进去之后我震惊了,warning: 999+
。先快速找到问题,要什么自行车。。
Xcode中全局搜索hasPrefix
关键字,带变量的全部加上断点。
run一下,问题依旧如此。。正如上面说的一样:
First throw call stack
下方均为地址,说明此函数应该在一个.a/.framework
中。
GO DIE…
PS.
- 关于
hasPrefix
函数的OC声明、swift声明均为完整字符串,所以安心全局搜索就能找到代码中已存在的hasPrefix
函数 - 顺便搜了一下该项目不包含第三方
.a
, 但是.framework
不少。。有CocoaPods集成有直接拖进来的。。
尝试Method Swizzling一下
这玩意既然在别人的framework中,那就更新一下咯,但是全部更新并不可取。。
framework的更新可能导致一些不可预估的可变因素。比如说API修改、内部逻辑变更导致外部使用不兼容、某些开发者喜欢直接修改framework、等。
所以先确定是哪个framework。
Method Swizzling。将其-[__NSCFConstantString hasPrefix:]: nil argument
直接过滤nil
,岂不美滋滋~~
好了先测试一下:
1 |
|
输出:
1 | Untitled.m:33:29: warning: null passed to a callee that requires a non-null argument [-Wnonnull] |
不崩溃了,赶紧粘过去,并在- (BOOL)safe_hasPrefix:(NSString *)str
中的return YES;
打上断点。
内心得到了极大的满足~ 美滋滋~~~
2分钟后。
断点停了,向- (BOOL)safe_hasPrefix:(NSString *)str
的str
传nil的调用者是[MOBFErrorReportService writeHTTPErrorMsg:error:]
。
将MOBFErrorReportService
放入Google看到第二个就是这个[MOBFDevice duid] - BUG提交- Mob官方论坛。文中指出SDK 版本: ShareSDKVersion-3.5.1
。
好了看一下ShareSDK
这个库。emmm 这个库是拖拽进去的。老哥说这项目接手之后没更新过这个库。
事已至此,确定是ShareSDK
的问题。
更新之,问题解决~ 真心的美滋滋~~
PS.
- 上文中关于类簇的 Method Swizzling,详见Method Swizzling的各种姿势。
寻找的过程部分特写
上面的逻辑并不是一气呵成,而是由于粗心也走了部分弯路,特列出来引以为戒。
看到全局断点真的走不到时
由于代码是公司代码,也没有demo,所以布吉岛为啥确实断点不会走。。。
MMP 看到了First throw call stack
之后第一反应是,这些地址应该可以还原到对应的位置。
然后想到了逆向里面的骚操作。顿时想逆向分析一下。。。
但由于看了一下.app
中的可执行文件有 ** 40M ** ,算了算了,IDA载入的有点慢,这要好久才能分析完。。
想了想天要下雨,媳妇和娃都在外面。果断放弃分析一波的冲动,来Method Swizzling
会更快。
看走眼的 Method Swizzling
之前看过这个Method Swizzling的各种姿势文章,印象深刻。
由于 ** Method Swizzling ** 风险不那么容易控制,所以并没用类簇相关的。
一顿cmd c / cmd v
之后改了类名就扔了过去。当时大约这个样子:
1 | - (BOOL)safe_hasPrefix:(NSString *)str { |
正如我刚在上面的备注一样。
脑袋一热,这类簇会不会有别的坑,然后过分的自信忽略了safe_
前缀,并写了个C的前缀比较:
1 | - (BOOL)safe_hasPrefix:(NSString *)str { |
一番梭哈之后,定位到了问题。
但是今天复盘的时候想到这个类簇的IMP正常的操作也不可能出现循环调用。。。。
然后发现昨天的一番梭哈真的是弱智。。惭愧惭愧。。。
写在最后
- 解决问题的方案在于知识面的宽度。(一直为我没有通过
First throw call stack
直接定位而害羞) - 着急下班的时候别写太多逻辑,容易漏。要么别着急,要么万分小心。
- 集成第三方SDK别瞎胡闹,使用代码直接集成(除非万不得已)。尽量使用CocoaPods这样的工具。
- 代码中的 ** warning ** 记得消一下。成天
999+
容易漏掉关键信息。 - 开发的第三方SDK一定要注意测试覆盖率。
- swift大法好。swift不用
!
绝对不会有机会出现func hasPrefix(_ str: String) -> Bool
中参数str
传空的可能 :)