O código de máquina pode ser traduzido para uma arquitetura diferente?

11

Portanto, isso está relacionado a uma pergunta sobre a execução de um servidor Windows no ARM . Portanto, a premissa da minha pergunta é: o código de máquina pode ser traduzido de uma arquitetura para outra , a fim de executar um binário em uma arquitetura diferente daquela em que foi compilado para executar.

O QEMU e outros emuladores podem traduzir as instruções rapidamente e, portanto, executar um executável em um computador para o qual não foi compilado. Por que não fazer essa tradução antes do tempo, e não em tempo real, a fim de acelerar o processo? Pelo meu conhecimento um tanto limitado de montagem, a maioria das instruções MOV, ADDcomo outras, deve ser portátil entre arquiteturas.

Qualquer coisa que não tenha um mapeamento direto pode ser mapeada para outro conjunto de instruções, pois todas as máquinas são Turing Complete. Fazer isso seria muito complicado? Não funcionaria por algum motivo que eu não estou familiarizado? Funcionaria, mas não produziria melhores resultados do que usar um emulador?

Kibbee
fonte
A técnica provavelmente caiu em desfavor porque (além de sua descamação) não é muito necessária. A portabilidade / padronização é (um pouco) melhor atualmente (mesmo que a Wintel tenha dominado o mundo) e, onde a emulação entre máquinas é realmente necessária (por exemplo, para um emulador de telefone em um ambiente de desenvolvimento de aplicativos), a emulação direta fornece uma resultado mais confiável e preciso. Além disso, os processadores são rápidos o suficiente para que o custo da emulação não seja um problema tão grave quanto no passado.
Daniel R Hicks

Respostas:

6

A resposta curta : você não pode traduzir um executável vinculado compilado. Embora tecnicamente possível, é altamente improvável de realizar (veja abaixo). No entanto , se você tiver o arquivo de origem do assembly (contendo as instruções e os rótulos), é muito possível fazê-lo (embora, de alguma maneira, você obtenha a fonte do assembly, a menos que o programa seja escrito em assembly, você deve ter o código fonte do programa original como bem, é melhor compilá-lo para a arquitetura diferente).


A resposta longa :

O QEMU e outros emuladores podem traduzir as instruções rapidamente e, portanto, executar um executável em um computador para o qual não foi compilado. Por que não fazer essa tradução antes do tempo, e não em tempo real, a fim de acelerar o processo?

Sei que pode parecer fácil em princípio, mas, na prática, é quase impossível por algumas razões principais. Para começar, diferentes conjuntos de instruções usam modos de endereçamento amplamente diferentes, diferentes estruturas de código de operação, tamanhos diferentes de palavras e alguns nem sequer têm as instruções necessárias.

Digamos que você precise substituir as instruções XYZpor mais duas instruções, ABCe DEF. Agora, você mudou efetivamente todos os endereços relativos / offset em todo o programa a partir desse ponto, para que você precise analisar e percorrer todo o programa e atualizar os desvios (antes e depois da alteração). Agora, digamos que uma das compensações mude significativamente - agora você precisa alterar os modos de endereçamento, o que pode alterar o tamanho do endereço. Isso forçará novamente a varredura de todo o arquivo e a recálculo de todos os endereços, e assim por diante.

Quando você escreve programas de montagem, pode usar rótulos, mas a CPU não - quando o arquivo é montado, todos os rótulos são calculados para serem relativos, absolutos ou deslocados. Você pode ver por que isso rapidamente se torna uma tarefa não trivial e quase impossível. Substituir uma única instrução pode exigir que você passe pelo programa inteiro centenas de vezes antes de prosseguir.

Pelo meu conhecimento um tanto limitado de montagem, a maioria das instruções como MOV, ADD e outras devem ser portáveis ​​entre arquiteturas.

Sim, mas observe os problemas descritos acima. E o tamanho da palavra da máquina? Comprimento do endereço? Ele tem os mesmos modos de endereçamento? Novamente, você não pode simplesmente "encontrar e substituir" instruções. Cada segmento de um programa possui um endereço definido especificamente. Os saltos para outras etiquetas são substituídos por endereços de memória literal ou offset quando um programa é montado.

Qualquer coisa que não tenha um mapeamento direto pode ser mapeada para outro conjunto de instruções, pois todas as máquinas são Turing Complete. Fazer isso seria muito complicado? Não funcionaria por algum motivo que eu não estou familiarizado? Funcionaria, mas não produziria melhores resultados do que usar um emulador?

Você está 100% correto de que isso é possível e seria muito mais rápido . No entanto, escrever um programa para fazer isso é incrivelmente difícil e altamente improvável, se não for por qualquer coisa, exceto pelos problemas descritos acima.

Se você tivesse o código-fonte real da montagem, seria trivial traduzir o código da máquina para outra arquitetura do conjunto de instruções. O próprio código da máquina, no entanto, é montado ; portanto, sem a fonte do assembly (que contém várias etiquetas usadas para calcular endereços de memória), torna-se incrivelmente difícil. Mais uma vez, alterar uma única instrução pode alterar os desvios de memória em todo o programa e exigir centenas de passes para recalcular os endereços.

Fazer isso para um programa com alguns milhares de instruções exigiria dezenas, senão centenas, de milhares de passes. Para programas relativamente pequenos, isso pode ser possível, mas lembre-se de que o número de passes aumentará exponencialmente com o número de instruções da máquina no programa. Para qualquer programa de tamanho decente, é quase impossível.

Avanço
fonte
Essencialmente, o que se deve fazer é "descompilar" ou "desmontar" o código do objeto de origem. Para código relativamente direto (especialmente código gerado por certos compiladores ou pacotes de geração de código onde há um "estilo" conhecido), a reinserção de rótulos e similares é bastante simples. Certamente, no entanto, os compiladores altamente otimizadores mais recentes gerariam código que era muito mais difícil de "agrupar" dessa maneira.
Daniel R Hicks
@ DanH, se você tem o código do objeto de origem, você praticamente tem a fonte do assembly ( não o código da máquina). O arquivo de objeto contém sequências nomeadas (lidas: rotuladas) de código de máquina a serem vinculadas. O problema ocorre quando você vincula os arquivos de código do objeto a um executável. Esses segmentos menores podem ser manipulados (ou submetidos a engenharia reversa) com muito mais facilidade do que um executável vinculado inteiro.
Revelação
Certamente, certos formatos de arquivo de objeto tornam o trabalho um pouco mais fácil. Alguns podem até conter informações de depuração, permitindo restaurar a maioria dos rótulos. Outros são menos úteis. Em alguns casos, muitas dessas informações são preservadas mesmo no formato de arquivo vinculado, em outros casos não. Há um número tremendo de diferentes formatos de arquivo.
Daniel R Hicks
2

Sim, o que você sugere pode ser e já foi feito. Não é muito comum, e eu não conheço nenhum sistema atual que use a técnica, mas definitivamente está dentro do campo da viabilidade técnica.

Costumava ser feito muito para permitir a portabilidade de código de um sistema para outro, antes que alguém atingisse a "portabilidade" grosseira que temos agora. Exigia uma análise complexa da "fonte" e poderia ser impedida pela modificação do código e outras práticas excêntricas, mas ainda era feita.

Mais recentemente, sistemas como o IBM System / 38 - iSeries - System i aproveitaram a portabilidade de código intermediário (semelhante aos bytecodes Java) armazenados com programas compilados para permitir a portabilidade entre arquiteturas incompatíveis de conjuntos de instruções.

Daniel R Hicks
fonte
Concorde que isso foi feito, geralmente com conjuntos de instruções muito mais antigos (mais simples). Houve um projeto da IBM na década de 1970 para converter programas binários 7xx antigos no System / 360.
27411 sawdust #
1

O código da máquina em si é específico da arquitetura.

Os idiomas que permitem fácil portabilidade entre várias arquiteturas (Java é provavelmente o mais conhecido) tendem a ser de nível muito alto, exigindo que intérpretes ou estruturas sejam instalados em uma máquina para que funcionem.

Essas estruturas ou intérpretes são escritos para cada arquitetura de sistema específica em que serão executados e, portanto, não são mais portáteis do que um programa "normal".

music2myear
fonte
2
As linguagens compiladas também são portáteis, não apenas as interpretadas, é o compilador que é específico da arquitetura, pois é o que traduz o código para o que a plataforma em que ele pode reconhecer. A única diferença é que os idiomas compilados são traduzidos no momento da compilação e os idiomas interpretados são traduzidos linha por linha, conforme necessário.
MaQleod
1

Absolutamente, é possível. O que é código de máquina? É apenas a linguagemque um computador em particular entende. Pense em você como o computador e você está tentando entender um livro escrito em alemão. Você não pode fazer isso, porque você não entende o idioma. Agora, se você pegar um dicionário em alemão e procurar a palavra "Kopf", você o traduzirá para a palavra em inglês "cabeça". O dicionário que você usou é chamado de camada de emulação no mundo dos computadores. Fácil né? Bem, fica mais difícil. Pegue a palavra alemã "Schadenfruede" e traduza para o inglês. Você verá que não há palavras no idioma inglês, mas há uma definição. O mesmo problema existe no mundo dos computadores, traduzindo coisas que não têm uma palavra equivalente. Isso dificulta as portas diretas, pois os desenvolvedores da camada de emulação precisam interpretar o que essa palavra significa e fazer com que o computador host a entenda. Às vezes, simplesmente não funciona da maneira que se esperaria. Todos nós vimos traduções engraçadas de livros, frases etc. na internet, certo?

Keltari
fonte
1

O processo que você descreve é ​​chamado de recompilação estática e foi feito, mas não de uma maneira geralmente aplicável. Isso significa que está além do possível, já foi feito várias vezes, mas exigiu trabalho manual.

Vale a pena pesquisar muitos exemplos históricos, mas eles são menos capazes de demonstrar as preocupações modernas. Encontrei dois exemplos que devem essencialmente fazer com que céticos completos questionem as pessoas que afirmam que tudo é difícil é impossível.

Primeiro, esse cara fez uma arquitetura e plataforma estática completa para uma ROM do NES. http://andrewkelley.me/post/jamulator.html

Ele faz alguns pontos muito bons, mas conclui que o JIT é ainda mais prático. Na verdade, não sei por que ele ainda não sabia que, para essa situação, esse pode ser o tipo de situação que a maioria das pessoas considera. Sem atalhos, exigindo precisão de ciclo completo e basicamente sem usar ABI. Se fosse tudo o que havia, poderíamos jogar o conceito no lixo e encerrar o dia, mas não é tudo e nunca foi ... Como sabemos disso? Porque todos os projetos de sucesso não usaram essa abordagem.

Agora, para as possibilidades menos óbvias, aproveite a plataforma que você já possui ... Starcraft em um computador de mão Linux ARM? Sim, a abordagem funciona quando você não restringe a tarefa exatamente ao que faria dinamicamente. Ao usar o Winlib, as chamadas da plataforma Windows são todas nativas, e só precisamos nos preocupar com a arquitetura.

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

Eu jogaria dólares para os donuts que a desaceleração é quase insignificante, considerando que a pandora portátil ARM é apenas um pouco mais forte que o Pi. As ferramentas que ele usou estão neste repositório.

https://github.com/notaz/ia32rtools

Esse cara descompilou muito manualmente, acredito que o processo poderia ser automatizado significativamente com menos trabalho ... mas ainda um trabalho de amor no momento. Não deixe ninguém lhe dizer que algo não é possível, nem mesmo deixe-me dizer que não é prático ... Poderia ser prático, assim que você inovar uma nova maneira de fazê-lo.

JM Becker
fonte
0

Teoricamente, sim, isso pode ser feito. O maior problema que entra em jogo é traduzir um aplicativo para um sistema operacional (ou kernel) para outro. Existem diferenças significativas entre as operações de baixo nível dos kernels Windows, Linux, OSX e iOS, que todos os aplicativos desses dispositivos precisam usar.

Mais uma vez, teoricamente, era possível escrever um aplicativo que pudesse decompor um aplicativo, bem como todo o código de máquina associado ao sistema operacional em que foi compilado para executar e recompilar todo esse código de máquina para outro dispositivo. No entanto, isso seria altamente ilegal em quase todos os casos e seria extremamente difícil de escrever. De fato, as engrenagens na minha cabeça estão começando a se agitar só de pensar nisso.

ATUALIZAR

Alguns comentários abaixo parecem discordar da minha resposta, no entanto, acho que eles estão perdendo meu argumento. Que eu saiba, não há nenhum aplicativo que possa pegar uma sequência de bytes executáveis ​​para uma arquitetura, decompô-la no nível do bytecode, incluindo todas as chamadas necessárias para bibliotecas externas, incluindo chamadas para o kernel do SO subjacente e remontá-lo para outro sistema e salvar o bytecode executável resultante . Em outras palavras, não há nenhum aplicativo que possa ser tão simples quanto o Notepad.exe, decomponha o pequeno arquivo de 190k existente e 100% o remonte em um aplicativo que possa ser executado no Linux ou OSX.

Entendo que o autor da pergunta queria saber que, se podemos virtualizar software ou executar aplicativos por meio de programas como o Wine ou o Parallels, por que não podemos simplesmente voltar a traduzir o código de bytes para sistemas diferentes. O motivo é que, se você deseja remontar completamente um aplicativo para outra arquitetura, decomponha todo o código de bytes necessário para executá-lo antes de remontá-lo. Há mais em todos os aplicativos do que apenas o arquivo exe, por exemplo, para uma máquina Windows. Todos os aplicativos do Windows usam os objetos e funções de baixo nível do kernel do Windows para criar menus, áreas de texto, métodos para redimensionar janelas, desenhar na tela, enviar / receber mensagens do SO e assim por diante ...

Todo esse código de bytes deve ser desmontado se você desejar remontar ao aplicativo e executá-lo em uma arquitetura diferente.

Aplicativos como o Wine interpretam os binários do Windows no nível de bytes. Eles reconhecem chamadas para o kernel e as convertem para funções relacionadas do Linux ou emulam o ambiente Windows. Mas isso não é uma retranslação de byte por byte (ou opcode para opcode). É mais uma tradução função a função e isso é um pouco diferente.

RLH
fonte
Não é de todo teórico. E há muitos aplicativos que executam outros binários em diferentes sistemas operacionais. Você já ouviu falar de vinho? Ele executa binários do Windows em diferentes sistemas operacionais, como Linux, Solaris, Mac OSX, BSD e outros.
Keltari
A diferença de sistemas operacionais pode ser facilmente identificada na maioria dos sistemas, usando um hipervisor para executar vários sistemas operacionais (ou para executar uma "camada" como o Wine em um sistema emulando outro). AFAIK, todos os processadores "modernos" não incorporados são "virtualizáveis", portanto, isso não requer emulação / tradução de conjunto de instruções.
Daniel R Hicks
0

Parece que todos os especialistas estão perdendo esse ponto: a 'tradução' é complexa, mas muito adequada para o computador (não inteligente, apenas laboriosa). Porém, após a tradução, os programas precisam de suporte do SO, por exemplo: GetWindowVersion não existe no Linux. Isso normalmente é fornecido pelo emulador (muito grande). Então você pode 'pré-traduzir' um programa simples, mas precisa vincular uma biblioteca enorme para executar de forma independente. Os programas de criação de imagens de todas as janelas vêm com seu próprio kernel.dll + user.dll + shell.dll ...

qak
fonte
Não é apenas trabalhoso, requer inteligência. Por exemplo, digamos que você veja alguma computação cujo resultado determina o endereço para o qual você pula, o que pode estar no meio de algo que parece ser uma única instrução.
David Schwartz