在调试字幕插件的时候,碰到了一个问题: 在切换文本字幕和图片字幕的过程中,会概率性的发生crash,串口显示signal 11,通过分析生成的coredump文件,得到了如下信息:
(敏感信息都以×代替,不影响分析问题)
(gdb) where
#0 0xf56317d0 in magazine_chain_pop_head () from /××/symbols/system/vendor/mmlib/libglib-2.0.so
#1 0xf56319c4 in magazine_chain_prepare_fields () from / ××//symbols/system/vendor/mmlib/libglib-2.0.so
#2 0xf5631c2c in magazine_cache_push_magazine () from / ××/symbols/system/vendor/mmlib/libglib-2.0.so
#3 0xf5632258 in thread_memory_magazine2_unload () from /××/symbols/system/vendor/mmlib/libglib-2.0.so
#4 0xf563275c in g_slice_free1 () from /××/symbols/system/vendor/mmlib/libglib-2.0.so
#5 0xf4f1b5bc in _gst_buffer_free (buffer=0xac2a4cb8) at gstbuffer.c:756
#6 0xf4f7205c in gst_mini_object_unref (mini_object=0xac2a4cb8) at gstminiobject.c:454
#7 0xdbadafc0 in gst_buffer_unref (buf=0xac2a4cb8) at /××/gst-out/usr/local/include/gstreamer-1.0/gst/gstbuffer.h:384
#8 0xdbadb1c4 in _gst_video_codec_frame_free (frame=0xac724c98) at gstvideoutils.c:43
#9 0xdbadb518 in gst_video_codec_frame_unref (frame=0xac724c98) at gstvideoutils.c:133
#10 0xdbacbfc8 in gst_video_decoder_release_frame (dec=0xab5461a8, frame=0xac724c98) at gstvideodecoder.c:2819
#11 0xdbaccc74 in gst_video_decoder_finish_frame (decoder=0xab5461a8, frame=0xac724c98) at ×.c:3041
#12 0xda3f83d6 in *function (self=0xab5461a8) at /××c/×.c:1753
#13 0xf4fcf814 in gst_task_func (task=0xab564e10) at gsttask.c:334
#14 0xf4fd0efc in default_func (tdata=0xab4347c8, pool=0xab101c58) at gsttaskpool.c:68
#15 0xf5645090 in g_thread_pool_thread_proxy () from /××/symbols/system/vendor/mmlib/libglib-2.0.so
#16 0xf56447e4 in g_thread_proxy () from /××/symbols/system/vendor/mmlib/libglib-2.0.so
#17 0xf6b905fc in __pthread_start (arg=0xd7119930, arg@entry=<error reading variable: value has been optimized out>) at bionic/libc/bionic/pthread_create.cpp:199
#18 0xf6b68236 in __start_thread (fn=<optimized out>, arg=<optimized out>) at bionic/libc/bionic/clone.cpp:41
#19 0x00000000 in ?? ()
(gdb)
对应的log文件也明确的显示,crash的确是发生在上面trace所显示的地方。
通过上面的信息,基本可以判断是释放gstbuffer的时候,内存操作时发生了空指针错误,但是在trace显示的代码里仔细检查了很久,确定这部分没有使用上的错误。
关键是测试中,问题发生没有明显的规律,所以很是挠头。
在该部分功能初步调试时,曾发生了一件比较奇怪的事情,就是我们缓存起来的gstbuffer有时候从缓存中去出来的时候,发现其pst发生了变化。由于该gstbuffer的指针会被传到后面的很多插件中,所以很难确定是谁对此做了修改,当时没有深究,只是从缓存中取出来的时候,做了一下判断,并采取了一些措施,使其不会影响正常的功能。
就是问题陷入僵局的时候,又发现了一个问题:有时候从queue中取出的audio数据会莫名其妙的变成了video数据,确定在queue中不会有人去故意touch数据,所以我们开始怀疑是该部分内存数据被覆盖了,也就是说,有人错误的提前释放了这块内存,被系统冲行进行了分配。
结合前面的第二个问题,进一步印证了我们的想法;
再回到crash的问题,应该也是应该被操作释放的内存,已经被人错误的释放过了,所以才会导致空指针的错误。
但是这些问题的线索里,涉及到了字幕,音频和视频数据,所以从不同的插件中进行详细的排查。
最后的结果与分析的相符,有一个地方,会对图片字幕的gstbuffer进行延后释放,但是该buffer会被即时的push到后面的插件中,并在使用完毕后进行unref; 问题就是在延后释放的时候也会进行unref,这个unref非常危险,因为由于延后的时间不确定,这个时候unref的buffer可能已经被系统重新分配了(甚至好多次了),不再是当时的buffer了。
这个分析很好的解释了上面出现的几个问题,并且修改后问题都的到了解决。
由于在gstreamer中,buffer会经过很多了插件,所以这种问题犯错的地方跟出错的地方可能相隔十万八千里,通过coredump的trace很难直接定位问题,需要仔细分析整个流程中buffer的处理过程,确保不会多或者少unref任何一个buffer。
知道原因后回推总是比较简单,正向分析中,有几个点需要注意:
1)如果buffer会被通过指针传向多个插件,需要特别注意ref和unref的计算。
2)如果自己buffer出现了比较奇怪的修改,首先确定是否有插件会做类似的修改,如果没有,就要考虑该buffer可能已经被系统回收再分配了,也就是说,有人偷偷的给你释放过了,这时候虽然你的buffer指针不为空,但是里面的数据已经发生了变化。
3)由于gstreamer中的内存管理采用了glib的slice机制,最后释放的内存会最可能被再次分配,甚至连尺寸都不变,只是里面的数据发生了变化,一旦被错误的释放,很容易在短时间内被再次分配,你手里拿的指针不为空,也就无法通过NULL Pointer check的方式检查出来,但是,这时候对其进行操作,后果很难预料。
4)另外,调试时有个比较笨的办法,在ref和unref的地方,不妨将buffer的指针打印出来,这样就可以跟踪这块内存被分配和释放的过程,有利于分析问题。