vam, опечатка в паре символов... Перефразирую: Код (Text): class Interface { public: virtual ~Interface() { } virtual void Method() = 0; }; Предположим что у тебя есть список объектов классов которые реализуют вот этот интерфейс. Ессно, для каждого класса реализация разная. At run time, у тебя есть указатели на instances и, ессно, их vtables. То есть, код делает три операции - получает указатель на объект - через него получает таблицу виртуальных методов - из этой таблицы получает указатель на метод Ессно, сами таблицы (vtables) генерируются at compile time, но объект, использующий одну из этих таблиц, инстанциируется at run time. То есть, значение этого указателя и, следовательно, используемый vtable коду (и декомпилеру) не известен.
Уважаемый s0larian, это всё мне очень хорошо известно, ты привел "классический" пример реализации виртуальности, он декомпилятором во всех вариациях обрабатывается. Run time, как такового в декомпиляторе нет, но его заменяет многопроходный анализатор кода. Но усложним пример, в одну переменную будем записывать указатели на разные и необязательно родственные классы (переменная в этом случае может выступать аналогом union), далее, при каких-то определенных условиях осуществить вызов указанной выше функции. Декомпилятору абсолютно неважно какая функция будет выполняться в run time в этом месте, главное, что шаблон вызова будет одним и тем же для всех вызываемых здесь функций. Примечание: шаблон вызова - это совокупность параметров объявления функции и включает в себя: тип возвращаемого значения, типы и кол-во аргументов, конвенцию вызова и имя функции без scope. Если декомпилятор построит или найдет шаблон вызова для этого места, то задача 100% решена. Все параметры шаблона, кроме последнего (имя), определяются в месте вызова функции по коду или анализом фрейма стека. Самое сложное - это найти имя, именно имя, а не точку входа в функцию или её адрес, которых может быть множество. В данном случае имя функции можно найти только через scope, выходя через него на соответствующую ячейку vftable, а scope в свою очередь находим анализом всех присвоений вызывающей функцию переменной.
Vam, я не понимаю что ты пытаешься решить. Вот как я вижу проблему №1: имея несколько вызовов виртуальных ф-ций через указатели, декомпилер должен выдать что-то типа этого: Код (Text): Class4 *p1 = (Class4 *)something; p1->Method2(...); Class10 *p2 = (Class10 *)something_else; p2->Method4(...); То есть, ты показываешь пользователю то что есть в коде: указатель, класс и ф-цию класса. Ессно если пользователь переименует что-то, то ты должен обновить все вызовы. Проблема №2 в том как определить аргументы. IMHO тут надо дать возможность пользователю определить/исправить т.к. ну уж очень много тут вариантов. В любом случае, этот вопрос отличен от декодирования/показа виртуальных вызовов. Так?
s0larian Нет, не совсем так. Приведу пример исходного кода, не после декомпилятора, а реального, из которого получен экзешник и всё станет понятно: Код (Text): class A { virtual void vFunc(void*, int); }; class B { virtual void vFunc(int); }; class C { void* m_pObject; void Func1(...); void Func2(...); }; void C::Func1(...) { if(...) m_pObject = new A(); else if(...) m_pObject = new B(); else if(...) m_pObject = new char[100]; else m_pObject = NULL; } void C::Func2(...) { if(...) ((A*)m_pObject)->vFunc(aa, 3); // s1 else if(...) ((B*)m_pObject)->vFunc(1); // s2 ... } Вот этот код, ессно после компиляции, и предстоит восстановить декомпилятору. В Func2 он встречает "неразрешимую" ситуацию, не может правильно восстановить строки s1 и s2. Вот, что у него получается при декомпиляции в строках s1 и s2: m_pObject->???(aa, 3); // s1 m_pObject->???(1); // s2 как видим, аргументы восстановлены, а имя вызываемой функции неизвестно. Вот этот вопрос и хотел обсудить, как правильно восстановить этот код. Все другие варианты виртуальности, даже для производных классов по указателю на базовый класс Код (Text): class A { virtual void v1(int) = 0; }; class B : public A { virtual void v1(int); }; class C : public A { virtual void v1(int); }; class D { A* m_pObject; void Func1(...); void Func2(...); }; void D::Func1(...) { if(...) m_pObject = new B(); else if(...) m_pObject = new C(); } void C::Func2(...) { m_pObject->v1(0); } декомпилятором восстанавливаются правильно. ------------------------------------------------------------ ЗЫ: Первоначально я думал, что для разрешения вопроса виртуальности достаточно определить тип вызывающей переменной, но это не так, любая переменная в точке вызова виртуальной функции может быть явно преобразована программистом к другому типу. Сам признак преобразования определим (в дебаг варианте кода) вот этими строками из поста №119 Код (Text): .text:007689F4 mov ecx, [ebp+var_20] .text:007689F7 mov [ebp+var_1C], ecx и задача состоит в определении типа преобразованной переменной.
Нет, я сказал совсем не то, что ты подумал, поясняю. В var_20 сохраняется компилятором переменная m_pObject типа void*, а в var_1C сохраняется уже преобразованная переменная (имени у нее нет, увы temporary) типа A*, ну а далее идет вызов функции... Это же только признак преобразования, если бы явного преобразования не было, то var_1C в коде бы отсутствовала и использовалась далее только бы var_20.
Вот реальный исходник такой функции Код (Text): zCPar_Symbol::~zCPar_Symbol() { if(m_pDataAddr && !f.tNoMemory && f.tTrueAlloc) { int type = f.tType; if((type == T_FLOAT) || (type == T_INT)) { if(f.tNumber > 1) delete (int*)m_pDataAddr; } else if(type == T_STRING) { if(f.tNumber <= 1) delete (zSTRING*)m_pDataAddr; else delete[] (zSTRING*)m_pDataAddr; } } m_pDataAddr = NULL; m_pParent = NULL; m_pNext = NULL; } Пояснения: это кусок парсера, который разбирает текст и в переменную m_pDataAddr типа void* записывается значение ключевых слов, которое может быть и массивом. Например, ВВВ = 100, в m_pDataAddr будет записано 100, тип int, если ВВВ = {100, 200, 300}, то m_pDataAddr = new int[3], если ВВВ = "строка", то m_pDataAddr = new zSTRING("строка") и т.д. Класс zSTRING - строковый контейнер и имеет виртуальный деструктор. Остальное понятно из текста. Я свой пример для лучшего понимания, конечно, немного утрировал, но это не влияет на асм реализацию. s0larian Я такого не говорил, отсутствии наследования - не значит отсутствие vtable, а отсутствие vtable - не значит прямой вызов. Тип вызова (прямой или косвенный) определяется по асм инструкции call ..., если параметр инструкции имеет тип immediate const и его значение попадает в сегмент .text кода, то это прямой вызов.
Вот ещё один очень простой исходник реальной функции Код (Text): template<class INDEX, class DATA> void zCSparseArray<INDEX, DATA>::DestructIndex(void* index) const { ((INDEX*)index)->~INDEX(); } Здесь может вызваться вообще что угодно, или обычный или виртуальный деструктор или вообще ничего (пустая функция) в зависимости от контекста использования шаблона.
Ну принципиально нельзя в общем случае установить, что за виртуальная ф-ция вызывается в данной точке. Это зависит от внешних раздражителей программы - входных данных, действий юзера и т.п. Т.е. придется моделировать не саму программу, а всю среду исполнения, в т.ч. среду обитания юзера.
Зачем так пессимистично, порассуждаем, если что не так, поправьте. Если есть виртуальная функция, значит есть класс и обязательно с конструктором. Если виртуальная функция вызывается значит должен быть указатель на класс, прямой - тип pointer, косвенный - адрес статического объекта. Значит должен существовать сам объект в памяти - динамический или статический, чтобы на него получить указатель. А создание объекта в памяти будет выполнено через вызов конструктора, а в конструкторе однозначно определяется таблица виртуальных функций. Цепочка замкнулась и этой информации нам достаточно чтобы разрешить ситуацию, при условии, что мы будем отслеживать тип каждой переменной. Определим три точки отслеживания переменной А - присвоение (А = ввв), изменение (А += 1) и использование (А-> или В = А), в скобках приведены, конечно, не все варианты. На каждую переменную заводим массив типа - в него складываем типы присвоения в точке присвоения. Точка изменения здесь не интересна, т.к. тип не меняется, а изменяется только значение. В точке использования анализируем типы в массиве и выбираем нужный... И всё, задача в принципе решена.
Vam Давайте возьмём более простой случай: в некоторой точке программы делается косвенный вызов ф-ции (т.е. вызов ф-ции по указателю). В общем случае установить какая именно ф-ция вызывается, нельзя, вот пример: Код (Text): typedef void (*FuncPtr)(); FuncPtr GetFuncPtrDependingOnUserInput() { switch (getchar()) { case 'A': return FuncA; case 'B': return FuncB; //... case 'Z': return FuncZ; } //... } //... FuncPtr pFuncPtr = GetFuncPtrDependingOnUserInput(); pFuncPtr(); // Как вы определите, которая из ф-ций FuncX здесь вызывается? Это зависит от того, что ввёл юзер, а не только от свойств программы. То же самое и с вызовом виртуальной ф-ции, только там ещё сложнее - уровень косвенности в 2 раза выше.
прочитал всю тему, осознал свое ничтожество:-(((( даже перестало казаться, что зарплату занижают и пора б ругаться опять... выражаю глубочайшее уважение топикстартеру.
green А зачем нам это нужно? Мы ведь восстанавливаем только исходный код, а не делаем эвристический эмулятор программы. В исходном коде в месте вызова и будет pFuncPtr();, я уже говорил, чтобы записать в общем виде вызов функции нам нужно построить её шаблон. Здесь параметры шаблона, как возвращаемое значение и аргументы определяем в точке вызова, имя, если не находим его в явном виде, будет сконструировано из места (адреса) размещения переменной FuncPtr pFuncPtr = GetFuncPtrDependingOnUserInput(); при такой записи переменная может быть локальная (стековая или регистровая при оптимизации), или глобальная в инициализированной области памяти данных. Возможные записи вызова функции в созданных исходниках будут соответственно такими: pFuncPtr(); __$Var40(); __$ebx(); funcptr_00C1155A(); Здесь всё просто и интерактивность не нужна.
Вопрос: Можно ли по содержимому двоичного файла (exe, dll, drv и т.д.) точно сказать каким компилятором создан этот файл, если да, то по какой информации? На данный момент конкретно интересует, можно ли различить файлы компилированные разными версиями VS. (6, 7, 2003, 2005, 2008).
GoldFinch А более конкретно можно, как я знаю это одно поле DWORD типа, какое число (номер версии) соответствует конкретному VS линковщику. Не один же у них линковщик для всех студий.
Vam Анализируй стартап-код, может он для разных версий в чем-то отличен? ЗЫ Можно еще посмотреть, как версия определяется в IDA