音视频开发:分析一个Gstreamer的插件的Segment Fault

发布于:2024-12-21 ⋅ 阅读:(19) ⋅ 点赞:(0)

最近的项目中,使用了Gstreamer的rtmpsink来推送RTMP到流媒体服务器,但是总是遇到奇怪的段错误,读了一下rtmpsink的源码。发现虽然有问题,但是可能会有编码上的问题,但是还不至于有段错误。

于是,觉得有可能是rtmpsink使用的librtmp库的问题。

coredump

使用

coredumpctl gdb

这里记录一下,coredumpctl这个命令。

coredumpctl使用list可以显示崩溃的coredump的列表,之后使用gdb加载的时候,可以直接通过PID来加载指定的coredump。

如,我们执行

coredumpctl list

返回了:

Wed 2024-12-18 15:11:31 CST  1422072     0     0  11 present   /bin/bash  
Wed 2024-12-18 16:46:17 CST  1427297     0     0  11 present   /bin/bash  
Wed 2024-12-18 17:20:12 CST  1429745     0     0  11 present   /bin/bash  
Wed 2024-12-18 17:31:28 CST  1430341     0     0  11 present   /bin/ls  
Wed 2024-12-18 18:35:07 CST  1438184     0     0  11 present   /bin/yes  
Thu 2024-12-19 09:14:29 CST  1477445     0     0  11 present   /bin/bash

,其中第一列是时间,第二列(CST之后)那列,就是PID号。

比如我们这时候执行:

coredumpctl gdb 1429745

,就可以把12月18号晚上五点二十那次崩溃的coredump加载进来。

librtmp

我们加载了gstreamer应用的coredump,之后通过gdb的bt命令,看到了崩溃的函数位于librtmp.so的RTMP_Write函数里面。

因为默认安装的librtmp没有安装debug-info,所以通过debuginfo-install librtmp来安装,之后就可以看到具体的调试信息。

安装之后,我们再次加载,通过bt查看堆栈,就看到了具体信息:

Program terminated with signal SIGSEGV, Segmentation fault.
--Type <RET> for more, q to quit, c to continue without paging--
#0  0x00007f646a400489 in ?? () from /usr/lib64/libc.so.6
[Current thread is 1 (LWP 1477490)]
(gdb) bt
#0  0x00007f646a400489 in  () at /usr/lib64/libc.so.6
#1  0x00007f646803ac00 in RTMP_Write
    (r=0x11c4000, buf=0x7f64540849d5 "\353Lu\251\215\236}D^M\253<@3\005\005\003{\036", <incomplete sequence \346>, size=65507) at rtmp.c:5165
#2  0x00007f6469acd36c in gst_rtmp_sink_render (bsink=0x11b5880, buf=0x7f645403d360) at gstrtmpsink.c:271
#3  0x00007f64699f60ee in  () at /usr/lib64/libgstbase-1.0.so.0
#4  0x00007f64699f6e60 in  () at /usr/lib64/libgstbase-1.0.so.0
#5  0x00007f6469f3f21b in  () at /usr/lib64/libgstreamer-1.0.so.0
#6  0x00007f6469f47343 in gst_pad_push () at /usr/lib64/libgstreamer-1.0.so.0
#7  0x00007f64690862a9 in  () at /usr/lib64/gstreamer-1.0/libgstcoreelements.so
#8  0x00007f6469f3f21b in  () at /usr/lib64/libgstreamer-1.0.so.0

此时,我们输入f 1,进入RTMP_Write的栈帧,得到了:

(gdb) f 1
#1  0x00007f646803ac00 in RTMP_Write (r=0x11c4000, 
    buf=0x7f64540849d5 "\353Lu\251\215\236}D^M\253<@3\005\005\003{\036", <incomplete sequence \346>, 
    size=65507) at rtmp.c:5165
5165          memcpy(enc, buf, num);
(gdb) 

看到这里,很明显这几个参数有内存越界了,所以我们打印变量值验证:

(gdb) f 1
#1  0x00007f646803ac00 in RTMP_Write (r=0x11c4000, 
    buf=0x7f64540849d5 "\353Lu\251\215\236}D^M\253<@3\005\005\003{\036", <incomplete sequence \346>, 
    size=65507) at rtmp.c:5165
5165          memcpy(enc, buf, num);
(gdb) p enc
$1 = 0x7f6454028bd2 ""
(gdb) p buf
$2 = 0x7f64540849d5 "\353Lu\251\215\236}D^M\253<@3\005\005\003{\036", <incomplete sequence \346>
(gdb) p num
$3 = -2
(gdb) 

num竟然是-2。

我们显示一下当前的源代码,看看这个-2是怎么来的。

(gdb) p num
$3 = -2
(gdb) l
5160              enc = pkt->m_body + pkt->m_nBytesRead;
5161            }
5162          num = pkt->m_nBodySize - pkt->m_nBytesRead;
5163          if (num > s2)
5164            num = s2;
5165          memcpy(enc, buf, num);
5166          pkt->m_nBytesRead += num;
5167          s2 -= num;
5168          buf += num;
5169          if (pkt->m_nBytesRead == pkt->m_nBodySize)
(gdb) p s2
$4 = -2
(gdb) 

原来,s2就是-2了。

而具体这个s2为什么会变成-2,就可以通过源码看一下了。

我们打开这个版本的librtmp的源码,找到RTMP_Write函数的实现:

  1 int
  2 RTMP_Write(RTMP *r, const char *buf, int size)
  3 {
  4   RTMPPacket *pkt = &r->m_write;
  5   char *pend, *enc;
  6   int s2 = size, ret, num;
  7 
  8   pkt->m_nChannel = 0x04;       /* source channel */
  9   pkt->m_nInfoField2 = r->m_stream_id;
 10 
 11   while (s2)
 12     {
 13       if (!pkt->m_nBytesRead)
 14         {
 15           if (size < 11) {
 16             /* FLV pkt too small */
 17             return 0;
 18           }
 19 
 20           if (buf[0] == 'F' && buf[1] == 'L' && buf[2] == 'V')
 21             {
 22               buf += 13;
 23               s2 -= 13;
 24             }
 25 
 26           pkt->m_packetType = *buf++;
 27           pkt->m_nBodySize = AMF_DecodeInt24(buf);
 28           buf += 3;
 29           pkt->m_nTimeStamp = AMF_DecodeInt24(buf);
 30           buf += 3;
 31           pkt->m_nTimeStamp |= *buf++ << 24;
 32           buf += 3;
 33           s2 -= 11;
 34 
 35           if (((pkt->m_packetType == RTMP_PACKET_TYPE_AUDIO
 36                 || pkt->m_packetType == RTMP_PACKET_TYPE_VIDEO) &&
 37             !pkt->m_nTimeStamp) || pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
 38             {
 39               pkt->m_headerType = RTMP_PACKET_SIZE_LARGE;
 40               if (pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
 41                 pkt->m_nBodySize += 16;
 42             }
 43           else
 44             {
 45               pkt->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
 46             }
 47 
 48           if (!RTMPPacket_Alloc(pkt, pkt->m_nBodySize))
 49             {
 50               RTMP_Log(RTMP_LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
 51               return FALSE;
 52             }
 53           enc = pkt->m_body;
 54           pend = enc + pkt->m_nBodySize;
 55           if (pkt->m_packetType == RTMP_PACKET_TYPE_INFO)
 56             {
 57               enc = AMF_EncodeString(enc, pend, &av_setDataFrame);
 58               pkt->m_nBytesRead = enc - pkt->m_body;
 59             }
 60         }
 61       else
 62         {
 63           enc = pkt->m_body + pkt->m_nBytesRead;
 64         }
 65       num = pkt->m_nBodySize - pkt->m_nBytesRead;
 66       if (num > s2)
 67         num = s2;
 68       memcpy(enc, buf, num);
 69       pkt->m_nBytesRead += num;
 70       s2 -= num;
 71       buf += num;
 72       if (pkt->m_nBytesRead == pkt->m_nBodySize)
 73         {
 74           ret = RTMP_SendPacket(r, pkt, FALSE);
 75           RTMPPacket_Free(pkt);
 76           pkt->m_nBytesRead = 0;
 77           if (!ret)
 78             return -1;
 79           buf += 4;
 80           s2 -= 4;
 81           if (s2 < 0)
 82             break;
 83         }
 84     }
 85   return size+s2;
 86 }   

这个实现读下来,嗯……真是一言难尽。

我们可以清楚地看到,为什么s2会变成-2。

因为:完全没有验证过啊!

虽然循环开头认真地校验了size的值:

 15           if (size < 11) {
 16             /* FLV pkt too small */
 17             return 0;
 18           }

可是,这里的size从来也没有更改过,更改的是s2!当然就解包越界就崩溃了。

rtmpsink

而且,通过这个函数实现,我们还发现,rtmpsink里面的代码也是问题很大的。

我们看到这个函数的末尾:

 81           if (s2 < 0)
 82             break;
 83         }
 84     }
 85   return size+s2;
 86 }

这个返回值,就很不明确。

s2小于0的时候,返回了与原缓冲区大小的和。我们只能猜测这是让我们上层的实现,通过判断这里是不是小于输入的size,来表示这里还差多少字节的解析拼包吧!

但是,rtmpsink里的实现:

   263   if (sink->have_write_error)  
   264     goto write_failed;  
   265    
   266   GST_LOG_OBJECT (sink, "Sending %" G_GSIZE_FORMAT " bytes to RTMP server",  
   267       gst_buffer_get_size (buf));  
   268    
   269   gst_buffer_map (buf, &map, GST_MAP_READ);  
   270    
   271   if (RTMP_Write (sink->rtmp, (char *) map.data, map.size) <= 0)  
   272     goto write_failed;  
   273    
   274   gst_buffer_unmap (buf, &map);  
   275   if (need_unref)  
   276     gst_buffer_unref (buf);  
   277    
   278   return GST_FLOW_OK;

完全没处理这种边界情况。只要是>0,就都返回GST_FLOW_OK。

至此,一个崩溃问题就分析差不多,需要从几个方面完善地修复bug了。


网站公告

今日签到

点亮在社区的每一天
去签到