• 【segmentation fault】 智能指针异常崩溃


     1 std::map<sio_t*, std::shared_ptr<Contextapc>> map_apc_context_;
     2 
     3 // 客户端读事件
     4 static void on_apc_recv(sio_t *io, void *buf, int readbytes)
     5 {
     6     // 获取客户端上下文
     7     std::shared_ptr<Context> context = Server::Instance()->ApcContextByIO(io);
     8     if (context != nullptr)
     9     {
    10         if (readbytes > 0)
    11         {
    12             // 接收客户端数据
    13             context->Receive(buf, readbytes);
    14         }
    15         else
    16         {
    17             // 客户端异常
    18             LOGW("[ on_apc_recv ] peer (%d) closed. len: %d", sio_fd(io), readbytes);
    19             Server::Instance()->RemoveApcContext(io);
    20         }
    21     }
    22     else
    23     {
    24         LOGW("[ on_apc_recv ] unknown context(%d). buf: %s, len: %d", sio_fd(io), (char *)buf, readbytes);
    25     }
    26 }
    27 
    28 // 连接事件
    29 static void on_apc_connected(sio_t *io)
    30 {
    31     // 设置读事件
    32     sio_setcb_read(io, on_apc_recv);
    33     sio_read(io);
    34     
    35     // 调用服务处理连接方法
    36     Server::Instance()->onapcConnected(io);
    37 }
    38 
    39 // 获取客户端连接上下文
    40 std::shared_ptr<Context> Server::ApcContextByIO(sio_t *io)
    41 {
    42     auto it = map_apc_context_.find(io);
    43 
    44     if (it != map_apc_context_.end())
    45     {
    46         return it->second;
    47     }
    48     else
    49     {
    50         return NULL;
    51     }
    52 }
    53 
    54 // 连接处理方法
    55 void Server::onapcConnected(sio_t *io)
    56 {
    57     // 创建智能指针
    58     std::shared_ptr<Contextapc> context = std::make_shared<Contextapc>(io); 
    59     // 对象保存在 map 数据结构中
    60     map_apc_context_[io] = context;
    61     LOGI("[ onapcConnected ] fd: %d, size: %d", sio_fd(io), map_apc_context_.size());
    62 }

    前几天压测服务代码,出现了崩溃,崩溃行数在13行

    context->Receive(buf, readbytes);

    1.因为是服务代码,信号11都已经被捕获了,只能看一些堆栈信息,错误定位在这一行,我的第一反应是智能指针 context 里管理的对象是空的,

    因此我注释了信号捕捉函数,得到coredump文件,通过分析coredump文件,context 管理的指针不是 NULL,我认为应该是 context 管理的对象被破坏了。--分析问题

    2.于是我仔细阅读了附近的代码,代码基本都是C++写的,内存操作的地方不多,没能看出问题。 --代码阅读

    3.codereview上无法看出问题,只能通过分析服务运行轨迹,看是否有思路。我在崩溃链路上加了很多日志,发现 on_apc_connected --> onapcConnected --> on_apc_recv 直接就崩溃了,当时很疑惑,因为刚刚建立链接,收到第一个报文就崩溃了,任何业务代码都没有执行,陷入僵局。   --业务分析

    4.继续复读代码,猜测是不是多线程场景下,该实例先被从A线程 map_apc_context_ 删除了,但是B线程刚好使用,因为我并没有对 map_apc_context_ 加锁保护,但是我90%确定这是一个单线程逻辑,不存在上述情况,但是我还是抱着侥幸心理,在使用 context 对象之前,判断了一下 context 是否有效,结果仍是是崩溃。  --怀疑标准库

    5.因为本人没有读过智能指针源码,我开始怀疑是智能指针有BUG,因此我修改代码,不直接使用 context 调用 Receive 方法,而是取出 context 管理的实例来调用 Receive 方法,然后崩溃的断点进入 Receive 函数内部一个使用成员属性的地方,这个崩溃信息很明确告诉我的确是 context 管理的对象出现问题了。   --确定崩溃的根本原因

    6.我继续复读代码,确信接收报文这段业务逻辑是没有问题的,那么只剩下一个选项,肯定是某个地方内存越界了,影响了 map_apc_context_ 这个对象,此时阅读代码的重点不再是连接的创建接收报文逻辑,而是转向 context 实力内部业务处理逻辑,发现以下代码

    if (msg->type & MSG_TYPE_TEST_BIT)
    {
        char buff[128] = { 0 };
        sprintf(buff, "debug message;");
        memcpy(apc_msg_body(msg) + strlen(apc_msg_body(msg)), buff, strlen(buff));
    }

    这里的msg本身的长度就是 apc_msg_body(msg) + strlen(apc_msg_body(msg)),这段代码居然还要在后面拼接上一段,很有可能内存泄漏了,但是我的消息码并不是MSG_TYPE_TEST_BIT,我认为不可能进入这段逻辑。我抱着试试看的心态将这段代码注释了,准备再次进行测试(因为只有在压测环境下才能复现问题,我自己并没有相应的环境,所以改完代码到测试阶段还有点时间)。就在我改完代码,我突然想到当时为了做内存管理,使用了jemalloc,是不是因为jemalloc做了内存管理,导致实际的内存泄漏被滞后了呢?于是我回滚代码,先将jemalloc注释掉,然后再进行压测,发现崩溃的地方就是上面分析的那块代码,因为先前同事使用了 & 操作,并不完全等同于== ,而我新定义的消息码刚好就能进入这段逻辑,从而导致了崩溃。    --找到方案

    总结:

        a. 出现 segmentation fault 肯定需要看附近的代码,如果附近的代码没有问题,就要考虑是否是别的地方的内存越界影响了崩溃断点

        b. 分析segmentation fault 绝对不能开 jemalloc

        c. 绝对不要怀疑第三方标准库

  • 相关阅读:
    《那些年啊,那些事——一个程序员的奋斗史》——45
    《那些年啊,那些事——一个程序员的奋斗史》——42
    《那些年啊,那些事——一个程序员的奋斗史》——48
    NOIP2018 Day2T2 填数游戏
    KMP 入门
    积性函数与反演
    CF1270G Subset with Zero Sum
    NOIP2018 Day2T3 保卫王国
    从1维到4维的字体设计,字体设计与数学,的确是密不可分
    4月收集的一些与字体设计相关的资料
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/16055809.html
Copyright © 2020-2023  润新知