Сообщений 0 Оценка 75 Оценить |
Приложение IKnown test
Демонстрационное приложение (COM-объект + console) IKnown test (25kb)
Приложение PdbView
Демонстрационное приложение (WTL MDI) PdbView (26kb)
Одной из основ COM-программирования является интерфейс IUnknown. Метод QueryInterface() этого интерфейса позволяет получать все прочие интерфейсы этого объекта. К сожалению, это подразумевает, что вызывающая сторона отлично осведомлена о возможных интерфейсах объекта и даже знает все GUID'ы этих интерфейсов. А если нет? Если возникает необходимость просто просмотреть все интерфейсы, в научно-познавательных целях?
Тут есть два пути: узнать у разработчика и перебрать все возможные варианты GUID'ов интерфейсов. Увы, скорости нынешних процессоров недостаточно для того, чтобы перебрать все возможные 2 в 128 вариантов, и хорошо бы как-то уменьшить размер перебора. Давайте поищем большие скопления GUID'ов и отправим их всех в IUnknown::QueryInterface().
Одно из таких скоплений это реестр Windows. Огромное количество интерфейсов прописано в HKEY_CLASSES_ROOT\Interface.
CRegKey key; if(ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("Interface"))) { TCHAR szInterface[MAX_PATH]; TCHAR szIID[64]; for (DWORD dwIndex = 0; ERROR_SUCCESS == ::RegEnumKey(key, dwIndex, szIID, sizeof(szIID) / sizeof(szIID[0])); ++dwIndex) { LONG cb = MAX_PATH; if (ERROR_SUCCESS != ::RegQueryValue(key, szIID, szInterface, &cb)) szInterface[0] = _T('\x0'); // TODO } } |
Такой способ использует DumpQIQS от Andrew Nosenko. Этот список дает приемлимые результаты, но, к сожалению, далеко не полон.
Довольно большое количество GUID'ов недокументированных интерфейсов находится в .lib файлах uuid.lib, dxguid.lib, ADSIid.Lib и им подобным. Формат этих файлов хорошо известен и я не буду вдаваться в технические детали, скажу лишь, что для нас представляют интерес только объекты класса IMAGE_SYM_CLASS_EXTERNAL размером 16 байт, т.е. sizeof(GUID). Пример IKnown test содержит целиком код для разбора .lib файлов на .obj и поиск GUID'ов в .obj файлах.
Гарантировано полный список всех интерфейсов, реализуемых объектами какого-либо модуля находится в его отладочном файле. Увы, раздобыть .pdb файл нужного компонента ничуть не проще, чем его исходный код. Проще все-же написать разработчику и прямо спросить: "А нету ли у вас интерфейса, реализующего то-то и то-то?".
Радостным исключением из этого правила явлвется Microsoft Windows NT. Полный набор .pdb файлов этой операционной системы (а она теперь включает Microsoft Internet Explorer и DirectX) доступен для бесплатной загрузки с Web-сервера Майкрософт под названием Windows Customer Support Diagnostics или через подписку MSDN. Стоит только заполучить в свои руки отладочный файл интересующиего нас модуля, как проблема перебора всех GUID'ов интерфейсов решается и решается на 100%.
Для этого нам нужно:
BOOL CALLBACK EnumSymbolsCallback( LPSTR szSymbolName, ULONG SymbolAddress, ULONG SymbolSize, PVOID pUserContext ) { // Обычно, GUID'ы объявляют как extern "C" так что их имена должны начинаться с '_'. if ('_' == szSymbolName[0] && !::IsBadReadPtr(LPVOID(SymbolAddress), sizeof(GUID))) { IID iid = GUID_NULL; ::CopyMemory(&iid, LPVOID(SymbolAddress), sizeof(GUID)); // TODO } return TRUE; } ::SymEnumerateSymbols(m_hProcess, dwModuleBase, EnumSymbolsCallback, NULL); |
Параметр SymbolSize, передаваемый в EnumSymbolsCallback не является действительным размером объекта. На самом деле, это просто разница адресов этого и следующего отладочных символов. Как правило, она совпадает с размером объекта, но бывает что и нет. |
Объект IKnown из тестового приложения реализует все три способа. Пример использования:
pKnown->LoadInterfacesFromRegistry(); pKnown->LoadInterfacesFromLibrary(OLESTR("Uuid.Lib")); // Загружаем символы для модуля, в котором находится реализация // метода QueryInterface этого объекта DWORD_PTR *pVTable = ((DWORD_PTR **)pUnk.p)[0]; pKnown->LoadInterfacesFromDebugSymbols( pKnown->GetPointerHModule(LPVOID(pVTable[0]))); pKnown->DumpUnknown(pUnk, PrintCallback, LPVOID(pKnown)); |
Ну хорошо, выяснили мы, какие интерфейсы есть у объекта. А толку-то? Что теперь делать с этим IID_OurInterfaceForInternalUseOnly? Где можно узнать про этот пресловутый IOurInterfaceForInternalUseOnly? Да все там же! В тех самых файлах с отладочной информацией. Чтобы понять, как это возможно, нужно сначала разобраться, что же такое COM-интерфейс и как его реализуют нынешние компиляторы.
Итак, COM-интерфейс это абстрактный базовый клас, все методы которого являются виртуальными, т.е. вызов этих методов осуществляется через специальную таблицу указателей на функции, известную как vftable. Например, вызов IUnknown::QueryInterface() преобразуется компилятором в
push eax // положить в стек второй параметр push esi // положить в стек первый параметр mov ecx,dword ptr [ebp+0Ch] // загрузить vftable в ECX mov edx,dword ptr [ecx] // получить в EDX адрес первой виртуальной функции (QueryInterface) call dword ptr [edx] // вызвать функцию по указателю. |
Таким образом, наша задача сводится к поиску таких таблиц и вычислению имен каждого из методов. Для этого нужно перебрать все отладочные символы и отобрать среди них все те, чьи имена выглядят как const <имя_класса>::'vftable' или const <имя_класса>::'vftable' {for 'имя_базового_класса'}. Дальше мы можем просто трактовать область памяти, адресуемую этим символом как массив указателей на функции.
BOOL CALLBACK EnumSymbolsCallback( LPSTR szSymbolName, ULONG dwSymbolAddress, ULONG dwSymbolSize, PVOID pUserContext ) { if (szSymbolName LIKE "const *::`vftable'*for `*`") { LPDWORD pdwMember = LPDWORD(dwSymbolAddress); PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)alloca( sizeof(IMAGEHLP_SYMBOL) + MAX_PATH); if (pSymbol) { DWORD dwDisplacement = 0; BOOL bOk; do { pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); pSymbol->MaxNameLength = MAX_PATH; bOk = ::SymGetSymFromAddr(::GetCurrentProcess(), *pdwMember, &dwDisplacement, pSymbol); if (bOk && 0 == dwDisplacement) { // TODO } } while(bOk); } } |
Тестовое приложениие PdbView пытается восстановить все классы заданного модуля если имеется соответствующий .pdb файл.
Данная статья носит чисто ознакомительный характер. Использование восстановленных интерфейсов может повлечь за собой нарушение авторских прав и преследоваться установленным порядком на территории Российской Федерации и за ее пределами.
Сообщений 0 Оценка 75 Оценить |