我的应用程序崩溃了,因为我调用的库函数会更改ESP,尽管它被声明为cdecl。
库(libclang.dll)是使用MinGW编译的,我在VC ++项目中使用它。函数导出为C函数,Dependency Walker告诉我他们有正确的cdecl调用约定。使用dllimport将函数导入到我的项目中,包括Clang的“index.h”文件。看来,并非所有功能都会破坏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个字节的对象。
当在内存中进行返回时,调用者将指向内存位置的指针作为第一个参数(隐藏)传递。被调用者填充内存,并返回指针。返回时,被调用者会从堆栈中弹出隐藏指针。
[更新3]
以下是GCC-Bug。似乎在4.6(64位)和4.7(32位)中,您可以使用新的 ms_abi函数属性来修复[Update 2]中描述的问题。
答案 0 :(得分:8)
GCC和Visual C ++没有实现相同的cdecl
调用约定。 Wikipedia explains:
cdecl的解释有一些变化,特别是如何返回值。因此,为不同的操作系统平台和/或不同的编译器编译的x86程序可能是不兼容的,即使它们都使用“cdecl”约定而不调用底层环境。 [...]为了传递“在内存中”,调用者分配内存并将指针作为隐藏的第一个参数传递给它;被调用者填充内存并返回指针,返回时弹出隐藏的指针。
最后一句是重要的一句:GCC的cdecl版本使被调用者清理隐藏的指针,而Visual C ++的cdecl版本则让调用者清理它。
答案 1 :(得分:5)
根据clang网站的说法,在开始使用时,你可以使用msvc构建它,所以为什么不给自己省点麻烦并构建一个msvc libclang,这将确保使用正确的调用约定和ABI。或者,您可以使用makefile通过msvc构建gcc。
答案 2 :(得分:4)
您似乎已在更新#2中发现了问题。如果你想在不修改clang库源的情况下解决这个问题,你可以使用一个调用约定来编写你自己的包装函数,这个函数是在GCC中编译的,这两个编译器实际上同意通过内存缓冲区返回值:
// compile this wrapper in MinGW - it will be able to call the
// original clang_getTranslationUnitCursor() correctly, since
// it'll have the same idea of how __cdecl shoudl handle values
// returned in a memory buffer
//
// Since both MSVC and GCC __stdcall functions seem to handle return
// values via memory in the same way, this wrapper should be callable
// by MSVC
CXCursor __stdcall wrapper_getTranslationUnitCursor(CXTranslationUnit tu)
{
return clang_getTranslationUnitCursor(tu);
}
希望你不必为太多的功能而这么做。
另一种方法是在MSVC中编译一个包装器,它使用汇编语言模块(或内联汇编)来处理调用约定的差异。
我想知道clang是否会考虑需要在代码中修复的内容?