如何通过cdecl调用约定来破坏ESP?

Cod*_*lad 4 c++ assembly mingw calling-convention visual-c++

我的应用程序崩溃了,因为我调用的库函数更改了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)
Run Code Online (Sandbox Code Playgroud)

以下函数调用将更改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)  
Run Code Online (Sandbox Code Playgroud)

我已经发布了一个问题,对于这个问题,但认为我特地问了一下调用约定CDECL检索有关的ESP currupted的可能性更具体的信息,因为我认为这可能是问题的根源......因此原谅这个"双后".

源代码也可能位于被调用的错误函数中(可能是由于我使用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  
Run Code Online (Sandbox Code Playgroud)

[更新2] 由于VC++和GCC都使用cdecl作为默认值,并且没有办法在GCC中强制执行另一个默认调用约定而没有在函数声明中明确说明它(对于有问题的函数没有这样做),我实际上确定cdecl随处可用.

我发现了这个链接,它说明了一些差异可以解释为什么某些功能有效而其他功能不起作用:

Visual C++/Win32

  • 内存中返回大于8个字节的对象.

  • 当在内存中进行返回时,调用者将指向内存位置的指针作为第一个参数(隐藏)传递.被调用者填充内存,并返回指针.该呼叫者与参数的休息一起弹出隐藏的指针.

MinGW g ++/Win32

  • 内存中返回大于8个字节的对象.

  • 当在内存中进行返回时,调用者将指向内存位置的指针作为第一个参数(隐藏)传递.被调用者填充内存,并返回指针.返回时,被调用者从堆栈弹出隐藏指针.

可能那是问题吗?有什么方法可以解决这个问题吗?或者我是否必须更改Clang的Index.h并切换到stdCall?

[更新3]

这是根据GCC-Bug.似乎在4.6(64位)和4.7(32位)中,您可以使用新的ms_abi函数属性来修复[Update 2]中描述的问题.

Ray*_*hen 8

GCC和Visual C++没有实现相同的cdecl调用约定.维基百科解释说:

cdecl的解释有一些变化,特别是如何返回值.因此,为不同的操作系统平台和/或不同的编译器编译的x86程序可能是不兼容的,即使它们都使用"cdecl"约定而不调用底层环境.[...]为了传递"在内存中",调用者分配内存并将指针作为隐藏的第一个参数传递给它; callee填充内存并返回指针,返回时弹出隐藏的指针.

最后一句是重要的一句:GCC的cdecl版本使被调用者清理隐藏的指针,而Visual C++的cdecl版本则让调用者清理它.


Nec*_*lis 5

根据clang网站,在开始时,你可以用msvc构建它,所以为什么不只是省去一些麻烦并构建一个msvc libclang,这将确保使用正确的调用约定和ABI.或者,您可以使用makefile通过msvc使用gcc进行构建.