网站首页> 文章专栏> 捉虫记
最近和小伙伴一起排查解决了一个bug,耗时两周有余,中间也经历一些曲折,有一些经验教训值得总结,遂作文以记之。
合作伙伴提供了包含yalantinglibs 基于C++20 协程的高性能coro_http 库的so给业务方使用,用于访问oss,实现高性能的文件上传下载。但是业务方在编译链接这个so的时候出现了segmentation fault的错误,通过gdb 查看堆栈,发现crash的地方在一个全局变量初始化的地方。这个全局变量是用来辅助实现在main之前注册的功能,利用了C++ 会在main之前初始化全局变量的特性,是C++ 中常用的一种手法,之前也介绍过。
在这个全局变量的初始化函数中调用了另外一个单例的成员函数实现注册,内部是一个map,先加锁,然后将key-value 插入到map中。代码看起来也是很简单的,但是crash的地方就在注册函数里。根据以往的经验,初步怀疑是全局变量初始化顺序引起的,因为C++全局变量的初始化顺序是不确定的。可能某个全局变量的初始化依赖了另外一个全局变量,但那个全局变量还没有被初始化,容易引起访问非法地址的问题。于是就沿着这个方向去排查问题。
全局变量只依赖了一个单例,而这个单例内部并没有依赖什么全局变量,只依赖了单例中的一个静态局部变量,正常情况下这不会出问题,因为单例是lazy 创建的,它的创建时机是更早的。我也加了一些日志来观察是否是因为单例本身还没初始化引起的,日志表明单例是先初始化的,因此可以初步排除单例未初始化的问题。
我们通过日志发现了一些问题,具体crash的那行代码是这样的:
std::lock_guard lock(mtx_);
mtx_ 是单例的成员变量。这么简单的一行代码怎么会引起segmentation fault错误呢?难道还是因为这个成员变量未初始化引起的吗?于是再加日志看看这个变量内容是不是正常的,日志表明这个变量的内容是正常的,初始化过的,问题查到这里似乎进入死胡同了,问题也变得诡异起来。
聪明的读者读到这里的时候能猜猜可能是什么原因吗?可以先猜一下,看看最后能不能猜中:)
既然crash必现,那总会留下蛛丝马迹的。是在加锁的地方crash,那就调试一下挂在加锁的哪行代码吧,调试发现挂在pthread的这行代码里:
__gthread_mutex_lock(mutex)
之后就crash了,没有更多信息了,这时候又开始怀疑是不是pthread的这个加锁函数引起的问题,我们猜测是不是因为phtread没有link,然后查看业务的link部分的脚本,发现pthread是首先就会link的,说明不是这个原因引起的。
那有没有可能是glibc 的bug呢,于是又查了一下相关的bug记录,希望找到一些类似的问题,查了两天也毫无头绪。小伙伴提议看看汇编代码,从更微观的层面去看一下具体挂在哪里了。
经过漫长又繁琐的跳转代码,终于找到更底层的crash的代码了,类似于这样:
_dl_sym(handle)
bthread::init_sys_mutex_lock()
bthread::first_sys_pthread_mutex_lock(mutex)
__gthread_mutex_lock(mutex)
这几行代码就很有意思了,原来调用_gthreadmutex_lock(mutex)时,会跳转到bthread mutex的代码中,这个发现让我们很意外,会不会是bthread的mutex引起的问题呢?似乎发现了一些蛛丝马迹!
为什么会跳到bthread的代码中呢,后来调查发现合作伙伴使用了brpc,而brpc会hook pthread的_gthreadmutex_lock函数,因此调用std::lock_guard lock(mtx_);
最终会跳到brpc mutex的代码中。
继续查看汇编代码,发现挂在这个地方:
...
leaveq
jmpq *0x2067fb1(%rip) #0x6ffff70085b4 <_ZN7bthreadL22sys_phtread_mutex_lockE>
jmpq的时候挂了,这是在跳转到sys_phtread_mutex_lock函数时挂了,打印出0x6ffff70085b4的内容发现是0,至此segmentation fault 的原因就知道了,jmpq的时候跳到一个指针为空的地址引起的。
前面已经知道这个函数是被brpc hook的函数,而这个函数地址是空的,说明hook失败了。会不会是因为业务方先link 了 pthread,合作伙伴的so 中的brpc后hook pthread导致hook 失败引起的呢?我们做了这样的测试,让业务方的代码先preload so,再link pthread,然后segmentation fault消失了,编译顺利完成!这也证明了就是brpc hook pthread引起的问题。
既然问题清楚了,那问题就好解决了。
后面翻看brpc最近的一些commit,发现有个commit支持禁用hook,然后更新brpc版本,禁用掉hook,问题解决。
经验主义很容易把我们带入歧途,还是需要根据事实和代码第一现场去排查问题。 另外,不要随便hook,尤其是编译器依赖的库如glibc等库,真的容易引起各种问题。
最后附上一首小诗:
秋游西溪湿地
碧波深处荻花苍,残叶枝头野柿黄。
泛泛轻舟惊白鹭,濛濛细雨品秋凉。
地址: www.purecpp.cn
转载请注明出处!
purecpp
一个很酷的modern c++开源社区
purecpp社区自2015年创办以来,以“Newer is Better”为理念,相信新技术可以改变世界,一直致力于现代C++研究、应用和技术创新,期望通过现代C++的技术创新来提高企业生产力和效率。
社区坚持只发表原创技术文章,已经累计发表了一千多篇原创C++技术文章;
组织了十几场的C++沙龙和C++大会,有力地促进了国内外C++开发者之间的技术交流;
开源了十几个现代C++项目,被近百家公司所使用,有力地推动了现代C++在企业中的应用。
期待更多的C++爱好者能参与到社区C++社区的建设中来,一起为现代C++开源项目添砖加瓦,一起完善C++基础设施和生态圈。
微信公众号:purecpp, 社区邮箱: purecpp@163.com