Asembler x86 w pigułce
Dodane przez DavidF dnia 08/12/2012 12:19
Słowem wstępu
Witaj! Odwiedzając ten temat pewnie jesteś zainteresowany programowaniem w Asemblerze. Dlaczego namawiam do poznania Asemblera? Ponieważ programy w tym języku mają bardzo mały rozmiar i są szybsze od programów pisanych w językach wysokiego poziomu (chociaż niektóre nowoczesne kompilatory mają doskonałą optymalizację).

Podstawowe pojęcia
- Asembler - niskopoziomowy język programowania.

- asembler - program dokonujący procesu asemblacji po którym otrzymujemy plik *.obj.

- Konsolidator/Linker - program, który na podstawie pliku *.obj dokonuje konsolidacji w wyniku czego otrzymujemy plik wykonywalny.

- Odpluskwiasz/Debugger - program do szukania błędów w programach. Pokazuje listing programu w języku Asembler i pozwala wykonywać program krok po kroku oraz śledzić zmiany w rejestrach/pamięci.

- Opkod - to symboliczna nazwa, której odpowiada kod bajtowy. Pisząc w Asemblerze, używamy właśnie opkodów (nazywanych też czasem mnemonikami). Później opkody są tłumaczone na kod bajtowy.

Systemy liczbowe
Do programowania w Asemblerze na pewno przyda się wiedza o dwóch dodatkowych systemach liczbowych (prócz dziesiętnego): dwójkowym (binarnym) i szesnastkowym (heksadecymalnym). Poniżej zamieszczam skrótowy opis:
- System binarny - podstawą systemu są dwie cyfry: 0 i 1. W systemie binarnym działa komputer i jest zapisywany kod programu. Przyjęło się, że liczby są zapisywane z literą b na końcu (np. 10001011b).
- System heksadecymalny - podstawą są tu cyfry od 0 do 9 i litery od A do F (w sumie szesnaście znaków). Dla człowieka jest on o wiele wygodniejszy od systemu binarnego. Liczby w tym systemie przyjęło się zapisywać z literą h na końcu (np. 23E0B3h).

To wszystko na temat systemów liczbowych. Warto jednak doczytać informacje na ich temat, a także nauczyć się je ręcznie przeliczać. Jednak w trakcie programowania zdecydowanie szybciej i wygodniej jest przeliczać te systemy za pomocą kalkulatora systemu Windows.

Jednostki informacji pamięci komputerowej
Do programowania w języku Asembler przyda się znajomość jednostek informacji pamięci komputerowej, które są związane również z rozmiarem rejestrów procesora czy zmiennych w pamięci. Warto je zatem znać i umieć je przeliczać.
- Bit - najmniejsza ilość informacji. Przyjmuje wartość 0 lub 1.
- Półbajt (ang. nibble) - są to 4 bity. Warto znać to określenie (nibble), bo można je spotkać w artykułach w języku angielskim.
- Bajt - jest to 8 bitów. Jego maksymalna wartość to 255d.
- Słowo (ang. word) - 2 bajty, czyli 16 bitów. Maksymalna wartość to 0FFFFh (lub 65535d).
- Podwójne słowo (ang. double word) - dwa słowa, czyli 4 bajty (32 bity). Maksymalna wartość to 0FFFFFFFFh (lub 4294967295d)
- Poczwórne słowo (ang. quad word) - cztery słowa, czyli 8 bajtów (64 bity).
- Kilobajt - 1024 bajty.
- Megabajt - 1024 kilobajty, 1 048 576 bajtów.

Rejestry procesora
Rejestry to komórki pamięci wewnątrz procesora służące mu do wykonywania różnych operacji. Warto wspomnieć, że operacje na rejestrach są o wiele szybsze niż na zmiennych w pamięci. Istnieją cztery rejestry ogólnego przeznaczenia (rysunek 1.3): EAX, EBX, ECX i EDX. Mają one po 32 bity (4 bajty). Każdy z nich dzieli się na dwie części po 16 bitów. Są to: "starsze słowo" (HIGH-WORD) i "młodsze słowo" (LOW-WORD). Przyjrzyjmy się temu na przykładzie rejestru EAX: młodsze słowo to rejestr AX, który z kolei dzieli się na dwa rejestry 8-bitowe: AH i AL. Warto też wspomnieć, że możemy używać tych rejestrów do czego chcemy, ale każdy z nich ma swoje specjalne przeznaczenie. Bity numerujemy od zera, czyli najmłodszy jest bit zerowy. To jednak nie wszystkie rejestry. Są jeszcze dwa rejestry indeksowe: EDI (rejestr przeznaczenia) i ESI (rejestr źródła), które dzielą się na DI i SI. Używa się ich do operacji na łańcuchach (np. na tekście). ESI przechowuje źródło (ang. source), a EDI - miejsce docelowe (ang. destination). Z kolei EBP (wskaźnik bazowy) i ESP (wskaźnik stosu) to rejestry wskaźnikowe; pierwszy z nich służy do adresowania, a drugi przechowuje wskaźnik wierzchołka stosu. Jeżeli modyfikujemy zawartość rejestrów ESI, EDI, EBP lub ESP, musimy je przywrócić, zanim wrócimy do systemu Windows.

Pozostałe rejestry to:
Rejestry segmentowe (16-bitowe): CS (ang. Code Segment), DS (ang. Data Segment), ES (ang. Extra Segment), SS (ang. Stack Segment) FS, GS.
Rejestr flag - EFLAGS (flagi, inaczej znaczniki, będą nas interesować, gdy będziemy debugować swoje programy), najważniejsze flagi to:
[LIST]
- CF (ang. Carry Flag) - znacznik przeniesienia. Flaga zostaje ustawiona, gdy podczas działania nastąpiło przeniesienie z bitu najbardziej znaczącego poza dostępny zakres zapisu. W przeciwnym wypadku znacznik ma wartość zero.
- ZF (ang. Zero Flag) - znacznik zerowy. Przyjmuje wartość 1, gdy wynikiem ostatnio wykonanego działania było zero.
- OF (ang. Overflow Flag) - znacznik nadmiaru/przepełnienia. Jest ustawiany, gdy w danym działaniu nastąpiło przeniesienie na najstarszy bit lub pożyczenie z tego bitu.

Stos
Jest to część pamięci, w której można przechowywać różne dane, aby później móc z nich skorzystać. Jak sama nazwa wskazuje, wartości są układane jak na stosie. Ostatnia odłożona wartość będzie zdjęta jako pierwsza. Możesz to sobie wyobrazić jako pudełko, do którego kładziesz kartki papieru. Ostatnia kartka jest wyciągana jako pierwsza. Instrukcja PUSH odkłada wartość na stos, natomiast POP zdejmuje i zapisuje do rejestru czy pamięci.

Budowa programu typu WIN/EXE
Kod źródłowy
.386 ; zestaw instrukcji
.model   flat, stdcall ; model pamięci i sposób wywoływania funkcji
option   casemap:none ; rozróżnianie małych i dużych liter
 
; pliki nagłówkowe
include      windows.inc
include      kernel32.inc
include      user32.inc
 
; biblioteki
includelib   user32.lib
includelib   kernel32.lib
 
.data
   ; dane zainicjowane
.data?
   ; dane niezainicjowane
.code
start: ;początek etykiety głównej
   ; instrukcje
end start ; koniec etykiety głównej






Program Witaj świecie! (ang. Hello World!)
Poniżej przedstawiam kod programu wyświetlającego okno informacyjne poprzez wywołanie funkcji MessageBox.
Kod źródłowy
.386
.model   flat, stdcall
option   casemap:none
 
include      windows.inc
include      kernel32.inc
include      user32.inc
 
includelib   user32.lib
includelib   kernel32.lib
 
.data
   MsgCaption db "Informacja",0
   MsgBoxText db "Witaj świecie!",0
 
.data?
 
.code
start:
   invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK
   invoke ExitProcess,NULL
end start




W powyższym kodzie najpierw mamy określony zestaw instrukcji (.386), następnie model pamięci i sposób wywoływania funkcji, później dyrektywę rozkazującą asemblerowi odróżniać małe i duże litery. Dalej mamy dołączone odpowiednie pliki nagłówkowe i biblioteki. Niżej w sekcji danych mamy zadeklarowane dwa ciągi znaków zakończone zerem (w DOS znakiem kończącym był znak $, tutaj jest to zero). W sekcji kodu mamy wywołanie funkcji MessageBox wraz z podaną listą argumentów, która wyświetla okno informacyjne (funkcja pochodzi z biblioteki user32.dll), niżej jest wywołanie funkcji ExitProcess, która kończy pracę programu (funkcja pochodzi z biblioteki kernel32.dll). Zapewne rodzi się w Twojej głowie pytanie skąd wziąć opisy tych wszystkich funkcji oraz informacje z jakich bibliotek pochodzą. Dokładną dokumentację można znaleźć na stronach MSDN oraz w pliku WIN32.HLP (Win32 Programmer's Reference), który można znaleźć w sieci.

Podstawowe instrukcje
Instrukcja kopiowania (mov)
Zacznijmy od najczęściej spotykanej instrukcji - MOV.
Składnia:
MOV cel, źródło

Chyba najczęściej spotykana instrukcja. Kopiuje wartość z operandu źródłowego do operandu celu. Z czego pierwsza wartość pozostaje niezmieniona.

ADD (Dodawanie)
Składnia:
ADD operand1, operand2

Instrukcja ADD dodaje wartość do rejestru lub komórki pamięci.

SUB (odejmowanie)
Składnia:
SUB operand1, operand2

Odejmuje wartość drugiego operandu od pierwszego i wynik zapisuje w pierwszym.

MUL (mnożenie)
Składnia:
MUL operand1

Mnoży rejestr EAX (lub AX, lub AL) przez podaną wartość. Mnoży liczby bez znaku. Wynik znajduje się w rejestrze EAX/AX/AL.

DIV (dzielenie)
Składnia:
DIV dzielnik

Instrukcja ta dzieli wartość w parze rejestrów EDX:EAX przez wartość podaną jako dzielnik. Dzielona jest zawsze wartość w EAX, wynik też będzie w EAX. Reszta z dzielenia natomiast w EDX.

INC (inkrementacja)
Składnia:
INC rejestr lub zmienna

Zwiększa wartość rejestru lub zmiennej o 1.

DEC (dekrementacja)
Składnia:
DEC rejestr lub zmienna

Działa odwrotnie do instrukcji INC. Zmniejsza podaną wartość o 1 (zmienna=zmienna-1).

Zakończenie
To już koniec artykułu. Dziękuję.