" Linear - Dissassembles all instructions in order, starting from some point (usually the entry point of a binary). Flow-oriented - These follow jumps and calls and continue disassembling from their target. They also might stop disassembling after return instructions, so avoid showing instructions that are unreachable (and thus probably not code at all).
Because flow-oriented disassemblers follow branchesm and because conditional branches exist, the disassembler has to make a decision. Often, for normal code, a disassembler can simply follow both (e.g. jump and don’t jump, disassemble from the target and from the next instruction). The problem is that there can be contradictary or incompatible jumps."
Chciałbym więc zacząć od podkreślenia, że "super simple" disassembler (gdyż linearny) jest zadaniem "super prostym". Tak generalnie. (Więc i każdy taki program, nie wyposażony w jakąś szczególną rozbudowę - cokolwiek jeszcze inne zadania, jest z definicji także bardzo prostą konstrukcją programistyczną.)
6502c ma liczne opkody "nielegalne", które się w najczęstszej praktyce tworzenia kodu pomija, więc rozróżnienie bloku kodu 'realnego' od tego, który się deasemblował "ponadmiarowo" (łatwo zgadnąć, że się nie wykonuje), nie stanowi problemu.
Dlatego "flow-oriented" disassembler dla 6502 to nie jest zadanie pierwszorzędne. (A jeszcze, równie ważki argument, blok danych za jednym podejściem poddawany deasemblacji, dla rozpoznania obszarów kodu i oddzielenia ich od reszty danych, jest objętościowo rzędu wielkości mniejszy od tego, jaki może być poddawany dla procesorów ponad 8-bitowych).
Troszkę inaczej sprawa może się przedstawiać w przypadku procesora 65816, gdyż ten nie ma opkodów, które stanowią łatwy orientacyjnie odnośnik (tego, że to 'fałszywy kod'). Wszystko na pozór może wydawać się kodem - o ile nie poddamy tego dość niejednoznacznej według jakiś usystematyzowań, własnej ocenie rozpoznania tego, czy taki kod ma sens i czy może stanowić część faktycznie wykonywanego kodu.
Dochodzi do tego konieczność rozpoznawania w jakim trybie procesor aktualnie pracuje na podstawie dwóch znaczników statusu procesora dla operacji na rejestrach i pamięci (jest to dość specyficzna cecha tego procesora, zdaje się). To zaś ma przełożenie bezpośrednio na to, z jakiej liczby argumentów (bajtów) w trybie adresowania natychmiastowego procesor musi skorzystać z pamięci za opkodem - więc ma to wpływ na proces interpretacyjny danych znajdujących się bezpośrednio i dalej potem za taką instrukcją (zmiana następcza całej interpretacji). Tym samym, nie jest możliwy do zastosowania najprostszy wariant deasemblera liniowego.
Cały ten, może niepotrzeby wstęp, niczemu konkretnemu nie służy. Zrobiłem tylko takie podstawowe omówienie tematu, gdyż powiedzmy że dookreśla postrzeganie tego, czym jest zrobienie deaseblera dla 6502 (z definicji dla mnie - niczym wyjątkowym).
Chciałbym teraz omówić to, co nazwałem "dea6502c_demo" (deasemblerem dla 6502c). Nie wiem ogólnie, czy jest potrzeba aby wyszło to poza tą postać "dema", jaką tak oficjalnie już upubliczniam (bezpośrednio poprzednia wersja działała już podobnie).
Z punktu widzenia praktyki, czy jest sens to rozwijać i w jakim kierunku. Rozsądnym mi się wydało, że taki deasembler mógłby się "relokować" od MEMLO (zmieniając ten wskaźnik) i pozostając w uśpieniu być wyłowywany na żądanie. To tylko "natyści" mogą uznać za jakoś ciekawą ofertę, bo reszta może korzystać z monitorów w emulatorach, które są wygodniejsze.
Takie wywołanie najprostsze jakie mi przychodzi do głowy to jednak coś jak RUN adres (o którym wiemy, że tam się relokował deasembler) z wcześniejszym określeniem adresu pamięci, od którego mamy mieć deasemblację (można byłoby to zrobić w sposób: pod adres+3 znajduje się słowo adresu pamięci do deasemblacji). To jest taki wariant, który jest dla mnie realny do zrobienia (trochę bardziej skomplikowane wywoływanie i obsługa deasemblera - raczej nie umiałbym na tą chwilę).
Teoretycznie można by też wykonać wersję deasemblera przewidzianą do otwierania plików z nagłówkami (atarowskimi i nie tylko) z odczytu z pamięci, do analizy poszczególnych bloków opisanych adresami nagłówków. To wersja raczej alternatywna wobec poprzedniej (deasemblacji z pamięci - adres 1:1).
Jaki jest w ogóle realnie sens deasemblacji podług różnych 'dialektów' dla nielegali? Można słusznie w to wątpić. Można zresztą kwestionować potrzebę deasemblacji opkodów nielegalnych w całości. Natomiast taki był tu zamysł i koncepcja, bo tego jeszcze nie widziałem na Atari (a nadarzyła się okazja coś takiego wykonać). Nie tyle, że dostrzegłem w tym jakiś ukryty 'potencjał' (praktyczny).
Jest jeszcze chyba problem taki, że deasembler nie przewiduje przekierowania z ekranu na urządzenie zewnętrzne wyniku swojej pracy. To można by zrobić (może konfigurować na bieżąco), ale wtedy powstaje pytanie, czy forma wyniku, jaki teraz podaje, jest akceptowalna - tzn. czy z tego da się potem stosunkowo prosto wyłuskać kod zdatny do asemblacji. Chodzi głównie o te wartości licznika PC (program counter), które po prostu będą w takiej sytuacji generalnie przeszkadzać (a niektóre z nich musiałyby zostać przemianowane na "labele" - te obecne w odwołaniach w argumentach rozkazów). To więc dużo zależy od tego, czy taką formę da się potem (zewnętrznie) dostosować do postaci w pełni nadającej się pod asemblację (wtórną), po róznych potencjalnie zmianach. Mnie się wydaje, że taka forma może już być faktycznie wystarczająca i przystosowana do dalszej obróbki (już nie tak bardzo skomplikowanej), ale może dałoby się coś tu zrobić jeszcze (przed "wypluciem" efektu deasemblacji do pliku).
Jeszcze w kwestii wykonania tego deasemblera się cokolwiek wypowiem.
Długość niezbędnego kodu i danych deasemblera przy obecnej jego podstawowej funkcjonalności, jak obecnie (z wyłączniem przełączania) to (w mojej ocenie) około 3,5 strony pamięci, czyli poniżej 1kb. Taka wersja zupełnie wystarczająca (z pewnym przyspieszeniem działania w obrębie przeszukiwań "krótkiej" tablicy mnemoników) to wciąż (o bajty) poniżej 1kb. Pełna wersja, ta zaprezentowana, to niepełnych 6 stron pamięci, gdyż zawiera dodatkową fukcję "wybieraczki" modelu dla interpretacji "nielegali" i objaśnienie tekstowe. (Pozostałe dane to tylko dane służące weryfikacji asemblacji - 6 stron tych danych, kolejnych wartości z zakresu bajta, po 6 powtórzeń. Stąd całość ogólnie więcej niż te 1,5kb zajmuje.) Te dwie dodatkowe funkcje opierają się na eksploatacji nowej funkcji .BI MADSa, jeśli można tak powiedzieć. Tzn. funkcja ta posłużyła mi "na brudno" do znalezienia wartości do jednej krótkiej tablicy, ale wprost nie nadaje się jeszcze do zastosowania w docelowym kodzie źródłowym (bo powoduje błędną asemblację - zgłaszałem już na forum). To bitowe podejście do danych zastosowałem więc do tej krótkiej tablicy (w przełączaniu "dialektów" dla nielegali), jak i do zakodowania tekstu. Jest to ta metoda, którą starałem się opisać w jednym z moich wpisów na forum (ew. zainteresowani skojarzą). Z tą tylko różnicą że wykorzystuje dodatkowo 'technikę' RLE (dla tekstów). Całość tego dadatkowego kodu z danymi to niecałe 2 strony (wynik bez rewelacji, ale chyba się wciąż opłaca).
Sam kod obługi "krótkiej tablicy mnemoników" mógł zostać uproszczony (w stosunku do poprzedniej wersji), gdyż nie korzystałem z pewnych "wyjątków" (zapis mmemoników), na które wcześniej starałem się przygotować taką obługę.
Przyznam się, że jestem z siebie zadowolony, w związku z tym, co udało mi się zrobić (każdy ma swoją miarę).
Wersja, choć nazwana demem, niekoniecznie będzie już rozwijana (o czy już napisałem), w związku z omawianymi wyżej wątpliwościami.
"Autor przedstawia krótki wstęp dotyczący tworzenia "super prostego" disassemblera dla procesora 6502c. Wskazuje, że z uwagi na pomijanie "nielegalnych" opkodów, rozróżnienie pomiędzy "prawdziwym" a "nadmiarowym" kodem nie jest problemem. Następnie autor omawia specyficzne cechy procesora 65816, które utrudniają liniową deasemblację. Pod koniec podkreśla, że wersja dema deasemblacji dla 6502c może nie być rozwijana dalej z powodu wątpliwości co do praktycznej użyteczności różnych "dialektów" deasemblacji opkodów "nielegalnych" oraz braku obsługi przekierowania wyników deasemblacji na zewnętrzne urządzenie. Autor publikuje plik z deasemblerem."
"z powodu wątpliwości co do praktycznej użyteczności różnych "dialektów" deasemblacji opkodów "nielegalnych" oraz braku obsługi przekierowania wyników deasemblacji na zewnętrzne urządzenie
…i tu bym się lekko przyczepił ;) ( w sensie wierności streszczenia )
ale co do konceptu odpowiedzi - super :)
(mógłbym sobie przemyśleć swój język komunikacji, ale cóż…)
Prosty (liniowy) deasembler procesora 65816 (wersja bardziej testowa / demonstracyjna).
Z uwagi na bity statusu procesora M i X, deasembler działa dość dziwnie - tzn. może się zatrzymywać (w potoku dekodowania bajtów) w sytuacji dwuznacznej, tj. interpreteacji rozkazu z adresacją natychmiastową.
Działają wtedy dwa klawisze (i tylko one): strzałka w górę - i dół (ułatwienie na emulatorze; odpowiadające kbcodem klawisze również działają bez wciśniętego ctrl).
Strzłka w dół oznacza, że akceptujemy "wydruk" deasemblera (co do długości argumentu dla rozkazu w trybie natychmiastowym). Analogicznie, strzałka w górę oznacza, że chcemy ponowić "wydruk" (poczynając od tej instrukcji) tylko z odmienną interpretacją długości argumentu dla rozkazu - jest to taki symboliczny przełącznik bitu M lub X (w zależności od rodzaju instrukcji).
Deasembler działa tak, że każda zmiana przełącznika bitu M lub X (strzałka w górę) powoduje iż powraca on do stanu 'podwyższonej czujności' - zatrzyma się na pewno przy kolejnej okazji dla bitu M i - osobno - X). Gdy raz już ustalimy sobie, że M (lub X) jest ustawiony na 8 bit lub 16, to deasembler może pominąć zatrzymanie na odpowiedniej istrukcji (domyślnie przyjąć określony stan M lub X). Dzieje się tak jednak tylko wtedy, gdy nie wciśniemy żadnego innego klawisza po strzałce w dół (kbcode, $2fc), którą akceptowaliśmy wybór stanu M lub X (przy czym niekoniecznie tego samego bitu statusu, którego dotyczy istrukcja pomijana tym razem przy zatrzymaniu). Jest to tryb "z automatu" (roztrzygania samodzielnie deasemblera stanu bitu statusu, bez potwierdzania). Aby temu 'zapobiec' wystarczy wcisnąć dowolny klawisz przed istrukcją, na której chcemy aby się deasembler zatrzymał.
Pewnym niuansem (niedoskonałością) jest to, że w stanie "podwyższonej czujności" deasemblera strzałka w dół działa "na dwa razy" (za pierwszym wciśnięciem klawisza deasemblacja nie ruszy). Tak po prostu wyszło.
Jeszcze inną niedoskonałością działania deasemblera jest to, że po powtórzeniu od danego miejsca/ adresu (od spodu ekranu, po linii przerwy) może się zdarzyć, że deasembler zatrzyma się przy interpretacji adresów, do których ma jeszcze wrócić po "nawrocie". Powinno to działać tak, by te (teraz już) 'śmiecie' ignorować, a on tego nie robi. Właściwym podejściem na taką okoliczność jest ignorowanie 'pytań' (potwierdzeń) deasemblera - strzałka w dół. Bardzo istotnym szczegółem jest tutaj to, że 'odpowiadając na pytanie', odpowiadać możemy błędnie, ponieważ stan bitu jest inny niż wynika to z 'wydruku' istrukcji, o który deasembler 'pyta'. Chodzi bowiem o to, że deasembler działa zgodnie z tym, co widać od spodu ekranu (w sensie ustawień bitów M i X), a nie od góry. A pyta dopiero gdy z dołu ekranu przesunie się na sam szczyt 'problematyczna' instrukcja z adresowaniem natychmiastowym.
W tym miejscu zwrócę uwagę na sposób w jaki program 'wewnętrznie' rozpoznaje to, kiedy się zatrzymać. Tryby natychmiastowe zależne od znaczników (można wyróżnić jeszcze tryb natychmiastowy niezależny od znaczników dla niektórych istrukcji) mają umieszczone ('sztucznie') na końcu " ;?" przy dekodingu. To pozwala na rozpoznanie tych znaków (konkretnie - 2 ostatnich) w pierwszej linii ekranu - a wtedy zaczynają działać te mechanizmy 'potwierdzania' właściwej interpretacji rozkazów zależnych od stanu znaczników. Jest to działanie w miarę skuteczne ale nie idealne w przedkładanej implementacji. Chodzi bowiem o ustawienie marginesu dla wyświetlania. On jest ustawiony 'fabrycznie' na 2, ale łatwo to przestawić. Deasembler na sztywno przyjmuje taki też właśnie rozmiar marginesu, dlatego zacznie działać (bardzo) źle, gdyby się to zmieniło (wersja "testowa" nie wymagała ode mnie większej przezorności).
Nie wiem, czy to wszystko co ważne, czy coś istotnego pominąłem. Jeśli znalazły by się jakieś pytania bądź sugestie, odniosę się jeszcze.
Wprowadziłem zmiany, które umożliwiają nagrywanie pliku wynikowego z deasemblacji kodu. Dodałem też kilka dodatkowych możliwości przesuwania się z adresem deasemblacji oraz możliwość generowania linii data.
{ Ściśle biorąc, zrobione to zostało jeszcze w czerwcu, do 28, tego roku, jednak przed samą publikacją, wczoraj, dokonałem kilku pomniejszych, dodatkowych a koniecznych jeszcze zmian działania i obsługi. Między innymi zapis do $2fe, z podpowiedzi, którą uzyskałem od Mono, dla celów wyświetlania linii data. }
Zanim przejdę do szczegółów (o klawiszologii zwłaszcza) kilka słów wyjaśnień, dlaczego to finalnie tak wyszło - zastrzeżenia będą uzasadnione (jeśli sami sprawdzicie).
Zmiany nałożyłem na poprzednią wersję - nie miałem dużej determinacji aby zbyt głęboko ingerować w istniejące rozwiązanie, poza tym całość nie bardzo miałem opracowaną, jak powinna docelowo wyglądać od strony optymalnego działania i obsługi. Jest to podejście typowe dla mnie, minimalistyczne - w sensie nakładu może także jakości (trudno do końca powiedzieć, czy mógłbym w tym wypadku lepiej) jak i ilości pracy z tym kodem, jaki już wcześniej był - niewątpliwym kosztem prezentacji i wygody użytkowania.
Ogólnie kod deasemblera jest z tą wersją 'przyspawany' do "ostatnich bitów ROMu AppleII GS" (jakąś specyficzną nazwę dla ATR musiałem sobie obrać), w przeliczeniu na bajty jest to $4000. (Taki cel do zobrazowania działania deasemblera akurat sobie dobrałem.)
{ ROM Apple2GS jest duży i znajduje się w kilku bankach pamięci, coś jak /$FC-/$FF }
Te dane jednakowoż można podmienić za pomocą podstawowych "technik hakerskich" z plikiem (program sam z siebie tego nie oferuje), a przy zmianie na inny adres pamiętać tylko o tym, że umieszczenie w pamięci musi odzwierciedlać to, jakie zajmuje kod oryginalnie, przy czym jednak najstarszy bit adresu docelowego (umieszczenia) ma być zgaszony (deasembler sobie z tym poradzi przy przeliczeniach adresów na właściwe - z ustawionym tym bitem). Ponieważ sam program deasemblera jest skompilowany pod adres $3800, to trzeba uniknąć umieszczenia czegoś (do deasemblacji) w tym obszarze. Gdyby do takiej zmiany adresu z oryginalnego ($c000) miało faktycznie dojść to oprócz tego trzeba wyszukać i odpowiednio zmienić (pseudo) rozkaz: mwa #adres ip (w aspekcie dostosowania "adres")
To teraz o działaniu i obsłudze.
Program wcześniej już oferował możliwość powrotu do poprzednio zdeasemblowanego adresu, jeśli był to rozkaz z argumentem natychmiastowym, na który miał wpływ któryś ze specjalnych znaczników procesora definiujących rozmiar na jakim procesor ma zamiar aktualnie działać (X i M). Działa to wszystko jak poprzednio.
Teraz jednak doszła możliwość cofania się w deasemblacji o małą liczbę bajtów. Żeby tę określić trzeba wcisnąć cyfrę. Dodatkowo cyfra z shift robi to samo przełączając się dodatkowo w deasemblacji - kod/ linie data. Ale - tu się objawia cały szkopuł w obsłudze, jaką udało mi się tylko przygotować - działa to "w drugim trybie" obsługi programu. O co chodzi?
Otóż pierwotnie nie przewidziano innych klawiszy do obsługi programu niż klawisze góra/dół i weszło to dość istotnie w logikę i strukturę kodu. Nie miałem ochoty tego już zmieniać. Wyszło z tego, że dostęp do tych dodakowych funkcji działania programu z klawiatury można uzyskać tylko w osobnym trybie, do którego dojść można przez spację.
Więc tak, naciśnięcie spacji daje nam dostęp do "drugiego trybu obsługi", przy czym przejście do niego nie jest zawsze skuteczne (!). Otóż tryb obsługi pierwszego trybu - gdy się nam włączy - to nie możemy się z niego przedostać do tego drugiego trybu - przy wspomnianej pomocy spacji - bo ten jest dominującym nad tym drugim. Dopiero wyjście z pierwszego trybu obsługi (który dotyczy tylko klawiszy strzałka góra/dół) 'uwalnia' drugi (w zasadzie to dostęp do niego) typ obsługi z klawiwiatury. W pierwszym trybie obługi jesteśmy, gdy deasemblacja się zatrzyma a nie operowaliśmy przed momentem spacją (by się zatrzymała).
Drugi tryb obsługi (wchodzimy w niego spacją) obejmuje następujące klawisze: 0-9 [+ shift], spacja - generowanie nast. linii.
Wygląda jednak na to (tak sobie to układam na bieżąco w głowie), że jest jeszcze obługa "poza trybami", czyli gdy leci potok deasemblowanych linii (co można powstrzymywać tylko znaną kombinacją edytora ctr+1). Wtedy działają następujące klawisze:
S - otwiera plik (zawsze o tej samej nazwie - "DATA.TXT" ) do zapisu deasemblowanych linii, W - wstrzymuje zapis deasemblowanych linii (powrót odbywa się poprzez ponowne wciśnięcie S), C - zamknięcie i ostecznie skuteczny zapis generowanego pliku tekstowego na dysk,
spacja - wejście w drugi tryb obsługi (wejście w pierwszy odbywa się z automatu).
Ogólnie chyba to cała klawiszologia, więc niespecjalnie tego dużo, natomiast jest jeszcze pewna technika radzenia sobie z niedoskonałościami użytkowania tego deasemblera o której muszę wspomnieć.
Jeśli zatrzyma nas deasemblacja na linii adresu, gdzie musimy zadecydować czy nam odpowiada rozmiar znacznika procesora (czy M czy X), a działaliśmy sobie właśnie w trybie drugim - generowaliśmy pojedyncze linie przez naciskanie spacji - to aby do niego wrócić na kolejną linię naciskamy kolejno: ctr+1 , strzałka w dół (czasem wymagane jest dwa razy) - jeśli teraz zaniknie nam kursor znaczy że jesteśmy na właściwej drodze - naciskamy spację (właśnie teraz przechodzimy ponownie w drugi tryb) i dopiero na koniec - ctr+1.
Z innych uwag: Zmiana koloru ramki jest pomocniczą sygnalizacją, że "nagrywa się" plik (skutecznie nacisnęliśmy klawisz S).
Wychodzimy z deasemblera tylko przez RESET.
Plik wynikowy (tekstowy) możemy oczywiście podglądnąć przez dos rozkazem: TYP DATA.TXT
Na dysku jest już taki (ale się zmaże przy kolejnym użyciu programu, gdy tylko wciśniemy S).