最近碰到了一个很奇怪的事情,有一个新的设备已经购买过来准备测试用,需要准备对应的驱动插件, 但是这个驱动插件在写好后,实际运行的时候会导致我很久以前写的一个插件崩溃。因为较低优先级, 所以负责的人并没有投入很大的精力去查这个问题,甚至给出了一个反而更加让人云里雾里的答复。 不过我还是希望能得到一个准确合理的答案,在国庆期间花了一点点时间去排查,最终得到了结果,排查过程如下:
假如手中有一个主程序 main,这个程序会以某种顺序加载插件,插件以相同的类模板编译成的 so 文件形式存在。 现在有若干插件,记为 A, B。
已知,插件 A 动态链接中包含了 libADependency.so,插件 B 动态链接中包含了 libBDependency.so。
当运行主程序 main 时,程序先加载了 A,后加载了 B, 发生 CoreDump,通过 gdb 查看,
显示的报错位置为 libBDependency.so 中的函数 TEST::TestFunc
生成的对象 TEST::TestObj
的类内函数 TestInstanceFunc
调用。
此时将插件 A 删除,再次运行主程序 main,运行一切正常,没有发生任何报错,也没有出现任何异常; 但恢复插件 A 后,运行主程序 main, 仍然会出现 CoreDump。
根据他人的测试反馈,若使用 debug 模式编译插件 B ,就会出现上述现象, 但若使用 release 模式进行编译,则不会出现上述现象。 ~虽然这个结论是错误的~
更换不同的几台设备,尽可能配置相同的环境后,运行程序 main 并加载 A、B 两个插件,会出现部分设备能复现, 但其他设备不能复现,也就是复现率不是百分百,尤其是我的主力机,就属于无法复现的设备之一,无论插件 B 在 编译时是使用 debug 模式还是 release 模式。
经过这些能复现问题的日志和不能复现问题的日志的比对,发现当先加载插件 A, 再加载插件 B 时,CoreDump 现象就会出现;当先加载插件 B,后加载插件 A 时,现象就消失。 于是配置环境并修改 main 源代码,让插件 A 的优先级远先于其他任何插件,经过这般操作,即使是此前无法复现该现象的主力机,也成功复现了该现象。 并且发现,无论插件 B 是以 debug 模式还是 release 模式进行编译,都仍然会发生 CoreDump 的现象。
由于插件 A 是新引入的插件,且 libADependency.so 仅插件 A 使用,而插件 B 已在各种情况下运行了很久, 并且 gdb 显示的报错位置也是动态库 libBDependency.so 中用了很久的函数,正常情况下不应会报错, 所以问题的根源在插件 A 或其动态链接的库,但插件 A 和其动态链接的库都没有明确使用了 libBDependency.so 的东西。
由于发生 CoreDump 的位置没有做异常处理 (~因为没想到会在这个地方崩溃,一般这里崩溃是无法分配到资源了,但使用场景下可以保证能够分配到~), 于是修改成了下述代码:
TEST::TestObj TestObject = TEST::TestFunc();
try {
TestObject.TestInstanceFunc();
}
catch (const TEST::exception& Err) {
Log::inf("Error info: %s", Err.what());
}
catch (const std::exception& Err) {
Log::inf("Error info: %s", Err.what());
}
catch (...) {
Log::inf("Unknown error happend");
}
再次运行程序 main, 仍然发生 CoreDump,但却没有捕捉到异常,此时怀疑问题并不在插件 B 的源代码中。
此时编译其他已有的插件并再次运行 main,发现,gdb 显示插件 C、D 同样会出现相同现象,且报错位置都和插件 B 相同,即同样通过 TEST::TestFunc
生成了 TEST::TestObj
对象,并随后调用了 TestInstanceFunc
。
由于插件 B、C、D 使用 libBDependency.so 的方式是独立实现的,理论上若是代码实现上的问题的话,不应该这些插件都会有问题,尤其是已经在各种情况下都运行过很久的前提下。
由于插件 A 中的代码没有任何和 libBDependency.so 相关的内容,因此怀疑是 libADependency.so 中静态链接了不同版本的 libBDependency.so,导致加载插件后,程序的符号表冲突(~理论上说,谁先加载并不代表谁的符号表会优先被使用,这个属于未定义行为~)。
此时(~不知道为啥,可能脑抽吧~)继续修改插件 B 的源码,修改为
TEST::TestObj TestObject = TEST::TestFunc();
try {
TestObject.TestOtherInstanceFunc();
}
catch (const TEST::exception& Err) {
Log::inf("Error info: %s", Err.what());
}
catch (const std::exception& Err) {
Log::inf("Error info: %s", Err.what());
}
catch (...) {
Log::inf("Unknown error happend");
}
仍然报错,并尝试了其他的类内函数也仍然报错。也就是说,TEST::TestFunc
返回的对象实际上不可用。
切换到 libADependency.so 所在目录,运行下列命令
strings libADependency.so | grep TEST
结果显示 libBDependency.so 中的所有函数都在其中,也就是说 libADependency.so 在编译时静态链接了某个版本的 libBDependency.so(~没有实际使用,但你不用为啥要静态链接进去啊~),而且静态链接的版本与插件 B、C、D 所使用的 libBDependency.so 不相同, 才会导致这次的 CoreDump 问题。