与CI和链接大战三百回合
Homura 我摸到了!

54707001_p0.png

攻撃隊、出撃!Vorwärts! pixiv:54707001

噩梦开始的地方

在C#的CI测试中(目前仅开启了ubuntu)DllImport报错DllNotFoundException。而报错的位置是我对自己搞的一个capi做的C#包装

本地尝试

遇到这种问题,我的第一反应还是先在本地的环境确认一下,这样的做法相对来说成本低很多,能够初步确认一些问题(但是由于自己配的环境的影响会导致很多问题无法排查)

自然本地是失败了,切到了Windows的机器上依然失败,又切到了公司内部的ubuntu服务器(我没有做过什么环境配置,可以认为相对干净一些)依然是失败了

查看类似的情况

项目中也有其他使用我们自己做的C#包装的测试,因此我想到了确认一下它的正确性。我使用了自己的分支,而主分支是没有问题的。自己这里确认的过程中还是不够严谨,应该直接查看对应测试成功与失败的用例的执行情况,只是用这样想当然的想法来考虑。

CI调试神器

各种尝试无果(忘了做了哪些,总之都没有效果),之后尝试在Windows和mac下进行测试,这两者居然是能通过的。

一时之间也没能想到有什么决定性的因素,后来想到以前看过本地模拟ci环境的东西,因此去搜索关于ci调试的信息,并且发现了这个神器

1
2
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3

只要将这一段加到GitHub action的yaml文件中,即可在执行到这里的时候停住。此时会不断刷新ssh连接的命令。

进入CI后

我做的第一时间是检查so本身是否存在问题

像以往一样写了一个最简单的main.c,之后 gcc main.c -L . llibname 查看报错。

我一直使用这样的方式来检查实际链接的时候因为哪些符号是undefined导致链接挂掉,简单易实施,久而久之也开始潜意识的认为这样能过链接就没有问题了,还是对链接了解不够。

这里出现了一个我忽略了的问题,也正是这个问题导致我浪费了大半天时间。因为这个库是我自己写的自己编的,不会依赖于系统库之外的so,我潜意识认为这里不会出问题,所以我没有使用ldd进行确认链接状态

启动一个裸docker测试

前面那一步做好也就不会有后面的那么多操作了..总之后面的操作也回顾一下

为了和跑测试的ci版本一致,启动了一个ubuntu18.04的docker(后面可以看到,幸好我这里选择了一致的版本,不然可能解决问题的时间需要更久…)

由于是非常干净的镜像,什么都没有,折腾了半天安装所需要的基本组建,开始编译并且执行测试,依然是存在问题。之后也没什么好思路,后来跑了一下上面提及的类似性质的相关测试的正确性,发现docker中也是错的。(这里如果我之前更严谨的确认了可能也会减少一些重复过程)

之后我就喊实现这块的同事和我一起看问题,切到了主分支测试也不能通过。后来不记得为什么了我随手敲了个ldd看了一下他的so,他看到了错误信息,一提醒我才看到。自己思考问题经常会钻牛角尖,以及经常会忽略掉一些信息,有的时候换个人从旁观者角度来看会好很多,自己想切换到旁观者角度还是有些难。

GLIBC_x.xx not found

看到的错误信息是这样的

1
./libxxx.so: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBCXX_3.xx' not found (required by ./libxxx.so)

由于这是一个非常干净的ubuntu,所以甚至没有这个东西。这个时候我意识到了自己的库在CI中会不会也是类似的原因,我之前是否ldd检查过,检查过的话是否是忽略了这么重要的调试信息?(这个时候由于没有清晰的思路和严谨的做法,开始怀疑之前是否做过这个测试)

随后意识到在ci中会不会也是类似的问题。于是进入了ci调试器,看到了

1
./libxxx.so: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by ./libxxx.so)

测试能通过的so中是没有类似的错误信息。使用strings /usr/lib/x86_64-linux-gnu/libc.so.6 查看其中的glibc版本信息,发现其中并没有我们所需的版本(注意不要去看libc.so,它虽然叫so但不是动态链接库,用file可以看到实际上是一个文本文件)

这时可以确认问题就在这里了。

“失误”

这个时候我觉得离谱的是这个库我是通过github ci编译的,最后ci不能通过,也许还有什么因素应该控制但是我没有做好的。

本来还想吐槽这个,最后想办法处理GLIBC版本不一致的时候发现自己编的时候用的是ubuntu-latest的ci…而测试的地方是18.04。这一部分是我不知道从哪里直接就拷贝过来的,没有确认是否有问题就进行拷贝(之前也没有遇到过这样的情况,确认可能也不会想到这一点。但是我也确实没有进行过核对)

同时这次的经历意识到了自己编的库还是应该尽量依赖低版本的glibc

关于glibc和GLIBC_XX

这个我整理起来发现东西不少,以及要控制一下文章长度,因此整理到了另一篇博客上

https://homura.live/2022/03/29/glibc-version/

问题总结

  1. GLIBC的version版本对应不上(错误的根本原因)
  2. 在写编译的ci的时候没有认真写,而是随便找了一个抄上就完事(错误的产生原因)
  3. 没有一套严谨的确认问题的思路(花费了我大半天的原因)

太多意想不到的地方出了问题,不论是ldd还是最后发现是系统版本错,也许是自己在这些地方都没有太注意,最后各种小问题堆积太多导致出现了这种坑。

事后诸葛亮

在此写一下理想的解决思路应该是怎么样的

  1. 发现CI出现问题,查看错误是DllNotFoundException
  2. 本地相同系统测试能通过,那么要确认查找的过程没有问题。多半是要排除掉环境变量的影响因素
  3. 确认路径查找是没有问题的以后进入CI环境查看dll的状态
    1. ldd查看依赖是否满足(本次错误看到a即可)
    2. 如果依赖满足的则再手动链接查看是否有undefined的符号之类
  4. 发现是glibc的问题,确认是否真的找不到版本(这里我真的遇到过(虽然是GLIBCXX)…安了conda以后因为会先找到conda的glibc,而这个版本可能又是不合要求的,然后就会报错了..解决方案是直接修改链接,如果你遇到类似的问题一搜就能搜到解决方案)
  5. glibc通常和环境有关,真的是没有匹配版本那么要查看环境的不同
  6. 由于是ci环境,那么肯定首先要看ci的yaml文件,对比配置的差异(问题解决)

先确认第三步其实也可以,最好的情况下我们先去做了第三步是会省了第二步的问题。但我觉得这种dll链接相关的路径查找的问题可能更多一些,总之这些都是需要确认的步骤。

这个思路是在本地能够通过测试的前提条件下。需要调的这种情况一般是要合并了之类,这种情况肯定是要先在本地做好测试的。没有做好的情况肯定是要先测试的

一些想法

自己经常会遇到这种离谱的问题,然而每个问题看起来再离谱最终都是会找到原因的,而且这个原因往往出现在意想不到的地方。想要减少这种现象的出现,只有明确自己行为的后果,以及不断踩坑的过程中形成一套自己的应对策略。(如果是别人的库那首先要注意的肯定是查看相关位置的源码)

这些问题的解决方式大多貌似都是口口相传,因此我打算再遇到这样类似的问题就进行一个记录。记录下我的思路,最终是如何解决问题的以及在这个途中有什么错误的想法,尽量避免第二次犯同样的错误,不断反思形成一套自己的解决问题方案,同时又会重新回顾在这之中有什么细节或者知识点是遗漏的。以后会增加更多这样的博客

遇到这种问题很重要的一点是如果不能调试,那解决问题可能要付出成倍的时间代价。像这个例子如果我不进去ci环境查看,也比较难确定是否真的是版本问题,不断更新ci打log也可以,但是非常非常麻烦且低效。

后面应该再写一个链接问题定位的博客(写的话大概会说一些自己踩过的坑)。我的思路未必全面,但是一定有着参考价值(下次一定,在新建文件了,难不成我还能咕咕咕吗)

  • 本文标题:与CI和链接大战三百回合
  • 本文作者:Homura
  • 创建时间:2022-03-29 23:34:17
  • 本文链接:https://homura.live/2022/03/29/Problem/solve-ci-and-link/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!