记一次线上问题定位过程

  1. 出现问题,但不能快速修复

    1. 系统在高峰期突然出现了大面积的core dump,通过gdb core文件发现,是core在发送数据到另一个服务的地方,打开堆栈对应的代码,是公司的一个基础库文件,只是简单的声明一个protobuf message对象,但这地方一般不太可能出现core啊,不然程序到处都有类似的做法,为啥不在其他地方core了?难道是服务的其他线程导致的?用gdb的thread apply all bt查看各个线程堆栈,大部分线程处于sleep或者等待条件信号中(线上服务等待处理请求吧),不太像是有问题啊,这就有点无解了。
    2. gdb打开其他机器实例的core文件看看,发现线程1还是一样的堆栈信息,显示core在protobuf message对象中。。。稍微瞄几眼protobuf声明对象的过程,无非就是申请内存,没什么值得怀疑的,如果这里有问题,服务中用到pb对象的地方很多,为什么偏偏就core在同一个地方?
  2. 降级止损

    1. 百思不得其解,查查服务的日志,发现有大量的请求下游服务超时,查看这个下游服务的负载情况,发现负载很高,难道是这个下游服务导致的?仔细一想,既然大量实例在同一时间core了,也会触发线上监控进程把服务重新拉起来,这样短时间内会有大量请求集中请求到下游某个服务的ip(下游服务有多个实例,但由于服务本身在数据还没准备好之前,不能向注册中心注册,不然会暴露名字给上游,所以这时候要访问下游只能通过ip来访问),所以下游服务负载高不是导致core的原因,采取了批量重启+请求降级处理。但很不幸,重启后进程服务了短暂的时间后又core了。
  3. 降级没用,继续找原因止损

    1. 看来还是得先把core的原因找出来。既然都是在线程1的同样的地方core了,这地方应该是有问题的,可以从这里入手。查看当前请求中的数据,发现不同core文件中有个字段的值是一致,这是否可以猜测是某种特殊的请求才会导致core?马上打电话找产品运营人员,先把这种流量屏蔽了,10分钟过去了,发现还真的不再core了,观察了半个小时,服务正常,这时已经是凌晨4点了。。目前只能先这样止损,等第二天找出core原因。
  4. 找到潜在原因,并修复

    1. 第二天上班仔细定位问题,还是跟昨晚一样,打开core文件,查看堆栈信息,对比源代码,发现core的地方的源代码的下面几行调用了一个序列化函数,而且这个函数是个inline的,因为inline函数在编译器编译的时候,只是把内容嵌入进去,不做函数调用,所以如果在这里core了,也不会有堆栈信息。为了验证这个猜测,gdb调试core文件的时候,我故意打印当前inline函数里面的变量,确实能打印出来。。(反证:如果程序还没执行到这里就core了,不可能能打印出变量信息),所以断定是在序列化的过程core了。那就把注意力聚焦在序列化上。序列化的实现是公司2005年的代码了,到现在也有16年的时间了,不太可能会出现问题吧?序列化初看很复杂,涉及到内存设计,序列化编码过程。仔细一看,大概意思就是把C++各个基础类型写入队列中,迭代器就先写元素个数,再写元素,没毛病啊?继续深挖,发现了一个关键的信息:序列化字符串的时候会判断大小,如果超出一定大小,会抛异常。。。对比了这个特殊流量的某个字符串长度,确实很长,问题应该就是出在这里了。查了相关资料,查到是gcc的低版本中,如果底层有抛异常,会被捕捉后直接退出,也不打印堆栈了,后来有人上报了这个bug并在gcc8中修复了。这就难怪我看core文件一直没看出core的真正地方了。针对本次core的案例,解决办法也很简单,修改那份陈年老旧的代码(当然如果通用一点,直接hook __cxa_throw,把堆栈打印出来),至此,问题已经定位到,并找到了解决方案。
    2. 在__cxa_throw中,底层是捕捉到异常后就退出:
      1. void * execute_native_thread_routine(){
            try {
             ...   
            }catch(...){
                std::terminate();
            }
        }
        View Code

         

      2. core dump之后,查看core文件是这样的
      3.  

  5. 方案

    1. hook __cxa_throw() 让每次生成的 coredump 都带上堆栈
    2. #include <iostream>
      #include <stdexcept>
      #include <thread>
      
      extern "C" { //加这3行代码,通过 hook __cxa_throw,直接 abort,可以避免 stack unwind。
      void __cxa_throw(void* ex, void* info, void (*dest)(void*)) { ::abort(); }
      }
      
      void func(){
          throw std::runtime_error("die");
      }
      
      int main() {
          std::thread t(func);
          t.join();
          return 0;
      }
      View Code

       

    3. 效果
    4.  

  6. 代码

    1. https://github.com/longbozhan/sample/tree/master/hook_exception
  7. 参考

    1. https://byronhe.com/post/cpp-throw-coredump-with-backtrace/
    2. https://abcdabcd987.com/libstdc++-bug/
posted on 2021-11-25 14:58  bytesmover  阅读(103)  评论(0编辑  收藏  举报