Можно ли "пересобрать" исполняемый файл под другую архитектуру?
Не дает уснуть вопрос — а нельзя ли как нибудь исхитриться и пересобрать испоняемый файл одной архитектуры, допустим, х64 в другуюю, допустим, х86 или армв7, чтобы запустить на айпаде.
Почему мне кажется это может быть возможно:
1.Из исполняемого файла можно получить ассемблер х86
2. ???
3. Получаем ассемблер армв6
4. собираем в бинарник армв6
По поводу шага2 —
gcc знает как собрать код под обе архитектуры
Не возможно ли часом какая-либо утилита преобразующая код на одном ассемблере в код на другом
Я понимаю, что архитектуры разные, разные регистры, но ведь есть между ними отображение, возможно сложное изза взаимного влияния команд друг на друга но все же
Или это невозможно?
ps если это вдруг уже реализовано, то дайте пожалуйста ссылку
pps очень интересно где почитать и понять в чем я не прав если это так
спасибо
Если у вас есть возможность написать на эту тему статью — было бы совсем великолепно
возможно. эта утилита — виртуальная машина. соответственно, достаточно собрать на другой архитектуре эмулятор данного процессора (например) bochs. правда, никто не обещает высокую производительность.
Вообще говоря такое невозможно (подразумевается невозможность ввиду бесконечно огромной трудоемкости). Если быть более точным, то ваш вариант перекомпиляции сводится к полному анализу дизассемблированного кода и, получив точный алгоритм работы, переписыванию его на ассемблере под другую архитектуру (под другую комбинацию ОС-процессор).
По опыту скажу, что даже на высокоуровневых языках переписывание кода под другую архитектуру может оказаться затруднительным. А делать это на уровне ассемблерного кода — даже врагу не пожелаешь.
Если захотите, то могу подробнее описать возможные подводные камни, проблемы и прочее.
Ну, во-первых, стоит отметить, что вопрос стоит не только в «пересборке» приложения под другую процессорную архитектуру, но и под другую ОС тоже.
Соответственно, первая же возникающая проблема — это API операционной системы. Практически все, начиная от операций чтения-записи до межпотоковой синхронизации, будет требовать вызовов различных функций ОС.
Понятное дело, что на различных системах API отличается не только интерфейсом, но и функционалом в принципе. Например, в Windows для синхронизации есть критические секции, а в линуксе — мьютексы.
Таким образом, имеем два варианта:
1) Либо вызов подобной системной функции (или даже процедуру, в которой есть такие вызовы) придется переписывать заново, чтобы использовать API целевой системы
2) Либо необходимо написать библиотеку, которая будет обладать исходным API и реализовывать требуемый функционал на целевой системе.
Первый вариант — это, собственно, то, как пишутся мультиплатформенные приложения: platform-dependent код прячется в отдельные модули, разные для каждой платформы, и закрывается уже общим API. Однако, в случае дизассемблированного кода можно представить насколько это сложнее (если разум вообще может представлять такие масштабы).
Второй вариант — это что-то типа Wine'а. В этом случае исходный код, в принципе, не трогается (нет нужды), но нужно писать множество библиотек и реализовывать загрузчик для всего этого.
Если же меняется только архитектура, то здесь тоже не легче. Во-первых, ОС тоже, фактически, меняется (например, с 32-битной на 64-битную, хотя тут есть односторонняя совместимость, которая позволяет запускать x86 приложения на amd64, но, тем не менее, нельзя совмещать бинарники разных архитектур). Меняются соглашения о вызовах для всех функций (на каких регистрах передаются параметры функций, на каких возвращаются, кто чистит стек итд). Кроме этого, меняются константы в коде (имеется в виду, например, что для инструкции jmp смещение для прыжка вычисляется по-разному), то есть требуется много чего пересчитывать. Кроме этого, если есть совсем разные архитектуры (RISC и CISC, например), то однозначно отобразить одно множество инструкций в другое в принципе невозможно. Тут уже нужно отображать множество последовательностей инструкций в лучшем случае. Кроме этого даже регистры невозможно однозначно друг в друга отобразить («нужен ли этот регистр или его уже можно перезатереть?», «если я положу значение на стек и сэкономлю регистр, то не поедут ли захардкоженные константы?», «переживает ли этот регистр вызов функции или нет?»).
И вообще, перенос чисто ассемблерного кода с одной архитектуры на другую можно сравнить с трансляцией кода с одного языка на другой. При трансляции же обычно строится внутренее представления отдельно взятых процедур и это внутренее представление уже переводят на целевой язык. Однако, для ассемблерного кода (тем более оптимизированного), несмотря на элементарный синтаксис, подобное внутренее представление построить невозможно из-за неструктуированности кода. Таким образом машинный анализ впадает в полный ступор. А человеческому тоже ничуть не легче.
PS. возможно слегка сумбурно описал, но я вижу проблему как-то так.
да, проблема с ОС понятно, поэтому я и приводил пример с математикой — там нет системных вызовов. Не совсем понятно почему отображение «в принципе не возможно» есть же эмуляторы в конце концов
Эмуляторы занимаются иным процессом: в рамках эмулятора воссоздается исходная архитектура (этакая песочница) в рамках которой работает приложение. Грубо говоря, можно сказать, что софтварными средствами создается сам процессор и эмулируется его работа (то есть взаимодействие регистров, чтения-записи в память и все остальное). Кроме этого, нативное приложение не может в рамках своего процесса использовать (напрямую звать ее функции и использовать результаты вычислений) эмулируемую библиотеку, т.к. они будут находится в разных адресных пространствах, да и вообще в разных средах исполнения.
Абсолютно нет. Эмулятор — это программа, которая просто исполняет другую программу как есть. То есть, у эмулятора, грубо говоря, есть несколько объектов, представляющих регистры, память и стек. Далее ему дается программа и эмулятор ее начинает выполнять: надо сложить два регистра -> эмулятор начинает исполнять кучу кода, которые складывают два виртуальных регистра. Причем для выполнения этой простой операции эмулятор должен:
1) Прочитать машинную инструкцию.
2) Найти объекты, соответствующие нужным регистрам
3) Взять их значения
4) Сложить и записать в соответствующую структуру данных
(Конечно, с учетом оптимизаций самого эмулятора это все делается гораздо быстрее и проще)
ну и разве нельзя это приспособить для выполнения библиотеки? я ведь два объекта на вод и хочу получить выход ( рассмотрим простейший случай — библиотека сложения чисел )
Можете считать, что сложность всей затеи экспоненциальна: на тривиальных задачах все просто. Но уже в момент, когда потребуется динамически выделить память, прочитать/записать файл, узнать время, то возникает туча проблем. Тогда даже эмуляцию нельзя будет упростить, не переписав кучу системных библиотек (или эмулировать и их, но каждая из них тянет и другие… все сведется к эмуляции ядра ОС).
И опять же, если библиотека тривиальна, то в любом случае проще ее написать с нуля.
Можно, но не нужно. называется эта штука Recompilation, но смысл в ней есть только с точки зрения оптимизации эмуляции. В любом случае получается не полноценный бинарник, а эмуляция, пусть и перекомпилированная и более оптимальная, чем эмуляция процессора.
Мораль: хотите, чтобы на вашей лопате запускался нужный софт? Пользуйтесь свободным софтом, у него таких проблем быть не может.
А существуют готовые решения? Запрос в гугл по слову Recompilation выводит только теорию.
На тему вашей морали
1. У свободного ПО хватает своих проблем, начиная с того что свободное НЕ значит с открытым исходным кодом, потом — свободное НЕ всегда значит хорошее и уж тем более свободное НЕ значит кроссплатформенное.
2. Что значит можно, но не нужно? То есть вы считаете нормальным тащить эмулятор процессора и запускать его на целевом устройстве ( пусть это будет ОС uLinux стиральной машины на процессоре армв7)
для того чтобы использовать .so файл скажем математики?
В связи с этим если бы мог поставить минус, то обязательно поставил бы. Но, буду рад если вы покажете ошибки в моем суждении.
Теоретически можно, но очень сложно.
У процессоров разные архитектуры (наборы команд, количество регистров).
У операционных систем разные системные вызовы (т.е. что надо сделать чтобы вывести символ на консоль, например).
Теоретически можно попытаться декомпилировать программу в язык (относительно) высокого уровня, а потом скомпилировать опять. Например Яву — декомпилируют. Произвольную программу — очень сложно
Байткод Java-приложений и архитектура JVM на порядок проще реальных процессорных архитектур и наборов машинных команд, и возможность декомпиляции (абсолютно однозначной) Java-приложений была заложена by-design.
В то же время, для native-приложений и исполняемых файлов декомпиляция возможна в совершенно мизерных случаях, при условии, что: 1. некоторый абстрактный компилятор компилировал приложение с учетом того, что потребуется ее декомпиляция, т.е. сохраняя дополнительную мета-информацию, ненужную для работы программы. 2. отсутствую platform-specific оптимизации, которые могут превратить ассемблерный код в такую лапшу, что невозможно будет разобраться даже с отдельной дизассемблированной функцией.
В общем случае декомпиляция (тем более однозначная), можно считать, что невозможна.
Примерно этим занимаются некоторые виртуальные машины, на ходу анализируя код и генерируя код для текущей архитектуры — по аналогии с JIT (или это так и называется).
Надо только уточнить, что эти виртуальные машины работают не с машинным кодом, а со специальным байткодом (такой, который, например, используется в Java или C#), который да, можно динамически компилировать под конкретную платформу (JIT — Just-In-Time compilation).
Теоретически нельзя так как ассемблер написан на инструкциях процессора и у разных процессоров разные инструкции.
Это все равно что сказать есть прога на си++, а давайте её на паскале запускать. Циклы то везде одинаковые почти.
а вот насчет адресного пространства -> это смотря как реализовать эмуляторную обертку, можно сденлать и так что она будет возвращать копию данных. В случае с мат библиотекой вполне достаточно получить лишь копию значения скажем синуса
Проблемы уже начинаются с момента, когда вам хочется вызвать этот синус. Т.к. эмулируемая мат. библиотека находится в своем собственном адресном пространстве, то вызвать ее из приложения невозможно, только через обращение к эмулятору. Но эмулятор, в свою очередь, не знает по какому адресу находится нужная функция. Для этого ему нужно знать адрес загрузки библиотеки и прочитать экспортную секцию dll-ки. Фактически, эмулятор должен иметь все способности обычного системного загрузчика.
Реализация эмулятора (как общего, так и какого-то частного назначения) конечно возможна. Вопрос трудозатратности и эффективности:
1) эмулятор реализовать с нуля скорее всего сложнее, чем с нуля написать новую мат. библиотеку.
2) выполнение библиотеки на эмуляторе заведомо будет медленее
3) высоки шансы, что исполнение библиотеки на эмуляторе будет отличаться от исполнения на исходной системе вплоть до ошибок исполнения.
почему нет получу код делающий то же самое, но на другой платформе. Учитывая наше предположение об отсутствии системных вызовов, выполнять операции обработки данных она будет вполне в состоянии