调试lambda内存损坏||在GDB中自动监视对象指针

时间:2014-06-20 13:15:39

标签: c++ memory lambda gdb corruption

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并不像我想要的那样工作。 手动抓住地址,然后看着该地址似乎有效

1 个答案:

答案 0 :(得分:0)

  

当调用函数时,如何在gdb中自动添加监视   我可以调试一些内存损坏吗?

在您的进程中加载​​的某些模块已经发生实际损坏之后,通常会检测到内存损坏。因此,手动调试对于真正复杂的项目可能不是非常有用。因为在您的进程中加载​​的任何第三方模块/库也可能导致此问题。从您的帖子看起来这个问题总是不可重现,这表明这可能与线程/同步问题有关,这会导致某种内存损坏。因此,根据我的经验,我强烈建议您专注于使用动态工具(Valgrind / Helgrind)重现问题。

但是正如您在问题中提到的那样,您可以使用Valgrind附加您的程序。所以你可能想要附上你的程序(a.out)以防你没有这样做。

$ valgrind --tool=memcheck --db-attach=yes ./a.out

这样,当您检测到第一个内存错误时,Valgrind将自动将程序附加到调试器中,以便您可以进行实时调试(GDB)。这似乎是找出问题根本原因的最佳方法。

但是我认为可能会有一些数据竞争场景导致内存损坏。所以你可能想用 Helgrind 检查/查找可能导致这种情况的数据竞争/线程问题问题

有关这些的更多信息,请参阅以下帖子:

https://stackoverflow.com/a/22658693/2724703

https://stackoverflow.com/a/22617989/2724703