Можно ли перевести машинный код на другую архитектуру?

11

Так что это отчасти связано с вопросом о запуске сервера Windows на ARM . Таким образом, предпосылка моего вопроса заключается в том, может ли машинный код быть переведен из одной архитектуры в другую , чтобы выполнить двоичный файл в архитектуре, отличной от той, для которой он был скомпилирован.

QEMU и другие эмуляторы могут переводить инструкции на лету и, следовательно, запускать исполняемый файл на компьютере, для которого он не был скомпилирован. Почему бы не сделать этот перевод заранее, а не на лету, чтобы ускорить процесс? Из моего нескольких ограниченного знания собраний, большинство инструкций нравится MOV, ADDи другие должен быть переносимы между архитектурами.

Все, что не имеет прямого сопоставления, может быть сопоставлено с каким-то другим набором инструкций, так как все машины являются Turing Complete. Будет ли это слишком сложно? Разве это не сработает вообще по какой-то причине, с которой я не знаком? Будет ли это работать, но не даст лучших результатов, чем использование эмулятора?

Kibbee
источник
Техника, вероятно, впала в немилость, потому что (в дополнение к ее излишеству) она не нужна много. Переносимость / стандартизация (немного) лучше в наши дни (хотя бы потому, что Wintel захватила весь мир), и там, где эмуляция между компьютерами действительно необходима (например, для эмулятора телефона в среде разработки приложений), прямая эмуляция обеспечивает более надежный и точный результат. Плюс, процессоры достаточно быстры, чтобы стоимость эмуляции не была такой серьезной проблемой, как в прошлом.
Даниэль Р Хикс

Ответы:

6

Краткий ответ : Вы не можете перевести скомпилированный связанный исполняемый файл. Хотя это технически возможно, это очень маловероятно (см. Ниже). Однако , если у вас есть исходный файл сборки (содержащий инструкции и метки), это очень возможно сделать (хотя, если вы каким-то образом получите исходный код сборки, если программа не написана на сборке, вы должны иметь исходный код программы как ну, так что для начала вам лучше скомпилировать его для другой архитектуры).


Длинный ответ :

QEMU и другие эмуляторы могут переводить инструкции на лету и, следовательно, запускать исполняемый файл на компьютере, для которого он не был скомпилирован. Почему бы не сделать этот перевод заранее, а не на лету, чтобы ускорить процесс?

Я знаю, что это может показаться простым в принципе, но на практике это практически невозможно по нескольким основным причинам. Для начала разные наборы команд используют в значительной степени разные режимы адресации, разные структуры кода операции, разные размеры слов, а в некоторых даже нет нужных вам инструкций.

Допустим, вам нужно заменить инструкцию еще XYZдвумя инструкциями, ABCи DEF. Теперь вы эффективно сместили все относительные / смещенные адреса во всей программе с этого момента, поэтому вам нужно будет проанализировать и пройти всю программу и обновить смещения (как до, так и после изменения). Теперь, скажем, одно из смещений значительно меняется - теперь вам нужно изменить режимы адресации, которые могут изменить размер адреса. Это снова заставит вас пересмотреть весь файл и пересчитать все адреса, и так далее, и так далее.

Когда вы пишете программы на ассемблере, вы можете использовать метки, но процессор не использует - когда файл собран, все метки рассчитываются как относительные, абсолютные или смещенные местоположения. Вы можете понять, почему это быстро становится нетривиальной задачей и почти невозможным. Замена одной инструкции может потребовать, чтобы вы прошли через всю программу сотни раз, прежде чем двигаться дальше.

Исходя из моего немного ограниченного знания сборки, большинство инструкций, таких как MOV, ADD и другие, должны быть переносимы между архитектурами.

Да, но посмотрите на вопросы, которые я изложил выше. Как насчет размера слова машины? Длина адреса? У него вообще есть одинаковые режимы адресации? Опять же, вы не можете просто «найти и заменить» инструкции. Каждый сегмент программы имеет специально определенный адрес. Переходы к другим меткам заменяются литеральными или смещенными адресами памяти при сборке программы.

Все, что не имеет прямого сопоставления, может быть сопоставлено с каким-то другим набором инструкций, так как все машины являются Turing Complete. Будет ли это слишком сложно? Разве это не сработает вообще по какой-то причине, с которой я не знаком? Будет ли это работать, но не даст лучших результатов, чем использование эмулятора?

Вы на 100% правы, что это возможно и будет намного быстрее . Тем не менее, написание программы для достижения этой цели невероятно сложно и невероятно, если не для чего-либо, кроме вопросов, которые я изложил выше.

Если бы у вас был фактический исходный код сборки, было бы тривиально перевести машинный код в другую архитектуру набора команд. Сам машинный код, однако, собирается , поэтому без источника сборки (который содержит различные метки, используемые для вычисления адресов памяти) это становится невероятно трудным. Опять же, изменение одной инструкции может изменить смещения памяти во всей программе и потребовать сотни проходов для пересчета адресов.

Выполнение этого для программы с несколькими тысячами инструкций потребует десятков, если не сотен тысяч проходов. Для относительно небольших программ это может быть возможно, но помните, что количество проходов будет экспоненциально увеличиваться с увеличением количества машинных инструкций в программе. Для любой программы достаточно приличного размера это практически невозможно.

Прорвать
источник
По сути, нужно «декомпилировать» или «дизассемблировать» исходный объектный код. Для относительно простого кода (особенно кода, сгенерированного некоторыми компиляторами или пакетами генерации кода, где существует известный «стиль»), повторная вставка меток и тому подобного довольно проста. Конечно, однако, новые высокооптимизирующие компиляторы будут генерировать код, который намного сложнее «взломать» таким образом.
Даниэль Р Хикс
@DanH Если у вас есть исходный объектный код, у вас в значительной степени есть исходный код сборки ( не машинный код). Объектный файл содержит именованные (читай: помеченные) последовательности машинного кода, которые должны быть связаны друг с другом. Проблема возникает, когда вы связываете файлы объектного кода в исполняемый файл. Эти меньшие сегменты могут быть обработаны (или обработаны в обратном порядке) намного проще, чем весь связанный исполняемый файл.
Прорыв
Конечно, некоторые форматы объектных файлов облегчают работу. Некоторые могут даже содержать отладочную информацию, что позволяет вам восстановить большинство меток. Другие менее полезны. В некоторых случаях большая часть этой информации сохраняется даже в формате связанных файлов, в других - нет. Существует огромное количество различных форматов файлов.
Даниэль Р Хикс
2

Да, то, что вы предлагаете, может быть и было сделано. Это не слишком распространено, и я не знаю ни одной современной системы, использующей эту технику, но она определенно находится в пределах технической осуществимости.

Раньше было много сделано для того, чтобы сделать возможным перенос кода из одной системы в другую, прежде чем кто-либо достиг даже той грубой «переносимости», которую мы имеем сейчас. Он требовал комплексного анализа «источника» и мог помешать модификации кода и другим странным практикам, но это все же было сделано.

В последнее время такие системы, как IBM System / 38 - iSeries - System i, воспользовались преимуществами переносимости промежуточного кода (аналогичного байт-кодам Java), хранимого в скомпилированных программах, для обеспечения переносимости между несовместимыми архитектурами набора команд.

Даниэль Р Хикс
источник
Согласитесь, что это было сделано, обычно с более старыми (более простыми) наборами инструкций. В 1970-х годах был реализован проект IBM по преобразованию старых двоичных программ 7xx в System / 360.
опилки
1

Сам машинный код зависит от архитектуры.

Языки, обеспечивающие легкую переносимость между несколькими архитектурами (вероятно, наиболее известной из них является Java), как правило, имеют очень высокий уровень, требующий установки интерпретаторов или сред на машине для их работы.

Эти структуры или интерпретаторы написаны для каждой конкретной архитектуры системы, на которой они будут работать, и поэтому сами по себе не являются более переносимыми, чем «обычная» программа.

music2myear
источник
2
Скомпилированные языки также переносимы, не только интерпретируемые языки, но и компилятор специфичен для конкретной архитектуры, поскольку именно он в конечном итоге переводит код в то, какую платформу он может распознать. Единственное отличие состоит в том, что скомпилированные языки переводятся во время компиляции, а интерпретируемые языки переводятся построчно по мере необходимости.
MaQleod
1

Абсолютно, это возможно. Что такое машинный код? Это просто языкчто конкретный компьютер понимает. Думайте о себе как о компьютере, и вы пытаетесь понять книгу, написанную на немецком языке. Вы не можете сделать это, потому что вы не понимаете язык. Теперь, если бы вы взяли словарь немецкого языка и посмотрели слово «Kopf», вы бы увидели, что оно переводится на английское слово «head». Используемый вами словарь называется эмуляционным слоем в компьютерном мире. Легко ли? Ну, это становится сложнее. Возьмите немецкое слово «Schadenfruede» и переведите его на английский. Вы увидите, что в английском языке нет слова, но есть определение. Та же проблема существует в компьютерном мире, переводя вещи, которые не имеют эквивалентного слова. Это затрудняет прямые порты, так как разработчикам уровня эмуляции приходится интерпретировать, что означает это слово, и заставить главный компьютер его понимать. Иногда это не работает так, как можно было бы ожидать. Мы все видели забавные переводы книг, фраз и т. Д. В Интернете, верно?

Keltari
источник
1

Процесс, который вы описываете, называется статической перекомпиляцией, и это было сделано, но не общепринятым способом. Это означает, что это невозможно, это было сделано много раз, но это требовало ручной работы.

Есть много исторических примеров, которые стоит исследовать, но они менее способны продемонстрировать современные проблемы. Я нашел два примера, которые должны заставить любого скептика задаться вопросом о людях, которые утверждают, что все сложно, невозможно.

Сначала этот парень сделал полную статическую архетектуру И платформу для NES ROM. http://andrewkelley.me/post/jamulator.html

Он делает несколько очень хороших замечаний, но приходит к выводу, что JIT все еще более практичен. Я на самом деле не уверен, почему он еще не знал, что для этой ситуации, это может быть тип ситуации, которую большинство людей рассматривают. Не требуя ярлыков, требуя точности полного цикла и практически не используя ABI. Если бы это было все, что было, мы могли бы выбросить концепцию в мусорное ведро и назвать это днем, но это еще не все и никогда не было ... Откуда мы это знаем? Потому что все успешные проекты не использовали этот подход.

Теперь, когда возможности менее очевидны, используйте уже имеющуюся платформу ... Starcraft на платформе Linux ARM? Да, подход работает, когда вы не ограничиваете задачу тем, что вы делаете динамически. При использовании Winlib все вызовы платформы Windows являются родными, и все, о чем мы должны беспокоиться - это архитектура.

http://www.geek.com/games/starcraft-has-been-reverse-engineered-to-run-on-arm-1587277/

Я бы бросил доллары в пончики, что замедление почти ничтожно, учитывая, что ARM ручной Пандора лишь немного сильнее, чем Pi. Инструменты, которые он использовал, находятся в этом хранилище.

https://github.com/notaz/ia32rtools

Этот парень декомпилировал очень вручную, я считаю, что процесс можно было бы значительно автоматизировать с меньшим количеством работы ... но все же это труд любви на данный момент. Не позволяйте никому говорить вам, что что-то невозможно, даже не позволяйте мне говорить вам, что это не практично ... Это может быть практично, как только вы создадите новый способ сделать это.

Дж. М. Беккер
источник
0

Теоретически, да, это может быть сделано. Большая проблема, которая входит в игру, - это перевод приложения для одной операционной системы (или ядра) в другую. Существуют значительные различия между операциями низкого уровня в ядрах Windows, Linux, OSX и iOS, которые должны использовать все приложения для этих устройств.

Еще раз, теоретически, можно написать приложение, которое может разложить приложение, а также весь машинный код, связанный с операционной системой, для которой оно было скомпилировано, и затем перекомпилировать весь этот машинный код для другого устройства. Тем не менее, это было бы крайне незаконно практически в каждом случае, и было бы крайне сложно написать. Фактически, шестерни в моей голове начинают заедать, просто думая об этом.

ОБНОВИТЬ

Несколько комментариев ниже, кажется, не согласны с моим ответом, однако, я думаю, что они упускают мою точку зрения. Насколько мне известно, нет приложения, которое может взять последовательность исполняемых байтов для одной архитектуры, разложить ее на уровне байт-кода, включая все необходимые вызовы внешних библиотек, включая вызовы ядра базовой ОС, и собрать его для другой системы и сохранить результирующий исполняемый байт-код . Другими словами, нет приложения, которое могло бы взять что-то столь же простое, как Notepad.exe, разложить небольшой файл размером 190 КБ и на 100% собрать его в приложение, которое могло бы работать в Linux или OSX.

Насколько я понимаю, задающий вопрос хотел знать, что если мы можем виртуализировать программное обеспечение или запускать приложения с помощью таких программ, как Wine или Parallels, то почему мы не можем просто повторно перевести байт-код для разных систем. Причина в том, что если вы хотите полностью пересобрать приложение для другой архитектуры, вы должны разложить весь байт-код, необходимый для его запуска, прежде чем собирать его. В каждом приложении есть нечто большее, чем просто исполняемый файл, скажем, для компьютера с Windows. Все приложения Windows используют низкоуровневые объекты и функции ядра Windows для создания меню, текстовых областей, методов изменения размера окна, рисования на дисплее, отправки / получения сообщений ОС и т. Д. И т. Д. И т. Д.

Весь этот байт-код должен быть разобран, если вы хотите повторно собрать приложение и заставить его работать на другой архитектуре.

Такие приложения, как Wine, интерпретируют двоичные файлы Windows на уровне байтов. Они распознают вызовы ядра и переводят эти вызовы либо в связанные функции Linux, либо эмулируют среду Windows. Но это не байт-байт (или код операции для кода операции) ретрансляции. Это скорее перевод из функции в функцию, и это немного отличается.

RLH
источник
Это не теоретически вообще. И есть множество приложений, которые запускают другие двоичные файлы в разных операционных системах. Вы слышали о вине? Он запускает двоичные файлы Windows в разных ОС, таких как Linux, Solaris, Mac OSX, BSD и другие.
Келтари
Разница в операционных системах может быть легко замечена на большинстве систем с помощью гипервизора для запуска нескольких операционных систем (или для запуска «слоя», такого как Wine, в одной системе, эмулирующей другую). AFAIK, все «современные» не встроенные процессоры являются «виртуализируемыми», поэтому для этого не требуется эмуляция / перевод набора команд.
Даниэль Р Хикс
0

Кажется, что все эксперты упускают этот момент: «перевод» сложен, но очень подходит для компьютера (не умный, просто трудолюбивый). Но после перевода программы нуждаются в поддержке ОС, например: GetWindowVersion не существует в Linux. Обычно это обеспечивается эмулятором (очень большой). Таким образом, вы могли бы «предварительно перевести» простые программы, но для независимой работы вам нужно создать ссылку на огромную библиотеку. Imaging каждой программы Windows поставляется с собственным kernel.dll + user.dll + shell.dll ...

qak
источник
Это не просто кропотливо, это требует интеллекта. Например, скажем, вы видите какое-то вычисление, результат которого определяет адрес, по которому вы переходите, который может быть в середине чего-то, что выглядит как отдельная инструкция.
Дэвид Шварц