TL; DR:如何在调用函数时自动在gdb中添加监视,以便调试一些内存损坏?
我目前正在处理C ++中的一些内存损坏 我主要看到4-5种类型的反复崩溃 - 所有这些都几乎没有任何意义,因此我猜测它必须与内存损坏有关。
这些崩溃只发生在生产服务器上,大约每2-5小时一次。 它们中的大多数包括访问或传递空指针,它可能首先存在于其中。 其中一个地方是一个捕获它的lambda。 (见下文)
显然看着核心转储,甚至在崩溃时附加了gdb valgrind:我花了好几个小时盯着valgrind的多个实例而没有成功。 启用gccs堆栈保护(-fstack-protector-all) 我试过查看代码&这些变化,但是我找不到任何东西(总共100k行代码,#34; On master,10,437个文件已经改变,有3,352,600个添加和85,495个删除。"自上次发布以来生产服务器)。我可能只是简单地错过了一些东西,或者没有找到合适的位置 - 我不能说。 使用cppcheck来查看代码是否存在明显错误
如果有更简单/更直接的方法来查找腐败发生的位置,请随意提出建议。
让我们看一些简化的代码。 我有一个类Socket,它管理客户端连接。 它的构造类似于
Listener::OnAccept(fd){
Socket* s = new Socket();
if (s->Setup(fd)){
// push into a vector and do some other things
}
}
Socket :: Setup调用(虚拟)Socket类的OnConnect,然后使用lambda创建一个ping事件:
Socket::OnConnect(){
m_pingEvent = new Event([this](Event* e){
if (!this->GotPong()){
// close connection
}else{
this->Ping();
}
}, 30 /*seconds*/, true /* loop */);
}
事件接受std :: function作为回调 在析构函数中删除m_pingEvent(如果已设置),如果它正在运行,它将取消该事件。
lambda会在nullptr上调用Ping,在此调用m_pingPacket-> Send()= 0x1f8,这会导致段错误。
我的问题 - 或者更确切地说是我提出的解决方案 - 会看到捕获的这个指针用于写入,这绝对不应该发生。 这只有一个小问题..
如果不手动添加每个指针,我怎么会看到如此高的指针? (大约400个并发连接与很多(dis)连接)
对于捕获的数据,我发现它位于__closure对象中:
(gdb) frame 2
#2 0x081b9d63 in operator() (e=0x9b2a748, __closure=0xb5a8318)
at net/socket/Client.cpp:151
151 net/socket/Client.cpp: No such file or directory.
(gdb) ptype __closure
type = const struct {
net::socket::Client * const __this;
} * const
只需将lambda移动到" auto callback ="就可以轻松创建lambda。其类型为:
(gdb) info locals
callback = {__this = 0xb4dd0948}
(gdb) ptype callback
type = struct {
net::socket::Client * const __this;
}
(gdb) print callback
$1 = {__this = 0xb4dd0948}
(这是gcc版本4.7.2(Debian 4.7.2-5)供参考,可能与其他编译器/版本不同)
在发布前不久,我意识到结构可能会在移入std :: function后更改地址(这是正确的吗?)
我一直在挖掘gnu"功能性"标题,但我还没有真正找到任何东西,我会继续寻找(并更新)
另一个注意事项:我发布这个完整的描述,其中包含所有细节,以防任何人为我提供更简单的解决方案。 (XY问题)
编辑:
(gdb) print *(void**)m_pingEvent->m_callback._M_functor._M_unused._M_object
$8 = (void *) 0xb4dd56d8
(gdb) print this
$4 = (net::socket::Client * const) 0xb4dd56d8
找到它:)
EDIT2:
break net/socket/Client.cpp:158
commands
silent
watch -l m_pingEvent->m_callback._M_functor._M_unused._M_object
continue
end
这有两个缺点:你一次只能看4个地址&一旦释放对象,就无法删除手表。 因此,它无法使用。
编辑3: 我已经弄清楚如何使用我编写的这个python脚本进行观察(从外部链接这个脚本,因为它很长):https://gist.github.com/imermcmaps/4a6d8a1577118645acf3
下一个问题是了解输出..
Added watch 7 -> 0x10eb2200
Hardware watchpoint 7: -location m_pingEvent->m_callback._M_functor._M_unused._M_obj
Old value = (void *) 0x10eba4b0
New value = (void *) 0x10eba400
net::Packet::Packet (this=0x10eb1088) at ../shared/net/Packet.cpp:13
就像它说的那样,它从旧的值改变了,它不应该是原始的值,因为我检查这个指针和指针值是否匹配,他们这样做。
编辑4(yay): 事实证明-l并不像我想要的那样工作。 手动抓住地址,然后看着该地址似乎有效
答案 0 :(得分:0)
当调用函数时,如何在gdb中自动添加监视 我可以调试一些内存损坏吗?
在您的进程中加载的某些模块已经发生实际损坏之后,通常会检测到内存损坏。因此,手动调试对于真正复杂的项目可能不是非常有用。因为在您的进程中加载的任何第三方模块/库也可能导致此问题。从您的帖子看起来这个问题总是不可重现,这表明这可能与线程/同步问题有关,这会导致某种内存损坏。因此,根据我的经验,我强烈建议您专注于使用动态工具(Valgrind / Helgrind)重现问题。
但是正如您在问题中提到的那样,您可以使用Valgrind附加您的程序。所以你可能想要附上你的程序(a.out)以防你没有这样做。
$ valgrind --tool=memcheck --db-attach=yes ./a.out
这样,当您检测到第一个内存错误时,Valgrind将自动将程序附加到调试器中,以便您可以进行实时调试(GDB)。这似乎是找出问题根本原因的最佳方法。
但是我认为可能会有一些数据竞争场景导致内存损坏。所以你可能想用 Helgrind 检查/查找可能导致这种情况的数据竞争/线程问题问题
有关这些的更多信息,请参阅以下帖子: