【发布时间】:2023-03-21 01:55:01
【问题描述】:
我的应用程序崩溃了,因为我调用的库函数更改了 ESP,尽管它被声明为 cdecl。
库 (libclang.dll) 是使用 MinGW 编译的,我在 VC++ 项目中使用它。这些函数被导出为 C 函数,Dependency Walker 告诉我它们具有正确的 cdecl 调用约定。这些函数通过包含 Clang 的“index.h”文件使用 dllimport 导入我的项目中。似乎并非所有函数都在破坏 ESP,因此某些函数执行成功,而其他函数则导致崩溃。
这是一个工作函数的组装:
// call to clang_getNumDiagnostics(TU); - works!
5AF3EFAB mov esi,esp
5AF3EFAD mov eax,dword ptr [ebp-30h]
5AF3EFB0 push eax
5AF3EFB1 call dword ptr [__imp__clang_getNumDiagnostics (5AF977E0h)]
5AF3EFB7 add esp,4
5AF3EFBA cmp esi,esp
5AF3EFBC call @ILT+7135(__RTC_CheckEsp) (5AF16BE4h)
下面的函数调用会改变 esp(加 4),从而由于 __RTC_CheckEsp 中的运行时检查而导致崩溃。
// call to clang_getTranslationUnitCursor(TU); - fails!
5AF3EFC1 mov esi,esp
5AF3EFC3 mov eax,dword ptr [ebp-30h]
5AF3EFC6 push eax
5AF3EFC7 lea ecx,[ebp-234h]
5AF3EFCD push ecx
5AF3EFCE call dword ptr [__imp__clang_getTranslationUnitCursor (5AF9780Ch)]
5AF3EFD4 add esp,8
5AF3EFD7 cmp esi,esp
5AF3EFD9 call @ILT+7135(__RTC_CheckEsp) (5AF16BE4h)
我已经针对这个问题发布了question,但我认为我专门询问了调用约定 cdecl 以检索有关 esp 损坏可能性的更具体信息,因为我认为这可能是问题的根源......所以请原谅这个“双重职位”。
源代码也可能在于调用了错误的函数(可能是由于我使用 dlltool 创建的 def 文件中存在问题,后来又从中创建了导入库 - 序数与 Dependency Walker 显示的不同 - 我试过了有更正的序数,但没有变化)。我觉得这不太可能是问题的根源,因为其他函数调用工作正常并返回正确的值......
谢谢!
[更新]
根据 __imp__clang_getTranslationUnitCursor 的要求组装
6660A4A0 push ebp
6660A4A1 mov ebp,esp
6660A4A3 push edi
6660A4A4 push ebx
6660A4A5 mov eax,dword ptr [ebp+8]
6660A4A8 mov ebx,eax
6660A4AA mov al,0
6660A4AC mov edx,14h
6660A4B1 mov edi,ebx
6660A4B3 mov ecx,edx
6660A4B5 rep stos byte ptr es:[edi]
6660A4B7 mov eax,dword ptr [ebp+8]
6660A4BA mov dword ptr [eax],12Ch
6660A4C0 mov eax,dword ptr [ebp+8]
6660A4C3 mov edx,dword ptr [ebp+0Ch]
6660A4C6 mov dword ptr [eax+10h],edx
6660A4C9 mov eax,dword ptr [ebp+8]
6660A4CC pop ebx
6660A4CD pop edi
6660A4CE pop ebp
6660A4CF ret 4
[更新 2] 由于 VC++ 和 GCC 都使用 cdecl 作为默认值,并且没有在函数声明中明确说明的情况下无法在 GCC 中强制使用另一个默认调用约定(对于有问题的函数没有这样做),我实际上确信 cdecl 无处不在.
我发现了这个 link,它说明了一些差异,可以解释为什么某些功能有效而其他功能无效:
Visual C++ / Win32
大于 8 字节的对象在内存中返回。
当在内存中返回时,调用者将指向内存位置的指针作为第一个参数(隐藏)传递。被调用者填充内存,并返回指针。 调用者 将隐藏指针与其余参数一起弹出。
MinGW g++ / Win32
大于 8 字节的对象在内存中返回。
当在内存中返回时,调用者将指向内存位置的指针作为第一个参数(隐藏)传递。被调用者填充内存,并返回指针。 被调用者在返回时从堆栈中弹出隐藏指针。
这可能是问题吗?有什么办法可以解决这个问题吗?还是我必须更改 Clang 的 Index.h 并切换到 stdCall?
[更新 3]
这里是对应的GCC-Bug。似乎在 4.6 (64bit) 和 4.7 (32bit) 中您可以使用新的 ms_abi 函数属性 来修复 [Update 2] 中描述的问题。
【问题讨论】:
-
假设您对导入函数的 cdecl 假设是正确的,则此代码看起来不错。当您进入 __imp__clang_getTranslationUnitCursor 调用时,代码是什么样的?
-
这是你的问题:ret 4。这不是 cdecl。
-
好的,我在帖子中添加了调用的程序集。我实际上不知道我可以使用 MinGW dll 来实现它,但似乎我可以......
-
我实际上认为 cdecl 是 gcc 的默认调用约定,当声明没有特别设置为另一个时。我用我发现的关于 cdecl 的新信息更新了我的帖子......
-
我不熟悉 MinGW g++,但你必须找到两个平台之间 100% 兼容的调用约定。
标签: c++ visual-c++ x86 mingw calling-convention