C++程序某动态库的日志配置文件被修改问题定位

首先介绍下,整套程序包括fdTestC、fdTestS、Server。
fdTestC主要有三部分组成:主程序fdTestC,调用库libR.so、libthird.so.

  • 业务功能关系: fdTestC通过libR.so与fdTestS交互; fdTestC通过libthird.so与Server交互。
  • libthird.so是一个壳,会下载真正的客户端库(客户端库需要一个日志配置文件,只有第一次初始化会创建,后边只读,然后传到zlog模块中,里边有文件锁),然后和对应的服务端交互。
  • fdTestC和fdTestS其实是一个程序,只是工作模式不同。

问题现象:程序运行过程中,libthird的日志配置文件被修改了(内容是fdTest的业务日志),进而导致所有与libthird相关业务失败。

问题定位过程:
从问题现象上分析,有两个猜测:fdTestC截取了libthird的日志文件句柄,或者是 libthird模块读取了fdTestC的buffer。
第一阶段:看日志及代码排查分析。
1、首先排查日志模块,日志是一个单例(和程序生命周期相同),里边一把锁,所有文件操作时上锁,切换文件时传过去。不太可能去拿其他句柄;
2、写入配置文件的日志,在fdTestC的日志文件中可以找到相同内容,出现的两次都是在日志文件中间,因此猜测1不太可能。(事后一段时间复盘,应该是最大怀疑点
3、分析libthird代码,业务代码第一次生成是写入固定内容,文件存在则只打开,没有写操作,然后就传入zlog。zlog作为开源库,可信度还是比较高的而且,里边有文件锁。 猜测2感觉也不现实。

第二阶段:hook跟踪
4、踩内存,用asan编译主程序跑了一把,未抓到有效内容(事后分析原因:asan原理是编译时插入桩代码,重实现malloc和free,因此可以排除主程序问题);
5、用hook勾住fwrite、write、puts、put,拷机复现后,看未抓取到有效堆栈(事后分析,事故函数用的printf,因此给漏了);
6、猜测可能是libthird的频繁初始化导致,重构优化fdTestC中关于libthird的初始化方法,一个server只init一次,问题不再出现;以为规避解决。
https://blog.csdn.net/whbing1471/article/details/112394403?spm=1035.2023.3001.6557&utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-2defaultOPENSEARCHRate-1.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task-blog-2defaultOPENSEARCHRate-1.nonecase

然而,fdTestS再次跑出配置文件被修改问题。需要继续定位,汗。。。。
第三阶段:写demo自测(事后发现,作用不大)
7、由于时隔很久,问题不太记得了,再次分析了一波代码,再次分析两个猜测可行性不大;
8、fdTestS加入测试程序,模拟出问题场景,拷机测试10天未复现。(期间放假)

各种扯皮,有规避手段,但是实施计划受限,需要继续定位。
第四阶段:回退程序,联手加日志定位排查
9、回退fdTestC到问题版本,fdTestC的日志中加入fd打印,libthird中配置文件的开关操作也加入打印,拷机复现,发现配置文件被修改的时候,libthird中配置文件被打开的fd是1. 没错,是标准输出;
10、从而理清了事故发生现场原因,fdTestC日志模块会写日志文件,同时写控制台,后台模式运行时,会写入fd 1(指向/dev/null)。如果fd 1被修改指向配置文件,那么配置文件就被修改了。再次运行程序,拷机观察fd,句柄1确实经常被改动。离问题真相貌似不远了。(事后一段时间复盘,应该可以中止了,1就是printf,当时意识到
11、再次,hook勾住fclose和close,拷机复现。(期间因为LD_PRELOAD加的位置不对,导致不生效,定位了一段时间《hook对第三方库也是生效的》)。复现后,分析堆栈是在libthird的epoll中,想不通。

第五阶段:怀疑踩内存
12、主程序和所有库(libthird的库未编译)都用asan编译,拷机复现踩内存问题,未复现;(事后一段时间复盘,不需要
13、结合堆栈分析存储日志和代码,怀疑是存储库代码导致。
原因:存储库机制,事件线程专门处理各种事件,然后标记fd;有专门的检测线程专门去清理异常fd及相关资源。存储库destroy、或者存储库socket检测到服务端异常时,会去标记fd,这时检测线程把fd及资源给关了,但是事件线程不知道,还有处理,导致异常。
14、修改为:异常时,先处理完,再标记,验证ok;
15、问题解决。

问题回顾:
1、问题出现后,应该把被修改文件的地方都加上fd打印,然后拷机复现(直接挂hook可能抓不全,printf这种想不到);
2、asan踩内存应该所有相关模块都编译,然后拷机运行检测;
3、hook会对程序和所有库都生效,不过位置要加对。
4、__thread是GCC内置的线程局部存储设施。_thread变量每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容