0

我有一个让我困惑的崩溃,到目前为止,我发现不可能始终如一地重现。代码使用 Visual Studio 2008 编译。

(当然是简化的)源代码如下所示:

class AbstractParentClass 
{
private:
    /* data members */
public:
    AbstractParentClass();
    /* 
       virtual functions ...
     */
}; 

class ChildClass : public AbstractParentClass
{
private:
    /* data members */
public:
    ChildClass();
    /* 
       overridden/implemented virtual functions ...
     */
};

void DifferentClass::func(const char ** strs)
{
     ChildClass child_class;
     int i = 0;
     [...]
}

崩溃转储的反汇编如下所示:

Library!DifferentClass::func:
612cab20 83ec58          sub     esp,58h
612cab23 56              push    esi
612cab24 57              push    edi
612cab25 8bf9            mov     edi,ecx
612cab27 8d4c2420        lea     ecx,[esp+20h]
612cab2b e8e053403f      call    a06cff10  
612cab30 8b742464        mov     esi,dword ptr [esp+64h]
[...]

将 func() 的源映射到反汇编,它最终看起来像这样:

Library!DifferentClass::func:
void DifferentClass::func(const char ** strs)
{
612cab20 83ec58          sub     esp,58h
612cab23 56              push    esi
612cab24 57              push    edi
612cab25 8bf9            mov     edi,ecx
     ChildClass child_class;
612cab27 8d4c2420        lea     ecx,[esp+20h]
612cab2b e8e053403f      call    a06cff10 
     int i = 0;
612cab30 8b742464        mov     esi,dword ptr [esp+64h]
[...]
}

在成功运行中(不同的机器,即使在同一台机器上,崩溃也不能可靠地重现),反汇编的唯一区别是调用指令,它最终正确映射到 ChildClass 的默认构造函数的地址,如这个:

00404e8b  call        ChildClass::ChildClass (40a3d0h)

而不是喜欢:

612cab2b  call        a06cff10

因此,在崩溃运行中,用作调用指令参数的 a06cff10 地址似乎来自谁知道在哪里,并且没有特别映射到任何东西。因此,可以预见的是,尝试访问该地址(以访问 ChildClass 默认构造函数)会导致访问冲突:

EXCEPTION_RECORD:  0012f688 -- (.exr 0x12f688)
ExceptionAddress: a06cff10
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: a06cff10
Attempt to read from address a06cff10

任何在故障转储中查看该地址的尝试确实表明该地址超出了进程的范围。

更新:因此,在阅读了来自 zvrba 的以下响应并进一步查看之后,有问题的调用似乎是静态库中的十几个函数调用中的第一个(这又是由 DLL 加载的),它们都有一个不正确的函数偏移。它们不是同一类中的所有功能。尽管所有类(调用和被调用的)都存在于同一个静态库中,但有三四个不同的类的功能受到影响。在第一个崩溃的调用中,指令是 e8e053403f 并且该指令中的 3F4053E0 偏移量应该偏移量仅为 53E0。所有其他实例都有相同的偏移问题。指令中的偏移量是 3F40XXXX,应该是 XXXX。额外的 3F400000 当然是将东西送到 Never Never Land。到目前为止,我还没有找到关于反汇编中的哪些函数地址有效以及哪些无效的模式。库中 DifferentClass 的一个成员函数将对 ChildClass 的所有调用都视为错误,而另一个成员函数 DifferentClass 将对 ChildClass 进行不同的调用看起来很好。

有没有人看到过这样的事情/对可能的原因有任何想法?

4

3 回答 3

2

您是否可能在另一个 DLL 中实现了子类构造函数?我怀疑发生的是,在崩溃运行中,DLL 被加载到另一个地址而不是其首选地址——您可以在 VS 调试器的模块窗口中检查它。这反过来会导致调用目标被错误计算(该特定调用指令是相对的)。程序集中的偏移量(E8 操作码后的 4 个字节)也很奇怪,看起来更像是尚未修复的重定位,而不是有效的偏移量。你如何加载那个DLL?

于 2010-12-30T07:27:48.553 回答
2

很难理解大部分源代码被省略的情况,尽管从您的注释和反汇编来看,ChildClass vtable 的地址似乎已损坏。这将有几个可能的原因,例如

  • 正如@contactmatt 提到的,数组/缓冲区溢出
  • 使用被破坏的对象
  • 使用单位化变量
  • 错误地转换/转换变量/指针
  • 将数据从缓冲区加载到对象中而不打包/检查字节对齐
  • 等等

首先,找到 vtable 地址并尝试单步调试调试器,检查 vtable 内存何时被覆盖。它可能比您指出的位置(40a3d0h)高几个字节:

  call        ChildClass::ChildClass (40a3d0h)

然后,查找可能在该时间点执行的代码。

警告:因为原因几乎未知,找到实际修复将意味着阅读大量代码/查看源代码控制版本以查看任何可能的危险。根据经验,导致损坏的问题(例如提到的问题之一)甚至可能不在存在访问冲突的代码行附近。

于 2010-12-30T09:06:33.233 回答
0

并不是说这有帮助,但是在我工作的地方,我正在修复由于访问 C 程序中的越界数组而导致的访问冲突错误。我只能偶尔在我的机器上重现它。我们能做的唯一解决办法是进行大量“数组越界”检查。

于 2010-12-30T06:23:38.547 回答