跟踪突然开始发生的段错误

2021-01-18 15:54:15

我想分享一个有关本周末帮助追踪的细分错误的故事。我认为segfault的最终根本原因很有趣,因为它与我要调试的代码无关。

我一直在维护obs-ios-camera-source的Linux分支,这是一个OBS插件,可让您将iPhone或iPad的摄像头和麦克风用作OBS中的视频和音频源。它与App Store中的“ Camera for OBS Studio”应用程序配合使用。对于想使用手机的摄像头而不是购买单独的摄像头的在线直播者来说,这种方法非常有用。对于不认识的人,OBS是Open Broadcaster Software的缩写。许多流媒体使用它来处理广播他们的流。它使您可以捕获音频和视频,将它们混合在一起,用它做各种很酷的事情,然后记录最终结果和/或将其流式传输到YouTube和Twitch等网站。

使这个插件在Linux上运行并不是很复杂,因为它已经写得很好,没有太多平台特定的代码。毕竟,现有的代码库已经在macOS和Windows上运行。它只是需要调整一些编译/链接选项,以使代码在Linux上愉快地运行。

无论如何,我敢肯定,有很多人一直在使用我的此插件的Linux端口而没有任何问题。我知道当我在Ubuntu 18.04或20.04中对其进行测试时,它对我来说很好用。我已经帮助其他发行版的人使其正常工作。我本人实际上并没有做任何流式传输-也许有一天!

星期五,GitHub用户rrondeau报告了一个问题:obs-ios-camera-source插件运行半年后没有出现问题,它突然开始导致OBS在他的计算机上进行段错误(当前运行Fedora 33)。他提供了堆栈跟踪,该跟踪表明段错误正在发生,原因是该插件启动了某些操作。之后,他使用GDB获得了更好的堆栈跟踪,该跟踪提供了有关调用函数和传递参数的更多信息:

实际的段错误发生在libsamba-sockets-samba4.so中一个名为“ socket_send”的函数内部,该函数由libusbmuxd中的一个函数调用,该函数作为obs-ios-camera-source插件源代码和用于通过USB与iOS设备通信。当我第一次在堆栈跟踪中看到此消息时,我的脑子想:“嗯……那很奇怪。为什么libusbmuxd不使用Samba的套接字代码而使用Samba的库呢?” (Samba是几乎每个Linux发行版都使用的Windows文件共享协议的实现)

我经过测试,无法在Ubuntu中重现该问题。我对Fedora基本上一无所知,但是我通过获取Fedora 33虚拟机,安装OBS以及编译插件来伪装自己的方式。我碰到了他所看到的完全相同的问题。

在我有机会深入了解并了解发生了什么之前,rrondeau击败了我一个正确的结论:Samba库中的代码被错误地调用了。 libusbmuxd有一个名为socket_send的函数,但是显然libsamba-sockets-samba4的函数(也称为socket_send)被意外地调用了。

老实说,这就是我们真正需要知道的。将libusbmuxd的socket_send函数重命名为其他函数,并更新对它的所有引用以使用新名称,从而解决了此问题。我仍然想了解为什么在此之前一切正常,却突然变成一个问题。我们为什么要调用Samba库?为什么iOS USB多路复用库甚至考虑与与Windows文件共享关联的库进行对话?

不知道该问题的答案令我感到困扰。我决定更深入地挖掘并确切地了解发生了什么。我从使用ldd开始,它列出了程序或库使用的所有动态库:

我截断了输出,因为它吐出了很长的库列表。从ldd的输出中可以看到,obs-ios-camera-source.so取决于libsamba-sockets-samba4.so。 ldd还会列出所有递归依赖项,并且我在插件源代码中找不到对“ samba”的任何引用,因此这可能是间接依赖项。我通过使用readelf仅显示直接依赖项来确认这一点:

在这一点上,我使用了ldd和readelf遍历了依赖树,并找出了与Samba库实际链接的内容。后来我知道我可以安装lddtree(pax-utils软件包的一部分)来自动执行此操作。无论哪种方式,这都使我发现通过libsmbclient包含了Samba库,而libsmbclient是libavformat(FFmpeg的一部分)的依赖项。 libavformat是libobs的依赖项。

在Ubuntu上重复该实验表明,Ubuntu上的libavformat不依赖libsmbclient。这解释了为什么我无法在Ubuntu上重现该问题。那么,为什么Fedora的(以及RPM Fusion的)libavformat版本依赖于libsmbclient?

事实证明,这是FFmpeg的编译时选项。 libavformat包含用于使用libsmbclient与Windows服务器通信的代码,但是您可以选择在编译时启用该选项。显然,Ubuntu选择不启用它,但RPM Fusion可以。实际上,我在RPM Fusion的提交邮件列表中找到了确切的帖子,其中添加了补丁以在FFmpeg中启用SMB支持。该补丁导致了整个问题的发生。如果Ubuntu的FFmpeg版本是在具有SMB支持的情况下构建的,那么我们早就已经看到了。对RPM Fusion的承诺是在2020年12月31日做出的,这解释了为什么rrondeau直到最近才开始发现该问题。

根本原因是,obs-ios-camera-source插件链接到两个都提供了名为socket_send函数的库:libsamba-sockets-samba4(通过libobs间接)和libusbmuxd。 libusbmuxd是静态链接的,但这并不能阻止其中的功能通过动态链接规则解析。因此,即使libusbmuxd是具有自己的socket_send内部实现的静态库,它仍在使用libsamba-sockets-samba4的实现。

我和rrondeau决定改变我们的控制权:嵌入在插件源代码中的libusbmuxd源代码。我们只需要在所有socket_函数之前添加一个“ usbmuxd_”前缀即可。可能有更复杂的方法通过链接器选项强制它使用自己的内部版本的socket_send,但我认为这可能是最简单的解决方案。它易于实施,并且可以完成工作。

事实证明,此段错误是一个非常简单的问题,需要解决和诊断。真的值得写博客文章吗?也许吧,也许不是。我绝对可以预见会有其他库组合出现此问题。 socket_create,socket_close,socket_send等是此类通用名称,可能会再次发生。这是一个提醒大家的好机会:不要在共享库中使用这样的通用函数名称,至少不要在导出的符号中使用!您很容易遇到类似这种情况。我认为,对于图书馆的导出符号而言,前缀绝对是个好主意。在这种情况下,libusbmuxd和Samba都违反了该准则。

这可能很棘手,因为默认情况下,除非另行指定,否则Linux上的动态库默认情况下会导出所有符号。这与Windows使用DLL的方式相反。 Windows DLL要求您指定要导出的功能。我实际上更喜欢这种方法!这是有关如何自定义Linux动态库符号的可见性的有趣参考。

libusbmuxd已经很早就解决了这个问题-它们现在仅导出打算公开的函数,这些函数具有usbmuxd_或libusbmuxd_前缀。我认为该插件的源代码中包含的版本要旧很多。为了娱乐,我尝试将链接修补程序中的可见性修补程序应用于插件的嵌入式libusbmuxd源代码。这些补丁不能完全适用,因为嵌入式libusbmuxd代码实际上是使用CMake构建的,因此我必须将编译器标志添加到CMakeLists.txt。之后,确实确实会导致libusbmuxd内部的socket_send函数被调用,从而修复了段错误。

你怎么看?试图说服Samba项目重命名其导出的套接字函数是否有意义,还是我会选择错误的树?我怀疑Samba的套接字库实际上是有意导出这些函数,以便其他Samba库可以调用套接字函数。重命名Samba导出的套接字函数以减少通用名称会导致大量不兼容,因为这些名称已经存在了多久?现在太晚了吗?我以为Samba导出的套接字函数应该带有“ samba_”前缀或类似名称是我错了吗?