Вызов метода COM случайно повреждает стек - PullRequest
0 голосов
/ 03 января 2012

У меня есть немного кода, который вызывает метод из 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. Редактировать: Сегфоут происходит в более позднем вызове, который я сейчас отлаживаю.

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

1 Ответ

5 голосов
/ 03 января 2012

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

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 другим способом.

...