Asembler x86/Narzędzia: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Mythov (dyskusja | edycje)
Mythov (dyskusja | edycje)
Nie podano opisu zmian
Linia 1:
== Narzędzia ==
Aby móc cokolwiek zrobić z naszą wiedzą nt. języka asemblera będziemy potrzebować kilku narzędzi, które nam to umożliwią.
 
=== Asembler i linker ===
Program tłumaczący kod z języku asemblera na pliki z wykonywalnym kodem nazywamy '''asemblerem'''. Obecnie jest 3 liderów w tej dziedzinie:
* '''Microsoft Macro Assembler ([http://www.masm32.com/ MASM])''' firmy Microsoft
Linia 12 ⟶ 11:
Który więc asembler wybrać? Jeśli interesuje Cię programowanie jedynie dla systemu Windows, dobrym wyborem jest MASM, jeśli chcesz mieć możliwość programowania pod niemal każdą platformę, wybierz asembler NASM (64-bitowa wersja jest obecnie w fazie testów). TASM także jest dobrym asemblerem, ale jest płatny, przez co nie polecam korzystania z niego, gdyż wydajność tworzonego kodu jest praktycznie identyczna w przypadku wszystkich 3 asemblerów (nie mają tu żadnego znaczenia programy znane z języków wysokiego poziomu jak optymalizatory).
 
=== Debuger ===
Debugery mają za zadanie pomóc programiście w odnalezieniu błędów w programie. Większość debugerów pozwala dodatkowo na wstawianie w kodzie tzw. punktów wstrzymania, gdzie to debugger wstrzymuje działanie programu do czasu dopóki nie dostanie odpowiedniej komendy od programisty. Debugery dodatkowo pozwalają na odczytanie wartości rejestrów procesora, czy wartości poszczególnych zmiennych. Obecnie najpopularniejszy debuger to '''[http://www.ollydbg.de/ OllyDbg]''', jednak do prostych zastosowań wystarczą również proste debugery dostarczane wraz z asemblerami. Wielu programistów uważa je za zupełnie opcjonalne narzędzie jednak większość starszych doświadczonych programistów jest ich fanatycznymi zwolennikami i tępi tą pierwszą grupę niemal jako heretyków. Ze względu na należne uznanie dla tej drugiej grupy, należy nie traktować tego narzędzia zbyt pobłażliwie.
 
=== Disasembler ===
Disasembler to program tłumaczący kod maszynowy na język asemblera. Z reguły proste disasemblery dostarczane są w pakiecie wraz z asemblerami (bardzo dobry disasembler dostarczany jest wraz z asemblerem NASM). Jako, że jest to proces dość złożony istnieje wiele komercyjnych disasemblerów, z których wg. ogólnej opinii najlepszy to '''[http://www.datarescue.com/idabase/index.htm IDA Pro Disassembler]'''. Dzięki tej grupie programów możemy wejrzeć w kod cudzych programów, jednak zdeasemblowany kod znacznie różni się od oryginału, przez co jego interpretacja może być dość trudna. Proces interpretacji tegoż kodu nazywamy mianem '''inżynierii wstecznej''' (ang. ''reverse engineering''). Disasemblery są z reguły używane w połączeniu z debugerami (IDA Pro Disassembler jest jednocześnie disasemblerem jak i debugerem).
 
=== IDE ===
Zintegrowane środowisko programistyczne (ang. ''integrated development environment'' ('''IDE''')) łączy w sobie wszystkie powyższe narzędzia dodając jeszcze edytor tekstowy oraz z reguły jakieś inne dodatkowe narzędzia. Większość asemblerów pracuje w trybie konsolowym, więc ręczne korzystanie z nich jest skrajnie niewygodne. IDE robią większość zbędnej pracy za nas. Nie ma wśród nich żadnego faworyta; oto kilka z nich:
* [http://www.programmersheaven.com/download/35506/download.aspx Chrome IDE] ([http://upload.wikimedia.org/wikibooks/pl/f/f5/Chrome_IDE_screenshot.png zrzut ekranu])
Linia 27 ⟶ 26:
Istnieje również możliwość pisania programów asemblerowych w bardzo popularnym, wielojęzykowym środowisku Eclipse z pluginem ''ASM Plugin''.
 
{{TODO|Wszystkie działy w tym rozdziale rozpaczliwie potrzebują rozwinięcia.}}
== Zaczynamy! ==
Zacznijmy od dawki kodu, żeby mieć pojęcie jak to w ogóle wygląda. Będzie to tradycyjny już program ''Hello World''. Poniżej znajdziesz kilka jego wersji, dla różnych asemblerów. Przeczytaj tylko rozdział dotyczący asemblera, który wybrałeś; resztę możesz pominąć.
=== Hello world! (MASM) ===
<pre>
.model small
.386
 
.data
tekst db "Hello World!",0Ah,0Dh,"$"
.stack 100h
.code
.startup
mov dx, offset tekst
mov ah, 09h
int 21h
.exit
end
</pre>
Program ten po uruchomieniu w konsoli wyświetla na ekranie tekst "Hello World!". Postaram się zrozumiale wyjaśnić o co w nim chodzi.
.model small
Dyrektywa .model pozwala zdefiniować z jakiego modelu pamięci ma korzystać nasz program. Model small oznacza segmentowy model pamięci z jednym segmentem kodu i jednym segmentem danych.
.386
Ta dyrektywa określa z jakiego zbioru instrukcji będzie korzystać nasz program. W tym przypadku określamy oczywiście, że chodzi nam o zbiór instrukcji procesorów 80386.
.data
Określa, że od tego miejsca w dół definiowany jest segment z danymi.
tekst byte "Hello World!",0Ah,0Dh,"$"
Wrzuca do segmentu z danymi ciąg znaków "Hello World!" zakończony znakiem nowej linii (0A0D) oraz znakiem $ określającym, iż jest to koniec naszego ciągu (dla migrantów z C/C++ - jest to odpowiednik znaku '\0'). ''tekst'' jest to nazwa dla naszego ciągu, zaś słowo ''db'' oznacza, że ma być on ciągiem bajtów.
.stack 100h
Dyrektywa tworzy segment stosu o wielkości 100h. Umieszczenie literki ''h'' za liczbą oznacza, że '''jest zapisana szesnastkowo''' (100h = 256 dziesiętnie).
.code
Początek definiowania segmentu z kodem.
.startup
Jest to makroinstrukcja, która w procesie kompilacji zastępowana jest w tym miejscu kodem wykonującym standardowe początkowe czynności (nadaje odpowiednie wartości rejestrom segmentowym, które są konieczne do poprawnego działania naszego programu).
mov dx, offset tekst
Jeśli zdeasemblujesz dowolny program, prawdopodobnie ujrzysz instrukcje podobne do tej umieszczone jedna za drugą. Każdej instrukcji asemblera używa się wg. schematu:
instrukcja argumentA, argumentB, argumentC...
Jeśli chodzi o akurat tą instrukcję - mov kopiuje zawartość B do A. A w tym przypadku to rejestr procesora dx, zaś B (''offset tekst'') to adres naszego ciągu znaków, wydobyty dzięki słowu ''offset'' (użycie samej nazwy zmiennej nie odnosiłoby się do jej adresu a o niego nam w tym przypadku chodzi).
mov ah, 09h
Kopiuje wartość 09h do naszego rejestru ah.
int 21h
Instrukcja int wywołuje podprogram obsługi przerwania (dokładnie co to są przerwania opisane jest w rozdziale [[Asembler X86/Przerwania|Instrukcje]]) o numerze podanym jako A (w tym przypadku chodzi o numer 21h). Podprogram obsługujący przerwanie o tym numerze wywołuje określoną funkcję o numerze przekazanym w rejestrze ah (przerwanie 21h, funkcja przerwania numer 09h).
.exit
Jest to makroinstrukcja wywołująca przerwanie 21h, funkcję 4Ch (dla ćwiczenia spróbuj zastąpić ją właściwym kodem przy użyciu instrukcji mov oraz int), która nie przyjmuje żadnych argumentów (tzn. makroinstrukcja ta nie przypisuje żadnych wartości rejestrom innym niż ah określającemu numer funkcji). Funkcja ta kończy działanie programu.
end
Określa że w tym miejscu kończy się kod. Asembler po napotkaniu tego słowa kończy proces asemblacji, niezależnie od tego, czy znajduje się jakiś kod jeszcze dalej.
 
=== Hello World! (NASM) ===
<pre>
segment dane
tekst db "Hello World!",0Ah,0Dh,"$"
 
segment stosik stack
resb 64
 
segment kod
..start:
mov ax, dane
mov ds, ax
mov ax, stosik
mov ss, ax
mov dx, tekst
mov ah, 9
int 0x21
mov ax, 0x4C00
int 0x21
end
</pre>
Program ten po uruchomieniu w konsoli wyświetla na ekranie tekst "Hello World!". Postaram się zrozumiale wyjaśnić o co w nim chodzi.
segment dane
Oznacza, że od tego miejsca w dół definiowany jest nowy segment o nazwie "dane".
 
tekst db "Hello World!",0Ah,0Dh,"$"
Ta linijka dodaje zmienną do obecnie definiowanego segmentu (w tym przypadku chodzi o segment "dane"). ''tekst'' to nazwa naszej zmiennej, ''db'' to typ naszej zmiennej (db - 1 bajt) zaś wszystko co znajduje się dalej w tej linijce to wartość początkowa dla naszej zmiennej. Jak widać jest to ciąg znaków zakończony znakami 0A0D (określające przejście do nowej linii) oraz znakiem $ oznaczającym koniec naszego ciągu (dla migrantów z C/C++ - jest to odpowiednik znaku '\0'). Gdyby zabrakło tego znaku, instrukcje operujące na naszej zmiennej, nie mogłyby określić gdzie jest jej koniec, więc wyjechałyby poza przydzielony jej obszar pamięci dopóki nie znalazłyby znaku $.
 
segment stosik stack
Tworzy segment stosu o nazwie ''stosik''...
 
resb 64
... i rozmiarze 64 bajtów. Dyrektywa ''resb'' jest podobna do poznanej przed chwilą dyrektywy ''db'', tyle że tworzy konkretną ilość zmiennych bez przydzielania im wartości początkowych.
 
segment kod
Analogicznie do pierwszej linijki w naszym programie, tworzy segment o nazwie ''kod''. Poniżej znajduje się jego definicja.
 
..start:
Nazywa obecną pozycję słówkiem "start". Poprzedzenie ''start:'' dwoma kropkami oznacza, że chcemy aby to miejsce było początkiem naszego programu.
 
mov ax, dane
mov ds, ax
mov ax, stosik
mov ss, ax
Instrukcja [[../Instrukcje/Transferowe#mov|mov]] kopiuje wartość drugiego parametru do pierwszego. Jako, że po starcie programu, rejestry segmentowe są niezainicjowane, musimy ręcznie przydzielić im adresy odpowiednich segmentów. W pierwszej linijce kopiujemy adres segmentu ''dane'' do rejestru ax. Następnie z rejestru ax kopiujemy go do rejestru segmentowego ds (instrukcja mov nie pozwala na bezpośrednie przydzielanie wartości rejestrom segmentowym, dlatego musieliśmy użyć rejestru ax jako pośrednika). W następnych 2 linijkach powtarzamy operację, tyle że kopiujemy adres segmentu stosik (który jest stosem naszego programu) do rejestru ss.
 
mov dx, tekst
mov ah, 9
int 0x21
Ten fragment kodu zacznę tłumaczyć od końca. Instrukcja [[../Instrukcje/Różne#int|int]] wywołuje ''podprogram obsługi przerwania'' o podanym numerze (poprzedzenie go przedrostkiem 0x oznacza liczbę w zapisie szesnastkowym). Podprogram ów wywołuje odpowiednią funkcję o numerze podanym w rejestrze ah (wcześniej nadaliśmy temu rejestrowi wartość 9, więc instrukcja ''int 0x21'' wywołała funkcję numer 9 przerwania numer 21 w zapisie szesnastkowym). Wywołana w tym przypadku funkcja wyświetla w konsoli ciąg znaków, którego adres znajduje w rejestrze dx (przydzieliliśmy temu rejestrowi adres naszej zmiennej ''tekst''). W efekcie na ekranie pojawi się, więc napis ''Hello World!''
 
mov ax, 0x4C00
int 0x21
Wywołuje funkcję przerwania 21 o numerze 0x4C00. Odpowiada ona za zakończenie działania programu i oddanie sterowania do systemu.
 
end
Dyrektywa określająca koniec naszego kodu. Nie ma ona znaczenia i jest ignorowana, lecz tradycyjnie powinna stać na końcu pliku z kodem.
 
=== Goodbye world... ===
Powyższy kod początkowo może trochę odstraszać, lecz jeśli masz pierwszy raz styczność z asemblerem prawdopodobnie spodziewałeś się czegoś znacznie gorszego. O asemblerze krąży wiele nieprawdziwych informacji, iż jest to trudny do nauki język. Tak naprawdę nie jest trudny - jest nieczytelny. Po kilku godzinach od napisania nie rozumie się swojego własnego kodu, dlatego nawet jeśli nie przejmowałeś się komentarzami programując w innych językach programowania (bo można w nich sobie radzić bez komentarzy), staraj się pokonać nieco swoje przyzwyczajenia i dopisywać w kodzie komentarze w kluczowych miejscach. Niestety luksus ten dotyczy nas jedynie przy pisaniu własnych programów. Jeśli uczysz się asemblera głównie pod kątem zrozumienia zdeasemblowanych plików, tam jest to o tyle utrudnione, że rzuceni jesteśmy w tysiące linii kodu, o którego strukturze nie mamy zielonego pojęcia, dodatkowo nie mamy do pomocy żadnych komentarzy a język asemblera jest bardzo nieczytelny. Zaczynaj od prostych programów, nie rzucaj się na głęboką wodę. Następnie przechodź do coraz większych starając się zmodyfikować w nich konkretną rzecz (np. pozamieniać znaczenie przycisków itp.) - przede wszystkim nie poddawaj się! Dzięki znajomości asemblera Twoje możliwości będą znacznie szersze.