值传递?址传递,慎用形参,崩溃修复记录

查询崩溃问题流程

  1. 拿到崩溃日志

  2. 查看崩溃线程、崩溃原因

  3. 查看崩溃函数堆栈

  4. 确定崩溃调用参数

  5. 根据控制台日志来具体分析问题

例子1:

  1. 拿到崩溃日志:
image.png
  1. 查看崩溃线程、崩溃原因
image.png

如图,崩溃线程是线程5,崩溃类型是EXC_BREAKPOINT(SIGTRAP),下表是常见的崩溃异常,可以看到EXC_BREAKPOINT(SIGTRAP)是一种调试器相关的,跟踪/断点捕获,多见于异常抛出。

UNIX 信号 注释
SIGSEGV 访问无效的内存地址。地址存在,但是应用程序无法访问。
SIGABRT 程序崩溃。由 C函数 abort() 初始化。通常意味着系统检测到某些事务出错,例如 assert() 或者 NSAssert() 校验失败。
SIGBUS 访问无效的内存地址。地址不存在,或对齐无效。(The address does not exist, or the alignment is invalid.)
SIGTRAP 调试器相关
SIGILL 尝试执行非法的、有缺陷、未知的或者需要权限的指令。
Mach 异常 描述 注释
EXC_BAD_ACCESS 错误内存访问 访问“错误”内存地址。“错误”可能指“地址不存在”或者“应用没有权限访问”。因此通常与 SIGBUSSIGSEGV 相关联。
EXC_CRASH 异常跳出 通常与 SIGABRT 相关联,意思是由于检测到代码抛出的未捕获异常而使应用程序异常退出。
EXC_BREAKPOINT 跟踪/断点捕获 通用与 SIGTRAP 相关联。可以由你自己的代码或者 NSExceptions 抛出时触发。
EXC_GUARD 违反了受保护资源的防护(Violated Guarded Resource Protection) 由违背受保护资源防护触发,例如‘某些文件描述符’。
EXC_BAD_INSTRUCTION 非法指令 通常与特定非法或未定义指令/操作数相关。
EXC_RESOURCE 资源限制 应用由于达到资源消耗限制而退出。
00000020 十六进制异常类型 非 'OS Kernel' 异常。
  1. 查看函数堆栈
image.png

如图所示,我们最后崩溃在libobjc.A.dylib的objc_opt_respondsToSelector+48的地方,实际上,这是objc是否响应selector的地方,我们可以查看objc的源码,以下选自objc4-838

// Calls [obj respondsToSelector]
BOOL
objc_opt_respondsToSelector(id obj, SEL sel)
{
#if __OBJC2__
 if (slowpath(!obj)) return NO;
 Class cls = obj->getIsa();
 if (fastpath(!cls->hasCustomCore())) {
 return class_respondsToSelector_inst(obj, sel, cls);
 }
#endif
 return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(obj, @selector(respondsToSelector:), sel);
}

为了弄清楚究竟崩在哪一行,我们需要把它转成汇编

image.png

注意,我们最后走到的是+48,这并不代表我们是执行完+48所对应的代码才崩溃的,恰恰是执行上一句代码崩溃,而上一句代码转成汇编后的返回地址是+48,而上一句对应的是

if (slowpath(!obj)) return NO;

也就是说此时objc不存在,结合前面的DDLog打印函数,我们基本可以确定我们打印的对象已经被释放了,但是指针还没有清空,即指针所指向的内存已释放,而指针本身的地址不为null,所以它指向了一块不可访问的内存。我们回到第5行,ResetVTSession,来确定打印的是个啥

image.png
  1. 确定崩溃参数,还记得我们前面说的吗,崩溃的偏移号不是代表我们执行完这一句才崩溃,而是上一句,之所以显示+112偏移地址是因为上一句执行完毕的返回地址是这个,可以很清楚的看到汇编其实已经给我们注释出来了,是"ResetVtSession = %@"调用出现的问题,我们转成正常代码

    image-20220801151644486.png

现在我们确定了引发崩溃的参数 vtSession.

  1. 现在我们来具体分析一下这个函数的究竟有什么问题,其实我们都不需要具体分析自己的日志就能看出来。
image.png

问题出在这里,vtSession = NULL,这是一句没什么作用的代码,反而很有迷惑性,为什么呢?我们来分析一下这个方法想干什么,先强制编完剩下的帧VTCompressionSessionCompleteFrames,相当于快速处理完还没处理的内容,然后VTCompressionSessionInvalidate(vtSession)CFRelease(vtSession),这两步是销毁session,并释放内存,最后再把vtSession置空,看起来perfect,但是不要忘了我们的参数vtSession是值传递!换句话说我们在函数内部的vtSession只是外部调用的值拷贝,就算我们把它置为空,也不影响外部的指针不为空,下次如果有其他线程重新调进来,就会引发崩溃。所以解决方案有两种,一是改为址传递,改为

void MHH264VideoSource::ResetVtSession(VTCompressionSessionRef& vtSession)

二是仍然是值传递,不过外面手动把调用指针置空

// before:
     ResetVtSession(this->m_portrait_vtSession);
     ResetVtSession(this->m_landscape_vtSession);
// after:
    ResetVtSession(this->m_portrait_vtSession);
    this->m_portrait_vtSession = nullptr;
    ResetVtSession(this->m_landscape_vtSession);
    this->m_landscape_vtSession = nullptr;

总结

我们先通过崩溃日志确定崩溃类型和崩溃原因,然后根据崩溃堆栈来具体锁定诱发崩溃的原因,然后再回到SDK层去查看具体引发崩溃的代码和变量,最后我们再根据自己的代码来具体排查为什么会这样。

后话:虽然后来复盘的时候第5步我并没有写根据日志来排查,那是因为最后看了一圈日志最后又查回来到这个函数,发现是这里的问题,我一开始其实没看出来这里的问题,复盘的时候想写简单点,毕竟业务上的设计各家各有不同,但是最基本的程序bug却是类似的。

后记

后面再分享一下我排查的具体操作吧,总体绕了一圈弯路

  1. 首先查看调用ResetVtSession的地方是ResetVtSession(this->m_landscape_vtSession)时崩溃,表明横屏编码器不存在,但是竖屏编码器能正常释放

  2. 接着查看调用释放的地方是verifyProcess(),这是一个嗅探机制,旨在查看当前的码流是否正常发送中,如果不正常,就重新创建编码器或刷新关键帧,根据日志判断,当时检测到竖屏码流不能正常发送中,于是重新创建了一个竖屏的编码器,但是横屏的没有创建,所以这一步可以得到一个信息:横屏的编码器压根儿没有(但是不能确定是已经释放了还是根本没创建)

  3. 接着看上面的日志,发现走到了创建流程,但是到加锁创建的那一步,直接被return掉了,这里可以确定,vtSession并不为空,说明上一次释放并没有把指针置空

image-20220801154958737.png
  1. 接着就回到了上面,发现是释放函数的问题。那么为什么之前一直没出过问题呢?因为之前是启动扩展进程,每次都是新创建一个进程,所以everything is new,上一个扩展进程反正已经没了,没置空也不影响,所以一直没啥问题,但是这一次咱们是用主进程采集流并发送的,导致整一个videoSource压根儿已经创建过就不用再创建了,所以这里vt_Session指针没置空就很危险了,不光会导致下一次创建的时候创建不了,而且一旦走到析构就直接咖喱给给了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 前言 此文是基于这些年工作中项目里面常见崩溃的一些总结,整理出来方便查阅,希望对大家都有所帮助。 App常见崩溃 ...
    Oneruofeng阅读 16,845评论 3 21
  • [转]HashMap和Hashtable的详细区别[https://juejin.cn/post/68449039...
    Ella_Eric阅读 288评论 0 2
  • 崩溃日志 如何得到crash report 当一个iOS应用程序崩溃时, 系统会创建一份crash日志保存在设备上...
    々莫等闲々阅读 2,896评论 0 2
  • 1、前言 iOS 13 最大的亮点是 Dark 模式,另一个就是苹果登录(Sign in with Apple)方...
    iHTCboy阅读 40,139评论 25 19
  • iOS崩溃是让iOS开发人员比较头痛的事情,app崩溃了,说明代码写的有问题,在这里了解一下XCode用来表示各种...
    Ly梦k阅读 5,616评论 0 6