从一个常见的崩溃开始
昨天下班时候一朋友碰到一个下面的崩溃,但是全局断点却无法断到指定位置。鉴于不信邪的态度,决定尝试一番。然后将此事记录如下文。
| 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传空的可能 :)