【问题标题】:What could cause this code to segfault什么可能导致此代码段错误
【发布时间】:2012-01-15 12:40:24
【问题描述】:

在下面的代码中,我在 signaling_thread_->Send(this, id, data); 的行中看到了一个段错误,它是从 PeerConnectionProxy 类的析构函数中调用的。

bool PeerConnectionProxy::Send(uint32 id, talk_base::MessageData* data) {
  if (!signaling_thread_)
    return false;
  signaling_thread_->Send(this, id, data);
  return true;
}

在 gdb 中运行,只要我对那行执行(gdb) step,我就会得到段错误和这个堆栈跟踪:

Program received signal SIGSEGV, Segmentation fault.

0x00000000 in ?? ()
(gdb) bt
#0  0x00000000 in ?? ()
#1  0xa782eed4 in webrtc::PeerConnectionProxy::Send (this=0xab889e80, id=6, data=0xbfffc1e8)
    at third_party/libjingle/source/talk/app/webrtc/peerconnectionproxy.cc:219
#2  0xa782e91a in ~PeerConnectionProxy (this=0xab889e80, __in_chrg=<value optimised out>)
    at third_party/libjingle/source/talk/app/webrtc/peerconnectionproxy.cc:145

...

就在那一行之前,我检查了signaling_thread_ 是非空的,正如预期的那样,这和数据也是如此。我很困惑是什么可能导致那里出现段错误或使堆栈最终达到 0x00000000。该代码仅在通过析构函数的代码路径上出现段错误。从许多其他地方调用发送函数没有问题。

2011-12-08 更新:

打开 stepi 和反汇编,我得到了这个:

0xa772eed2  219   signaling_thread_->Send(this, id, data);
   0xa772eea4 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+24>:     8b 45 08   mov    0x8(%ebp),%eax
   0xa772eea7 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+27>:     8b 40 0c   mov    0xc(%eax),%eax
   0xa772eeaa <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+30>:     8b 00  mov    (%eax),%eax
   0xa772eeac <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+32>:     83 c0 40   add    $0x40,%eax
   0xa772eeaf <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+35>:     8b 08  mov    (%eax),%ecx
   0xa772eeb1 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+37>:     8b 45 08   mov    0x8(%ebp),%eax
   0xa772eeb4 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+40>:     8d 70 04   lea    0x4(%eax),%esi
   0xa772eeb7 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+43>:     8b 45 08   mov    0x8(%ebp),%eax
   0xa772eeba <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+46>:     8b 40 0c   mov    0xc(%eax),%eax
   0xa772eebd <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+49>:     8b 55 10   mov    0x10(%ebp),%edx
   0xa772eec0 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+52>:     89 54 24 0c    mov    %edx,0xc(%esp)
   0xa772eec4 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+56>:     8b 55 0c   mov    0xc(%ebp),%edx
   0xa772eec7 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+59>:     89 54 24 08    mov    %edx,0x8(%esp)
   0xa772eecb <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+63>:     89 74 24 04    mov    %esi,0x4(%esp)
   0xa772eecf <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+67>:     89 04 24   mov    %eax,(%esp)
=> 0xa772eed2 <_ZN6webrtc19PeerConnectionProxy4SendEjPN9talk_base11MessageDataE+70>:     ff d1  call   *%ecx

ecx 是 0x0,所以这就是造成段错误的原因,但我仍然不明白发生了什么。该行的其他代码看起来与ecx 无关,除非我读错了。

【问题讨论】:

  • 看起来可能是堆栈损坏。我建议在 Valgrind 下重新运行。
  • 您是否通过stepi 而不是step 获得更多信息?
  • 更新了 stepi 的结果。我仍在尝试让应用程序在 valgrind 中运行。
  • C++ 对象的一部分(在许多情况下)是一个虚拟表,用于处理virtual 方法。很可能,您的已损坏 - valgrind 也许 能够提供帮助。除此之外,您可以在 gdb 中使用数据访问断点来查找其损坏的时间。

标签: c++ segmentation-fault destructor


【解决方案1】:

每当我遇到涉及析构函数的段错误时,通常通过使析构函数virtual 来解决它。如果这对您还没有意义,请不要担心。

当一个对象要被销毁时,首先会调用析构函数,然后会尝试释放该对象。通常,析构函数本身是完全成功的,但释放内存的尝试由于一个模糊的问题而失败,我将在后面尝试描述。 (这个answer引用了C++标准的相关部分。)

你确定段错误发生在析构函数期间吗?或者它可能在析构函数完成后立即发生。你能在相关析构函数的末尾加上一个 printf 吗?

我将假设析构函数本身是成功的,并且错误在析构函数之后立即发生,在尝试释放内存期间。

考虑这个结构:

struct A {
    int x;
};
A a;

这里,很明显&amp;a == &amp;(a.x)

而 B 继承自 A:

struct B : public A {
};
B b;

再说一遍,&amp;b == &amp;(b.x)

但如果涉及到虚方法,事情就会变得棘手。

struct C : public B {
   virtual void foo() {}
};
C c;

现在,&amp;c != &amp;(c.x)。这是因为c 的第一个真正条目是(依赖于编译器的)实际上是一个 vtable,它列出了诸如 foo() 之类的函数的位置。现在想象下面的代码:

{
    A * p = new C;
    delete p;
}

语句delete p 认为它处理的是A 类型的对象,但它实际上处理的是C 类型的对象。析构函数将正确运行,但尝试调用free(p) 将是错误的,因为它没有使用正确的地址。就像int *p = malloc(100); free(p+1)。

如果有疑问,请在每个类中放置一个 virtual 析构函数,如果您可能会在子类中使用虚函数从它继承。

【讨论】:

    【解决方案2】:

    最可能的原因是signaling_thread_ 是一个悬空指针——它曾经指向某个东西,但该东西已经被delete'd,留下一个不为空的指针,但可能会导致崩溃如果你试图用它做任何事情(比如调用它的Send方法)。

    既然你说这是从析构函数中调用的,那么删除调用很可能早先发生在同一个析构函数中......

    【讨论】:

    • 我想我现在已经找到了。我正在处理的代码混合了 boost::shared_ptr 和原始指针(这似乎是一个非常糟糕的主意)。当代理仍然有一个指向它的原始指针时,signaling_thread_ 正在被 shared_ptr (在析构函数堆栈的上方)删除。以正确的顺序显式清除 shared_ptrs 就可以了。
    • 是的。混合 share_ptrs 和原始指针是一个非常糟糕的主意 - 如果您在一个对象上至少有 1 个 shared_ptr,那么将所有指向它的指针共享的开销非常小。
    【解决方案3】:

    %ecx 保存Send 方法的虚拟表条目,该方法由于某种原因已被清零。最常见的析构函数是在调用PeerConnectionProxy::Send 之前删除signaling_thread_。另一种可能性是在之前调用signaling_thread_ 方法时出现缓冲区溢出,这会覆盖虚拟表条目。另一种可能性是析构函数中的缓冲区溢出,它覆盖了signaling_thread_ 指针。如果您将代码发布到您的析构函数中,我们或许可以缩小范围。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-14
      • 2011-01-10
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      相关资源
      最近更新 更多