Asembler x86/Pierwszy program/NASM
Hello World!
[edytuj]Zacznijmy od dawki kodu, aby w ogóle mieć pojęcie, jak wygląda kod w języku Asembler. Będzie to tradycyjny już program Hello World, który można napotkać w niemal każdym podręczniku do nauki programowania w dowolnym języku (za zadanie ma po prostu wyświetlenie napisu Hello World!). Uwaga: jak już wcześniej było wspomniane, konkretny kod zapisany w Asemblerze wykonuje się na konkretnej maszynie działającej pod kontrolą danego systemu operacyjnego.
DOS/Windows
[edytuj]segment .data
tekst db "Hello World!",0Dh,0Ah,"$"
segment stosik stack
resb 64
segment .text
mov ax, .data
mov ds, ax
mov ax, stosik
mov ss, ax
mov dx, tekst
mov ah, 9
int 21h
mov ax, 4C00h
int 21h
Program ten po uruchomieniu w konsoli wyświetla na ekranie tekst "Hello World!". Postaram się zrozumiale wyjaśnić, o co w nim chodzi.
segment .data
Oznacza, że od tego miejsca w dół definiowany jest nowy segment o nazwie ".data".
tekst db "Hello World!",0Dh,0Ah,"$"
Ta linijka dodaje zmienną do obecnie definiowanego segmentu (w tym przypadku chodzi o segment ".data"). 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 0D0A (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 (stack) o nazwie stosik i...
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 .text
Analogicznie do pierwszej linijki w naszym programie, tworzy segment o nazwie .text. Poniżej znajduje się jego definicja.
mov ax, .data
mov ds, ax
mov ax, stosik
mov ss, ax
Instrukcja 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 .data 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 21h
Ten fragment kodu zacznę tłumaczyć od końca. Instrukcja int wywołuje podprogram obsługi przerwania o podanym numerze (zakończenie go przyrostkiem h 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 21h 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!.
Uwaga: należy zaznaczyć, że przerwanie 21h oraz opisana funkcja obsługiwane są przez MS-DOS, przez co kod nie jest przenośny na inne platformy niż Windows.
mov ax, 4C00h
int 21h
Wywołuje funkcję przerwania 21 o numerze 4C00h. Odpowiada ona za zakończenie działania programu i oddanie sterowania do systemu.
Linux
[edytuj]Poniżej znajduje się źródło programu działającego pod systemem operacyjnym Linux.
;; hello.asm
;; Wypisuje określony ciąg 14 znaków
;;
segment .data
msg db "Hello World!", 0Ah ; umieszcza w segmencie danych ciąg znaków zakończony znakiem końca linii
segment .text
global _start
_start:
mov eax, 4
mov ebx, 1
mov ecx, msg ; adres pierwszego znaku do wyświetlenia
mov edx, 14 ; liczba znaków do wyświetlenia
int 80h ; wywołanie funkcji systemowej wyświetlającej ciąg znaków o danej długości
; wyjscie z programu
mov eax, 1
xor ebx, ebx
int 0x80
; KONIEC PROGRAMU
Aby zainstalować kompilator NASM
w systemach z rodziny RedHat/Fedora
wystarczy jako administrator systemu
wydać polecenie yum install nasm.
Aby skompilować program należy wcześniej upewnić się, ża posiadamy zainstalowany kompilator NASM. Następnie należy wydać polecenie:
nasm -f elf hello.asm
Otrzymamy wówczas plik hello.o, który należy zlinkować do postaci wykonywalnej poleceniem:
ld hello.o -o hello
Aby uruchomić tak przygotowany program należy wprowadzić:
./hello
Goodbye world...
[edytuj]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 zdeasemberowanych 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 Asembler 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ę modyfikować 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.