Le code machine peut-il être traduit dans une architecture différente?


11

C'est donc en quelque sorte lié à une question sur l' exécution d'un serveur Windows sur ARM . La prémisse de ma question est donc la suivante: le code machine peut-il être traduit d'une architecture à une autre afin d'exécuter un binaire sur une architecture différente de celle sur laquelle il a été compilé.

QEMU et d'autres émulateurs peuvent traduire les instructions à la volée, et donc exécuter un exécutable sur un ordinateur pour lequel il n'a pas été compilé. Pourquoi ne pas faire cette traduction à l'avance, plutôt qu'à la volée afin d'accélérer le processus? De ma connaissance quelque peu limitée de l'assemblage, la plupart des instructions comme MOV, ADDet d'autres devraient être portables à travers les architectures.

Tout ce qui n'a pas de mappage direct peut être mappé à un autre ensemble d'instructions, car toutes les machines sont Turing Complete. Est-ce que cela serait trop compliqué? Cela ne fonctionnerait-il pas du tout pour une raison que je ne connais pas? Cela fonctionnerait-il, mais ne donnerait-il pas de meilleurs résultats que l'utilisation d'un émulateur?


La technique est probablement tombée en désuétude car (en plus de sa fragilité), elle n'est pas beaucoup nécessaire. La portabilité / standardisation est (légèrement) meilleure de nos jours (ne serait-ce que parce que Wintel a conquis le monde) et, lorsque l'émulation inter-machine est vraiment nécessaire (par exemple, pour un émulateur de téléphone dans un environnement de développement d'application), l'émulation directe fournit un résultat plus fiable et précis. De plus, les processeurs sont suffisamment rapides pour que le coût de l'émulation ne soit pas un problème aussi grave que par le passé.
Daniel R Hicks

Réponses:


6

La réponse courte : vous ne pouvez pas traduire un exécutable compilé et lié. Bien que techniquement possible, il est hautement improbable d'accomplir (voir ci-dessous). Cependant , si vous avez le fichier source de l' assembly (contenant les instructions et les étiquettes), il est très possible de le faire (bien que si vous obtenez en quelque sorte la source de l'assembly, à moins que le programme ne soit écrit en assembly, vous devriez avoir le code source du programme d'origine comme eh bien, vous feriez mieux de le compiler pour les différentes architectures pour commencer).


La réponse longue :

QEMU et d'autres émulateurs peuvent traduire les instructions à la volée, et donc exécuter un exécutable sur un ordinateur pour lequel il n'a pas été compilé. Pourquoi ne pas faire cette traduction à l'avance, plutôt qu'à la volée afin d'accélérer le processus?

Je sais que cela peut sembler facile en principe, mais en pratique, c'est presque impossible pour plusieurs raisons principales. Pour commencer, différents jeux d'instructions utilisent des modes d'adressage largement différents, différentes structures d'opcode, différentes tailles de mot et certains n'ont même pas les instructions dont vous avez besoin.

Supposons que vous deviez remplacer l'instruction XYZpar deux autres instructions, ABCet DEF. À partir de ce moment, vous avez effectivement déplacé toutes les adresses relatives / décalées dans l'ensemble du programme, vous devez donc analyser et parcourir le programme entier et mettre à jour les décalages (avant et après la modification). Maintenant, disons que l'un des décalages change de manière significative - vous devez maintenant changer les modes d'adressage, ce qui pourrait changer la taille de l'adresse. Cela vous obligera à nouveau à analyser à nouveau l'intégralité du fichier et à recalculer toutes les adresses, et ainsi de suite et ainsi de suite.

Lorsque vous écrivez des programmes d'assemblage, vous pouvez utiliser des étiquettes, mais pas l'UC - lorsque le fichier est assemblé, toutes les étiquettes sont calculées pour être des emplacements relatifs, absolus ou décalés. Vous pouvez voir pourquoi cela devient rapidement une tâche non triviale et presque impossible. Le remplacement d'une seule instruction peut vous obliger à parcourir l'ensemble du programme des centaines de fois avant de continuer.

De ma connaissance quelque peu limitée de l'assemblage, la plupart des instructions comme MOV, ADD et autres devraient être portables à travers les architectures.

Oui, mais regardez les problèmes que j'ai décrits ci-dessus. Qu'en est-il de la taille des mots de la machine? Longueur de l'adresse? At-il même les mêmes modes d'adressage? Encore une fois, vous ne pouvez pas simplement "rechercher et remplacer" les instructions. Chaque segment d'un programme a une adresse spécifiquement définie. Les sauts vers d'autres étiquettes sont remplacés par des adresses mémoire littérales ou décalées lorsqu'un programme est assemblé.

Tout ce qui n'a pas de mappage direct peut être mappé à un autre ensemble d'instructions, car toutes les machines sont Turing Complete. Est-ce que cela serait trop compliqué? Cela ne fonctionnerait-il pas du tout pour une raison que je ne connais pas? Cela fonctionnerait-il, mais ne donnerait-il pas de meilleurs résultats que l'utilisation d'un émulateur?

Vous avez 100% raison que c'est à la fois possible et serait beaucoup plus rapide . Cependant, écrire un programme pour y parvenir est incroyablement difficile et hautement improbable, sinon pour autre chose que les problèmes que j'ai décrits ci-dessus.

Si vous disposiez du code source de l'assembly, il serait trivial de traduire le code machine en une autre architecture de jeu d'instructions. Le code machine lui-même, cependant, est assemblé , donc sans la source d'assemblage (qui contient diverses étiquettes utilisées pour calculer les adresses mémoire), cela devient incroyablement difficile. Encore une fois, la modification d'une seule instruction peut modifier les décalages de mémoire dans l'ensemble du programme et nécessiter des centaines de passes pour recalculer les adresses.

Faire cela pour un programme avec quelques milliers d'instructions nécessiterait des dizaines sinon des centaines de milliers de passes. Pour des programmes relativement petits, cela peut être possible, mais n'oubliez pas que le nombre de passes augmentera de façon exponentielle avec le nombre d'instructions machine dans le programme. Pour tout programme d'une taille suffisamment décente, c'est presque impossible.


Il s'agit essentiellement de «décompiler» ou de «désassembler» le code objet source. Pour le code relativement simple (en particulier le code généré par certains compilateurs ou packages de génération de code où il existe un "style" connu), la réinsertion d'étiquettes et similaires est assez simple. Il est certain, cependant, que les nouveaux compilateurs hautement optimisateurs généreraient du code beaucoup plus difficile à "bloquer" de cette façon.
Daniel R Hicks

@DanH si vous avez le code objet source, vous avez à peu près la source de l'assembly ( pas le code machine). Le fichier objet contient des séquences nommées (lues: étiquetées) de code machine à relier entre elles. Le problème survient lorsque vous liez les fichiers de code objet dans un exécutable. Ces segments plus petits peuvent être traités (ou rétroconçus) beaucoup plus facilement qu'un exécutable lié entier.
Percée

Certes, certains formats de fichiers objets facilitent un peu le travail. Certains peuvent même contenir des informations de débogage, vous permettant de restaurer la plupart des étiquettes. D'autres sont moins utiles. Dans certains cas, une grande partie de ces informations est conservée même dans le format de fichier lié, dans d'autres cas non. Il existe un grand nombre de formats de fichiers différents.
Daniel R Hicks

2

Oui, ce que vous proposez peut être et a été fait. Ce n'est pas trop courant, et je ne connais aucun système actuel qui utilise la technique, mais c'est certainement bien dans le domaine de la faisabilité technique.

Auparavant, il fallait faire beaucoup pour permettre le portage de code d'un système à un autre, avant que quiconque n'ait atteint la "portabilité" grossière que nous avons maintenant. Cela nécessitait une analyse complexe de la "source" et pouvait être contrecarré par la modification du code et d'autres pratiques bizarres, mais c'était toujours fait.

Plus récemment, des systèmes comme IBM System / 38 - iSeries - System i ont profité de la portabilité du code intermédiaire (similaire aux bytecodes Java) stocké avec des programmes compilés pour permettre la portabilité entre des architectures de jeux d'instructions incompatibles.


Convenez que cela a été fait, généralement avec des ensembles d'instructions beaucoup plus anciens (plus simples). Il y avait un projet IBM dans les années 1970 pour convertir les anciens programmes binaires 7xx en System / 360.
sciure

1

Le code machine lui-même est spécifique à l'architecture.

Les langages qui permettent une portabilité aisée sur plusieurs architectures (Java est probablement le plus connu) ont tendance à être de très haut niveau, nécessitant l'installation d'interprètes ou de frameworks sur une machine pour qu'ils fonctionnent.

Ces frameworks ou interprètes sont écrits pour chaque architecture système spécifique sur laquelle ils s'exécuteront et ne sont donc pas, en eux-mêmes, plus portables qu'un programme "normal".


2
Les langages compilés sont également portables, pas seulement les langages interprétés, c'est le compilateur qui est spécifique à l'architecture car c'est finalement ce qui traduit le code en ce que la plateforme sur laquelle il se trouve peut reconnaître. La seule différence est que les langues compilées sont traduites au moment de la compilation et les langues interprétées sont traduites ligne par ligne selon les besoins.
MaQleod

1

Absolument, c'est possible. Qu'est-ce que le code machine? C'est juste la languequ'un ordinateur particulier comprend. Considérez-vous comme l'ordinateur et vous essayez de comprendre un livre écrit en allemand. Vous ne pouvez pas le faire, car vous ne comprenez pas la langue. Maintenant, si vous deviez prendre un dictionnaire allemand et rechercher le mot "Kopf", vous le verriez se traduire par le mot anglais "head". Le dictionnaire que vous avez utilisé est ce qu'on appelle une couche d'émulation dans le monde informatique. Facile non? Eh bien, cela devient plus difficile. Prenez le mot allemand «Schadenfruede» et traduisez-le en anglais. Vous verrez qu'il n'y a pas de mot dans la langue anglaise, mais il y a une définition. Le même problème existe dans le monde informatique, traduire des choses qui n'ont pas un mot équivalent. Cela rend les ports directs difficiles car les développeurs de la couche d'émulation doivent faire une interprétation de ce que signifie ce mot et faire comprendre à l'ordinateur hôte. Parfois, cela ne fonctionne tout simplement pas comme on pourrait s'y attendre. Nous avons tous vu des traductions amusantes de livres, de phrases, etc. sur Internet, n'est-ce pas?


1

Le processus que vous décrivez s'appelle la recompilation statique, et il a été fait, mais pas d'une manière généralement applicable. Ce qui signifie que c'est plus que possible, cela a été fait plusieurs fois, mais cela a nécessité un travail manuel.

Il existe de nombreux exemples historiques qui méritent d'être étudiés, mais ils sont moins en mesure de démontrer les préoccupations modernes. J'ai trouvé deux exemples qui devraient essentiellement inciter tous les sceptiques à remettre en question les personnes qui prétendent que tout est difficile est impossible.

Ce gars a d'abord fait une architecture complète ET une plate-forme statique pour une ROM NES. http://andrewkelley.me/post/jamulator.html

Il soulève de très bons points, mais conclut que JIT est encore plus pratique. Je ne sais pas vraiment pourquoi il ne savait pas déjà que pour cette situation, cela pourrait être le type de situation que la plupart des gens considèrent. Ne prenant aucun raccourci, exigeant une précision de cycle complète et n'utilisant essentiellement aucun ABI. Si c'était tout, nous pourrions jeter le concept à la poubelle et l'appeler un jour, mais ce n'est pas tout et ça n'a jamais été ... Comment le savons-nous? Parce que tous les projets réussis n'ont pas utilisé cette approche.

Maintenant, pour les possibilités moins évidentes, tirez parti de la plate-forme que vous avez déjà ... Starcraft sur un ordinateur de poche Linux ARM? Oui, l'approche fonctionne lorsque vous ne contraignez pas la tâche à exactement ce que vous feriez dynamiquement. En utilisant Winlib, les appels de la plate-forme Windows sont tous natifs, tout ce dont nous devons nous soucier, c'est de l'architecture.

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

Je jetterais des dollars aux beignets que le ralentissement est presque négligeable, étant donné que la pandora ARM portable n'est qu'un peu plus forte que la Pi. Les outils qu'il a utilisés se trouvent dans ce référentiel.

https://github.com/notaz/ia32rtools

Ce gars a décompilé très manuellement, je pense que ce processus pourrait être automatisé de manière significative avec moins de travail ... mais toujours un travail d'amour pour le moment. Ne laissez personne vous dire que quelque chose n'est pas possible, ne me laissez même pas vous dire que ce n'est pas pratique ... Cela pourrait être pratique, dès que vous innovez une nouvelle façon de le faire.


0

En théorie, oui, cela peut être fait. Le plus gros problème qui entre en jeu est la traduction d'une application pour un système d'exploitation (ou noyau) à un autre. Il existe des différences importantes entre les opérations de bas niveau des noyaux Windows, Linux, OSX et iOS, que toutes les applications de ces appareils doivent utiliser.

Encore une fois, théoriquement, on pourrait écrire une application qui pourrait décomposer une application ainsi que tout le code machine associé au système d'exploitation sur lequel elle a été compilée, puis recompiler tout ce code machine pour un autre appareil. Cependant, cela serait hautement illégal dans presque tous les cas et serait extrêmement difficile à écrire. Il fait, les engrenages dans ma tête commencent à se gripper juste en y pensant.

MISE À JOUR

Quelques commentaires ci-dessous semblent en désaccord avec ma réponse, cependant, je pense qu'ils manquent de mon point. À ma connaissance, aucune application ne peut prendre une séquence d'octets exécutables pour une architecture, la décomposer au niveau du bytecode, y compris tous les appels nécessaires aux bibliothèques externes, y compris les appels au noyau du système d'exploitation sous-jacent et la réassembler pour un autre système et enregistrer le bytecode exécutable résultant . En d'autres termes, aucune application ne peut prendre quelque chose d'aussi simple que Notepad.exe, décomposer le petit fichier 190k qu'il est et le réassembler à 100% dans une application qui pourrait fonctionner sous Linux ou OSX.

Je crois comprendre que le demandeur de la question voulait savoir que si nous pouvons virtualiser des logiciels ou exécuter des applications via des programmes comme Wine ou Parallels, pourquoi ne pouvons-nous pas simplement retraduire le code octet pour différents systèmes. La raison est que si vous souhaitez réassembler complètement une application pour une autre architecture, vous devez décomposer tout le code d'octet nécessaire pour l'exécuter avant de la réassembler. Il y a plus dans chaque application que le fichier exe, par exemple, pour une machine Windows. Toutes les applications Windows utilisent les fonctions et les objets du noyau Windows de bas niveau pour créer des menus, des zones de texte, des méthodes de redimensionnement des fenêtres, dessiner à l'écran, envoyer / recevoir des messages du système d'exploitation, etc., etc.

Tout ce code d'octet doit être désassemblé si vous souhaitez le réassembler à l'application et le faire fonctionner sur une architecture différente.

Des applications comme Wine interprètent les binaires Windows au niveau de l'octet. Ils reconnaissent les appels au noyau et traduisent ces appels vers les fonctions Linux associées ou émulent l'environnement Windows. Mais ce n'est pas une retraduction octet par octet (ou opcode pour opcode). Il s'agit plutôt d'une traduction fonction par fonction et c'est un peu différent.


Ce n'est pas du tout théorique. Et il existe de nombreuses applications qui exécutent d'autres binaires sur différents systèmes d'exploitation. Connaissez-vous Wine? Il exécute des binaires Windows sur différents systèmes d'exploitation, tels que Linux, Solaris, Mac OSX, BSD et autres.
Keltari

La différence dans les systèmes d'exploitation peut facilement être affinée sur la plupart des systèmes en utilisant un hyperviseur pour exécuter plusieurs systèmes d'exploitation (ou pour exécuter une "couche" telle que Wine sur un système émulant un autre). AFAIK, tous les processeurs "modernes" non embarqués sont "virtualisables", donc cela ne nécessite aucune émulation / traduction de jeu d'instructions.
Daniel R Hicks

0

Il semble que tous les experts manquent ce point: la «traduction» est complexe mais très adaptée à l'ordinateur (pas intelligente, juste laborieuse). Mais après la traduction, les programmes ont besoin du support du système d'exploitation, ex: GetWindowVersion n'existe pas sous Linux. Ceci est normalement fourni par l'émulateur (très grand). Vous pouvez donc «pré-traduire» un programme simple, mais vous devez créer un lien vers un immense bibliothèque pour fonctionner de manière indépendante. L'imagerie de tous les programmes Windows est livrée avec son propre kernel.dll + user.dll + shell.dll ...


Ce n'est pas seulement laborieux, cela demande de l'intelligence. Par exemple, supposons que vous voyez un calcul dont le résultat détermine l'adresse à laquelle vous accédez, qui peut être au milieu de quelque chose qui semble être une instruction unique.
David Schwartz
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.