c++ - 调试 lambda 内存损坏 ||自动监视 GDB 中的对象指针

标签 c++ memory lambda gdb corruption

TL;DR:调用函数时如何在 gdb 中自动添加监视以便调试内存损坏?

我目前正在处理 C++ 中的一些内存损坏问题 我主要看到 4-5 种类型的重新发生崩溃 - 所有这些都没有什么意义,所以我猜它必须与内存损坏有关。

这些崩溃只发生在生产服务器上,大约每 2-5 小时发生一次。 它们中的大多数包括访问或传递空指针,而空指针一开始就不可能存在。 这些地方之一是捕获它的 lambda。 (见下文)

显然查看了核心转储,甚至在它崩溃时附加了 gdb valgrind:我花了几个小时盯着 valgrind 的多个实例,但都没有成功。 启用 gccs 堆栈保护 (-fstack-protector-all) 我已经尝试查看代码和更改,但我不可能找到任何东西(总共 100k 行代码,“在 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,它在 this=0x1f8 上调用 m_pingPacket->Send(),这会导致段错误。

我的问题 - 或者更确切地说是我提出的解决方案 - 是观察捕获的 this 指针以进行写入,这绝对不应该发生。 这只有一个小问题..

如果不手动添加每个指针,我怎么会看到如此大量的指针? (大约 400 个并发连接,有很多(断开)连接)

至于捕获的数据,我发现这是在 __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 时,只需将 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

找到了:)

编辑2:

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

就像是说它是从旧值更改而来的,它甚至不应该是原始值,因为我正在检查 this 指针和指针值是否匹配,它们确实匹配。

编辑 4(是的): 结果 watch -l 并没有像我想要的那样工作。 手动抓取地址然后看那个地址好像可以用

最佳答案

How do I automatically add a watch in gdb when a function is called so I can debug some memory corruption?

在您的进程中加载​​的某些模块已经发生真正的损坏之后,通常会检测到内存损坏。所以手动调试对于真正复杂的项目可能不是很有用。因为在你的进程中加载​​的任何第三方模块/库也可能导致这个问题。从您的帖子看来,这个问题似乎始终无法重现,这表明这可能与导致某种内存损坏的线程/同步问题有关。因此,根据我的经验,我强烈建议您集中精力在动态工具下重现问题( 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

关于c++ - 调试 lambda 内存损坏 ||自动监视 GDB 中的对象指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24328193/

相关文章:

c++ - 功能仍然失败,不确定为什么...运行测试用例的想法?

c++ - 如何在组合函数中使用 boost::asio::defer()?

c++ - std::unordered_map vector 下标超出范围

python - 避免 Python 函数内重复的大数组计算

java - 可以在流上计算 SHA-1 算法吗?内存占用少?

c# - 什么更具可读性?

c++ - 为什么可以在没有定义的情况下使用枚举类型

java - 为什么堆内存使用量和加载类的数量不断增加?

c# - 在 C# 中使用 Dictionary<T, Func<List, bool>> 查找列表中最常见的事件

java - 根据java中的过滤器的lambda表达式映射