Czy kod maszynowy można przetłumaczyć na inną architekturę?

11

Jest to więc związane z pytaniem o uruchomienie serwera Windows na ARM . Tak więc przesłanka mojego pytania brzmi: czy kod maszynowy może być tłumaczony z jednej architektury na drugą w celu wykonania pliku binarnego na architekturze innej niż ta, na której został skompilowany.

QEMU i inne emulatory mogą tłumaczyć instrukcje w locie, a zatem uruchomić plik wykonywalny na komputerze, dla którego nie został skompilowany. Dlaczego nie zrobić tego tłumaczenia z wyprzedzeniem, zamiast w locie, aby przyspieszyć proces? Z mojego dość ograniczoną wiedzę na montaż, większość instrukcji podoba MOV, ADDa inne powinny być przenośne na wszystkich architekturach.

Wszystko, co nie ma bezpośredniego mapowania, można zmapować na inny zestaw instrukcji, ponieważ wszystkie maszyny są Turing Complete. Czy zrobienie tego byłoby zbyt skomplikowane? Czy to nie zadziałałoby z jakiegoś powodu, którego nie znam? Czy to zadziała, ale nie przyniesie lepszych rezultatów niż użycie emulatora?

Kibee
źródło
Technika prawdopodobnie popadła w niełaskę, ponieważ (oprócz jej łuszczenia się) nie jest zbyt potrzebna. Przenośność / standaryzacja jest obecnie (nieznacznie) lepsza (choćby dlatego, że Wintel przejął świat), a tam, gdzie naprawdę potrzebna jest emulacja między urządzeniami (np. Emulator telefonu w środowisku programowania aplikacji), bezpośrednia emulacja zapewnia bardziej wiarygodny i dokładny wynik. Ponadto procesory są na tyle szybkie, że koszt emulacji nie jest tak poważnym problemem, jak w przeszłości.
Daniel R Hicks

Odpowiedzi:

6

Krótka odpowiedź : nie można przetłumaczyć skompilowanego, połączonego pliku wykonywalnego. Chociaż jest to technicznie możliwe, jest wysoce nieprawdopodobne do osiągnięcia (patrz poniżej). Jeśli jednak masz plik źródłowy zestawu (zawierający instrukcje i etykiety), jest to bardzo możliwe (chociaż jeśli w jakiś sposób uzyskasz źródło zestawu, chyba że program jest napisany w zestawie, powinieneś mieć oryginalny kod źródłowy programu jako cóż, lepiej więc skompiluj to dla innej architektury na początek).


Długa odpowiedź :

QEMU i inne emulatory mogą tłumaczyć instrukcje w locie, a zatem uruchomić plik wykonywalny na komputerze, dla którego nie został skompilowany. Dlaczego nie zrobić tego tłumaczenia z wyprzedzeniem, zamiast w locie, aby przyspieszyć proces?

Wiem, że może to wydawać się łatwe, ale w praktyce jest to prawie niemożliwe z kilku głównych powodów. Na początek różne zestawy instrukcji używają zasadniczo różnych trybów adresowania, różnych struktur kodu operacyjnego, różnych rozmiarów słów, a niektóre nawet nie mają potrzebnych instrukcji.

Powiedzmy, że musisz zastąpić instrukcję XYZdwiema kolejnymi instrukcjami ABCoraz DEF. Teraz skutecznie przesunąłeś wszystkie adresy względne / przesunięcia w całym programie od tego momentu, więc musisz przeanalizować i przejść przez cały program i zaktualizować przesunięcia (zarówno przed zmianą, jak i po niej). Powiedzmy, że jedna z przesunięć znacznie się zmienia - teraz musisz zmienić tryby adresowania, które mogą zmienić rozmiar adresu. To znowu zmusi cię do ponownego przeskanowania całego pliku i ponownego obliczenia wszystkich adresów, i tak dalej, i tak po czwarte.

Podczas pisania programów asemblerowych możesz używać etykiet, ale procesor tego nie robi - po skompletowaniu pliku wszystkie etykiety są obliczane jako lokalizacje względne, bezwzględne lub przesunięte. Możesz zobaczyć, dlaczego to szybko staje się nietrywialnym zadaniem, a prawie niemożliwe. Zastąpienie pojedynczej instrukcji może wymagać przejścia całego programu setki razy przed przejściem dalej.

Z mojej nieco ograniczonej wiedzy na temat montażu większość instrukcji takich jak MOV, ADD i inne powinny być przenośne w różnych architekturach.

Tak, ale spójrz na problemy, które przedstawiłem powyżej. Co z rozmiarem słowa maszyny? Długość adresu? Czy ma nawet te same tryby adresowania? Ponownie nie możesz po prostu „znaleźć i zamienić” instrukcje. Każdy segment programu ma konkretnie określony adres. Przeskoki do innych etykiet są zastępowane literalnymi lub przesuniętymi adresami pamięci podczas składania programu.

Wszystko, co nie ma bezpośredniego mapowania, można zmapować na inny zestaw instrukcji, ponieważ wszystkie maszyny są Turing Complete. Czy zrobienie tego byłoby zbyt skomplikowane? Czy to nie zadziałałoby z jakiegoś powodu, którego nie znam? Czy to zadziała, ale nie przyniesie lepszych rezultatów niż użycie emulatora?

Masz 100% rację, że jest to możliwe i byłoby o wiele szybsze . Jednak napisanie programu do osiągnięcia tego celu jest niewiarygodnie trudne i wysoce nieprawdopodobne, jeśli nie z wyjątkiem problemów, które przedstawiłem powyżej.

Jeśli posiadasz aktualny kod źródłowy zestawu, przetłumaczenie kodu maszynowego na inną architekturę zestawu instrukcji byłoby banalne. Sam kod maszynowy jest jednak składany , więc bez źródła asemblera (które zawiera różne etykiety używane do obliczania adresów pamięci) staje się niezwykle trudne. Ponownie zmiana pojedynczej instrukcji może zmienić przesunięcia pamięci w całym programie i wymagać setek przejść w celu ponownego obliczenia adresów.

Wykonanie tego dla programu z kilkoma tysiącami instrukcji wymagałoby dziesiątek, jeśli nie setek tysięcy przejść. W przypadku stosunkowo małych programów może to być możliwe, ale pamiętaj, że liczba przejść wzrośnie wykładniczo wraz z liczbą instrukcji maszynowych w programie. Dla każdego programu o wystarczającej wielkości jest to prawie niemożliwe.

Przełom
źródło
Zasadniczo należy „zdekompilować” lub „zdemontować” kod źródłowy obiektu. W przypadku stosunkowo prostego kodu (zwłaszcza kodu generowanego przez niektóre kompilatory lub pakiety generujące kod, w którym występuje znany „styl”), ponowne wstawienie etykiet i tym podobnych jest dość proste. Z pewnością jednak nowsze wysoce optymalizujące kompilatory wygenerowałyby kod, który był o wiele trudniejszy do „grockowania” w ten sposób.
Daniel R Hicks
@ DanH, jeśli masz kod obiektu źródłowego, w zasadzie masz źródło asemblera ( nie kod maszynowy). Plik obiektowy zawiera nazwane (czytane: oznaczone) sekwencje kodu maszynowego, które należy ze sobą połączyć. Problem pojawia się, gdy podłączysz pliki kodu obiektowego do pliku wykonywalnego. Te mniejsze segmenty mogą być obsługiwane (lub poddawane inżynierii wstecznej) znacznie łatwiej niż cały połączony plik wykonywalny.
Przełom
Z pewnością niektóre formaty plików obiektowych ułatwiają to zadanie. Niektóre mogą nawet zawierać informacje debugowania, co pozwala przywrócić większość etykiet. Inni są mniej pomocni. W niektórych przypadkach duża część tych informacji jest zachowywana nawet w formacie połączonego pliku, w innych przypadkach nie. Istnieje ogromna liczba różnych formatów plików.
Daniel R Hicks
2

Tak, to, co sugerujesz, może być i zostało zrobione. Nie jest to zbyt powszechne i nie znam żadnych obecnych systemów, które wykorzystują tę technikę, ale zdecydowanie jest w zakresie technicznej wykonalności.

Kiedyś dużo się działo, aby umożliwić przenoszenie kodu z jednego systemu do drugiego, zanim ktokolwiek osiągnął nawet prymitywną „przenośność”, którą mamy teraz. Wymagało to złożonej analizy „źródła” i mogło być utrudnione przez modyfikację kodu i inne dziwne praktyki, ale nadal tak było.

Ostatnio systemy takie jak IBM System / 38 - iSeries - System i skorzystały z możliwości przenoszenia kodu pośredniego (podobnego do kodów bajtowych Java) przechowywanego w skompilowanych programach, aby umożliwić przenoszenie między niekompatybilnymi architekturami zestawów instrukcji.

Daniel R. Hicks
źródło
Zgadzam się, że zostało to zrobione, zwykle przy użyciu znacznie starszych (prostszych) zestawów instrukcji. W latach 70. istniał projekt IBM dotyczący konwersji starych programów binarnych 7xx na System / 360.
trociny
1

Sam kod maszynowy jest specyficzny dla architektury.

Języki, które pozwalają na łatwą przenośność na wielu architekturach (Java jest prawdopodobnie najbardziej znana), mają zwykle bardzo wysoki poziom, wymagając zainstalowania interpreterów lub platform na komputerze, aby mogły działać.

Te frameworki lub interpretatory są napisane dla każdej konkretnej architektury systemu, na której będą uruchamiane, a zatem same w sobie nie są bardziej przenośne niż „normalny” program.

music2myear
źródło
2
Języki skompilowane są również przenośne, a nie tylko języki interpretowane, to kompilator jest specyficzny dla architektury, ponieważ ostatecznie tłumaczy kod na to, na jakiej platformie może rozpoznać. Jedyna różnica polega na tym, że języki kompilowane są tłumaczone w czasie kompilacji, a języki interpretowane są tłumaczone wiersz po wierszu, zależnie od potrzeb.
MaQleod,
1

Oczywiście, jest to możliwe. Co to jest kod maszynowy? To tylko językktóre rozumie dany komputer. Pomyśl o sobie jak o komputerze i starasz się zrozumieć książkę napisaną po niemiecku. Nie możesz tego zrobić, ponieważ nie rozumiesz języka. Teraz, jeśli weźmiesz niemiecki słownik i odszukasz słowo „Kopf”, zobaczysz, że przekłada się ono na angielskie słowo „głowa”. Używany słownik to tak zwana warstwa emulacji w świecie komputerów. Łatwe, prawda? Cóż, staje się trudniejsze. Weź niemieckie słowo „Schadenfruede” i przetłumacz je na angielski. Zobaczysz, że nie ma słowa w języku angielskim, ale istnieje definicja. Ten sam problem istnieje w świecie komputerów, tłumacząc rzeczy, które nie mają odpowiednika. Utrudnia to bezpośrednie porty, ponieważ twórcy warstwy emulacji muszą dokonać interpretacji tego słowa i sprawić, aby komputer-host zrozumiał. Czasami po prostu nie działa tak, jak można by się spodziewać. Wszyscy widzieliśmy śmieszne tłumaczenia książek, zwrotów itp. W Internecie, prawda?

Keltari
źródło
1

Opisany proces nazywa się rekompilacją statyczną i został wykonany, ale nie w ogólnie obowiązujący sposób. Oznacza to, że jest to niemożliwe, zostało to zrobione wiele razy, ale wymagało to pracy ręcznej.

Istnieje wiele przykładów historycznych wartych zbadania, ale są one mniej zdolne do wykazania współczesnych obaw. Znalazłem dwa przykłady, które zasadniczo powinny sprawić, że jakikolwiek sceptyczny sceptyk zostanie zakwestionowany przez ludzi, którzy twierdzą, że wszystko jest trudne, jest niemożliwe.

Najpierw ten facet wykonał pełną statyczną architekturę ORAZ platformę dla ROM NES. http://andrewkelley.me/post/jamulator.html

Robi bardzo dobre uwagi, ale konkluduje, że JIT jest jeszcze bardziej praktyczny. Właściwie nie jestem pewien, dlaczego nie wiedział, że w tej sytuacji może to być sytuacja, którą większość ludzi bierze pod uwagę. Bez skrótów, wymagający pełnej dokładności cyklu i zasadniczo bez użycia ABI. Gdyby to było wszystko, moglibyśmy wrzucić ten pomysł do kosza i nazwać go dniem, ale to nie wszystko i nigdy nie było… Skąd to wiemy? Ponieważ wszystkie udane projekty nie stosowały tego podejścia.

Teraz, gdy możliwości są mniej oczywiste, wykorzystaj platformę, którą już masz ... Starcraft na podręcznym systemie ARM? Tak, podejście działa, gdy nie ograniczysz zadania dokładnie do tego, co zrobiłbyś dynamicznie. Korzystając z Winlib, wszystkie wywołania platformy Windows są rodzime, musimy się tylko martwić architekturą.

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

Rzucałbym pączkom dolary, że spowolnienie jest prawie znikome, biorąc pod uwagę, że ręczna pandora ARM jest tylko trochę silniejsza niż Pi. Narzędzia, których użył, znajdują się w tym repozytorium.

https://github.com/notaz/ia32rtools

Ten facet zdekompilował się bardzo ręcznie, uważam, że proces ten można znacznie zautomatyzować przy mniejszym nakładzie pracy ... ale w tej chwili wciąż wysiłku miłości. Nie pozwól nikomu powiedzieć, że coś jest niemożliwe, nawet nie powiem, że to nie jest praktyczne ... To może być praktyczne, gdy tylko wprowadzisz nowy sposób, aby to zrobić.

JM Becker
źródło
0

Teoretycznie tak, można to zrobić. Największym problemem, który pojawia się w grze, jest tłumaczenie aplikacji dla jednego systemu operacyjnego (lub jądra) na inny. Istnieją znaczne różnice między operacjami niskiego poziomu jądra systemu Windows, Linux, OSX i iOS, z których muszą korzystać wszystkie aplikacje dla tych urządzeń.

Po raz kolejny teoretycznie można napisać aplikację, która mogłaby rozpakować aplikację, a także cały kod maszynowy powiązany z systemem operacyjnym, na którym została skompilowana, a następnie ponownie skompilować cały ten kod maszynowy dla innego urządzenia. Byłoby to jednak wysoce nielegalne w prawie każdym przypadku i byłoby niezwykle trudne do napisania. Faktem jest, że koła zębate w mojej głowie zaczynają łapać się na samą myśl o tym.

AKTUALIZACJA

Kilka komentarzy poniżej wydaje się nie zgadzać z moją odpowiedzią, jednak myślę, że nie rozumiem tego. Według mojej wiedzy, nie ma aplikacji, która mogłaby pobrać sekwencję bajtów wykonywalnych dla jednej architektury, rozłożyć ją na poziomie kodu bajtowego, w tym wszystkie niezbędne wywołania bibliotek zewnętrznych, w tym wywołania do jądra systemu operacyjnego, i złożyć je dla innego systemu i zapisać wynikowy wykonywalny kod bajtowy . Innymi słowy, nie ma aplikacji, która mogłaby zająć się czymś tak prostym, jak Notepad.exe, rozłożyć mały 190k plik, który jest, i w 100% złożyć go ponownie w aplikację, która mogłaby działać w systemie Linux lub OSX.

Rozumiem, że osoba zadająca pytanie chciała wiedzieć, że jeśli możemy wirtualizować oprogramowanie lub uruchamiać aplikacje za pomocą programów takich jak Wine lub Parallels, dlaczego nie możemy po prostu ponownie przetłumaczyć kodu bajtowego dla różnych systemów. Powodem jest to, że jeśli chcesz w pełni złożyć aplikację dla innej architektury, musisz zdekomponować cały bajt-kod potrzebny do uruchomienia go przed ponownym złożeniem. Każda aplikacja zawiera coś więcej niż tylko plik exe dla komputera z systemem Windows. Wszystkie aplikacje systemu Windows używają obiektów jądra systemu Windows i funkcji niskiego poziomu do tworzenia menu, obszarów tekstowych, metod zmiany rozmiaru okna, rysowania na wyświetlaczu, wysyłania / odbierania komunikatów systemu operacyjnego itd. Itd.

Cały ten bajt-kod musi zostać zdemontowany, jeśli chcesz ponownie złożyć w aplikacji i uruchomić go na innej architekturze.

Aplikacje takie jak Wine interpretują pliki binarne Windows na poziomie bajtów. Rozpoznają połączenia z jądrem i tłumaczą je na powiązane funkcje Linuksa lub emulują środowisko Windows. Ale to nie jest retranslacja bajt po bajcie (lub opcode dla opcode). Jest to bardziej tłumaczenie funkcji dla funkcji i jest to nieco inne.

RLH
źródło
To wcale nie jest teoretyczne. Istnieje wiele aplikacji, które uruchamiają inne pliki binarne w różnych systemach operacyjnych. Czy słyszałeś o Winie? Działa z plikami binarnymi Windows na różnych systemach operacyjnych, takich jak Linux, Solaris, Mac OSX, BSD i inne.
Keltari
Różnicę w systemach operacyjnych można łatwo wyrównać w większości systemów za pomocą hiperwizora do uruchamiania wielu systemów operacyjnych (lub do uruchamiania „warstwy”, takiej jak Wine, emulując inny system). AFAIK, wszystkie „nowoczesne” niewbudowane procesory są „wirtualizowalne”, więc nie wymaga emulacji / tłumaczenia zestawu instrukcji.
Daniel R Hicks
0

Wydaje się, że wszystkim ekspertom brakuje tego punktu: „tłumaczenie” jest złożone, ale bardzo odpowiednie dla komputera (brak inteligentnego, po prostu pracowity). Ale po tłumaczeniu programy wymagają wsparcia systemu operacyjnego, np .: GetWindowVersion nie istnieje w systemie Linux. Zwykle jest to dostarczane przez emulator (bardzo duży). Możesz więc „wstępnie przetłumaczyć” proste programy, ale musisz połączyć się z ogromną biblioteką, aby działać niezależnie. Obrazowanie programów każdego systemu Windows ma własny kernel.dll + user.dll + shell.dll ...

qak
źródło
To nie tylko pracochłonne, ale wymaga inteligencji. Załóżmy na przykład, że widzisz obliczenia, których wynik określa adres, na który przeskakujesz, który może znajdować się w środku czegoś, co wydaje się być pojedynczą instrukcją.
David Schwartz