首先,我确实意识到这不是一个具有绝对答案的完美的问与答风格的问题,但是我想不出任何措辞来使它更好地工作。我认为没有绝对的解决方案,这就是为什么我将其发布在此处而不是Stack Overflow的原因之一。
在过去的一个月中,我一直在重写相当旧的服务器代码(mmorpg),以使其更加现代,并且易于扩展/修改。我从网络部分开始,并实现了一个第三方库(libevent)来为我处理事务。通过所有的重构和代码更改,我在某个地方引入了内存损坏,而我一直在努力寻找发生错误的地方。
我似乎无法在我的开发/测试环境中可靠地重现它,即使实现原始机器人来模拟某些负载时,我也不会再崩溃(我修复了会导致某些问题的libevent问题)
到目前为止,我已经尝试过:
摆脱困境-直到事情崩溃(可能需要1天以上的生产时间或仅仅一个小时)崩溃之前,我才真正感到困惑,这之前肯定没有无效的写入,肯定在某个时候它将访问无效的内存并且不会覆盖内容机会?(是否可以“扩展”地址范围?)
代码分析工具,即coverage和cppcheck。尽管他们确实指出了代码中的一些.nastiness和边缘情况,但没有什么严重的。
记录该过程,直到它与gdb崩溃(通过undodb),然后以反向方式工作。/ sounds /这样的声音应该是可行的,但是我要么通过使用自动完成功能最终导致gdb崩溃,要么由于一些可能的分支(一个损坏导致另一个损坏,因此导致内部迷失)在内部libevent结构中丢失上)。我想如果能看到指针最初属于/分配指针的地方,那将消除大多数分支问题,那就太好了。但是我无法使用undodb运行valgrind,而且我正常的gdb记录的速度实在太慢了(如果甚至可以与valgrind结合使用)。
代码审查!我自己(彻底)并让一些朋友来检查我的代码,尽管我怀疑它是否足够彻底。我当时在考虑也许要雇用一名开发人员与我一起进行一些代码审查/调试,但是我付不起太多钱,而且我也不知道该去哪里寻找愿意为小工作而工作的人-如果他没有找到问题或根本没有资格,那就不要钱。
我还应该指出:我通常会得到一致的回溯。在某些地方发生崩溃,主要与套接字类以某种方式损坏有关。它是指向不是套接字的东西的无效指针,还是套接字类本身被乱码覆盖(部分?)。尽管我怀疑它在那里崩溃最多,因为那是最常用的部分之一,因此它是第一个被使用的损坏的内存。
总而言之,这个问题使我忙了将近2个月(无论是开还是关,都是一个业余项目),这确实让我感到沮丧,以至于我变得脾气暴躁,并想放弃。我只是想不出我应该怎么做才能发现问题。
我错过了任何有用的技术吗?你怎么处理那件事呢?(这可能不那么普遍,因为没有太多相关信息。或者我真的是盲人?)
编辑:
一些重要的规格:
通过gcc 4.7使用c ++(11)(版本由debian wheezy提供)
代码库大约有15万行
编辑以回复david.pfx帖子:(抱歉响应缓慢)
您是否在仔细记录崩溃情况以寻找模式?
是的,我仍然遗漏了最近发生的崩溃事件
几个地方真的很相似吗?用什么方式?
好吧,在最新版本中(每当我添加/删除代码或更改相关结构时,它们似乎都会改变),它总是会陷入项目计时器方法中。基本上,一个项目有一个特定的时间,在该时间之后,它会过期,并将更新的信息发送给客户端。无效的套接字指针将在Player类中(据我所知仍然有效),大部分与此有关。在正常关闭后,我还将在清理阶段遇到大量崩溃,该崩溃将破坏所有未明确破坏的静态类(__run_exit_handlers
在回溯中)。大多数情况下只涉及std::map
一个类,但猜测这只是第一件事。
损坏的数据是什么样的?零?Ascii?模式?
我还没有找到任何模式,对我来说似乎有点随机。很难说,因为我不知道腐败从哪里开始。
它与堆有关吗?
这完全与堆有关(我启用了gcc的堆栈保护,但没有捕获任何东西)。
腐败发生在a之后
free()
吗?
您将不得不对此进行详细说明。您是说要让已经释放的对象指向周围吗?一旦对象被销毁,我会将每个引用都设置为null,所以除非我在某处错过了东西,否则不行。那应该在valgrind中显示出来,但是没有。
网络流量是否有与众不同的东西(缓冲区大小,恢复周期)?
网络流量由原始数据组成。因此,对于更复杂的事物,使用char数组,(u)intX_t或packed(以除去填充)结构,每个数据包都有一个标头,该标头由id和数据包大小本身组成,并针对预期大小进行了验证。它们大约为10-60字节,最大的(内部“启动”数据包,在启动时触发一次)大小为几Mb。
大量的生产断言。在损坏蔓延之前及早地发生崩溃。
我曾经有一次与std::map
腐败相关的崩溃,每个实体都有其“视图”的地图,每个实体都可以看到它,反之亦然。我在前面和后面添加了200byte的缓冲区,将其填充为0x33,并在每次访问之前对其进行了检查。腐败刚刚消失了,我必须搬走一些东西使它腐败了。
战略性日志记录,因此您可以准确地知道之前发生了什么。当您更接近答案时,将其添加到日志记录中。
它的工作..扩展。
无奈之下,您可以保存状态并自动重启吗?我可以想到一些实现此目的的生产软件。
我有点做。该软件由一个主要的“高速缓存”进程和一些其他工作进程组成,它们均访问高速缓存以获取并保存内容。因此,每次崩溃我都不会失去太多进展,它仍然会断开所有用户的连接,依此类推,这绝对不是解决方案。
并发:线程,竞争条件等
有一个mysql线程可以执行“异步”查询,尽管这一切都未曾动过,并且仅通过具有所有锁定功能的函数才与数据库类共享信息。
中断
有一个中断计时器可以阻止它锁定,如果它在30秒内没有完成一个周期,它只会中止运行,但是该代码应该是安全的:
if (!tics) {
abort();
} else
tics = 0;
volatile int tics = 0;
每次循环完成时增加tic 。也是旧代码。
事件/回调/异常:意外损坏状态或堆栈
正在使用许多回调(异步网络I / O,计时器),但它们不应做任何不好的事情。
异常数据:异常输入数据/时序/状态
我有一些与此相关的案例。在仍在处理数据包时断开套接字会导致访问nullptr等,但是到目前为止,这些值很容易发现,因为在告诉类本身完成后立即清除了每个引用。(破坏本身由循环处理,每个循环删除所有被破坏的对象)
对异步外部过程的依赖。
关心详细吗?上面提到的高速缓存过程就是这种情况。我唯一能想到的就是无法足够快地完成工作并使用垃圾数据,但是事实并非如此,因为那也使用了网络。相同的数据包模型。