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


 1 std::map> 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 = 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 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 context = std::make_shared(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. 绝对不要怀疑第三方标准库

相关