Kann Maschinencode in eine andere Architektur übersetzt werden?


11

Dies hängt also mit einer Frage zum Ausführen eines Windows-Servers auf ARM zusammen . Die Prämisse meiner Frage ist also, ob Maschinencode von einer Architektur in eine andere übersetzt werden kann, um eine Binärdatei auf einer anderen Architektur als der auszuführen, für die sie kompiliert wurde.

QEMU und andere Emulatoren können die Anweisungen im laufenden Betrieb übersetzen und daher eine ausführbare Datei auf einem Computer ausführen, für den sie nicht kompiliert wurden. Warum nicht diese Übersetzung im Voraus und nicht im laufenden Betrieb durchführen, um den Prozess zu beschleunigen? Aus meiner etwas eingeschränkt Kenntnis der Montage, wie die meisten der Befehle MOV, ADDund andere sollten über Architekturen tragbar sein.

Alles, was keine direkte Zuordnung hat, kann einem anderen Befehlssatz zugeordnet werden, da alle Maschinen Turing Complete sind. Wäre das zu kompliziert? Würde es aus irgendeinem Grund, den ich nicht kenne, überhaupt nicht funktionieren? Würde es funktionieren, aber keine besseren Ergebnisse erzielen als mit einem Emulator?


Die Technik ist wahrscheinlich in Ungnade gefallen, weil sie (zusätzlich zu ihrer Schuppigkeit) nicht viel benötigt wird. Portabilität / Standardisierung ist heutzutage (etwas) besser (schon allein, weil Wintel die Welt erobert hat), und wenn eine maschinenübergreifende Emulation wirklich erforderlich ist (z. B. für einen Telefonemulator in einer App-Entwicklungsumgebung), bietet die direkte Emulation eine zuverlässigeres und genaueres Ergebnis. Außerdem sind Prozessoren schnell genug, sodass die Kosten für die Emulation nicht so schwerwiegend sind wie in der Vergangenheit.
Daniel R Hicks

Antworten:


6

Die kurze Antwort : Sie können eine kompilierte, verknüpfte ausführbare Datei nicht übersetzen. Obwohl dies technisch möglich ist, ist dies höchst unwahrscheinlich (siehe unten). Allerdings , wenn Sie die haben Assembler - Quelldatei (mit den Anweisungen und Etiketten), ist es sehr gut möglich , zu tun (obwohl , wenn Sie irgendwie die Montage Quelle erhalten, es sei denn , das Programm in Assembler geschrieben ist, sollten Sie das ursprüngliche Programm Quellcode als Nun, es ist besser, wenn Sie es für die verschiedenen Architekturen kompilieren.


Die lange Antwort :

QEMU und andere Emulatoren können die Anweisungen im laufenden Betrieb übersetzen und daher eine ausführbare Datei auf einem Computer ausführen, für den sie nicht kompiliert wurden. Warum nicht diese Übersetzung im Voraus und nicht im laufenden Betrieb durchführen, um den Prozess zu beschleunigen?

Ich weiß, dass es im Prinzip einfach erscheinen mag, aber in der Praxis ist es aus einigen Hauptgründen fast unmöglich. Zu Beginn verwenden verschiedene Befehlssätze stark unterschiedliche Adressierungsmodi, unterschiedliche Opcode-Strukturen, unterschiedliche Wortgrößen, und einige verfügen nicht einmal über die Anweisungen, die Sie benötigen.

Angenommen, Sie mussten die Anweisung XYZdurch zwei weitere Anweisungen ersetzen , ABCund DEF. Jetzt haben Sie effektiv alle relativen / Offset-Adressen im gesamten Programm von diesem Punkt an verschoben, sodass Sie das gesamte Programm analysieren und durchgehen und die Offsets (sowohl vor als auch nach der Änderung) aktualisieren müssen. Angenommen, einer der Offsets ändert sich erheblich. Jetzt müssen Sie die Adressierungsmodi ändern, wodurch sich die Größe der Adresse ändern kann. Dadurch werden Sie erneut gezwungen, die gesamte Datei erneut zu scannen und alle Adressen neu zu berechnen, und so weiter und so fort.

Wenn Sie Assembly-Programme schreiben, verwenden Sie möglicherweise Beschriftungen, die CPU jedoch nicht. Wenn die Datei zusammengestellt wird, werden alle Beschriftungen als relative, absolute oder versetzte Positionen berechnet. Sie können sehen, warum dies schnell zu einer nicht trivialen Aufgabe wird und nahezu unmöglich ist. Wenn Sie eine einzelne Anweisung ersetzen möchten, müssen Sie möglicherweise das gesamte Programm hunderte Male durchlaufen, bevor Sie fortfahren können.

Aufgrund meiner etwas eingeschränkten Kenntnisse in der Montage sollten die meisten Anweisungen wie MOV, ADD und andere architekturübergreifend portierbar sein.

Ja, aber schauen Sie sich die oben beschriebenen Probleme an. Was ist mit der Wortgröße der Maschine? Adresslänge? Hat es überhaupt die gleichen Adressierungsmodi? Auch hier können Sie Anweisungen nicht einfach "finden und ersetzen". Jedes Segment eines Programms hat eine spezifisch definierte Adresse. Sprünge zu anderen Bezeichnungen werden beim Zusammenstellen eines Programms durch Literal- oder Offset-Speicheradressen ersetzt.

Alles, was keine direkte Zuordnung hat, kann einem anderen Befehlssatz zugeordnet werden, da alle Maschinen Turing Complete sind. Wäre das zu kompliziert? Würde es aus irgendeinem Grund, den ich nicht kenne, überhaupt nicht funktionieren? Würde es funktionieren, aber keine besseren Ergebnisse erzielen als mit einem Emulator?

Sie haben zu 100% Recht, dass beides möglich ist und viel schneller wäre . Das Schreiben eines Programms, um dies zu erreichen, ist jedoch unglaublich schwierig und höchst unwahrscheinlich, wenn nicht für irgendetwas anderes als die oben beschriebenen Probleme.

Wenn Sie den eigentlichen Assembly-Quellcode hätten, wäre es trivial, den Maschinencode in eine andere Befehlssatzarchitektur zu übersetzen. Der Maschinencode selbst wird jedoch zusammengestellt , sodass es ohne die Assembly-Quelle (die verschiedene Beschriftungen enthält, die zum Berechnen von Speicheradressen verwendet werden) unglaublich schwierig wird. Das Ändern eines einzelnen Befehls kann wiederum die Speicherversätze im gesamten Programm ändern und Hunderte von Durchläufen erfordern, um die Adressen neu zu berechnen.

Dies für ein Programm mit einigen tausend Anweisungen zu tun, würde Zehntausende, wenn nicht Hunderttausende von Durchgängen erfordern. Bei relativ kleinen Programmen ist dies möglicherweise möglich. Beachten Sie jedoch, dass die Anzahl der Durchgänge mit der Anzahl der Maschinenanweisungen im Programm exponentiell zunimmt. Für jedes Programm mit einer angemessenen Größe ist dies nahezu unmöglich.


Im Wesentlichen muss der Quellobjektcode "dekompiliert" oder "disassembliert" werden. Für relativ einfachen Code (insbesondere Code, der von bestimmten Compilern oder Codegenerierungspaketen generiert wird, bei denen ein bekannter "Stil" bekannt ist) ist das erneute Einfügen von Beschriftungen und dergleichen ziemlich einfach. Sicherlich würden neuere hochoptimierende Compiler jedoch Code generieren, der auf diese Weise weitaus schwieriger zu "grocken" ist.
Daniel R Hicks

@DanH Wenn Sie den Quellobjektcode haben, haben Sie so ziemlich die Assembly-Quelle ( nicht den Maschinencode). Die Objektdatei enthält benannte (gelesen: beschriftete) Sequenzen von Maschinencode, die miteinander verknüpft werden sollen. Das Problem tritt auf, wenn Sie die Objektcodedateien mit einer ausführbaren Datei verknüpfen. Diese kleineren Segmente können viel einfacher gehandhabt (oder rückentwickelt) werden als eine gesamte verknüpfte ausführbare Datei.
Durchbruch

Bestimmte Objektdateiformate erleichtern die Arbeit sicherlich ein wenig. Einige enthalten möglicherweise sogar Debugging-Informationen, mit denen Sie die meisten Labels wiederherstellen können. Andere sind weniger hilfreich. In einigen Fällen bleibt ein Großteil dieser Informationen auch im verknüpften Dateiformat erhalten, in anderen Fällen nicht. Es gibt eine enorme Anzahl verschiedener Dateiformate.
Daniel R Hicks

2

Ja, was Sie vorschlagen, kann und wurde getan. Es ist nicht allzu häufig und ich kenne keine aktuellen Systeme, die diese Technik verwenden, aber es liegt definitiv im Bereich der technischen Machbarkeit.

Früher wurde viel getan, um die Portierung von Code von einem System auf ein anderes zu ermöglichen, bevor irgendjemand die sogar grobe "Portabilität" erreicht hatte, die wir jetzt haben. Es erforderte eine komplexe Analyse der "Quelle" und konnte durch Codemodifikationen und andere seltsame Praktiken verhindert werden, wurde aber dennoch durchgeführt.

In jüngerer Zeit haben Systeme wie IBM System / 38 - iSeries - System i die Portabilität von Zwischencode (ähnlich wie Java-Bytecodes) genutzt, der mit kompilierten Programmen gespeichert wurde, um die Portabilität zwischen inkompatiblen Befehlssatzarchitekturen zu ermöglichen.


Stimmen Sie zu, dass dies normalerweise mit viel älteren (einfacheren) Befehlssätzen geschehen ist. In den 1970er Jahren gab es ein IBM-Projekt zur Konvertierung alter 7xx-Binärprogramme in System / 360.
Sägemehl

1

Der Maschinencode selbst ist architekturspezifisch.

Sprachen, die eine einfache Portabilität über mehrere Architekturen hinweg ermöglichen (Java ist wahrscheinlich die bekannteste), sind in der Regel sehr hoch und erfordern die Installation von Interpreten oder Frameworks auf einem Computer, damit sie funktionieren.

Diese Frameworks oder Interpreter sind für jede spezifische Systemarchitektur geschrieben, auf der sie ausgeführt werden, und sind daher an und für sich nicht portabler als ein "normales" Programm.


2
Kompilierte Sprachen sind ebenfalls portabel und nicht nur interpretierte Sprachen. Der Compiler ist architekturspezifisch, da er letztendlich den Code in die Plattform übersetzt, auf der er sich befindet. Der einzige Unterschied besteht darin, dass kompilierte Sprachen zur Kompilierungszeit übersetzt werden und interpretierte Sprachen nach Bedarf Zeile für Zeile übersetzt werden.
MaQleod

1

Absolut möglich. Was ist Maschinencode? Es ist nur die Sprachedass ein bestimmter Computer versteht. Stellen Sie sich als Computer vor und Sie versuchen, ein Buch in deutscher Sprache zu verstehen. Sie können es nicht tun, weil Sie die Sprache nicht verstehen. Wenn Sie nun ein deutsches Wörterbuch nehmen und das Wort "Kopf" nachschlagen, wird es in das englische Wort "Kopf" übersetzt. Das von Ihnen verwendete Wörterbuch wird in der Computerwelt als Emulationsebene bezeichnet. Einfach richtig? Nun, es wird schwieriger. Nehmen Sie das deutsche Wort "Schadenfruede" und übersetzen Sie es ins Englische. Sie werden sehen, dass es kein Wort in der englischen Sprache gibt, aber es gibt eine Definition. Das gleiche Problem besteht in der Computerwelt, wenn Dinge übersetzt werden, die kein gleichwertiges Wort haben. Dies macht direkte Ports schwierig, da die Entwickler der Emulationsschicht eine Interpretation der Bedeutung dieses Wortes vornehmen und den Host-Computer verstehen lassen müssen. Manchmal funktioniert es einfach nicht so, wie man es erwarten würde. Wir haben alle lustige Übersetzungen von Büchern, Phrasen usw. im Internet gesehen, oder?


1

Der von Ihnen beschriebene Prozess wird als statische Neukompilierung bezeichnet und wurde nur nicht allgemein anwendbar durchgeführt. Das heißt, es ist unmöglich, es wurde schon oft gemacht, aber es erforderte manuelle Arbeit.

Es gibt viele historische Beispiele, die es wert sind, untersucht zu werden, aber sie sind weniger in der Lage, die modernen Anliegen aufzuzeigen. Ich habe zwei Beispiele gefunden, die im Wesentlichen alle Skeptiker dazu bringen sollten, die Leute in Frage zu stellen, die behaupten, dass alles, was schwer ist, unmöglich ist.

Zuerst hat dieser Typ eine vollständige statische Archetektur UND Plattform für ein NES-ROM erstellt. http://andrewkelley.me/post/jamulator.html

Er macht einige sehr gute Punkte, kommt aber zu dem Schluss, dass JIT noch praktischer ist. Ich bin mir eigentlich nicht sicher, warum er nicht bereits wusste, dass dies für diese Situation die Art von Situation sein könnte, die die meisten Menschen in Betracht ziehen. Nehmen Sie keine Abkürzungen, fordern Sie die volle Zyklusgenauigkeit und verwenden Sie im Wesentlichen überhaupt keinen ABI. Wenn es alles wäre, könnten wir das Konzept in den Müll werfen und es einen Tag nennen, aber es ist nicht alles und war es nie ... Woher wissen wir das? Weil alle erfolgreichen Projekte diesen Ansatz nicht verwendeten.

Nutzen Sie für die weniger offensichtlichen Möglichkeiten die Plattform, die Sie bereits haben ... Starcraft auf einem Linux ARM-Handheld? Ja, der Ansatz funktioniert, wenn Sie die Aufgabe nicht auf genau das beschränken, was Sie dynamisch tun würden. Bei Verwendung von Winlib sind alle Windows-Plattformaufrufe nativ. Wir müssen uns nur um die Architektur kümmern.

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

Ich würde Donuts Dollars geben, dass die Verlangsamung fast vernachlässigbar ist, wenn man bedenkt, dass die ARM-Handheld-Pandora nur ein bisschen stärker ist als der Pi. Die von ihm verwendeten Tools befinden sich in diesem Repository.

https://github.com/notaz/ia32rtools

Dieser Typ hat sich sehr manuell dekompiliert. Ich glaube, dass der Prozess mit weniger Arbeit erheblich automatisiert werden kann ... aber im Moment immer noch eine Liebesarbeit. Lassen Sie sich von niemandem sagen, dass etwas nicht möglich ist. Lassen Sie sich nicht einmal sagen, dass es nicht praktikabel ist. Es könnte praktisch sein, sobald Sie einen neuen Weg finden, dies zu erreichen.


0

Theoretisch kann dies ja getan werden. Das größere Problem, das ins Spiel kommt, ist die Übersetzung einer Anwendung für ein Betriebssystem (oder einen Kernel) in ein anderes. Es gibt signifikante Unterschiede zwischen den Low-Level-Operationen des Windows-, Linux-, OSX- und iOS-Kernels, die alle Anwendungen für diese Geräte verwenden müssen.

Theoretisch könnte man wieder eine Anwendung schreiben, die eine Anwendung sowie den gesamten Maschinencode, der dem Betriebssystem zugeordnet ist, auf dem sie kompiliert wurde, zerlegen und dann den gesamten Maschinencode für ein anderes Gerät neu kompilieren könnte. Dies wäre jedoch in nahezu jedem Fall höchst illegal und äußerst schwer zu schreiben. Tatsächlich fangen die Zahnräder in meinem Kopf an, sich zu fassen, wenn ich nur daran denke.

AKTUALISIEREN

Ein paar Kommentare unten scheinen mit meiner Antwort nicht übereinzustimmen, aber ich denke, sie verfehlen meinen Standpunkt. Meines Wissens gibt es keine Anwendung, die eine Folge von ausführbaren Bytes für eine Architektur auf Bytecode-Ebene zerlegen kann, einschließlich aller erforderlichen Aufrufe an externe Bibliotheken, einschließlich Aufrufen des zugrunde liegenden Betriebssystemkerns, und diese für ein anderes System wieder zusammensetzen und speichern kann resultierender ausführbarer Bytecode . Mit anderen Worten, es gibt keine Anwendung, die etwas so Einfaches wie Notepad.exe aufnehmen, die kleine 190-KB-Datei zerlegen und zu 100% zu einer Anwendung zusammensetzen könnte, die unter Linux oder OSX ausgeführt werden könnte.

Nach meinem Verständnis wollte der Fragesteller wissen, dass wir den Bytecode für verschiedene Systeme nicht einfach neu übersetzen können, wenn wir Software virtualisieren oder Anwendungen über Programme wie Wine oder Parallels ausführen können. Der Grund dafür ist, dass Sie, wenn Sie eine Anwendung für eine andere Architektur vollständig neu zusammenstellen möchten, den gesamten Bytecode zerlegen müssen, der zum Ausführen erforderlich ist, bevor Sie sie wieder zusammensetzen. Jede Anwendung enthält mehr als nur die exe-Datei, beispielsweise für einen Windows-Computer. Alle Windows-Anwendungen verwenden die Windows-Kernelobjekte und -Funktionen auf niedriger Ebene, um Menüs, Textbereiche, Methoden zum Ändern der Fenstergröße, Zeichnen auf der Anzeige, Senden / Empfangen von Betriebssystemnachrichten usw. zu erstellen.

Der gesamte Bytecode muss zerlegt werden, wenn Sie die Anwendung wieder zusammensetzen und auf einer anderen Architektur ausführen möchten.

Anwendungen wie Wine interpretieren Windows-Binärdateien auf Byte-Ebene. Sie erkennen Aufrufe an den Kernel und übersetzen diese Aufrufe entweder in verwandte Linux-Funktionen oder sie emulieren die Windows-Umgebung. Dies ist jedoch keine Byte-für-Byte-Neuübersetzung (oder Opcode für Opcode). Es ist eher eine Funktion-für-Funktion-Übersetzung, und das ist ganz anders.


Es ist überhaupt nicht theoretisch. Und es gibt viele Anwendungen, die andere Binärdateien auf verschiedenen Betriebssystemen ausführen. Hast du von Wein gehört? Es führt Windows-Binärdateien unter verschiedenen Betriebssystemen wie Linux, Solaris, Mac OSX, BSD und anderen aus.
Keltari

Der Unterschied in den Betriebssystemen kann auf den meisten Systemen leicht behoben werden, indem ein Hypervisor verwendet wird, um mehrere Betriebssysteme auszuführen (oder um eine "Schicht" wie Wine auf einem System auszuführen, das ein anderes emuliert). AFAIK, alle "modernen" nicht eingebetteten Prozessoren sind "virtualisierbar", daher erfordert dies keine Befehlssatzemulation / -übersetzung.
Daniel R Hicks

0

Anscheinend fehlt allen Experten dieser Punkt: Die 'Übersetzung' ist komplex, aber sehr gut für den Computer geeignet (nicht intelligent, nur mühsam). Nach der Übersetzung benötigen Programme jedoch Betriebssystemunterstützung, z. B.: GetWindowVersion ist unter Linux nicht vorhanden. Dies wird normalerweise vom Emulator geliefert (sehr groß). Sie können also einfache Programme vorab übersetzen, müssen jedoch eine Verknüpfung zu einer riesigen Bibliothek herstellen, um unabhängig zu arbeiten. Imaging-Programme für jedes Windows werden mit einer eigenen kernel.dll + user.dll + shell.dll ...


Es ist nicht nur mühsam, es erfordert Intelligenz. Angenommen, Sie sehen eine Berechnung, deren Ergebnis die Adresse bestimmt, zu der Sie springen. Diese befindet sich möglicherweise in der Mitte einer einzelnen Anweisung.
David Schwartz
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.