Вызов метода 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 другим способом.