Y_Mur ООП это подход, подход к написанию программы целиком, который оправдывает себя в случае средних и больших проектов. Смысл его в увеличении уровня абстракции. Но вся эта абстрактность теряется при компиляции. Инкапсуляция, наследование, полиморфизм -- всё это либо невосстановимо, либо восстановимо в самых простых случаях при соблюдении множества условий(debug-info, определённые настройки и версия компилятора). Я ни в коем случае не отговариваю автора от реализации, если он действительно знает, что делает. Мне лишь интересно узнать, что именно он наработал в процессе решения задачи, какие-нибудь новые приёмы или особенности. Действительно интересно. Ну и побольше примеров хотелось бы.
Именно. inline это не copy/paste, т.к. компилер анализирует data flow, проводит constant propagation и пототом убирает мёртвый код (то есть то у чего нету side effects). Я уже спрашивал у TS что будет делать его tool в Release без debug info когда в оригитале было Код (Text): myVect.erase(myVect.begin()); Кста, про шаблоны - кто-нить смотрел что выплёвывает компилер для такого выражения: Код (Text): std::for_each(ovservers_.begin(), ovservers_.end(), boost::bind(&StatusHandler::OnStart, _1, steps));
W4FhLF Абстрактность восстанавливается просто - каждый абстрактный класс имеет "пустую" статическую таблицу виртуальных функций из purecall, т.е. из нулевых элементов, которая восстановима по производным классам. Сам по себе абстрактный класс (без производных) компилятором не обрабатывается. С шаблонностью сложнее, но вы думаете для чего я использую IDA как базу для декомпилятора - не только для разбора ассемблерных инструкций, но и как инструмент реализующий FLIRT технологию с сигнатурами библиотек. Большинство полных имен библиотечных функций будет IDA восстановлено, а далее - дело техники декомпилятора. Уже ответил. Наиболее просто по конструктору или декструктору, если их нет - то сложнее, все зависит от структуры базового класса, но методы распознавания есть - по вызовам функций, по статичности элементов, по data flow и т.д. Что такое extern "C" в С++ надеюсь знают все, так вот под ним и скрыта эта особенность языка С. Именем. В большинстве случаев ссылка и указатель распознаются однозначно, а где это сделать невозможно - значит различий в них нет и не принципиально их представление. Мне по умолчанию больше нравится указатель. Это все непринципиальные свойства языка (на исполняемость кода не влияют), хотя косвенным образом (а местами и прямым) и интерактивностью это реализуемо. Y_Mur Правильно подмечено - на все библиотеки могут быть применены сигнатуры, я ещё не встречал, чтобы IDA не распознала MFC в какой либо программе. green Зачем же так критично, все зависит от функции, у компилятора есть свои ограничения на создание инлайн (не всякое объявление inline будет всегда и везде встроенным кодом). Я уже говорил - здесь могут помочь сигнатуры функций и интерактивность. Если же в простейшем случае тело функции имеет вид: а = в;, то такую функцию восстановить невозможно, хотя и здесь все зависит от типов а и в. W4FhLF Примеров декомпиляции разных функций могу привести несколько сотен, только что это даст без построения и понимания структуры данных. Сейчас работаю над завершающими проходами декомпилятора и построением cpp и h файлов. После завершения этой работы и будут новые примеры. Хотя могу привести ещё один пример: s0larian Однозначно ответить на этот вопрос невозможно. Все зависит от типа переменной myVect.
Vam AFAIK, это далеко не так. Точнее должно быть далеко не так - встречаются классы с vtable, забитой purecall. Но это ошибки программиста. В VC++ для абстрактных классов следует указывать __declspec(novtable) или описывать как __interface. Тогда компилятор не будет генерировать для класса vtable. В посл. версиях VC++ качество инлайна очень хорошее - фактически без разницы, объявить ли ф-цию как инлайн или вставить её код в нужное место вручную. И генерируемый бинарный код может быть настолько зависим от контекста, что никакие сигнатуры не помогут. Будут использоваться разные регистры, код инлайн ф-ции может быть просто перемешан с окружающим кодом. Если в инлайн ф-ции есть цикл, количество итераций которого зависит от аргумента ф-ции, то в отдельных инлайнах компилятор может развернуть цикл. И т.п. С другой стороны, распознавание инлайн-ф-ций не так критично. Ведь задача воссоздать оригинальный исходник не ставится.
green Я имел в виду не сигнатуры по коду или ассемблеру, а по промежуточному (или окончательному) проходу декомпилятора. Я уже упоминал об одной такой реализации с интерактивностью выше - выделил строки декомпилированного текста и сказал, что хочу такую-то инлайн... Это не ошибки программиста, а одна из реализаций компилятора. Не следует, а можно, применительно к тому, что мы хотим реализовать. Вот цитата относительно novtable: "This form of _declspec can be applied to any class declaration, but should only be applied to pure interface classes, that is classes that will never be instantiated on their own. The _declspec stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class."
green Сказано же: "but should only be applied to pure interface classesthat is classes, that will never be instantiated on their own.", но должна быть только применена к абстрактным интерфейсным классам, которые никогда не будут реализованы самостоятельно. Интерфейс подразумевает связь между разными программами или программой и библиотекой (dll), что по сути одно и тоже. Так вот клиентская часть не должна иметь __declspec(novtable), а библиотечная или серверная для сокращения размеров кода может иметь эту декларацию. По крайней мере в MFC реализовано именно так.
Vam Не совсем понял, если честно. Вы учитываете, что абстрактный класс может содержать любые методы(не только pure virtual), а так же то, что pure virtual method может иметь тело и код наравне с любым другим? Про виртуальное наследование я ничего не увидел. Я имею ввиду: Код (Text): class B: virtual public A { /* ... */ }; Хотя, я сейчас глянул как это реализовано в студии, там отдельный флаг заводится, а в конструкторе проверяется. Возможно статически разбор сделать реально, хотя как в других компиляторах понятия не имею. Стандартом это не определяется. Хм, как это по конструктору/деструктору? Я вот сейчас скомпилировал такой пример: Код (Text): class A { public: // bad style, just for testing :) std::string s; public: A(): s("") {} ~A() {} }; class B { A a; public: B() {} ~B() {} void set_string(std::string& new_s) { a.s = new_s; } }; int main() { B* b = new B; return 0; } Вот как выглядит конструктор класса B: Код (Text): B() {} push ebp mov ebp,esp push ecx mov dword ptr [ebp-4],ecx mov ecx,dword ptr [this] call A::A (401000h) mov eax,dword ptr [this] mov esp,ebp pop ebp ret Далее сделал класс A наследуемым: Код (Text): class A { public: // bad style, just for testing :) std::string s; public: A(): s("") {} ~A() {} }; class B: public A { public: B() {} ~B() {} void set_string(std::string& new_s) { s = new_s; } }; int main() { B* b = new B; return 0; } Конструктор выглядит так: Код (Text): B() {} push ebp mov ebp,esp push ecx mov dword ptr [ebp-4],ecx mov ecx,dword ptr [this] call A::A (401000h) mov eax,dword ptr [this] mov esp,ebp pop ebp ret Найдите 10 отличий С деструкторами история та же. Каким именно? Как именно вы восстанавливаете/определяете шаблонность процедуры на уровне исполняемого кода? Поиском "<>" в имени функций из debug-info? Так это не решение. Можно пример? Я вот взял такой вариант: Код (Text): void set(int& a) { a = 10; } void set(int* a) { *a = 10; } int main() { int i; set(i); set(&i); return 0; } Получил такой асм-листинг: Код (Text): set(i); lea edx,[ebp-10h] push edx call set (4018A0h) add esp,4 set(&i); lea eax,[ebp-10h] push eax call set (401890h) add esp,4 void set(int& a) { push ebp mov ebp,esp a = 10; mov eax,dword ptr [a] mov dword ptr [eax],0Ah } pop ebp ret void set(int* a) { push ebp mov ebp,esp *a = 10; mov eax,dword ptr [a] mov dword ptr [eax],0Ah } pop ebp ret Фигасе. Я на вскидку могу привести несколько примеров, где использование ссылок и указателей -- две большие разницы на уровне самого языка. Т.е. код с использованием указателей и ссылок не эквивалентен, он даже не сможет скомпилироваться. Не говоря уже о семантике и высокоуровневой логике(хотя про неё однозначно забыть придётся). Инкапсуляция, а точнее полнейшая её утрата при декомпиляции, как один из трёх китов ООП, не принципиальное свойство? Смело Ну это опять нечто С-подобное. Меня больше интересует восстановление классов, наследственных связей, абстрактности и шаблонности. Что я по-прежнему считаю невозможным в общем случае.
Vam Вы говорили, что novtable для абстрактных классов следует указывать не всегда. Я и спросил, о каких исключениях речь.
W4FhLF Вообще-то отличить в бинарном коде виртуальное наследование от обычного довольно легко, по крайней мере для человека. Такое наследование реализовано добавлением уровня косвенности между derived и base - смещение к base не константа, а берется из таблицы смещений vbtable.
W4FhLF В этом простом примере отличий нет, для работы такого кода совсем неважно как будет выглядеть декларация класса - class B: public A {...}; или class B { A a; ... }; Любое решение имеет право на жизнь, если оно приводит к нужному результату, а вариантов решения может множество, а не только debug-info, но и сигнатуры библиотек. Сами себе противоречите, ваш пример показывает что отличий между ссылкой и указателем может и не быть, если не верите, то найдите в нем 10 отличий. А там где они на уровне кода есть, то будут распознаны однозначно. green А я на него и ответил... ИМНО обсуждать сейчас что реализуемо, а что нет, да ещё в чистой релиз версии не имеет смысла пока не реализован до конца дебаг вариант. Если хотите чем-то помочь, то можете высказать свое мнение по приведенным примерам, что в них устраивает, а что необходимо доработать.
На уровне машинного кода их скорее всего и не будет в большинстве случаев. Вы же сказали, что вы умеете распознавать ссылки в большинстве случаев. Что значит на уровне кода есть? На уровне кода есть масса отличий для любых сылок и указателей. Такие заявления странные. Вы что не видите разницы между ссылкой и указателем на уровне языка? Их как минимум десяток на первый взгляд. Покажите пример в подтверждение своим словам выше. Опять debug-info? Я хочу сказать, что ссылки в подавляющем большинстве случаев ссылки реализуется компилятором через указатели, либо вообще под них вообще не выделяется место(8.3.2 References: It is unspecified whether or not a reference requires storage). Но это не значит, что в коде можно все ссылки заменить указателями. Это будет неверно, код теряет логику. Ага, конечно. Да давайте вообще писать весь код в main. Я пишу у себя: Код (Text): class Sedan: protected Car; А вы пишите: Код (Text): class Sedan { public: Car var_258h; }; И вы говорите -- это одно и тоже. Да, конечно, на уровне машинного кода всё очень похоже. Но в абстракции весь смысл С++ Только вместо имён будет непонятно что. RTTI мало того, что не обязан присутстсовать в программе, так ещё и не гарантирует(стандарт оставляет это за компилятором), что хранимая информация хоть сколь-нибудь визуально будет соответстовать оригиналу. Шаблоны не только в популярных библиотеках используются, они используются повсеместно. Я уже говорил об этом. Если я не знаю определения класса zSTRING какой в нём смысл? Покажите как он восстанавливается. А так же: Но даже с учётом всего этого программа при компиляции теряет слишком многое. Задача, имхо, чисто академическая.
В общем смысл в том, что семантика практически невосстановима в общем случае. А что такое С++ без семантики? Слегка модифицированный С. И смысл в такой декомпиляции может быть лишь в том, чтобы заниматься анализом какого-то конкретного алгоритма, восстановить же хоть сколь-нибудь осмысленную архитектуру без полной gebug-info в общем случае невозможно. К такому выводу я для себя пришёл.
Vam Можно получить этот декомпилятор в каком-либо виде для теста? По одной изолированной ф-ции трудно судить... Интересно, почему вы выбрали в качестве исходной точки наличие полной дебаг-инфы? Во-первых, это нехарактерно для реальной задачи. Во-вторых, как я понимаю, добывать данные, которые сейчас доступны как дебаг-инфа, придётся не какой-то независимой процедурой, а в процессе, собственно, декомпиляции. Не потребует ли это радикальной переделки существующей архитектуры? Вы сейчас можете полагаться на данные, которые в принципе нельзя получить без дебаг-инфы.
green Пост №17 дает полный ответ на этот вопрос. Здесь дело не в данных, а в реализации полноты их представления. За основу взяты контейнеры и типы хранения информации с их свойствами из дебаг инфы Мicrosoft. Например: SymTagArrayType контейнер массива имеет следующую структуру Код (Text): class vSymTypeArray : public vSymType { friend class vMsSymType; int m_nNumber; // Exact tSize vSymType* m_pBaseType; // Exact tType vSymType* m_pIndexType; public: virtual ~vSymTypeArray() {} virtual const char* GetTagStr() { return "SymTagArrayType"; } virtual zSTRING GetName(); virtual zSTRING GetDeclaration(const zSTRING&); virtual int GetSize(); virtual vSymType* GetType() { return m_pBaseType; } virtual vSymType* GetBaseType(zCArray<vVarType>&); virtual BOOL IsArray() { return TRUE; } vSymType* GetIndex() { return m_pIndexType; } }; где: m_nNumber - кол-во элементов в массиве, m_pBaseType - тип хранимых данных, m_pIndexType - тип индекса массива. Данной структурой можно описать различные массивы как по размерности, размеру, типу данных и индекса. Так вот, все контейнеры для хранения всех существующих типов данных были позаимствованы у Microsoft и заполняются пока не на стадии компиляции, а предварительно ей специальным алгоритмом разбора разных типов дебаг инфы. Хранятся эти контейнеры в специальных хешируемых таблицах, а далее алгоритм простой (от наличия дебаг инфы не зависит) - декомпилятор находит (распознает или ещё что-то, по имени, размеру, месту размещения и т.д.) новый или уже существующий (неважно какой) тип, заполняет шаблон и отправляет его с запросом в хештаблицу, в зависимости от запроса может быть получена информация о типе или создан новый тип или откорректирован существующий, если это допустимо заданным уровнем точности. Если данные менялись, то хештаблица сохраняет ссылку на то место откуда был запрос и уровень точности. Высшая точность имеет приоритет над низшей и может откорректировать этот тип, но не наоборот. По данным одинаковой точности, но разных типов может быть построен union, но необязательно, всё зависит от типов. Все это уже реализовано и работает. Пока всё - и так уже много сказал. Не совсем понял это высказывание. Как можно полагаться на данные, которые нельзя получить? Но могу гарантировать, что те данные, которые получены декомпилятором достоверны в плане их реализации (т.е. из них может быть скомпилирован рабочий код), в плане же представления на С++ возможны отличия от оригинальных исходников, т.к. 100% эта задача неразрешима. Пока нет, нужно доделать некоторые моменты (встречается слишком большая вариация кода в зависимости от типов данных), т.е. не все существующие комбинации пока обрабатываются. Проверял на четырех разных экзешниках - пока % автоматического прохода по ним составляет от 10 до 60%, далее встречаем или исключение или диагностическое сообщение с остановом декомпиляции. Вот эти моменты сейчас и убиваю.
Отличный проект. Могу только пожелать автору не сдаться на полпути и довести его до конца, ну или хотя бы до какой-нибудь логически завершенной стадии. В принципе проблема декомпиляции - одна из самых сложных и малоизученных (а значит и интересных на сегодняшний день. Сам я заканчиваю сейчас реализацию декомпилятора для Явы, но там свои специфические требования и сложности. Если рассматривать задачу в целом, то есть два подхода: 1) Прагматический. Декомпиляция под определенный компилятор, наличие дополнительной информации (debug) и т.д. 2) Общеакадемический. Чистая декомпиляция язык в язык. Лично мне интересен только второй. Слишком много зависимостей в первом случае, выйдет новая версия компилятора - и начинай все с начала. Поэтому тем, кто собирается сейчас писать собственный декомпилятор с нуля, я бы предложил несколько иной путь: написать для начала просто декомпилятор ассемблера в C. Шут с ними, с классами, наследованием и шаблонами. Это все потом придет. Нужен инструмент, который выдавал бы сишный код с двумя качествами 1) полное сохранение семантики и 2) компилируемость для _любого_ ассемблерного "исходника". Если удастся одолеть эту первую ступень, то все остальное пойдет в разы легче. За темой буду следить и надеюсь в будущем к ней еще вернуться.
Stiver На мой взгляд во втором случае не меньше зависимостей, т.к. исполняемый код создается конкретным компилятором. Взять хотя бы исходник консольной программы без всяких наворотов и скомпилировать его двумя широко распространенными компиляторами VS и Builder, а затем сравнить полученный код - и мы увидим массу специфических отличий, свойственных каждому компилятору. Следовательно, академический декомпилятор должен их учитывать, а это уже зависимость от компилятора. А если взять проекты с MFC и VCL, то здесь вообще будет невозможно получить рабочий листинг без учета особенностей компилятора.
Желающих помочь с интерактивностью, как я вижу, нет. Есть ещё одна задача не менее интересная, но более самостоятельная - написать утилиту, создающую базы данных type info в стиле MS debug info и сигнатур инлайн функций и макросов в произвольном стиле из include файлов стандартных библиотек (С, С++, MFC, boost и др.), по аналогии с IDAшными til и sig базами, но с другим уровнем информативности. В дальнейшем может использоваться любыми декомпиляторами в Си.
Vam А можно озву4ить весь список? Мне кажется, Vam, вы быстрее найдете содевелоперов сделав свой проект open source и выложив где нибудь на sourceforge.