Robiłem to wiele razy i nadal to robię. W tym przypadku, gdy twoim głównym celem jest czytanie, a nie pisanie asemblera, czuję, że to ma zastosowanie.
Napisz swój własny deasembler. Nie w celu stworzenia kolejnego największego deasemblera, ten jest wyłącznie dla Ciebie. Celem jest nauczenie się zestawu instrukcji. Czy uczę się asemblera na nowej platformie, pamiętając asemblera dla platformy, którą kiedyś znałem. Zacznij od kilku wierszy kodu, dodając na przykład rejestry i ping-pongowanie między demontażem wyjścia binarnego a dodawaniem coraz bardziej skomplikowanych instrukcji po stronie wejściowej:
1) nauczyć się zestawu instrukcji dla konkretnego procesora
2) nauczyć się niuansów pisania kodu w asemblerze dla wspomnianego procesora, tak aby można było poruszać każdym bitem kodu operacji w każdej instrukcji
3) uczysz się zestawu instrukcji lepiej niż większość inżynierów, którzy używają go do zarabiania na życie
W twoim przypadku jest kilka problemów, zwykle polecam zestaw instrukcji ARM na początek, obecnie jest więcej dostarczanych produktów opartych na ARM niż jakichkolwiek innych (w tym komputery x86). Ale prawdopodobieństwo, że używasz teraz ARM i nie znasz wystarczająco asemblera, aby pisać kod startowy lub inne procedury, wiedząc, że ARM może, ale nie musi, pomóc w tym, co próbujesz zrobić. Drugim i ważniejszym powodem dla ARM jest to, że długości instrukcji mają stały rozmiar i są wyrównane. Demontaż instrukcji o zmiennej długości, takich jak x86, może być koszmarem jako pierwszy projekt, a celem jest tutaj nauczenie się zestawu instrukcji, aby nie tworzyć projektu badawczego. Trzeci ARM to dobrze wykonany zestaw instrukcji, rejestry są tworzone równo i nie mają indywidualnych specjalnych niuansów.
Musisz więc dowiedzieć się, od jakiego procesora chcesz zacząć. Proponuję najpierw msp430 lub ARM, potem ARM najpierw lub drugi, a potem chaos x86. Bez względu na platformę, każda platforma, z której warto korzystać, ma arkusze danych lub podręczniki programistów wolne od dostawcy, które zawierają zestaw instrukcji, a także kodowanie kodów operacyjnych (bity i bajty języka maszynowego). Aby dowiedzieć się, co robi kompilator i jak napisać kod, z którym kompilator nie musi się zmagać, dobrze jest znać kilka zestawów instrukcji i zobaczyć, jak ten sam kod wysokiego poziomu jest implementowany w każdym zestawie instrukcji z każdym kompilatorem z każdą optymalizacją oprawa. Nie chcesz zajmować się optymalizacją kodu tylko po to, aby stwierdzić, że ulepszyłeś go dla jednego kompilatora / platformy, ale znacznie gorzej dla wszystkich innych.
Aha do deasemblacji zestawów instrukcji o zmiennej długości, zamiast po prostu zaczynać od początku i deasemblować każde czterobajtowe słowo liniowo przez pamięć, tak jak w przypadku ARM lub co dwa bajty, jak w przypadku msp430 (msp430 ma instrukcje o zmiennej długości, ale nadal możesz sobie z tym poradzić przechodzenie liniowo przez pamięć, jeśli zaczniesz od punktów wejścia z tablicy wektorów przerwań). Dla zmiennej długości chcesz znaleźć punkt wejścia na podstawie tabeli wektorów lub wiedzy o tym, jak uruchamia się procesor i postępować zgodnie z kodem w kolejności wykonania. Musisz całkowicie zdekodować każdą instrukcję, aby wiedzieć, ile bajtów jest używanych, a następnie, jeśli instrukcja nie jest bezwarunkową gałęzią, załóżmy, że następny bajt po tej instrukcji jest kolejną instrukcją. Musisz również przechowywać wszystkie możliwe adresy oddziałów i założyć, że są to początkowe adresy bajtów, aby uzyskać więcej instrukcji. Pewnego razu udało mi się wykonać kilka przejść przez plik binarny. Zaczynając od punktu wejścia, oznaczyłem ten bajt jako początek instrukcji, a następnie dekodowałem liniowo przez pamięć, aż trafiłem do gałęzi bezwarunkowej. Wszystkie cele gałęzi zostały oznaczone jako adresy początkowe instrukcji. Wykonałem wiele przejść przez plik binarny, dopóki nie znalazłem żadnych nowych celów gałęzi. Jeśli w jakimkolwiek momencie napotkasz instrukcję 3-bajtową, ale z jakiegoś powodu oznaczyłeś drugi bajt jako początek instrukcji, masz problem. Jeśli kod został wygenerowany przez kompilator wysokiego poziomu, nie powinno to mieć miejsca, chyba że kompilator robi coś złego, jeśli kod ma ręcznie napisany asembler (jak powiedzmy stara gra zręcznościowa), jest całkiem możliwe, że będą rozgałęzienia warunkowe, które nigdy nie mogą się wydarzyć, jak r0 = 0, po których nastąpi skok, jeśli nie zero. Być może będziesz musiał ręcznie edytować te z pliku binarnego, aby kontynuować. Dla twoich bezpośrednich celów, które zakładam, że będą na x86, nie sądzę, że będziesz miał problem.
Polecam narzędzia gcc, mingw32 to łatwy sposób na użycie narzędzi gcc w systemie Windows, jeśli Twoim celem jest x86. Jeśli nie, mingw32 plus msys jest doskonałą platformą do generowania kompilatora krzyżowego ze źródeł binutils i gcc (ogólnie całkiem łatwe). mingw32 ma pewne zalety w stosunku do cygwin, takie jak znacznie szybsze programy i unikasz piekła dll cygwin. gcc i binutils pozwolą ci pisać w C lub asemblerze i demontować twój kod, a jest więcej stron internetowych niż możesz przeczytać, pokazujących, jak zrobić jedną lub wszystkie trzy. Jeśli masz zamiar robić to z zestawem instrukcji o zmiennej długości, bardzo polecam użycie zestawu narzędzi, który zawiera dezasembler. Na przykład program do deasemblacji innej firmy dla x86 będzie wyzwaniem, ponieważ nigdy nie wiadomo, czy został on poprawnie zdemontowany. Niektóre z nich są również zależne od systemu operacyjnego, celem jest skompilowanie modułów do formatu binarnego, który zawiera instrukcje oznaczania informacji z danych, aby dezasembler mógł wykonać dokładniejszą pracę. Innym wyborem dla tego głównego celu jest posiadanie narzędzia, które można skompilować bezpośrednio do asemblera w celu inspekcji, a następnie mieć nadzieję, że gdy kompiluje się do formatu binarnego, tworzy te same instrukcje.
Krótka (dobrze trochę krótsza) odpowiedź na Twoje pytanie. Napisz dezasembler, aby nauczyć się zestawu instrukcji. Zacząłbym od czegoś RYZYKOWEGO i łatwego do nauczenia, takiego jak ARM. Gdy już znasz jeden zestaw instrukcji, inne stają się znacznie łatwiejsze do przyswojenia, często w ciągu kilku godzin, dzięki trzeciemu zestawowi instrukcji możesz prawie natychmiast rozpocząć pisanie kodu, korzystając z arkusza danych / instrukcji obsługi składni. Wszystkie procesory, których warto używać, mają arkusz danych lub podręcznik referencyjny, który opisuje instrukcje z dokładnością do bitów i bajtów opkodów. Naucz się procesora RISC, takiego jak ARM i CISC, takiego jak x86, na tyle, aby poczuć różnice, takie jak konieczność przechodzenia przez rejestry dla wszystkiego lub możliwość wykonywania operacji bezpośrednio na pamięci z mniejszą liczbą rejestrów lub bez nich. Trzy instrukcje operandów kontra dwie itd. Podczas strojenia kodu wysokiego poziomu, skompilować dla więcej niż jednego procesora i porównać dane wyjściowe. Najważniejszą rzeczą, której się nauczysz, jest to, że bez względu na to, jak dobrze napisano kod wysokiego poziomu, jakość kompilatora i dokonane wybory optymalizacyjne mają ogromny wpływ na rzeczywiste instrukcje. Polecam llvm i gcc (z binutils), żadna z nich nie produkujeświetny kod, ale są one przeznaczone dla wielu platform i dla wielu celów i oba mają optymalizatory. Oba są bezpłatne i można łatwo tworzyć kompilatory krzyżowe ze źródeł dla różnych procesorów docelowych.