2012-01-03 4 views
0

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

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

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

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

Данная функция относится к IDirect3D9::GetAdapterModeCount, вызывается из обертки D3D8-to-9 (часть a graphics upgrade project for old games). Для получения дополнительной информации, the full file is here.

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

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 и снова, как мой метод возвращает, если разрешено выполнять в этой точке.

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

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

+0

Что произойдет, если вы закомментируете вызов 'gpVoodooLogger-> LogMessage'? – MSN

+0

@MSN Никаких изменений, этот вызов, кажется, работает нормально. Проверка ESP происходит и выходит из строя до ее вызова. Комментирование другого вызова действительно помогает, но является важным. – ssube

+0

Нет веской причины, что несбалансированное значение ESP приведет к краху вашего приложения. ESP восстанавливается функцией эпилога. Я бы просто исходил из предположения, что * обе проблемы имеют одну и ту же причину, внутреннее повреждение состояния программы. –

ответ

5

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

Я могу ответить на вашу озабоченность.

ecx,dword ptr [eax+8] 

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

Что-то посмотреть. Шаг через код сборки, пока не достигнет этой точки:

6423861B call  edx 
6423861D cmp   esi,esp 

В этот момент проверки ЭСИ и особ регистров (в VS просто наведите курсор мыши на имена регистров).

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

Update:

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

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

+0

Hm. До звонка у меня ESI на 1635636 и ESP по 1635624 (на 12 выше, как и должно быть). Однако после вызова они равны 1635636 и 1635632 соответственно. Кажется, это говорит о том, что на самом деле это * вызов как-то, но тот же самый вызов работает в другом коде, поэтому мне нужно это выяснить. – ssube

+0

@peachykeen см. Мое обновление – Gerald

+0

Nice catch. Раньше это было пятно, где нужно было два 'void *' нужно было называть 'IDirect3D8 * 'и' IDirect3D9 * ', но у 8 была опечатка и была передана позже. Теперь он рушится в другом, менее запутанном месте. : D – ssube