andruha123 Пока нет - аппетит приходит во время еды. Пока рано, надо хоть одну стадию довести до логического завершения, а далее... видно будет. Но на вторую задачу я и сейчас готов предоставить часть исходников.
green Какая есть..., но база для размещения type info у меня готова, осталось только её заполнить - а это упрощает задачу. Имеется в виду VS? Может для первой части задачи и можно, только я не знаю как заставить его скомпилировать полностью библиотеку, он ведь использует выборочный алгоритм. Но в реализации второй части он точно не поможет.
Vam Я думаю, стоит порыть тему precompiled headers. В pch-файле должна находиться вся информация о типах из инклудов. Иначе задача по сложности сравнима с написанием C++ frontend. Наверное, лучше использовать для этого gcc, т.к. микрософтовский PCH абсолютно недокументирован.
green и В общем случае это конечно может решить первую часть задачи, при условии известности формата pch файла MS или наличия соответствующей type info после gcc. Но, хотелось бы большего: Во-первых: extern "C" обгрызает типы до безобразия, кроме простого имени от них ничего не остается, а я хочу полную информацию о типе, которая есть в h файле. Во-вторых: В type info нужно добавить ещё один тип данных, которого нет у MS, это define и добавить не в простом виде, а в виде enumа, так как он сгруппирован в h файле, например, Window Message (WM_...) Не совсем так, задача намного проще, написать парсер ключевых слов, пробежаться по h файлу с анализом ключевых блоков и заполнением информационных структур (эта часть есть), затем записать их в базу данных (файл). Повторить на всю директорию(и). Причем за один проход можно решить обе части задачи.
Vam extern "C" не позволяет определить типы аргументов ф-ции по её имени. А в debug info информация о параметрах сохраняется независимо от extern "C". Гм... Во-первых нужен полноценный С препроцессор. Ладно, можно взять готовый. Далее, нужно учитывать области видимости, наследование. без это нельзя, как минимум, корректно разрулить typedef-ы и enum-ы. А как быть с шаблонами? Сделать инстанциирование для всех вариантов параметров невозможно, придётся сохранять в базе не конкретные типы, как в реальной debug info, а шаблонные. И потом в декомпиляторе решать задачу поиска не типа, а шаблона типа, что должно быть на порядок сложнее, как я понимаю. Даже если известно имя инстанциированного типа (например, из частичной дебаг инфы или имени ф-ции), то определить этот тип, т.е. выполнить инстанциирование шаблона - в общем случае задача очень нетривиальная.
green Конечно, так оно и есть, сам разбирал debug info... Согласен. В дальнейшем по нему можно сделать разные слепки type info для различных ключей компиляции. До обсуждения этого вопроса предлагаю для однозначности понимания определить что же нам необходимо от type info. По большому счету MS debug info (далее ДИ) состоит из двух видов информации: 1. symbol info (далее СИ) - это то, что реально существует в коде программы (тела функций, их блоков, статические данные и т.д.). Каждый тип СИ описывается своим образом и имеет один общий атрибут - адрес размещения в памяти. 2. type info (далее ТИ) - это абстрактное понятие и в коде программы отсутствует. Каждый тип ТИ описывается своим образом и имеет один общий атрибут - идентификатор типа. Варианты обработки связей между СИ и ТИ: 1. Полная ДИ состоит из СИ и ТИ. Связь между ними осуществлена просто - те СИ, которые имеют тип имеют атрибут, в который записывается идентификатор типа соответствующего ТИ. На стадии загруэки ДИ идентификатор типа в СИ заменяется на указатель на ТИ и таким образом идентификаторы исчезают, а мы имеем полную картину связей реального кода программы и типами информации. Опивывать как в этом случае работает декомпилятор не нужно, и так все понятно. В этом случае базы данных ТИ по библиотекам не нужны, но нужны базы сигнатур инлайн функций. 2. Частичная ДИ (минимальная) состоит только из СИ, причем сильно урезанных, имеются практически всего два атрибута - имя и адрес размещения в памяти. В этом случае нам нужны базы данных ТИ по библиотекам. Понятно, что в программе будет и user type info, отсутствующая в библиотеках, но это вопрос отдельный, декомпилятор будет её восстанавливать по определенным правилам. Но теперь встает вопрос, как восстановить связь между СИ и ТИ. Для этой цели подходит только имя СИ, причем это имя должно быть полным (манглед) или уникальным, иначе задача неразрешима. Декомпилятор разбирая конкретный СИ по его имени (деманглед) находит соответствующий ТИ и делает между ними связку. 3. ДИ отсутствует. В этом случае декомпилятор строит СИ блоки с одним атрибутом - адрес в памяти, так же здесь нам нужны базы данных ТИ по библиотекам. Вопрос восстановления связи между СИ и ТИ здесь может быть решен через IDA с механизмом FLIRT и сигнатурами тел библиотечных функций, т.е. полное имя СИ, которое в варианте 2 мы брали из ДИ, здесь будем брать из IDA. Далее осуществляется связка аналогично варианту 2. Понятно, что могут встречаться и другие варианты когда программа слинкована из модулей с разным уровнем ДИ, но это будет просто набор вариантов 1 - 3. Теперь вернемся к "нашим баранам": Про какую область видимости здесь идет речь? Если по коду, то это бесполезно, смотрим, как выполнена связка между СИ и ТИ в этом случае - через имя типа, а оно одно на всю программу. Наследование - да, учитывать можно и нужно, но это свойство только ТИ, и оно будет отражено при разборе библиотечной ТИ в соответствующих атрибутах. Согласен, в MS ДИ в ТИ шаблонов как таковых вообще нет, так как в СИ реализации производится инстанциация под конкретный тип параметров шаблона. Здесь в библиотечной ТИ достаточно будет иметь один тип с общими параметрами шаблона и уникальным именем, т.к. С++ не позволяет иметь под одним именем разное кол-во шаблонных параметров. Связка конкретной инстанциации СИ шаблона с ТИ шаблона будет выполнена по имени. На завершающей стадии декомпилятор просто (а может и не очень) объединит инстанциации СИ шаблона в одно тело. Конечно у меня есть ещё нерешенные вопросы по MS ДИ, но это тема другого поста...
Так я разбираю все блоки MS debug info (ДИ), даже те, которые и не нужны, но в дальнейшем могут понадобится, то имеется пара нерешенных вопросов. Надеюсь на вашу помощь. 1. В ДИ в type info (ТИ) имеется блок модификатор типа - LF_MODIFIER, с двумя полями: - m_nAttribute - тип модификатора: const, volatile или unaligned - m_Type - идентификатор типа, к которому применим этот модификатор. Применить модификатор к указанному типу (например, int) нельзя, т.к. тип один на всю программу. Можно создать копию типа и связать её с модификатором, но тогда возникает другой вопрос, как связать такой тип с символом из symbol info (СИ)? Может быть это недоделка MS? 2. В ДИ внутри СИ встречается символ типа S_XXXXXXXXX_32 с кодом идентификатора 0x1012 (ХХХ - это моё, имени этого символа не нашел) и размером в 32 байта. Что это может быть такое? По остальным блокам ДИ, которые конечно встретились мне в различных файлах, пока вопросов больше нет.
Vam - тебе надо определиться в тех целях, ради чего ты делаешь декомпилятор : Ты хочешь получить удобочитаемый и удобопонимаемый Си-код для анализа работы программы? Ты хочешь получить абы какой Си-код для внесения в него изменений и последующей компиляции ? В любом случае есть смысл типизировать имена переменных из просто VAR в String_Var198, Bool_Var1E4, и т.п. и добавлять объявление используемых переменных в начало Код (Text): int main( int iArgc, char **ppArgv ) { struct _stat buf; struct _utimbuf utim; BYTE ReadData[10]; int result; DWORD ByteRead; bool bAdjust = false; .... В принципе любой ассемблерный код можно переписать на Си/Паскале, с чем многие успешно справляются вручную при выпуске программ делающих тоже, что и дизассемблированные исходники. Тема декомпиляции/дешифрации интересная и захватывающая - в любом случае полезна для понимания и приобретения навыков программирования. На счёт оптимизированных кусков кода оптимизаторами. Интел например маниакально рекомендует избавляться от условных JMP конструкций и превращать программу в линейную безжумповую, чтобы не опустошать неоптимизированный конвеер процессора. http://narod.ru/disk/2440987000/Intel_64_32_Architectures_Optimization_Reference_Manual_248966_2007.pdf.html Поэтому иногда могут встречаться абсолютно нелогичные силиконовые конструкции Example 3-1. Assembly Code with an Unpredictable Branch Код (Text): cmp a, b ; Condition jbe L30 ; Conditional branch mov ebx const1 ; ebx holds X jmp L31 ; Unconditional branch L30: mov ebx, const2 L31: в итоге превращается в Example 3-2. Code Optimization to Eliminate Branches Код (Text): xor ebx, ebx ; Clear ebx (X in the C code) cmp A, B setge bl ; When ebx = 0 or 1 ; OR the complement condition sub ebx, 1 ; ebx=11...11 or 00...00 and ebx, CONST3; CONST3 = CONST1-CONST2 add ebx, CONST2; ebx=CONST1 or CONST2 Иногда при создании декомпилятора может помочь статистический метод накопления вариантов ассемблерных шаблонов типичных языковых конструкций. То есть пишем простую программу, дизассемблируем её и находим шаблоны. Мне подобный метод помогал - правда в отношении другой системы программирования. Рекомендую сосредоточиться на придании понятливости декомпилированному тексту.
Simaticov Цели мне понятны - получить рабочий Си-код для анализа работы программы, максимально приближенный к оригинальным исходникам + иметь возможность вносить в него изменения и делать последующую компиляцию. Это всё делается, второй пример (пост №43) это показывает, в первом же примере листинг промежуточный и в нем отсутствуют типы локальных переменных. Все же эти var_ промежуточные, созданные декомпилятором в debug mode, при следующих проходах от них ничего не останется (см. второй пример). и Знаю и делаю так, чтобы после обработки декомпилятором они выглядели так, как в первоначальном исходнике. Спасибо. Вот ещё один пример почти готового листинга функции: Код (Text): int XD3D_AppIDDEnumCallback(_GUID* pGUID, signed char* lpDriverDescription, signed char* lpDriverName, void* lpContext) { IDirectDraw7* pDD = 0; if(!DirectDrawCreateEx(pGUID, (LPVOID*)&pDD, &IID_IDirectDraw7, 0)) if(pGUID) dxDeviceGUIDflag = 1; dxDeviceGUID = *pGUID; else dxDeviceGUIDflag = 0; pDD->GetDeviceIdentifier(&dddi, 0); int curDriver = 0; IDirect3D7* pD3D; if(!pDD->QueryInterface(IID_IDirect3D7, (void**)&pD3D)) pD3D->EnumDevices(XD3D_EnumDeviceCallback, &curDriver); dxDeviceModeNum[dxDeviceNumber] = 0; pDD->EnumDisplayModes(0, 0, 0, XD3D_EnumModesCallback); if(dxDeviceModeNum[dxDeviceNumber] > 0) dxDeviceNumber += 1; if(pD3D) pD3D->Release(); pD3D = 0; if(pDD) pDD->Release(); return dxDeviceNum - 1 != dxSelectDevice; }
Vam Прошу прощения, плохо слежу за этой веткой. Это что текст на выходе вашего декомпилера?! Где скачать его можно? Уже хочу его помучить.
_basmp_ Принимается , но если бы следили лучше, то не возникли бы эти вопросы. Да, при условии присутствия полной дебаг инфы. Ответ в посте №55.
Ранее писал вот сейчас и повышаю этот процент, а заодно привожу созданные исходники к более читабельному (и как можно ближе к оригиналу) виду. Процесс этот не быстрый, в исследуемых экзешниках несколько тысяч функций и надо каждую после декомпиляции сравнить с исходной вручную.
Прошу ответить на вопрос: Почему после вызова одной и той же функции с постоянным кол-вом аргументов из стека изымается разное кол-во байт, а в функцию через стек всегда передается требуемое кол-во аргументов? Примеры функции с двумя аргументами и возвращаемым float значением: Код (Text): .text:005DD7D5 mov ecx, [ebp+arg_4] .text:005DD7D8 push ecx ; 2ой аргумент 4 байта .text:005DD7D9 push 2 .text:005DD7DB mov ecx, [ebp+var_4] .text:005DD7DE add ecx, 10h .text:005DD7E1 call sub_4016E5 .text:005DD7E6 mov edx, [eax] .text:005DD7E8 push edx .text:005DD7E9 push 1 .text:005DD7EB mov ecx, [ebp+var_4] .text:005DD7EE add ecx, 10h .text:005DD7F1 call sub_4016E5 .text:005DD7F6 mov eax, [eax] .text:005DD7F8 push eax .text:005DD7F9 push 0 .text:005DD7FB mov ecx, [ebp+var_4] .text:005DD7FE add ecx, 10h .text:005DD801 call sub_4016E5 .text:005DD806 mov ecx, [eax] .text:005DD808 push ecx .text:005DD809 lea ecx, [ebp+var_1C] .text:005DD80C call sub_4057CC .text:005DD811 push eax ; 1ый аргумент 4 байта .text:005DD812 call j_operator_ ; вызываемая функция .text:005DD817 add esp, 4 ; изымается 4 байта .text:005DD81A fstp [esp+7Ch+var_7C] и Код (Text): .text:005D34CE mov ecx, [ebp+arg_4] .text:005D34D1 add ecx, 18h .text:005D34D4 push ecx ; 2ой аргумент 4 байта .text:005D34D5 mov edx, [ebp+arg_4] .text:005D34D8 add edx, 24h .text:005D34DB push edx .text:005D34DC mov eax, [ebp+arg_0] .text:005D34DF add eax, 24h .text:005D34E2 push eax .text:005D34E3 lea ecx, [ebp+var_CC] .text:005D34E9 push ecx .text:005D34EA call sub_4085CB .text:005D34EF add esp, 0Ch .text:005D34F2 push eax ; 1ый аргумент 4 байта .text:005D34F3 call j_operator_ ; вызываемая функция .text:005D34F8 add esp, 8 ; изымается 8 байт .text:005D34FB fcomp ds:__real@4@00000000000000000000 или Код (Text): .text:005D3645 mov eax, [ebp+arg_4] .text:005D3648 add eax, 18h .text:005D364B push eax ; 2ой аргумент 4 байта .text:005D364C lea ecx, [ebp+var_20] .text:005D364F push ecx ; 1ый аргумент 4 байта .text:005D3650 call j_operator_ ; вызываемая функция .text:005D3655 add esp, 8 ; изымается 8 байт .text:005D3658 push ecx .text:005D3659 fstp dword ptr [esp+254h+var_258+4]
Судя по Код (Text): add esp, 4 ; изымается 4 байта .text:005DD81A fstp [esp+7Ch+var_7C] ==[esp] и Код (Text): add esp, 8 ; изымается 8 байт .text:005D3658 push ecx --- выделяем память .text:005D3659 fstp dword ptr [esp+254h+var_258+4] == [esp] можно предположить, что в первом случае просто соптимизировано выделение в стеке места под результат.
Velheart Проанализировав все вызовы этой функции, пришел к выводу, что это действительно так, хотя компиляция выполнялась без оптимизации и Microsoft на этот счет ничего не говорит. Вывод: Если функция имеет соглашение вызова __ccall и возвращаемое значение float (4 байта) или double (8 байт) и оно используется только как временное (для передачи последним аргументом в следующую за вызываемой функцию), то кол-во изымаемых из стека байт = кол-ву выделенных под аргументы байт - размер возвращаемого значения. Спасибо за подсказку.
Vam На самом деле в release по умолчанию происходит оптимизация. Есть такая штука как доступ к локальным переменным через esp и управление переменными (выделение, уничтожение внутри бока C++). P.S Затеял ты конечно интересную и сложную вещь, если получится можно тебе будет памятник ставить.