什么可能导致此代码段错误

时间:2011-12-08 01:01:00

标签: c++ segmentation-fault destructor

在下面的代码中,我看到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,除非我只是读错了。

3 个答案:

答案 0 :(得分:3)

最有可能的原因是signaling_thread_是一个悬空指针 - 它常常指向某个东西,但是某些东西已经delete'd,留下一个非空的指针,但是如果您尝试对其执行任何操作(例如在其上调用Send方法),可能会导致崩溃。

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

答案 1 :(得分:1)

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

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

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

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

考虑这个结构:

struct A {
    int x;
};
A a;

此处,显然&a == &(a.x)

B继承自A:

struct B : public A {
};
B b;

再次,&b == &(b.x)

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

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

现在,&c != &(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 :(得分:1)

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