¿Se puede traducir el código de máquina a una arquitectura diferente?

11

Entonces, esto está relacionado con una pregunta sobre cómo ejecutar un servidor de Windows en ARM . Entonces, la premisa de mi pregunta es, ¿se puede traducir el código de máquina de una arquitectura a otra para ejecutar un binario en una arquitectura diferente a la que se compiló para ejecutarse?

QEMU y otros emuladores pueden traducir las instrucciones sobre la marcha y, por lo tanto, ejecutar un ejecutable en una computadora para la que no se compiló. ¿Por qué no hacer esta traducción con anticipación, en lugar de hacerlo sobre la marcha para acelerar el proceso? Desde mi conocimiento algo limitado del ensamblaje, la mayoría de las instrucciones MOV, ADDcomo otras, deberían ser portables entre arquitecturas.

Cualquier cosa que no tenga una asignación directa podría asignarse a algún otro conjunto de instrucciones, ya que todas las máquinas están Turing Complete. ¿Sería esto demasiado complicado? ¿No funcionaría en absoluto por alguna razón con la que no estoy familiarizado? ¿Funcionaría, pero no daría mejores resultados que usar un emulador?

Kibbee
fuente
La técnica probablemente ha caído en desgracia porque (además de su descamación) no se necesita mucho. La portabilidad / estandarización es (ligeramente) mejor en estos días (aunque solo sea porque Wintel se ha apoderado del mundo) y, donde la emulación entre máquinas es realmente necesaria (por ejemplo, para un emulador de teléfono en un entorno de desarrollo de aplicaciones), la emulación directa proporciona un Resultado más confiable y preciso. Además, los procesadores son lo suficientemente rápidos como para que el costo de la emulación no sea un problema tan grave como en el pasado.
Daniel R Hicks

Respuestas:

6

La respuesta breve : no puede traducir un ejecutable compilado y vinculado. Si bien es técnicamente posible, es altamente improbable lograrlo (ver más abajo). Sin embargo , si tiene el archivo fuente del ensamblado (que contiene las instrucciones y las etiquetas), es muy posible hacerlo (aunque si de alguna manera obtiene el origen del ensamblado, a menos que el programa esté escrito en ensamblado, debe tener el código fuente del programa original como bueno, para empezar sería mejor compilarlo para las diferentes arquitecturas).


La respuesta larga :

QEMU y otros emuladores pueden traducir las instrucciones sobre la marcha y, por lo tanto, ejecutar un ejecutable en una computadora para la que no se compiló. ¿Por qué no hacer esta traducción con anticipación, en lugar de hacerlo sobre la marcha para acelerar el proceso?

Sé que puede parecer fácil en principio, pero en la práctica, es casi imposible por algunas razones principales. Para comenzar, diferentes conjuntos de instrucciones utilizan modos de direccionamiento muy diferentes, diferentes estructuras de código de operación, diferentes tamaños de palabras, y algunos ni siquiera tienen las instrucciones que necesita.

Digamos que necesita reemplazar la instrucción XYZcon dos instrucciones más, ABCy DEF. Ahora ha cambiado efectivamente todas las direcciones relativas / offset en todo el programa a partir de ese momento, por lo que necesitaría analizar y revisar todo el programa y actualizar las compensaciones (tanto antes como después del cambio). Ahora, supongamos que uno de los desplazamientos cambia significativamente: ahora debe cambiar los modos de direccionamiento, lo que podría cambiar el tamaño de la dirección. Esto nuevamente lo obligará a volver a escanear todo el archivo y volver a calcular todas las direcciones, y así sucesivamente.

Cuando escribe programas de ensamblaje, puede usar etiquetas, pero la CPU no: cuando se ensambla el archivo, todas las etiquetas se calculan como ubicaciones relativas, absolutas o desplazadas. Puede ver por qué esto se convierte rápidamente en una tarea no trivial y casi imposible. Reemplazar una sola instrucción puede requerir que pase por todo el programa cientos de veces antes de continuar.

Desde mi conocimiento algo limitado del ensamblaje, la mayoría de las instrucciones como MOV, ADD y otras deberían ser portátiles en todas las arquitecturas.

Sí, pero mira los problemas que describí anteriormente. ¿Qué pasa con el tamaño de palabra de la máquina? Longitud de la dirección? ¿Tiene incluso los mismos modos de direccionamiento? Una vez más, no puede simplemente "buscar y reemplazar" instrucciones. Cada segmento de un programa tiene una dirección definida específicamente. Los saltos a otras etiquetas se reemplazan con direcciones de memoria literales o desplazadas cuando se ensambla un programa.

Cualquier cosa que no tenga una asignación directa podría asignarse a algún otro conjunto de instrucciones, ya que todas las máquinas están Turing Complete. ¿Sería esto demasiado complicado? ¿No funcionaría en absoluto por alguna razón con la que no estoy familiarizado? ¿Funcionaría, pero no daría mejores resultados que usar un emulador?

Estás 100% en lo cierto al decir que es posible y sería mucho más rápido . Sin embargo, escribir un programa para lograr esto es increíblemente difícil y altamente improbable, si no es por nada, excepto por los problemas que describí anteriormente.

Si tuviera el código fuente del ensamblado real, sería trivial traducir el código de la máquina a otra arquitectura de conjunto de instrucciones. Sin embargo, el código de la máquina en sí está ensamblado , por lo que sin la fuente de ensamblaje (que contiene varias etiquetas utilizadas para calcular las direcciones de memoria), se vuelve increíblemente difícil. Nuevamente, cambiar una sola instrucción puede cambiar las compensaciones de memoria en todo el programa y requerir cientos de pases para volver a calcular las direcciones.

Hacer esto para un programa con unos pocos miles de instrucciones requeriría decenas, si no cientos de miles de pases. Para programas relativamente pequeños, esto puede ser posible, pero recuerde que el número de pasadas aumentará exponencialmente con el número de instrucciones de la máquina en el programa. Para cualquier programa de un tamaño suficientemente decente, es casi imposible.

Penetración
fuente
Esencialmente, lo que uno tiene que hacer es "descompilar" o "desarmar" el código fuente del objeto. Para el código relativamente sencillo (especialmente el código generado por ciertos compiladores o paquetes de generación de código donde hay un "estilo" conocido), la reinserción de etiquetas y similares es bastante simple. Ciertamente, sin embargo, los compiladores más nuevos y altamente optimizados generarían código que era mucho más difícil de "asimilar" de esta manera.
Daniel R Hicks
@DanH si tiene el código fuente del objeto, prácticamente tiene la fuente del ensamblado ( no el código de la máquina). El archivo objeto contiene secuencias nombradas (leídas: etiquetadas) de código de máquina que se vincularán entre sí. El problema surge cuando vincula los archivos de código objeto a un ejecutable. Estos segmentos más pequeños se pueden manejar (o realizar ingeniería inversa) mucho más fácilmente que un ejecutable vinculado completo.
Avance
Ciertamente, ciertos formatos de archivos de objetos hacen el trabajo un poco más fácil. Algunos incluso pueden contener información de depuración, lo que le permite restaurar la mayoría de las etiquetas. Otros son menos útiles. En algunos casos, gran parte de esta información se conserva incluso en el formato de archivo vinculado, en otros casos no. Hay una gran cantidad de formatos de archivo diferentes.
Daniel R Hicks
2

Sí, lo que sugieres puede ser y se ha hecho. No es demasiado común, y no conozco ningún sistema actual que use la técnica, pero definitivamente está dentro del ámbito de la viabilidad técnica.

Solía ​​hacerse mucho para permitir la portabilidad de código de un sistema a otro, antes de que alguien hubiera logrado la "portabilidad" cruda que tenemos ahora. Se requería un análisis complejo de la "fuente" y podía verse obstaculizado por la modificación del código y otras prácticas extrañas, pero aún así se hizo.

Más recientemente, sistemas como IBM System / 38 - iSeries - System i han aprovechado la portabilidad del código intermedio (similar a los códigos de bytes Java) almacenados con programas compilados para permitir la portabilidad entre arquitecturas de conjuntos de instrucciones incompatibles.

Daniel R Hicks
fuente
De acuerdo en que esto se ha hecho, generalmente con conjuntos de instrucciones mucho más antiguos (más simples). Hubo un proyecto de IBM en la década de 1970 para convertir viejos programas binarios 7xx a System / 360.
aserrín
1

El código de la máquina en sí es específico de la arquitectura.

Los lenguajes que permiten una fácil portabilidad entre múltiples arquitecturas (Java es probablemente el más conocido) tienden a tener un nivel muy alto, lo que requiere que se instalen intérpretes o marcos en una máquina para que funcionen.

Estos marcos o intérpretes están escritos para cada arquitectura de sistema específica en la que se ejecutarán y, por lo tanto, no son, en sí mismos, más portátiles que un programa "normal".

music2myear
fuente
2
Los lenguajes compilados también son portables, no solo los lenguajes interpretados, sino que el compilador es específico de la arquitectura, ya que es lo que finalmente traduce el código a lo que puede reconocer la plataforma en la que se encuentra. La única diferencia es que los idiomas compilados se traducen en tiempo de compilación y los idiomas interpretados se traducen línea por línea según sea necesario.
MaQleod
1

Absolutamente, es posible. ¿Qué es el código de máquina? Es solo el idiomaque una computadora en particular entiende. Piensa en ti mismo como la computadora y estás tratando de entender un libro escrito en alemán. No puedes hacerlo, porque no entiendes el idioma. Ahora, si tomaras un diccionario de alemán y buscaras la palabra "Kopf", verías que se traduce a la palabra inglesa "head". El diccionario que usó se llama capa de emulación en el mundo de las computadoras. Fácil verdad? Bueno, se pone más difícil. Tome la palabra alemana "Schadenfruede" y traduzca al inglés. Verá que no hay una palabra en el idioma inglés, pero hay una definición. El mismo problema existe en el mundo de las computadoras, traduciendo cosas que no tienen una palabra equivalente. Esto dificulta los puertos directos ya que los desarrolladores de la capa de emulación tienen que interpretar lo que significa esa palabra y hacer que la computadora host la entienda. A veces simplemente no funciona de la manera que uno esperaría. Todos hemos visto traducciones divertidas de libros, frases, etc. en Internet, ¿verdad?

Keltari
fuente
1

El proceso que describe se llama Recompilación estática, y se ha realizado, pero no de una manera generalmente aplicable. Lo que significa que está más allá de lo posible, se ha hecho muchas veces, pero requirió trabajo manual.

Hay muchos ejemplos históricos que vale la pena investigar, pero son menos capaces de demostrar las preocupaciones modernas. He encontrado dos ejemplos que esencialmente deberían hacer que cualquier escéptico completo cuestione a las personas que afirman que todo es difícil.

Primero, este tipo hizo una plataforma y una arquitectura estática completa para una ROM NES. http://andrewkelley.me/post/jamulator.html

Él hace algunos puntos muy buenos, pero concluye que JIT es aún más práctico. En realidad, no estoy seguro de por qué él no sabía que para esta situación, este podría ser el tipo de situación que la mayoría de la gente considera. No tomar atajos, exigir precisión de ciclo completo y esencialmente no usar ABI en absoluto. Si fuera todo lo que hubiera, podríamos tirar el concepto a la basura y llamarlo un día, pero no es todo y nunca fue ... ¿Cómo sabemos esto? Porque todos los proyectos exitosos no utilizaron este enfoque.

Ahora, para las posibilidades menos obvias, aproveche la plataforma que ya tiene ... ¿Starcraft en una computadora de mano Linux ARM? Sí, el enfoque funciona cuando no restringe la tarea a exactamente lo que haría dinámicamente. Al usar Winlib, las llamadas de la plataforma Windows son nativas, de lo que tenemos que preocuparnos es de la arquitectura.

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

Tiraría dólares a las donas porque la desaceleración es casi insignificante, teniendo en cuenta que la pandora de mano ARM es solo un poco más fuerte que la Pi. Las herramientas que utilizó están en este repositorio.

https://github.com/notaz/ia32rtools

Ese tipo se descompuso de forma muy manual, creo que ese proceso podría automatizarse significativamente con menos trabajo ... pero todavía una labor de amor en este momento. No dejes que nadie te diga que algo no es posible, ni siquiera me dejes decirte que no es práctico ... Podría ser práctico, tan pronto como innovaras una nueva forma de hacerlo.

JM Becker
fuente
0

Teóricamente, sí, esto se puede hacer. El mayor problema que entra en juego es traducir una aplicación para un sistema operativo (o kernel) a otro. Existen diferencias significativas entre las operaciones de bajo nivel de los núcleos de Windows, Linux, OSX e iOS, que todas las aplicaciones para esos dispositivos tienen que usar.

Una vez más, teóricamente, uno podría escribir una aplicación que pudiera descomponer una aplicación, así como todo el código de máquina asociado con el sistema operativo para el que fue compilado y luego volver a compilar todo ese código de máquina para otro dispositivo. Sin embargo, eso sería altamente ilegal en casi todos los casos y sería extremadamente difícil de escribir. De hecho, los engranajes en mi cabeza están empezando a agarrotarse solo de pensarlo.

ACTUALIZAR

Un par de comentarios a continuación parecen estar en desacuerdo con mi respuesta, sin embargo, creo que están perdiendo mi punto. Que yo sepa, no hay ninguna aplicación que pueda tomar una secuencia de bytes ejecutables para una arquitectura, descomponerla en el nivel de código de bytes, incluidas todas las llamadas necesarias a las bibliotecas externas, incluidas las llamadas al núcleo del sistema operativo subyacente y volver a ensamblarlo para otro sistema y guardar el código de bytes ejecutable resultante . En otras palabras, no hay una aplicación que pueda tomar algo tan simple como Notepad.exe, descomponer el pequeño archivo de 190k que es y volver a ensamblarlo al 100% en una aplicación que podría ejecutarse en Linux u OSX.

Tengo entendido que el autor de la pregunta quería saber que si podemos virtualizar software o ejecutar aplicaciones a través de programas como Wine o Parallels, ¿por qué no podemos simplemente volver a traducir el código de bytes para diferentes sistemas? La razón es que si desea volver a ensamblar completamente una aplicación para otra arquitectura, debe descomponer todo el código de bytes que se necesita para ejecutarla antes de volver a ensamblarla. Hay más en cada aplicación que solo el archivo exe, por ejemplo, para una máquina Windows. Todas las aplicaciones de Windows utilizan los objetos y funciones del núcleo de Windows de bajo nivel para crear menús, áreas de texto, métodos para cambiar el tamaño de las ventanas, dibujar en la pantalla, enviar / recibir mensajes del sistema operativo, etc., etc.

Todo ese código de byte debe desmontarse si desea volver a ensamblarlo en la aplicación y hacer que se ejecute en una arquitectura diferente.

Las aplicaciones como Wine interpretan los binarios de Windows a nivel de byte. Reconocen las llamadas al kernel y traducen esas llamadas a funciones Linux relacionadas o emulan el entorno de Windows. Pero, eso no es una traducción de byte por byte (o código de operación para código de operación). Es más una traducción de función por función y eso es bastante diferente.

RLH
fuente
No es teórico en absoluto. Y hay muchas aplicaciones que ejecutan otros binarios en diferentes sistemas operativos. ¿Has oído hablar del vino? Ejecuta binarios de Windows en diferentes sistemas operativos, como Linux, Solaris, Mac OSX, BSD y otros.
Keltari
La diferencia en los sistemas operativos se puede mejorar fácilmente en la mayoría de los sistemas mediante el uso de un hipervisor para ejecutar múltiples sistemas operativos (o para ejecutar una "capa" como Wine en un sistema que emula a otro). AFAIK, todos los procesadores no integrados "modernos" son "virtualizables", por lo que esto no requiere emulación / traducción del conjunto de instrucciones.
Daniel R Hicks
0

Parece que a todos los expertos les falta este punto: la 'traducción' es compleja pero muy adecuada para la computadora (no inteligente, solo laboriosa). Pero después de la traducción, los programas necesitan soporte del sistema operativo, por ejemplo: GetWindowVersion no existe en Linux. Esto normalmente lo proporciona el emulador (muy grande). Por lo tanto, podría 'pretraducir' un programa simple, pero debe vincularlo a una gran biblioteca para ejecutar de forma independiente. Las imágenes de cada programa de Windows vienen con su propio kernel.dll + user.dll + shell.dll ...

qak
fuente
No es solo laborioso, requiere inteligencia. Por ejemplo, supongamos que ve algún cálculo cuyo resultado determina la dirección a la que salta, que puede estar en el medio de algo que parece ser una sola instrucción.
David Schwartz