VS2005 C++ 损坏的虚拟表

发布于 2024-07-13 22:13:13 字数 1401 浏览 10 评论 0原文

我目前正在开发一个相当大(而且很旧)的代码库,最近升级到了 VS2005 (SP1)。 我和我的团队正在更改/更新/替换此代码中的模块,但我们偶尔会遇到 vtable 似乎损坏的问题。 我不是虚拟表方面的专家,但这些似乎肯定被破坏了。 错误通过以下错误来体现:

运行时检查失败#0 - ESP 的值未在函数调用中正确保存。 这通常是用一种调用约定声明的函数和用另一种调用约定声明的函数指针调用的结果。

当然,这个错误可能有很多其他原因,但在调试(调试构建)时,我实际上可以验证我想要操作的对象的虚函数表看起来很奇怪:

引用每个虚函数表的堆栈和堆看起来很好,并且指针vtable 与映射文件完美匹配。 这向我表明这不是内存覆盖错误或类似错误,因为它会影响堆栈和堆,而不是存储 vtable 的位置。 (它们存储在只读区域中,对吗?)无论如何,到目前为止一切似乎都很好。 但是,当查看 vtable 的内存时,我发现所有值,如果我将它们解释为指针,尽管它们处于相同的范围内(例如 0x00f203db 0x00f0f9be 0x00ecdda7 0x00f171e1),但它们与映射文件中的任何条目都不匹配,其中许多条目甚至没有对齐到 4 个字节。 我不知道 VS2005 如何构建 vtable 的所有细节,但这对我来说看起来是错误的。 如果这是正确的行为,也许有人可以向我解释一下?

我想我的问题可以归结为是什么导致了这种行为? 例如,当类层次结构过于复杂时,链接器中是否存在已知的错误? 以前有人见过类似的东西吗? 目前,我们可以通过将函数从受影响的类移动到内联(可怕的东西!)来解决崩溃问题,但显然这不是一个可行的长期解决方案。

感谢您的任何见解!

更新:有人要求我提供有关该项目的更多详细信息,我当然会提供。 然而,首先,问题并不完全与 ESP 值未保存错误相关。 我最感兴趣的是为什么我在 vtable 中看到奇怪的值。 也就是说,这里有一些附加信息: 该解决方案依赖于多个外部和内部项目,但这些项目很长一段时间没有更改,都使用相同的调用约定。 看起来有问题的代码都在解决方案的一个相当标准的 C++“主”项目中。 所有代码都是使用相同的编译器构建的。 该解决方案也不使用任何 dll,但链接了大量静态库:

SHFolder.lib、python25.lib、dxguid.lib、d3d9.lib、d3dx9.lib、dinput8.lib、ddraw.lib、dxerr9.lib、ws2_32 .lib、mss32.lib、Winmm.lib、vtuneapi.lib、vttriggers.lib、DbgHelp.lib、kernel32.lib、user32.lib、gdi32.lib、winspool.lib、comdlg32.lib、advapi32.lib、shell32.lib 、 ole32.lib、oleaut32.lib、uuid.lib、odbc32.lib、odbccp32.lib

I'm currently working on a quite big (and old, sigh) code base, recently upgraded to VS2005 (SP1). Me and my team are changing/updating/replacing modules in this code as we go but we have occasionally been running into problems where the vtables seems broken. I am no expert on vtables but these sure seems to be broken. The errors manifests itself with this error:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

Of course there can be plenty of other reasons for this error but when debugging (Debug build) I can actually verify that the vtables for the object I want to operate on look strange:

The stack and heap that reference each vtable looks fine and the pointers to the vtables match perfectly to the map file. This indicates to me that this is not a memory overwriting bug or similar, since then it would affect the stack and heap rather than where the vtables are stored. (They are stored in a read only area right?) Anyway, all seems good so far. But when looking at the memory of the vtable I find that all values, if I interpret them as pointers, although they are in the same range (Eg. 0x00f203db 0x00f0f9be 0x00ecdda7 0x00f171e1) does not match any entry in the map file and many of them are not even aligned to 4 bytes. I don't know all the details of how VS2005 builds the vtables, but this looks wrong to me. If this is correct behavior, perhaps somebody can explain this to me?

I guess my question boils down to what can cause this behavior? Is there any know bugs in the linker when having too complex class hierarchies for example? Has anybody seen anything similar before? Currently we are able to get around our crashes by moving functions from the affected class to inline (scary stuff!) but clearly this is not a feasible long term solution.

Thanks for any insight!

Update: I've been asked for more details about the project and of course I will supply this. First however, the question is not entirely related to the ESP value not being saved error. What I am most interested in is why I see the strange values in the vtable. That said, here is some additional info: The solution relies on several external and internal projects but these have not been changed in a long time, all uses the same calling convention. The code where it seems to break is all within the one pretty standard C++ "main" project of the solution. All code is built with the same compiler. The solution also doesn't use any dlls but links with plenty of static libraries:

SHFolder.lib, python25.lib, dxguid.lib, d3d9.lib, d3dx9.lib, dinput8.lib, ddraw.lib, dxerr9.lib, ws2_32.lib, mss32.lib, Winmm.lib, vtuneapi.lib, vttriggers.lib, DbgHelp.lib, kernel32.lib, user32.lib, gdi32.lib, winspool.lib, comdlg32.lib, advapi32.lib, shell32.lib, ole32.lib, oleaut32.lib, uuid.lib, odbc32.lib, odbccp32.lib

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

我的奇迹 2024-07-20 22:13:13

我发现了问题。 确实很愚蠢,但导致问题的类层次结构有一个名为 GetObject 的虚拟函数,它与同名的 Windows #define 冲突。 头文件以不同的顺序包含这些 Windows 头文件,这使链接器感到困惑。 所以,实际上问题是vtables损坏了,但我没想到这是原因! 好吧,你每天都会学到一些东西......

但是,非常感谢所有回复!

I found the problem. Silly really but the class hierarchy that caused the problem had a virtual function called GetObject which conflicted with the windows #define with the same name. The header files included these windows header files in different order, which confused the linker. So, in fact the problem was corrupted vtables, but I didn't expect this to be reason! Well you learn something every day...

However, big thanks to all that replied!

我很坚强 2024-07-20 22:13:13

我认为这里的重要提示是该错误的“这通常是调用使用一种调用约定声明的函数以及使用不同的调用约定声明的函数指针的结果”。 在我看来,调用者的 API 和处理调用的库之间存在不匹配。

此外,您可能会混合使用不同编译器构建的代码。 关于这个项目的性质,您还能告诉我们什么吗? 您调用的函数是否位于外部库中? 或者您可以调试整个调用堆栈吗?

编辑:您说该项目不使用任何 DLL。 那么静态库呢?

I think the big hint here is in the "This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention" part of that error. It seems to me that there is a mismatch between the caller's API and the library which is handling the call.

Also, it might be the case that you are mixing code built with different compilers. What more can you tell us about the nature of this project? Is the function you are calling located in an external library? Or can you debug through the entire call stack?

Edit: You said that the project doesn't use any DLL's. What about static libraries?

冧九 2024-07-20 22:13:13

请注意增量链接和 Edit+Continue 对函数地址(包括 v 表条目)的影响。 它的工作原理是通过跳转表间接进行方法调用。 这允许链接器在需要重新定位方法时修补跳转表,而无需重新链接整个映像。 该跳转表中的地址间隔 5 个字节。 它们不会出现在 .map 文件中。 当您切换到程序集视图并跟踪调用的执行时,真的很容易看到。

这也是您应该用来诊断 RTC 故障的技术。 找出实际调用的方法。 最可能的原因是您已将虚拟方法添加到类中,但该类的客户端未重新编译。 在 v 表中使用了错误的槽。 通常,更改接口而不是 IID 时也会出现 COM 问题。

Beware of the effects that incremental linking and Edit+Continue will have on function addresses, including v-table entries. It works by making method calls indirectly through a jump table. That allows the linker to patch the jump table when it needs to relocate the method without having the relink the entire image. The addresses in that jump table are 5 bytes apart. They won't appear in the .map file. It is really easy to see when you switch to Assembly view and trace execution of the call.

Which is also the technique you should use to diagnose the RTC failure. Find out what method is actually getting called. The most likely reason for this is that you've added virtual methods to a class but a client of that class wasn't recompiled. Using the wrong slot in the v-table. Classically also a COM problem when changing interfaces but not IIDs.

末骤雨初歇 2024-07-20 22:13:13

每当我收到这样的消息时,答案总是涉及重新编译部分或全部代码。 作为第一步,我会尝试完全重建。 Sqook 关于外部库的建议听起来也很合理,并且再次需要您使用与主代码相同的调用约定重新编译该库(如果可能的话)。

我有时发现“构建”命令可能会丢失需要重新编译的文件,这可能会导致出现您的消息。 再次强调,全面重建将会解决问题。

Whenever I've had a message like this, the answer has always involved recompiling some part or all of the code. I'd try a full rebuild as a first step. Sqook's suggestion about an external library also sounds plausible, and again would involve you recompiling that library with the same calling conventions as your main code, if that was possible.

I have sometimes found that the Build command can miss files that need to be recompiled, which can lead to your message. Again, a full rebuild will straighten things out.

つ可否回来 2024-07-20 22:13:13

当我以前遇到过这个错误时,总是在涉及 COM 时发生。
几乎总是与可重入性相关 - 您使用 COM 吗? 您是否使用 STA、消息过滤器?

When I've had this error before it's always been when COM has been involved.
Nearly always it's been specifically related to re-entrancy - are you using COM? Are you using STA, message filters?

欢你一世 2024-07-20 22:13:13

我遇到了完全相同的问题 - 在对象上调用重载的虚拟函数导致“ESP 未正确保存”错误,但调试显示编译器为此调用生成了错误的 vtable 偏移量,因此另一个函数具有更多参数被调用。 被调用函数更新了 ESP,就好像调用者已将更多参数压入堆栈一样,这又导致返回时出现无效的 ESP 值。

当我将包含错误类的头文件放在源文件顶部后,问题就消失了。 我还没有进一步调查到底是什么导致了这个问题,但我猜这是同样的情况 - 有些定义弄乱了虚拟成员的声明。

希望能帮助其他遇到同样问题的人。

I had exactly the same problem - calling an overloaded virtual function on an object resulted in the "ESP was not properly saved" error, but debugging showed that the compiler had generated a wrong offset into the vtable for this call, so another function with more parameters was getting called. The called function updated the ESP as if the caller had pushed more parameters on the stack, which in turn resulted in an invalid ESP value on return.

The problem disappeared after I put the header files including the class at fault at the top of the source file. I haven't investigated further what exactly caused this, but I guess it was for the same situation - some define messing with the declaration of a virtual member.

Hope that helps others that stumble into the same problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文