【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. 绝对不要怀疑第三方标准库