目录
1、初始问题描述
2、使用Process Explorer工具查看到处理音视频业务的rtcmpdll.dll模块没有加载起来
3、使用Dependency Walker工具查看到rtcmpdll.dll依赖的库有问题
4、更新库之后Debug程序启动时就发生异常,程序闪退
5、VS调试时看不到有效的函数调用堆栈,使用Windbg启动目标程序去查看异常时的函数调用堆栈
6、引入rtcmediacontrol音频处理插件的原因
7、分析引发WebRTC开源库内部调用C运行时函数abort强制结束进程的原因
7.1、初步分析
7.2、查看WebRTC开源库对应的源码,分析程序的走向
7.3、找到触发abort终止进程操作的最终原因
8、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931 最近在项目中遇到了一个比较典型的问题,由于调用WebRTC开源库的RegisterAudioCallBack接口的线程与创建ADM音频设备管理对象的线程不是同一个线程,触发了WebRTC内部在Debug下的Check校验失败,触发了WebRTC内部调用abort接口强行将程序进程终止,导致程序发生闪退。本文讲述一下这一问题的完整排查过程。
1、初始问题描述
为了排查会议中的相关问题,在Visual Studio中对代码进行Debug下的调试,发现视频窗口无法显示对应与会终端的视频,与会终端的摄像头是开着的,测试同事电脑上安装的Release版本软件终端入会后是可以看到其他与会终端的视频图像的。于是,要排查一下我这边Debug版本软件为啥不显示其他与会终端的视频图像。
2、使用Process Explorer工具查看到处理音视频业务的rtcmpdll.dll模块没有加载起来
以前遇到过类似的问题,处理音视频业务的组件库rtcmpdll.dll是动态启动的,是不是这个库没有启动起来?
rtcmpdll.dll库是在初始化组件的模块时底层调用LoadLibrary或者LoadLibraryEx动态启动的。于是启动Process Explorer工具,找到Debug版本的程序进程,查看进程启动的dll库列表:
rtcmpdll.dll确实没有启动起来(没有加载到进程空间中)。
3、使用Dependency Walker工具查看到rtcmpdll.dll依赖的库有问题
rtcmpdll.dll之所以没有启动起来,基本是因为rtcmpdll.dll依赖的底层库有问题,一般有两种情况:
1)依赖的dll库,在系统中找不到。这个可能是打包安装程序时,没有将dll库打包到安装包中导致的。
2)调用了被依赖的库中的接口,但在当前系统中找到的该dll库中找不到该接口或者接口的参数不一致。这一般是主dll库与被依赖的dll库版本不一致导致的。
可以使用Dependency Walker工具看一下。启动该工具,将rtcmpdll.dll库拖进工具中,发现其调用了其依赖的rtcmediacontrol.dll中的RegisterRtcLogCallBack接口:
但在rtcmediacontrol.dll库中找不到该接口,那应该是rtcmpdll.dll和rtcmediacontrol.dll库版本不一致导致的。
4、更新库之后Debug程序启动时就发生异常,程序闪退
于是和rtcmediacontrol库的开发同事确认了一下,他们最近确实发布了新版本的rtcmediacontrol库,于是取来最新Debug版本的rtcmediacontrol库,放到Debug路径下,重新启动VS调试,结果一启动就报错了:
打开Call stack函数调用堆栈页面,也看不到有效的函数调用堆栈:
以前遇到过调用IsBadReadPtr导致VS报错的,于是点击继续调试按钮,结果还是报错,查看报错时的函数调用堆栈,也看不到具体是哪个函数触发的,也看不到具体的函数调用堆栈。
对于在VS中调试启动程序报错时看不到有效的函数调用堆栈的问题,我们遇到很多次了,可以尝试使用Windbg启动Debug版本的exe主程序,Windbg能感知到程序启动时发生异常并中断下来,然后就可以看到发生异常时的函数调用堆栈。
这个问题有些奇怪,只有Debug版本程序在启动后会闪退,Release版本的程序是没有问题的!测试同事那边安装的最新Release版本,运行是没问题的,启动时不会报错!软件的日常开发和维护主要是在IDE Debug下进行调试的,而Debug下程序启动后有闪退,直接导致程序没法进行Debug调试,所以这个问题必须要排查解决!我们还需要搞清楚为啥Debug下闪退、Release下没问题,要排查软件中可能存在的隐患!
因为Debug和Release下的不同代码控制或内存差异,可能会出现Debug和Release下运行的不同现象。比如Debug下运行没问题,Release下运行有异常,这在日常项目中比较常见。而本例中遇到的Release下运行正常、Debug下闪退的问题,是比较少见的!越是少见的问题,我们越要研究,要高清楚为什么会出现这样的问题!
5、VS调试时看不到有效的函数调用堆栈,使用Windbg启动目标程序去查看异常时的函数调用堆栈
于是启动Windbg,打开Debug版本的exe主程序,即通过Windbg启动目标程序,一上来就遇到了调用IsbadReadPtr引发的异常中断:
输入g命令跳过去即可,连续遇到三次这样的中断,所以连续g了三次。
结果又遇到了调用DebugBreak引发的中断:
DebugBreak是系统API函数,调用该函数是为了让当前正在调试的调试器中断下来,比如正在调试的IDE、正在调试的Windbg等。调试器中断下来后,就可以查看此时的函数调用堆栈,就知道当前发生什么问题了。
于是在DebugBreak触发Windbg中断下来时,输入kn命令查看此时的函数调,找来了相关模块的pdb文件,发现是rtcmediacontrol库调用了WebRTC开源库中的RegisterAudioCallBack接口触发的。
6、引入rtcmediacontrol音频处理插件的原因
我们在软件中要实现会议中扬声器的静音,最好的做法是,在收到平台服务器给过来的音频数据,不解码播放就可以了。但试了WebRTC的很多接口,不是达不到效果,就是多次频繁操作静音会引发崩溃。
如果按照理想的做法,在收到远端传过来的音频数据不解码播放,需要去修改WebRTC内部关于混音的代码,但这回牵涉到很多代码,比较复杂,不好修改。所以,中途引入了一个规避的方法,让上层去实现扬声器静音,不再依赖WebRTC库内部的实现。
具体的做法是,让UI层通过COM组件技术去将当前软件进程的声音关闭掉,这样就听不到会议中的声音了。关闭目标进程的声音的相关代码,可以参照下面的文章:
VC++打开或关闭目标进程的声音(附源码)https://blog.csdn.net/chenlycly/article/details/128966612但这有个问题,整个进程的声音都没有了,这样进程中的其他声音都不播放了,比如IM子系统中收到消息的提示音都听不到了。所以,这种做法也不是很合适。
后来为了彻底解决这个扬声器静音的问题,引入了rtcmediacontrol库,把这个库作为WebRTC库引入的音频处理插件,在这个库去控制是否去解码播放音频数据。
7、分析引发WebRTC开源库内部调用C运行时函数abort强制结束进程的原因
7.1、初步分析
WebRTC库内部调用DebugBreak让调试器中断下来,紧接着应该就是abort将进程终止掉,如下:
在Windbg中输入g命令将DebugBreak引发的中断跳过去,紧接着就弹出了abort终止调试的提示框。
对于WebRTC内部先调用DebugBreak后调用abort将进程强行终止掉的场景,以前我们遇到过,当时使用malloc去申请一段内存,结果malloc返回NULL,内存申请失败,然后就触发了强行终止进程的操作。估计是WebRTC开源库认为,内存申请失败会导致相关数据没法处理,相关业务没法执行下去,进程没有活下去的必要了,所以就强行将进程终止掉。
在调用abort之前,调用DebugBreak函数,就是让调试器感知一下,可以查看函数调用堆栈,看看当前执行了什么操作。
从函数调用堆栈看,调用的webrtc::AudioDeviceBuffer::RegisterAudioCallBack函数怎么位于rtcmediacontrol.dll模块中呢?这是因为rtcmediacontrol.dll库引用了WebRTC开源库,引用的静态库,不是动态库,所以还归属于rtcmediacontrol.dll库。
7.2、查看WebRTC开源库对应的源码,分析程序的走向
根据调用Windbg中显示的函数调用堆栈中的函数AudioDeviceBuffer::RegisterAudioCallBack及行号,到WebRTC开源代码中找到对应的代码行,如下所示:
对应的代码行为82行,但82行对应的是一行打印日志的代码,应该不是这行代码引起的。函数调用堆栈中显示的行号,是当前函数调用被调用函数的返回地址那一行,所以应该是81行代码引发DebugBreak调用的。
81行代码是一个叫做RTC_DCHECK_RUN_ON的宏,根据名称大概猜测出来,当前这个宏是用来做Debug下Check的。所以这个Check应该是Debug下的Check,Release下不执行这个Check,是不是这个Check内部在检测到条件不满足时触发了DebugBreak和abort调用了呢?
这个Debug Check是不是导致Debug下有闪退、Release下没有闪退的原因呢?经后面研究得知,确实是这样的,正是这个Debug Check导致Debug和Release下不同表现的。
于是Go到RTC_DCHECK_RUN_ON宏的内部实现代码:
果然是不满足条件时,就会调用rtc_FatalMessage接口,rtc_FatalMessage接口会调用FatalLog接口,这个FatalLog接口中会先调用DebugBreak、后调用abort强制将进程关闭掉。
这个地方有一个控制变量RTC_DCHECK_IS_ON宏,应该是通过这个宏去感知当前是不是Debug版本的,GO到RTC_DCHECK_IS_ON的定义处:
果然是和NDEBUG相关的,如果当前是Debug版本,RTC_DCHECK_IS_ON宏就被定义为1;如果当前是Release版本,则宏会被定义为0。
7.3、找到触发abort终止进程操作的最终原因
GO到RTC_DCHECK_RUN_ON内部,看看为啥条件不满足Check。内部调用了RTC_DCHECK宏,该宏中的判断条件是(x)->IsCurrent(),如下:
在rtcmediacontrol库中从WebRTC的音频设备管理类类继承出一个子类,在这个子类中对音频进行控制。上述判断条件是(x)->IsCurrent(),估计是判断调用RegisterAudioCallBack接口时所在线程是不是和创建ADM音频设管理类的线程是不是同一个线程,WebRTC内部要求这这两处函数调用所在的线程必须是同一个线程。
创建ADM音频设备管理类对象和对RegisterAudioCallBack接口的调用都是由组件层去做的,组件的同事查看代码得知,这两个操作确实不在同一个线程中执行的,一个是在singals线程,一个是在Worker线程中,所以不在一个线程中,所以调用RegisterAudioCallBack接口时触发Check失败,导致调用了rtc_FatalMessage接口,进而调用了DebugBreak和abort接口,所以导致程序启动时的闪退。
8、最后
本问题中,程序启动时会去调用RegisterAudioCallBack接口,会触发RTC_DCHECK校验不通过,然后触发DebugBreak和abort的调用,导致Debug版本程序闪退。但这个Check只在Debug下设置,Release下不会生效,所以Release下不会闪退。
之前音视频编解码组在对rtcmediacontrol自测时,主要进行的是Release下的自测。然后音视频编解码组将库发到组件那边,组件那边进行的也是Release下的联调,然后编译将新版本发布到我们产品流上,产品流上编译的Release安装包在测试机器上安装后运行也没问题,所以Debug下的闪退一直没暴露出来。直到我们产品这边需要更新底层库搭建最新的Debug运行环境时才暴露出来。