Как соглашение о вызовах cdecl может испортить ESP? - PullRequest
4 голосов
/ 13 февраля 2012

Мое приложение падает, потому что библиотечная функция, которую я вызываю, изменяет 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)  

Я уже опубликовал вопрос для этой проблемы, но мне показалось, что я специально спросил о соглашении о вызовах cdecl, чтобы получить более конкретную информацию о возможности прерывания esp, так как я думаю, что это может быть источником проблема ... Таким образом, извините этот "двойной пост".

Источник также может быть вызван неверной вызываемой функцией (возможно, из-за проблемы в файле def, который я создал с помощью dlltool, а затем создал импорт lib из - ординалы отличались от показанных Dependency Walker - я попробовал это с исправленными порядковыми номерами, но без изменений). Я чувствую, что это вряд ли источник проблемы, потому что другие вызовы функций работают нормально и возвращают правильные значения ...

Спасибо!

[Update]

По запросу сборка для __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 используется везде .

Я нашел эту ссылку , в которой указаны некоторые различия, которые могут объяснить, почему некоторые функции работают, а другие нет:

Visual C ++ / Win32

  • Объекты размером более 8 байт возвращаются в память.

  • Когда в памяти выполняется возврат, вызывающий передает указатель на ячейку памяти в качестве первого параметра (скрытый). Вызываемый заполняет память и возвращает указатель. Абонент выводит скрытый указатель вместе с остальными аргументами.

MinGW г ++ / Win32

  • Объекты размером более 8 байт возвращаются в память.

  • Когда в памяти выполняется возврат, вызывающая сторона передает указатель на ячейку памяти в качестве первого параметра (скрытый). Вызываемый заполняет память и возвращает указатель. callee извлекает скрытый указатель из стека при возврате.

может в этом проблема? Есть ли способ, которым я могу решить эту проблему? или мне нужно изменить Index.h Clang и переключиться на stdCall?

[Обновление 3]

Вот соответствующий GCC-Bug . Кажется, что в 4.6 (64-битной) и 4.7 (32-битной) вы можете использовать новый атрибут функции ms_abi , чтобы исправить проблему, описанную в [Обновление 2].

Ответы [ 3 ]

8 голосов
/ 14 февраля 2012

GCC и Visual C ++ не реализуют одно и то же cdecl соглашение о вызовах. Википедия объясняет :

Существуют некоторые различия в интерпретации cdecl, особенно в том, как возвращать значения.В результате программы x86, скомпилированные для разных платформ операционных систем и / или с помощью разных компиляторов, могут быть несовместимы, даже если они обе используют соглашение "cdecl" и не обращаются к базовой среде.[...] Чтобы передать «в память», вызывающая сторона выделяет память и передает указатель на нее как скрытый первый параметр;вызываемый заполняет память и возвращает указатель, выдавая скрытый указатель при возврате.

Последнее предложение является важным: версия cdecl в GCC делает вызываемого пользователя чистым скрытый указатель, тогда как версия Visual C ++из cdecl оставляет его для очистки вызывающей стороны.

5 голосов
/ 14 февраля 2012

в соответствии с сайтом clang, при начале работы вы можете создать его с помощью msvc, так почему бы не избавить себя от некоторых проблем и создать libvlang msvc, который обеспечит правильные соглашения о вызовах и использование ABI.альтернативно, вы можете использовать make-файл для сборки с помощью gcc через msvc.

4 голосов
/ 14 февраля 2012

Похоже, вы определили проблему в своем обновлении № 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 об этом, что нужно исправить в их коде?

...