Вызов метода COM случайно повреждает стек

У меня есть немного кода, который вызывает метод из COM-объекта (IDirect3D9), но каждый вызов вызывает ошибку проверки времени выполнения #0. Ошибка вызвана тем, что ESP не сохраняется должным образом во время вызова, поэтому возникает проблема стека (так как все COM-методы __stdcall). Необычной частью является простота подписи метода и обстоятельства.

Код построен только в 32-битном режиме, с MSVC 10 (VS 2010 SP1), с использованием заголовков и библиотек DirectX SDK (июнь 2010). Я переустановил SDK, чтобы убедиться, что заголовки не были испорчены, без удачи.

Я запускал код с подключенным отладчиком VS и WinDBG, а также несколько раз после перезагрузки / обновления драйверов. Проблема возникает каждый раз и идентична. Включение проверки кучи (и большинства других опций) в gflags, по-видимому, не дает больше информации, равно как и не работает с Application Verifier. Оба просто сообщают об одной и той же ошибке, что и всплывающее окно, или об ошибке, произошедшей вскоре после.

Без вызова (вместо этого возвращая постоянное значение) программа запускается, как и ожидалось. У меня нет идей о том, что здесь может пойти не так.

Рассматриваемая функция IDirect3D9::GetAdapterModeCountвызывается из оболочки D3D8-to-9 (часть проекта обновления графики для старых игр). Для более общей информации полный файл здесь.

Я перепробовал все следующие формы звонка:

UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);

UINT r = m_Object->GetAdapterModeCount(0, (D3DFORMAT)22);

UINT adapter = D3DADAPTER_DEFAULT;
D3DFORMAT format = D3DFMT_X8R8G8B8; // and other values
UINT r = m_Object->GetAdapterModecount(adapter, format);

Все из которых вызывают сбой проверки.m_Objectявляется действительнымIDirect3D9и используется ранее для множества других вызовов, в частности:

201, 80194887, Voodoo3D8, CVoodoo3D8::GetAdapterCount() == 3
201, 80195309, Voodoo3D8, CVoodoo3D8::GetAdapterIdentifier(0, 2, 0939CBAC) == 0
201, 80195309, Voodoo3D8, CVoodoo3D8::GetAdapterDisplayMode(0, 0018F5B4) == 0
201, 80196541, Voodoo3D8, CVoodoo3D8::GetAdapterModeCount(0, D3DFMT_X8R8G8B8) == 80

Последовательность записывается с помощью кода трассировки отладки, и кажется, что она верна и возвращает ожидаемые значения (3 монитора и т. Д.). Первые 3 вызова, от одного и того же объекта с моей стороны (один экземплярCVoodoo3D8), все успешно, без предупреждений о стеке. Четвертый нет.

Если я изменить порядок звонков, чтобы вызвать GetAdapterModeCountвызываться непосредственно перед любым другим в том же объекте, появляется та же ошибка проверки во время выполнения. Из тестирования это, как представляется, исключает немедленный предыдущий вызов, разбивающий стек; все 4 метода, вызывающие эти 4 функции, происходят в разных местах и ​​вызываютGetAdapterModeCountгде-нибудь из этого файла вызывает проблему.

Что подводит нас к необычной части. Другой класс (CVoodoo3D9) также вызывает ту же последовательностьIDirect3D9методы, с похожими параметрами, но не сбои (это эквивалентный класс-оболочка для D3D9). Объекты не используются в одно и то же время (код выбирается или другой в зависимости от процесса рендеринга, который мне нужен), но каждый из них каждый раз дает одинаковое поведение. Код для другого класса содержится в другом файле, что привело меня к подозрению о проблемах препроцессора (подробнее об этом позже).

После того, как это не предоставило никакой информации, я проверил соглашения о вызовах моего кода и параметров. Опять ничего не вышло на свет. Кодовая база компилируется с/w4 /wXи имеет в течение некоторого времени SAL для большинства функций и все правила PREfast включены (и проходят).

В частности, вызов завершается неудачно, когда вызывается в этом классе, независимо от того, происходит ли вызов моего метода из моего кода или другой программы, использующей объект.Сбой происходит независимо от того, где он вызывается, но только внутри этого файла.

Полный метод:

UINT STDMETHODCALLTYPE CVoodoo3D8::GetAdapterModeCount(UINT Adapter)
{
    UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);

    gpVoodooLogger->LogMessage(LL_Debug, VOODOO_D3D_NAME, Format("CVoodoo3D8::GetAdapterModeCount(%d, D3DFMT_X8R8G8B8) == %d") << Adapter << r);

    return r;
}

Сбой проверки происходит сразу после звонкаGetAdapterModeCount и снова, как мой метод возвращает, если разрешено выполнять до этой точки.

Выходная информация препроцессора, заданная параметром preprocess-to-file, имеет объявление метода (из d3d9.h) правильно как:

virtual __declspec(nothrow) UINT __stdcall GetAdapterModeCount( UINT Adapter,D3DFORMAT Format) = 0;

Объявление моего метода по сути идентично:

virtual __declspec(nothrow) UINT __stdcall GetAdapterModeCount(UINT Adapter);

Мой метод едва расширяется, становясь:

UINT __stdcall CVoodoo3D8::GetAdapterModeCount(UINT Adapter)
{
    UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);

    gpVoodooLogger->LogMessage(LL_Debug, L"Voodoo3D8", Format("CVoodoo3D8::GetAdapterModeCount(%d, D3DFMT_X8R8G8B8) == %d") << Adapter << r);

    return r;
}

Вывод препроцессора кажется правильным для обоих методов, в объявлении и определении.

Список сборки до точки отказа:

    UINT STDMETHODCALLTYPE CVoodoo3D8::GetAdapterModeCount(UINT Adapter)
    {
642385E0  push        ebp  
642385E1  mov         ebp,esp  
642385E3  sub         esp,1Ch  
642385E6  push        ebx  
642385E7  push        esi  
642385E8  push        edi  
642385E9  mov         eax,0CCCCCCCCh  
642385EE  mov         dword ptr [ebp-1Ch],eax  
642385F1  mov         dword ptr [ebp-18h],eax  
642385F4  mov         dword ptr [ebp-14h],eax  
642385F7  mov         dword ptr [ebp-10h],eax  
642385FA  mov         dword ptr [ebp-0Ch],eax  
642385FD  mov         dword ptr [ebp-8],eax  
64238600  mov         dword ptr [ebp-4],eax  
        UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);
64238603  mov         esi,esp  
64238605  push        16h  
64238607  push        0  
64238609  mov         eax,dword ptr [this]  
6423860C  mov         ecx,dword ptr [eax+8]  
6423860F  mov         edx,dword ptr [this]  
64238612  mov         eax,dword ptr [edx+8]  
64238615  mov         ecx,dword ptr [ecx]  
64238617  push        eax  
64238618  mov         edx,dword ptr [ecx+18h]  
6423861B  call        edx  
6423861D  cmp         esi,esp  
6423861F  call        _RTC_CheckEsp (6424B520h)  
64238624  mov         dword ptr [r],eax  

Для уточнения ошибка приходит на 6423861F (призыв к _RTC_CheckEsp), предполагая, что колл или подготовка сломали стек. Я работаю с предположением, что, поскольку один и тот же вызов работает в других местах, это не что-то, что нарушает вызов.

На мой неподготовленный глаз единственная необычная часть - это пара mov register, dword ptr [register+8], Поскольку это 32-битная система, я не уверен, если +8 может быть слишком большим приращением или как оно может попасть в сборку, если это так.

Вскоре после того, как мой метод возвращается, очевидно из-за прерывания вызова ESP, программа segfaults. Если я не позвоню GetAdapterModeCount и просто вернуть значение, программа выполняется как ожидалось.

Кроме того, в аналогичной точке происходит сбой сборки выпуска (без RTC) со стеком:

d3d8.dll!CEnum::EnumAdapterModes()  + 0x13b bytes   
Voodoo_DX89.dll!ClassCreate()  + 0x963 bytes

Хотя я не уверен в последствиях адреса. Насколько я могу судить, это не то же самое место, где segfaults в отладочных сборках; они находятся в программе после того, как мои методы возвращаются, это происходит во время одного из моих методов, который извлекает данные из D3D8. Изменить: Segfault происходит в более поздний вызов, который я в настоящее время отлаживаю.

На данный момент, я в полной растерянности относительно того, что идет не так и как, и я не могу проверить.

1 ответ

Решение

Я не вижу ничего плохого в том, что вы делаете или с вашим сгенерированным кодом сборки.

Я могу ответить на вашу одну проблему, хотя.

ecx,dword ptr [eax+8]

То, что это делает, перемещает адрес m_Object в регистр ecx. +8 - это смещение в вашем классе к m_Object, что, вероятно, правильно.

На что посмотреть. Пройдите через ассемблерный код, пока не дойдете до этой точки:

6423861B  call        edx  
6423861D  cmp         esi,esp

В этот момент проверьте регистры esi и esp (в VS просто наведите указатель мыши на имена регистров).

Перед выполнением вызова ESI должен быть на 12 выше, чем ESP. После звонка они должны быть равны. Если это не так, напишите, что они есть.

Обновить:

Итак, что бросается в глаза, так это то, что из 4 методов, которые вы показываете, которые вы вызываете, только GetAdapterModeCount имеет разную подпись между D3D8 и D3D9, и эта подпись отличается на 4 байта, что является разницей в вашем стеке.

Как получается m_Object? Поскольку это какой-то адаптер между D3D8 и D3D9, возможно ли, что ваш m_Object на самом деле является объектом IDirect3D8, который в какой-то момент приводится как IDirect3D9? Это объяснило бы ошибку и почему она работает в другом контексте, если вы получаете объект D3D другим способом.

Другие вопросы по тегам