Декомпилятор С++

Тема в разделе "WASM.RESEARCH", создана пользователем Vam, 16 июл 2008.

  1. W4FhLF

    W4FhLF New Member

    Публикаций:
    0
    Регистрация:
    3 дек 2006
    Сообщения:
    1.050
    Y_Mur
    ООП это подход, подход к написанию программы целиком, который оправдывает себя в случае средних и больших проектов. Смысл его в увеличении уровня абстракции. Но вся эта абстрактность теряется при компиляции. Инкапсуляция, наследование, полиморфизм -- всё это либо невосстановимо, либо восстановимо в самых простых случаях при соблюдении множества условий(debug-info, определённые настройки и версия компилятора).

    Я ни в коем случае не отговариваю автора от реализации, если он действительно знает, что делает. Мне лишь интересно узнать, что именно он наработал в процессе решения задачи, какие-нибудь новые приёмы или особенности. Действительно интересно.

    Ну и побольше примеров хотелось бы.
     
  2. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Именно. inline это не copy/paste, т.к. компилер анализирует data flow, проводит constant propagation и пототом убирает мёртвый код (то есть то у чего нету side effects).

    Я уже спрашивал у TS что будет делать его tool в Release без debug info когда в оригитале было

    Код (Text):
    1. myVect.erase(myVect.begin());
    Кста, про шаблоны - кто-нить смотрел что выплёвывает компилер для такого выражения:
    Код (Text):
    1. std::for_each(ovservers_.begin(), ovservers_.end(),
    2.         boost::bind(&StatusHandler::OnStart, _1, steps));
     
  3. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    W4FhLF
    Абстрактность восстанавливается просто - каждый абстрактный класс имеет "пустую" статическую таблицу виртуальных функций из purecall, т.е. из нулевых элементов, которая восстановима по производным классам. Сам по себе абстрактный класс (без производных) компилятором не обрабатывается.
    С шаблонностью сложнее, но вы думаете для чего я использую IDA как базу для декомпилятора - не только для разбора ассемблерных инструкций, но и как инструмент реализующий FLIRT технологию с сигнатурами библиотек. Большинство полных имен библиотечных функций будет IDA восстановлено, а далее - дело техники декомпилятора.
    Уже ответил.
    Наиболее просто по конструктору или декструктору, если их нет - то сложнее, все зависит от структуры базового класса, но методы распознавания есть - по вызовам функций, по статичности элементов, по data flow и т.д.
    Что такое extern "C" в С++ надеюсь знают все, так вот под ним и скрыта эта особенность языка С.
    Именем.
    В большинстве случаев ссылка и указатель распознаются однозначно, а где это сделать невозможно - значит различий в них нет и не принципиально их представление. Мне по умолчанию больше нравится указатель.
    Это все непринципиальные свойства языка (на исполняемость кода не влияют), хотя косвенным образом (а местами и прямым) и интерактивностью это реализуемо.
    Y_Mur
    Правильно подмечено - на все библиотеки могут быть применены сигнатуры, я ещё не встречал, чтобы IDA не распознала MFC в какой либо программе.
    green
    Зачем же так критично, все зависит от функции, у компилятора есть свои ограничения на создание инлайн (не всякое объявление inline будет всегда и везде встроенным кодом). Я уже говорил - здесь могут помочь сигнатуры функций и интерактивность. Если же в простейшем случае тело функции имеет вид: а = в;, то такую функцию восстановить невозможно, хотя и здесь все зависит от типов а и в.
    W4FhLF
    Примеров декомпиляции разных функций могу привести несколько сотен, только что это даст без построения и понимания структуры данных. Сейчас работаю над завершающими проходами декомпилятора и построением cpp и h файлов. После завершения этой работы и будут новые примеры. Хотя могу привести ещё один пример:
    s0larian
    Однозначно ответить на этот вопрос невозможно. Все зависит от типа переменной myVect.
     
  4. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Vam
    AFAIK, это далеко не так. Точнее должно быть далеко не так - встречаются классы с vtable, забитой purecall. Но это ошибки программиста.
    В VC++ для абстрактных классов следует указывать __declspec(novtable) или описывать как __interface. Тогда компилятор не будет генерировать для класса vtable.
    В посл. версиях VC++ качество инлайна очень хорошее - фактически без разницы, объявить ли ф-цию как инлайн или вставить её код в нужное место вручную. И генерируемый бинарный код может быть настолько зависим от контекста, что никакие сигнатуры не помогут. Будут использоваться разные регистры, код инлайн ф-ции может быть просто перемешан с окружающим кодом. Если в инлайн ф-ции есть цикл, количество итераций которого зависит от аргумента ф-ции, то в отдельных инлайнах компилятор может развернуть цикл. И т.п.

    С другой стороны, распознавание инлайн-ф-ций не так критично. Ведь задача воссоздать оригинальный исходник не ставится.
     
  5. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    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."
     
  6. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Vam
    В каких случаях __declspec(novtable) для абстрактного класса неприменима или нежелательна?
     
  7. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    green
    Сказано же: "but should only be applied to pure interface classesthat is classes, that will never be instantiated on their own.", но должна быть только применена к абстрактным интерфейсным классам, которые никогда не будут реализованы самостоятельно.
    Интерфейс подразумевает связь между разными программами или программой и библиотекой (dll), что по сути одно и тоже. Так вот клиентская часть не должна иметь __declspec(novtable), а библиотечная или серверная для сокращения размеров кода может иметь эту декларацию. По крайней мере в MFC реализовано именно так.
     
  8. W4FhLF

    W4FhLF New Member

    Публикаций:
    0
    Регистрация:
    3 дек 2006
    Сообщения:
    1.050
    Vam
    Не совсем понял, если честно. Вы учитываете, что абстрактный класс может содержать любые методы(не только pure virtual), а так же то, что pure virtual method может иметь тело и код наравне с любым другим?

    Про виртуальное наследование я ничего не увидел. Я имею ввиду:

    Код (Text):
    1. class B: virtual public A
    2. {
    3.  /* ... */
    4. };
    Хотя, я сейчас глянул как это реализовано в студии, там отдельный флаг заводится, а в конструкторе проверяется. Возможно статически разбор сделать реально, хотя как в других компиляторах понятия не имею. Стандартом это не определяется.

    Хм, как это по конструктору/деструктору? Я вот сейчас скомпилировал такой пример:

    Код (Text):
    1. class A
    2. {
    3. public: // bad style, just for testing :)
    4.     std::string s;
    5.  
    6. public:
    7.     A(): s("") {}
    8.     ~A() {}
    9. };
    10.  
    11. class B
    12. {
    13.     A a;
    14. public:
    15.     B() {}
    16.     ~B() {}
    17.  
    18.     void set_string(std::string& new_s) { a.s = new_s; }
    19. };
    20.  
    21.  
    22. int main()
    23. {
    24.     B* b = new B;
    25.     return 0;
    26. }
    Вот как выглядит конструктор класса B:

    Код (Text):
    1.     B() {}
    2. push        ebp  
    3. mov         ebp,esp
    4. push        ecx  
    5. mov         dword ptr [ebp-4],ecx
    6. mov         ecx,dword ptr [this]
    7. call        A::A (401000h)
    8. mov         eax,dword ptr [this]
    9. mov         esp,ebp
    10. pop         ebp  
    11. ret
    Далее сделал класс A наследуемым:

    Код (Text):
    1. class A
    2. {
    3. public: // bad style, just for testing :)
    4.     std::string s;
    5.  
    6. public:
    7.     A(): s("") {}
    8.     ~A() {}
    9. };
    10.  
    11. class B: public A
    12. {
    13. public:
    14.     B() {}
    15.     ~B() {}
    16.  
    17.     void set_string(std::string& new_s) { s = new_s; }
    18. };
    19.  
    20.  
    21. int main()
    22. {
    23.     B* b = new B;
    24.     return 0;
    25. }
    Конструктор выглядит так:

    Код (Text):
    1.     B() {}
    2.   push        ebp  
    3.   mov         ebp,esp
    4.   push        ecx  
    5.   mov         dword ptr [ebp-4],ecx
    6.   mov         ecx,dword ptr [this]
    7.   call        A::A (401000h)
    8.   mov         eax,dword ptr [this]
    9.   mov         esp,ebp
    10.   pop         ebp  
    11.   ret
    Найдите 10 отличий :)
    С деструкторами история та же.

    Каким именно? Как именно вы восстанавливаете/определяете шаблонность процедуры на уровне исполняемого кода? Поиском "<>" в имени функций из debug-info? Так это не решение.

    Можно пример? Я вот взял такой вариант:

    Код (Text):
    1. void set(int& a)
    2. {
    3.     a = 10;
    4. }
    5.  
    6. void set(int* a)
    7. {
    8.     *a = 10;
    9. }
    10.  
    11. int main()
    12. {
    13.     int i;
    14.  
    15.     set(i);
    16.     set(&i);
    17.  
    18.     return 0;
    19. }
    Получил такой асм-листинг:

    Код (Text):
    1.     set(i);
    2.   lea         edx,[ebp-10h]
    3.   push        edx  
    4.   call        set (4018A0h)
    5.   add         esp,4
    6.     set(&i);
    7.   lea         eax,[ebp-10h]
    8.   push        eax  
    9.   call        set (401890h)
    10.   add         esp,4
    11.  
    12.  
    13. void set(int& a)
    14. {
    15.   push        ebp  
    16.   mov         ebp,esp
    17.     a = 10;
    18.   mov         eax,dword ptr [a]
    19.   mov         dword ptr [eax],0Ah
    20. }
    21.   pop         ebp  
    22.   ret
    23.  
    24. void set(int* a)
    25. {
    26.   push        ebp  
    27.   mov         ebp,esp
    28.     *a = 10;
    29.   mov         eax,dword ptr [a]
    30.   mov         dword ptr [eax],0Ah
    31. }
    32.   pop         ebp  
    33.   ret
    Фигасе. :) Я на вскидку могу привести несколько примеров, где использование ссылок и указателей -- две большие разницы на уровне самого языка. Т.е. код с использованием указателей и ссылок не эквивалентен, он даже не сможет скомпилироваться. Не говоря уже о семантике и высокоуровневой логике(хотя про неё однозначно забыть придётся).

    Инкапсуляция, а точнее полнейшая её утрата при декомпиляции, как один из трёх китов ООП, не принципиальное свойство? Смело :)

    Ну это опять нечто С-подобное. Меня больше интересует восстановление классов, наследственных связей, абстрактности и шаблонности. Что я по-прежнему считаю невозможным в общем случае. :)
     
  9. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Vam
    Вы говорили, что novtable для абстрактных классов следует указывать не всегда. Я и спросил, о каких исключениях речь.
     
  10. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    W4FhLF
    Вообще-то отличить в бинарном коде виртуальное наследование от обычного довольно легко, по крайней мере для человека. Такое наследование реализовано добавлением уровня косвенности между derived и base - смещение к base не константа, а берется из таблицы смещений vbtable.
     
  11. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    W4FhLF
    В этом простом примере отличий нет, для работы такого кода совсем неважно как будет выглядеть декларация класса - class B: public A {...}; или class B { A a; ... };
    Любое решение имеет право на жизнь, если оно приводит к нужному результату, а вариантов решения может множество, а не только debug-info, но и сигнатуры библиотек.
    Сами себе противоречите, ваш пример показывает что отличий между ссылкой и указателем может и не быть, если не верите, то найдите в нем 10 отличий. А там где они на уровне кода есть, то будут распознаны однозначно.
    green
    А я на него и ответил...

    ИМНО обсуждать сейчас что реализуемо, а что нет, да ещё в чистой релиз версии не имеет смысла пока не реализован до конца дебаг вариант. Если хотите чем-то помочь, то можете высказать свое мнение по приведенным примерам, что в них устраивает, а что необходимо доработать.
     
  12. W4FhLF

    W4FhLF New Member

    Публикаций:
    0
    Регистрация:
    3 дек 2006
    Сообщения:
    1.050
    На уровне машинного кода их скорее всего и не будет в большинстве случаев. Вы же сказали, что вы умеете распознавать ссылки в большинстве случаев.

    Что значит на уровне кода есть? На уровне кода есть масса отличий для любых сылок и указателей. Такие заявления странные. Вы что не видите разницы между ссылкой и указателем на уровне языка? Их как минимум десяток на первый взгляд.
    Покажите пример в подтверждение своим словам выше. Опять debug-info?
    Я хочу сказать, что ссылки в подавляющем большинстве случаев ссылки реализуется компилятором через указатели, либо вообще под них вообще не выделяется место(8.3.2 References: It is unspecified whether or not a reference requires storage). Но это не значит, что в коде можно все ссылки заменить указателями. Это будет неверно, код теряет логику.

    Ага, конечно. Да давайте вообще писать весь код в main. Я пишу у себя:
    Код (Text):
    1. class Sedan: protected Car;
    А вы пишите:

    Код (Text):
    1. class Sedan
    2. {
    3. public:
    4.   Car var_258h;
    5. };
    И вы говорите -- это одно и тоже. Да, конечно, на уровне машинного кода всё очень похоже. Но в абстракции весь смысл С++ :)

    Только вместо имён будет непонятно что. RTTI мало того, что не обязан присутстсовать в программе, так ещё и не гарантирует(стандарт оставляет это за компилятором), что хранимая информация хоть сколь-нибудь визуально будет соответстовать оригиналу.

    Шаблоны не только в популярных библиотеках используются, они используются повсеместно.

    Я уже говорил об этом. Если я не знаю определения класса zSTRING какой в нём смысл? Покажите как он восстанавливается. А так же:
    Но даже с учётом всего этого программа при компиляции теряет слишком многое. Задача, имхо, чисто академическая.
     
  13. W4FhLF

    W4FhLF New Member

    Публикаций:
    0
    Регистрация:
    3 дек 2006
    Сообщения:
    1.050
    В общем смысл в том, что семантика практически невосстановима в общем случае. А что такое С++ без семантики? Слегка модифицированный С. И смысл в такой декомпиляции может быть лишь в том, чтобы заниматься анализом какого-то конкретного алгоритма, восстановить же хоть сколь-нибудь осмысленную архитектуру без полной gebug-info в общем случае невозможно.

    К такому выводу я для себя пришёл. :)
     
  14. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    Vam
    Можно получить этот декомпилятор в каком-либо виде для теста? По одной изолированной ф-ции трудно судить...

    Интересно, почему вы выбрали в качестве исходной точки наличие полной дебаг-инфы?
    Во-первых, это нехарактерно для реальной задачи.
    Во-вторых, как я понимаю, добывать данные, которые сейчас доступны как дебаг-инфа, придётся не какой-то независимой процедурой, а в процессе, собственно, декомпиляции. Не потребует ли это радикальной переделки существующей архитектуры? Вы сейчас можете полагаться на данные, которые в принципе нельзя получить без дебаг-инфы.
     
  15. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    green
    Пост №17 дает полный ответ на этот вопрос.
    Здесь дело не в данных, а в реализации полноты их представления. За основу взяты контейнеры и типы хранения информации с их свойствами из дебаг инфы Мicrosoft. Например: SymTagArrayType контейнер массива имеет следующую структуру
    Код (Text):
    1. class vSymTypeArray : public vSymType
    2. {
    3.     friend class vMsSymType;
    4.  
    5.     int             m_nNumber;      // Exact tSize
    6.     vSymType*           m_pBaseType;        // Exact tType
    7.     vSymType*           m_pIndexType;
    8. public:
    9.     virtual ~vSymTypeArray() {}
    10.     virtual const char* GetTagStr() { return "SymTagArrayType"; }
    11.     virtual zSTRING GetName();
    12.     virtual zSTRING GetDeclaration(const zSTRING&);
    13.     virtual int GetSize();
    14.     virtual vSymType* GetType() { return m_pBaseType; }
    15.     virtual vSymType* GetBaseType(zCArray<vVarType>&);
    16.     virtual BOOL IsArray() { return TRUE; }
    17.     vSymType* GetIndex() { return m_pIndexType; }
    18. };
    где: m_nNumber - кол-во элементов в массиве, m_pBaseType - тип хранимых данных, m_pIndexType - тип индекса массива.
    Данной структурой можно описать различные массивы как по размерности, размеру, типу данных и индекса. Так вот, все контейнеры для хранения всех существующих типов данных были позаимствованы у Microsoft и заполняются пока не на стадии компиляции, а предварительно ей специальным алгоритмом разбора разных типов дебаг инфы. Хранятся эти контейнеры в специальных хешируемых таблицах, а далее алгоритм простой (от наличия дебаг инфы не зависит) - декомпилятор находит (распознает или ещё что-то, по имени, размеру, месту размещения и т.д.) новый или уже существующий (неважно какой) тип, заполняет шаблон и отправляет его с запросом в хештаблицу, в зависимости от запроса может быть получена информация о типе или создан новый тип или откорректирован существующий, если это допустимо заданным уровнем точности. Если данные менялись, то хештаблица сохраняет ссылку на то место откуда был запрос и уровень точности. Высшая точность имеет приоритет над низшей и может откорректировать этот тип, но не наоборот. По данным одинаковой точности, но разных типов может быть построен union, но необязательно, всё зависит от типов. Все это уже реализовано и работает. Пока всё - и так уже много сказал.
    Не совсем понял это высказывание. Как можно полагаться на данные, которые нельзя получить? Но могу гарантировать, что те данные, которые получены декомпилятором достоверны в плане их реализации (т.е. из них может быть скомпилирован рабочий код), в плане же представления на С++ возможны отличия от оригинальных исходников, т.к. 100% эта задача неразрешима.
    Пока нет, нужно доделать некоторые моменты (встречается слишком большая вариация кода в зависимости от типов данных), т.е. не все существующие комбинации пока обрабатываются. Проверял на четырех разных экзешниках - пока % автоматического прохода по ним составляет от 10 до 60%, далее встречаем или исключение или диагностическое сообщение с остановом декомпиляции. Вот эти моменты сейчас и убиваю.
     
  16. Stiver

    Stiver Партизан дзена

    Публикаций:
    0
    Регистрация:
    18 дек 2004
    Сообщения:
    812
    Адрес:
    Germany
    Отличный проект. Могу только пожелать автору не сдаться на полпути и довести его до конца, ну или хотя бы до какой-нибудь логически завершенной стадии.

    В принципе проблема декомпиляции - одна из самых сложных и малоизученных (а значит и интересных :) на сегодняшний день. Сам я заканчиваю сейчас реализацию декомпилятора для Явы, но там свои специфические требования и сложности. Если рассматривать задачу в целом, то есть два подхода:
    1) Прагматический. Декомпиляция под определенный компилятор, наличие дополнительной информации (debug) и т.д.
    2) Общеакадемический. Чистая декомпиляция язык в язык.
    Лично мне интересен только второй. Слишком много зависимостей в первом случае, выйдет новая версия компилятора - и начинай все с начала. Поэтому тем, кто собирается сейчас писать собственный декомпилятор с нуля, я бы предложил несколько иной путь: написать для начала просто декомпилятор ассемблера в C. Шут с ними, с классами, наследованием и шаблонами. Это все потом придет. Нужен инструмент, который выдавал бы сишный код с двумя качествами 1) полное сохранение семантики и 2) компилируемость для _любого_ ассемблерного "исходника". Если удастся одолеть эту первую ступень, то все остальное пойдет в разы легче.

    За темой буду следить и надеюсь в будущем к ней еще вернуться.
     
  17. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Stiver, то есть это HexRays, который вроде как в 2), но использует много трюков из 1)
     
  18. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Stiver
    На мой взгляд во втором случае не меньше зависимостей, т.к. исполняемый код создается конкретным компилятором. Взять хотя бы исходник консольной программы без всяких наворотов и скомпилировать его двумя широко распространенными компиляторами VS и Builder, а затем сравнить полученный код - и мы увидим массу специфических отличий, свойственных каждому компилятору. Следовательно, академический декомпилятор должен их учитывать, а это уже зависимость от компилятора. А если взять проекты с MFC и VCL, то здесь вообще будет невозможно получить рабочий листинг без учета особенностей компилятора.
     
  19. Vam

    Vam New Member

    Публикаций:
    0
    Регистрация:
    16 июл 2008
    Сообщения:
    149
    Желающих помочь с интерактивностью, как я вижу, нет. Есть ещё одна задача не менее интересная, но более самостоятельная - написать утилиту, создающую базы данных type info в стиле MS debug info и сигнатур инлайн функций и макросов в произвольном стиле из include файлов стандартных библиотек (С, С++, MFC, boost и др.), по аналогии с IDAшными til и sig базами, но с другим уровнем информативности. В дальнейшем может использоваться любыми декомпиляторами в Си.
     
  20. andruha123

    andruha123 New Member

    Публикаций:
    0
    Регистрация:
    18 мар 2004
    Сообщения:
    15
    Адрес:
    USA, CA
    Vam
    А можно озву4ить весь список?

    Мне кажется, Vam, вы быстрее найдете содевелоперов сделав свой проект open source и выложив где нибудь на sourceforge.