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