C++调用指令时程序崩溃(C0000005 访问冲突)
我遇到了一次让我困惑的崩溃,到目前为止我发现不可能一致地重现该崩溃。该代码是使用 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 的默认构造函数的地址,例如this:
00404e8b call ChildClass::ChildClass (40a3d0h)
而不是 like:
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 当然是把东西送到梦幻岛。到目前为止,我还没有找到关于反汇编中哪些函数地址有效以及哪些函数地址无效的模式。库中 DifferentClass 的一个成员函数对 ChildClass 的所有调用都将是错误的,而另一个成员函数 DifferentClass 将对 ChildClass 进行不同的调用,看起来很好。
有没有人见过这样的事情/对其可能的原因有任何想法?
I have a crash that's puzzling me and that I thus far have found impossible to consistently reproduce. The code is compiled with Visual Studio 2008.
The (simplified, of course) source code looks like this:
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;
[...]
}
The disassembly from the crash dump looks like this:
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]
[...]
Mapping the source of func() against the disassembly, it winds up looking like this:
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]
[...]
}
In a successful run (different machine, though even on the same machine, the crash is not reliably reproducible), the only difference in the disassembly is the call instruction, which winds up mapping correctly to the address of the default constructor of ChildClass, like this:
00404e8b call ChildClass::ChildClass (40a3d0h)
rather than like:
612cab2b call a06cff10
So in the crash run, that a06cff10 address that serves as the call instruction's parameter seems to be coming from who-knows-where and is not mapped to anything in particular. And so, predictably, trying access that address (to get to the ChildClass default constructor) is resulting in an access violation:
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
Any attempt to look at that address in the crash dump indeed indicates that the address is out of bounds for the process.
UPDATE: So after reading the response below from zvrba and looking further at it, the problematic call seems to be the first of a dozen or so function calls within a static library (that is in turn loaded by a DLL) that all have an incorrect function offset. They're not all functions in the same class. There are three or four different classes with functions affected, though all the classes (both invoking and being invoked) live in that same static library. In this first call which crashed things, the instruction was e8e053403f and the 3F4053E0 offset in that instruction should have been an offset of just 53E0. All of the other instances have the same offset problem. The offset in the instruction is 3F40XXXX, when it should be just XXXX. The extra 3F400000 is of course sending things off into Never Never Land. So far, I haven't picked up on an pattern with regard to which function addresses within the disassembly are valid and which or not. One member function of DifferentClass in the library will have all of its calls to ChildClass as bad, whereas another member function DifferentClass will have a different call into ChildClass look just fine.
Has anyone out there seen something like this/have any thoughts on likely causes thereof?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您是否可能在另一个 DLL 中实现子类构造函数?我怀疑发生的情况是,在崩溃运行中,DLL 被加载到其首选地址之外的另一个地址 - 您可以在 VS 调试器的模块窗口中检查这一点。这反过来会导致调用目标被错误计算(该特定调用指令是相对的)。程序集中的偏移量(E8 操作码后的 4 个字节)也非常奇怪,看起来更像是尚未修复的重定位,而不是有效的偏移量。如何加载该 DLL?
Did you maybe implement child class constructor in another DLL? What I suspect happens is that, in the crash run, the DLL is loaded at another address than its preferred address -- you can check that in the modules window in VS debugger. This in turn results in a call target being miscalculated (that particular call instruction is relative). The offset in the assembly (4 bytes after the E8 opcode) is also very weird and looks more like a relocation that has not been fixed up than a valid offset. How do you load that DLL?
尽管从您的注释和反汇编来看,大部分源代码被省略,但很难理解发生了什么,看起来 ChildClass vtable 的地址已被损坏。这可能有几个可能的原因,例如,
从而导致数组/缓冲区溢出。首先,找到 vtable 地址并尝试单步执行调试器,检查 vtable 内存何时被覆盖。它可能比您指出的位置 (40a3d0h) 高几个字节:
然后,查找在该时间点可能正在执行的代码。
警告:因为原因几乎未知,找到实际的修复方法意味着阅读大量代码/检查源代码控制版本以查看任何可能的危险。
根据经验,导致损坏的问题(例如提到的问题之一)甚至可能不在发生访问冲突的代码行附近。
It's hard to understand what's going on with most of the source code omitted, although from your comments and disassembly, it looks like the address of the ChildClass vtable is being corrupted. This would have several possible causes, e.g.
First, locate the vtable address and try stepping through the debugger, checking when the vtable memory gets overwritten. It's likely a few bytes above the location (40a3d0h) you pointed out:
Then, look for the code that might be executing during that point in time.
Caveat: because the cause is pretty much unknown, finding the actual fix would mean reading through a lot of code/going through your source control versions to see any possible hazards.
From experience, the problem (such as one of those mentioned) causing the corruption might not even be anywhere near the line of code with the access violation.
这并不是说这有帮助,而是在我工作的地方,我正在修复因访问 C 程序中的越界数组而导致的访问冲突错误。我只能偶尔在我的机器上重现它。我们唯一能做的修复就是进行大量“数组越界”检查。
Not that this helps, but where I work I was fixing a Access Violation error result from accessing an out of bounds array in a C program. I was only able to reproduce it once in a while on my machine. The only fix we could do was to put a lot of 'array out of bounds' checks in place.