551. £
Poznaj Asemblera i dołącz
do elity programistów
D ow iedz się,
jak jest zbudowany
i jak działa procesor
Poznaj język Asem bler
i narzędzia u m ożliw iające
tw orzenie w nim program ów
N aucz się pisać
w ydajny kod działający
w system ach D O S i W indow s
D A W ID F A R B A N IE C
Spis treści
1. Podstawowe informacje 7
1.1. Słowem wstępu 7
1.2. Architektura x86 — podstawowe informacje 10
1.3. Budowa programu dla podsystemu DOS (16-bitowego) 13
1.4. Budowa programu dla systemu Windows (32-bitowego) 15
2. Podstawowe instrukcje procesora 18
2.1. Instrukcja kopiowania i instrukcje arytmetyczne 18
2.2. Instrukcje logiczne i przesunięć bitowych 20
2.3. Instrukcje wywołania procedury i powrotu 26
2.4. Instrukcja porównania i instrukcje skoku 29
2.5. Instrukcje do operacji na łańcuchach znaków 30
3. Makroinstrukcje 35
3.1. Makroinstrukcje kontroli przepływu 35
3.2. Makroinstrukcje do tworzenia pętli 36
4. Praca z danymi 39
4.1. Adresowanie i wskaźniki 39
4.2. Zmienne i stałe 41
4.3. Praca ze strukturami 42
5. Programowanie w systemie Windows 45
5.1. Konsola w systemie Windows 45
5.2. Proste okno dialogowe 47
5.3. Odczyt myszki i klawiatury 51
5.4. Operacje na plikach i alokacja bloków pamięci 52
5.5. Tworzenie menu 55
5.6. Kontrolka przycisku (Button) 56
5.7. Kontrolka pola tekstowego (Edit) 57
5.8. Kontrolka wielokrotnego wyboru (CheckBox) 60
3
5.9. Kontrolka pojedynczego wyboru (RadioButton) 62
5.10. Kontrolka listy rozwijanej (ComboBox) 62
5.11. Kontrolka listy (ListBox) 64
5.12. Kontrolka paska postępu (ProgressBar) 65
5.13. Kontrolka widoku drzewa (TreeView) 66
5.14. Kontrolka widoku listy (ListView) 68
5.15. Kontrolka suwaka (TrackBar) 70
5.16. Kontrolka podpowiedzi (Tooltip) 71
5.17. Kontrolka do wprowadzania adresu IP (IPAddress) 78
5.18. Kontrolka tekstu o bogatym formatowaniu (RichEdit) 83
5.19. Kontrolki w nowym stylu (pliki .manifest) 85
5.20. Podpięcia do systemu Windows (Hooks) 87
5.21. Ekran powitalny (Splashscreen) 89
5.22. Ikona w zasobniku systemowym (Tray) 91
5.23. Podmiana procedury obsługi okna 93
5.24. Programowanie aplikacji wielowątkowych 95
5.25. Tworzenie bibliotek DLL 97
5.26. Pliki odwzorowane w pamięci 99
5.27. Pobieranie adresu IP lokalnego komputera 99
6. Dodatkowe zestawy instrukcji 102
6.1. Korzystanie z instrukcji koprocesora (FPU) 102
6.2. Korzystanie z rozszerzeń MMX i SSE 110
6.3. Nowe rozszerzenie —Advanced Vector Extensions (AVX) 118
7. Asembler 64-bitowy (x86-64) 120
7.1. Wstęp do Asemblera dla architektury 64-bitowej 120
8. Opis rozkazów procesorów z rodziny 80x86 125
8.1. Instrukcje na literę A 125
8.2. Instrukcje na literę B 128
8.3. Instrukcje na literę C 132
8.4. Instrukcje na literę D 137
8.5. Instrukcje na literę E 139
8.6. Instrukcje na literę H 139
8.7. Instrukcje na literę I 140
8.8. Instrukcje na literę J 143
8.9. Instrukcje na literę L 144
8.10. Instrukcje na literę M 148
8.11. Instrukcje na literę N 151
4 | Asembler. Leksykon k eszonkowy
8.12. Instrukcje na literę O 152
8.13. Instrukcje na literę P 154
8.14. Instrukcje na literę R 156
8.15. Instrukcje na literę S 160
8.16. Instrukcje na literę T 168
8.17. Instrukcje na literę V 169
8.18. Instrukcje na literę W 169
8.19. Instrukcje na literę X 170
9. Dyrektywy asemblera MASM 172
9.1. Etykiety kodu 172
9.2. Warunkowa kontrola przepływu 173
9.3. Alokacja danych 175
9.4. Przyrównania 177
9.5. Makra 177
9.6. Procedury 178
9.7. Rodzaj zestawu instrukcji procesora 179
9.8. Bloki powtórzeń 182
9.9. Zakres 183
9.10. Segmenty 184
9.11. Uproszczone segmenty 185
9.12. Łańcuchy znaków 187
9.13. Struktury i rekordy 188
9.14. Różne 189
Dodatki 192
A. Tablica kodów ASCII 192
B. Potęgi liczby dwa 193
C. Lista opkodów procesora 80x86 196
Skorowidz 202
Sp s treśc | 5
6 j Asembler. Leksykon k eszonkowy
Rozdział 1. Podstawowe informacje
1.1. Słowem wstępu
Witaj! Zapraszam do lektury leksykonu języka Asembler. Niektórzy
z pewnością się zastanawiają, dlaczego warto poznać właśnie Asembler.
Mówi się, że programy pisane w tym języku są znane z tego, iż są
małe i szybkie. I rzeczywiście takie właśnie są, a to dlatego, że mają
taki kod, jaki my sami stworzymy, a nie taki, jaki zostanie przetłu
maczony przez kompilator. Co ważne, nie posiadają również zbędnych
bibliotek. Na jednym z wykładów z programowania niskopoziomowe-
go usłyszałem kiedyś istotną uwagę: programista piszący w językach
wysokiego poziomu żyje w swoim własnym świecie i nie ma pojęcia, co
się tak naprawdę dzieje w środku. Dzięki programowaniu w Asemblerze
możemy zrozumieć, jak działa procesor czy jak jest zbudowany pro
gram, i nauczyć się wykorzystywać jeszcze inne aspekty niskopo-
ziomowego programowania, o których programista zajmujący się je
dynie pisaniem na przykład w Javie nie ma w ogóle pojęcia. Dlatego
każdy szanujący się programista powinien poznać przynajmniej pod
stawy Asemblera.
Podstawowe pojęcia
Asembler (wielką literą) — niskopoziomowy język programowania.
asembler (małą literą) — program przeprowadzający proces asem-
blacji, po którym otrzymujemy plik *.obj.
Konsolidator (ang. linker) — program, który po przeprowadzeniu
wcześniejszej asemblacji dokonuje konsolidacji, czyli łączenia. W wyni
ku jego działania otrzymujemy plik wykonywalny *.exe.
Odpluskwiacz (ang. debugger) — program do wyszukiwania błędów
w programach. Przedstawia kod programu w Asemblerze i pozwala
wykonywać go instrukcja po instrukcji, co ułatw ia analizę aplikacji
i wykrywanie błędów.
7
Potrzebne narzędzia
Podczas pisania leksykonu używałem asemblera MASM, który można
pobrać za darmo ze strony http://masm32.com/, oraz środowiska pro
gramistycznego WinAsm, które również jest dostępne bezpłatnie na
stronie http://winasm.net/. Do szukania błędów i analizy naszych pro
gramów może się przydać debugger — ja polecam Olly Debugger
(http://ollydbg.de/).
Na początku należy zainstalować pakiet MASM32, następnie środo
wisko WinAsm (przy czym kolejność jest tu istotna), a później można
jeszcze doinstalować wymieniony wcześniej Olly Debugger.
Ważne jest, aby w środowisku WinAsm odpowiednio ustawić ścieżki.
Aby to zrobić, należy wybrać w górnym menu Tools/Options/Files &
Paths. Przykładowe ścieżki przedstawiam na rysunku 1.1.
Options
General [ Files & Paths pEditor Intellisense | KeyWords Colors Miscellaneous |
Binary Path V*1asm32^in Li
Indude Path yiasm32\[ndude LI
Library Path Wasm32\Jb i
Keyword File ^WinASM\
Idąc od lewej do prawej, pierwszy przycisk dokonuje asemblacji,
drugi linkowania, trzeci wykonuje obie te czynności naraz, czwarty
uruchamia gotowy program, a piąty kończy działanie programu.
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). Po
niżej zamieszczam skrótowy opis:
• System binarny — podstawą systemu są dwie cyfry: 0 i 1. W sys
temie binarnym działa komputer i jest zapisywany kod pro
gramu. 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 syste
mie 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ść jedno
stek 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).
Rozdz ał 1. Podstawowe nformacje | 9
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.
1.2. Architektura x86 — podstawowe informacje
Jeżeli chcesz programować w Asemblerze x86, musisz bliżej poznać tę
architekturę. Poniżej zostaną opisane najważniejsze rejestry procesora.
Wytłumaczę także, czym jest stos.
Rejestry procesora
Rejestry to komórki pamięci wewnątrz procesora służące mu do wy
konywania różnych operacji. Warto wspomnieć, że operacje na reje
strach 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. destina
tion). 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 prze
chowuje 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.
10 | Asembler. Leksykon k eszonkowy
EAX (A k u m u la to r)
A X
AH A L
I I . 1 1 1 1 1 1 1111 i i i i i
31 15 7 0
--------r--------
S ta rs z e s ło w o M ło d s z e s ło w o
EBX (R e je s tr bazowy)
BX
BH BL
i i i i i i i i i i i i i i i i i i i i i i
31 L5 7 0
S ta rs z e sło w o M łodsz e sło w o
ECX (L ic z n ik )
c x
CH CL
i ;............... ¡ m m i i i M M I I I i i i i i i i
31 15 7 0
S ta rs z e s ło w o M ło d s z e sło w o
EDX ( R e je s tr danych)
DX
DH
......
DL
I I I I 1 1 I I 1 1 1 1 1 1 1 L i i i 1 1 1
31 ,
5 7 0
1 1--------
S ta rs z e s ło w o M ło d sze s ło w o
Rysunek 1.3. Rejestry ogólnego przeznaczenia
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 intere
sować, gdy będziemy debugować swoje programy):
• 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.
• PF (ang. Parity Flag) — znacznik parzystości. Jest ustawiany,
gdy w wyniku wykonania działania liczba bitów o wartości 1
w mniej znaczącym bajcie jest parzysta.
• AF (ang. Auxillary Flag) — znacznik przeniesienia pomocni
czego. Jest ustawiany, gdy nastąpiło przeniesienie z bitu numer
3 na bit numer 4 lub pożyczenie z bitu numer 4.
Rozdz ał 1. Podstawowe nformacje | 11
• ZF (ang. Zero Flag) — znacznik zerowy. Przyjmuje wartość 1,
gdy wynikiem ostatnio wykonanego działania było zero.
• SF (ang. Sign Flag) — znacznik zmiany znaku. Jest ustawiany,
gdy najbardziej znaczący bit (czyli najstarszy bit, nazywany
bitem znaku) w otrzymanym wyniku równa się jeden.
• OF (ang. Overflow Flag) — znacznik nadmiaru/przepełnienia.
Jest ustawiany, gdy w danym działaniu nastąpiło przeniesie
nie na najstarszy bit lub pożyczenie z tego bitu.
• TF (ang. Trap Flag) — jest ustawiany przy debugowaniu. Pro
cesor wywołuje wtedy tylko jedną instrukcję, a następnie do
chodzi do przerwania, co pozwala dołączonemu debuggerowi
zbadać program.
• IF (ang. Interrupt Flag) — zezwolenie na przerwanie.
• DF (ang. Direction Flag) — znacznik kierunku.
• IOPL (ang. I/O Privilege Level) — informuje o priorytecie dostępu
do portów wejścia-wyjścia. CPL (ang. Current Priority Level)
musi być większe lub równe od tego znacznika, aby program
miał dostęp do portów wejścia-wyjścia. Dostępny tylko w pro
cesorach 286 i nowszych.
• NT (ang. Nested Task) — dostępny tylko w procesorach 286
i nowszych.
• RF (ang. Resume Flag) — znacznik wznowienia; dostępny tylko
w procesorach 386 i nowszych.
• VM(ang. Virtual 8086 Mode) — włączenie trybu wirtualnego;
dostępny tylko w procesorach 386 i nowszych.
• AC (ang. Alignment Check) — dostępny tylko w procesorach
486SX i nowszych.
• VIF (ang. Virtual Interrupt Flag) — dostępny tylko w procesorach
Pentium i nowszych.
• VIP (ang. Virtual Interrupt Pending) — dostępny tylko w pro
cesorach Pentium i nowszych.
• ID (ang. Identification) — odczyt rodzaju procesora; dostępny
tylko w procesorach Pentium i nowszych.
12 | Asembler. Leksykon k eszonkowy
• Rejestry koprocesora: ST0...ST7 — do operacji na liczbach zmien
noprzecinkowych.
• Rejestry MMX (64-bitowe): mm0...mm7.
• Rejestry SSE (128-bitowe): xmm0...xmm7.
• EIP — rejestr zawierający adres aktualnie wykonywanej instrukcji.
To już wszystkie rejestry, które będą nas interesowały.
Umiejscowienie znaczników w rejestrze EFLAGS zostało zaprezentowane
na rysunku 1.4.
3lbo|29|28|27|26|25|24|23|22|212019181716N14i:14um £■7e.u mzy .
Z - _¡f ZT££ £ 3 S B
Rysunek 1.4. Umiejscowienie znaczników w rejestrze EFLAGS
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 pierw
sza. Instrukcja PUSH odkłada wartość na stos, natomiast POP zdejmuje
i zapisuje do rejestru/pamięci.
1.3. Budowa programu dla podsystemu DOS
(16-bitowego)
W programowaniu dla podsystemu DOS korzystamy z rejestrów 16-
bitowych oraz rejestrów segmentowych. Z kolei do wykonywania
różnych czynności, takich jak na przykład wyświetlanie tekstu czy
operacje na plikach, korzystamy z przerwań DOS oraz BIOS. Istnieją
tutaj dwa rodzaje programów: programy typu COM (zajmują jeden
segment, tj. 64 kB) oraz programy typu EXE. Warto również zaznaczyć,
że programy 16-bitowe (DOS-owe) nie uruchomią się już na nowych,
64-bitowych systemach operacyjnych.
Rozdz ał 1. Podstawowe nformacje | 13
Budowa programu dla podsystemu DOS
Budowa programu typu COM jest następująca:
.model tin y ; m odel pam ięci
.data
; dane
.code
.startu p
; instrukcje
.e x it
end
Natomiast budowa programu typu EXE wygląda tak:
.model small ; model pam ięci
.stack 100h ; stos
.data
; dane
.code
s t a r t :
mov a x , @data ; pobierz adres segmentu danych
mov ds, ax ; umieść g o w rejestrze DS (Data Segment)
; instrukcje
end s ta rt
Gotowe szablony aplikacji DOS-owych zawiera środowisko WinAsm.
Wystarczy wybrać z górnego menu File/New Project i przejść na za
kładkę DOS.
Przerwania
Jak już wspomniałem we wstępie, programowanie dla DOS-a opiera
się na przerwaniach. Ogólna zasada wywoływania przerwań polega na
umieszczeniu w odpowiednim rejestrze numeru funkcji, a następnie
wywołaniu odpowiedniego przerwania za pomocą instrukcji INT.
Dla przykładu poniżej zamieszczam program typu EXE wyświetlający
tekst na konsoli:
.model small
.stack 100h
.data
msg db "H ello W o rld !",1 3 ,1 0 ,"$ "; napis musi się kończyć znakiem $
.code
s t a r t :
mov ax, @data ; pobierz adres segmentu danych
mov ds, ax ; i umieść g o w rejestrze DS
mov dx, o ffs e t msg ; pobierz adres napisu do rejestru DX
14 | Asembler. Leksykon k eszonkowy
mov ah, 9 ; funkcja 09h w rejestrze AH oznacza ”wypisz tekst na
standardowe wyjście”
in t 21h ; wywołanie przerwania DOS o numerze 21h
mov ax, 0C07h ;funkcja 0Ch = ”odśwież bufor i czytaj ze standardowego wejścia'
in t 21h ; czeka na naciśnięcie klawisza
mov ax, 4C00h ;funkcja wyjścia
in t 21h ; wywołanie przerwania DOS o numerze 21h
end s ta rt
Obecnie mało kto pisze programy dla DOS-a w Asemblerze, ale po
stanowiłem o tym wspomnieć, gdyż czasem może się to przydać
(choćby na zajęciach na niektórych uczelniach w Polsce).
1.4. Budowa programu dla systemu Windows
(32-bitowego)
Przy programowaniu dla systemu DOS korzysta się z rejestrów
16-bitowych oraz przerwań, natomiast w programowaniu dla sys
temu Windows (32-bitowego) będziemy korzystać z 32-bitowych re
jestrów procesora oraz funkcji WinAPI. Jeżeli chodzi o pamięć, w pod
systemie DOS były segmenty, tutaj natomiast mamy jeden typ pamięci
— płaski (ang. flat). Oznacza to, że pamięć tworzy dużą, ciągłą prze
strzeń o rozmiarze 4 GB.
Budowa programu dla systemu Windows:
.386 ; zestaw instrukcji
.model f l a t , s td c a ll ; model pam ięci i sposób wywoływaniafunkcji
option casemap:none ; rozróżnianie małych i dużych liter
; pliki nagłówkowe
in cl ude windows.inc
i ncl ude kernel32. inc
includ e user32.inc
; biblioteki
in cl u d elib u se r3 2 .lib
in cl u d elib k e rn e l3 2 .lib
.data
; dane zainicjowane
.d a ta ?
; dane niezainicjowane
.code
s ta r t: ; początek etykiety głównej
; instrukcje
end s ta r t ; koniec etykiety głównej
Rozdz ał 1. Podstawowe nformacje | 15
Program „Witaj, świecie!"
Poniżej przedstawiam kod programu wyświetlającego okno infor
macyjne poprzez wywołanie funkcji MessageBox.
.386
.model f l a t , std c a ll
option casemap:none
i nclude windows.i nc
in cl ude kernel 32.inc
includ e user32.inc
in cl udelib u se r3 2 .lib
in cl udelib kernel 3 2 .lib
.data
MsgCaption db "In fo rm a cja ",0
MsgBoxText db "W ita j, ś w ie c ie !",0
.d a ta ?
.code
s ta r t:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgCaption, MB_OK
invoke Ex itPro cess, NULL
end s ta rt
W powyższym kodzie najpierw mamy określony zestaw instrukcji
(.386), następnie model pamięci i sposób wywoływania funkcji, póź
niej zaś dyrektywę rozkazującą asemblerowi odróżniać małe i duże
litery. Dalej zostają dołączone odpowiednie pliki nagłówkowe i bibliote
ki. Niżej w sekcji danych zostały zadeklarowane dwa ciągi znaków
zakończone zerem (w DOS-ie znakiem kończącym był znak $, tutaj
jest to zero). W sekcji kodu pojawiło się wywołanie funkcji MessageBox
wraz z podaną listą argumentów, która wyświetla okno informacyjne
(funkcja pochodzi z biblioteki user32dll). Niżej znajduje się wywołanie
funkcji ExitProcess, która kończy pracę programu (funkcja pochodzi
z biblioteki kernel32.dll).
Zapewne zadajesz sobie teraz pytanie, skąd wziąć opisy tych wszystkich
funkcji oraz informacje, z jakich bibliotek pochodzą. Dokładną doku
mentację można znaleźć na stronach MSDN oraz w pliku WIN32.HLP
(Win32 Programmer's Reference), który można znaleźć w sieci.
Funkcje WinAPI zawsze zwracają wartość w rejestrze EAX. Funkcje,
których wyniki są 64-bitowe, w 32-bitowym systemie zwracają
w parze EDX: EAX.
16 | Asembler. Leksykon k eszonkowy
Szablon aplikacji dla systemu Windows zawiera nasze środowisko
WinAsm. Wystarczy wybrać z górnego menu File/New Project, przejść
na zakładkę Bare Bone i wskazać EXE.
Pseudoinstrukcja INVOKE
Na koniec kilka słów o pseudoinstrukcji INVOKE. Jak zapewne zauwa
żyłeś, w powyższym kodzie wywoływaliśmy funkcję, podając jej na
zwę i argumenty po przecinku. Standardowo w Asemblerze się tak
nie robi — przeważnie odkłada się argumenty na stos przy użyciu
instrukcji PUSH i wywołuje funkcję za pomocą instrukcji CALL. Jednak
makroasembler MASM ma pseudoinstrukcję, która pozwala wywo
ływać funkcję w ten prosty i czytelny sposób. Dla porównania poni
żej przedstawię wywołanie tej samej funkcji z użyciem INVOKE i bez.
Wywołanie funkcji MessageBox z użyciem pseudoinstrukcji INVOKE:
invoke MessageBox, NULL, addr MsgText, addr MsgCaption, MB_OK
Wywołanie tej samej funkcji bez użycia pseudoinstrukcji INVOKE:
push MB_OK
push o ffs e t MsgCaption
push o ffs e t MsgText
push NULL
c a ll MessageBox
Korzystanie z INVOKE jest warte polecenia, gdyż dzięki temu kod jest
bardziej przejrzysty i łatwiej się ustrzec błędu przy podawaniu ar
gumentów funkcji.
Ten rozdział był małym wprowadzeniem do programowania w Asem
blerze dla systemu Windows. Poznałeś w nim budowę prostego pro
gramu. W dalszych rozdziałach opiszę najważniejsze instrukcje, gdyż
programowanie w Asemblerze to przede wszystkim instrukcje pro
cesora, a nie tylko wywoływanie funkcji WinAPI.
Rozdz ał 1. Podstawowe nformacje | 17
Rozdział 2. Podstawowe instrukcje procesora
2.1. Instrukcja kopiowania i instrukcje arytmetyczne
Instrukcja kopiowania (MOV)
Zacznijmy od najczęściej spotykanej instrukcji — MOV.
Składnia: MOV cel, źródło
To chyba najczęściej spotykana instrukcja. Kopiuje wartość z operandu
źródłowego do operandu celu, przy czym pierwsza wartość pozostaje
niezmieniona.
Na przykład:
mov eax, 25 ; EAXprzyjmuje wartość 25
mov ecx, eax ; skopiuj do ECX wartość z EAX
mov zmienna, eax ; skopiuj do zmiennej wartość z EAX
Dlaczego nie jest MOV zmienna, zmienna? Ponieważ procesor nie
potrafi wykonać czegoś takiego. Musimy posłużyć się rejestrem
pomocniczym (najpierw skopiować wartość do rejestru, a potem
do zmiennej) lub odłożyć wartość na stos (instrukcja PUSH), po
czym przy użyciu instrukcji POP zdjąć ją ze stosu i umiejscowić
w drugiej zmiennej.
Oprócz kopiowania wartości stałej do rejestru/zmiennej czy kopio
wania wartości między rejestrami możemy skopiować wartość z da
nego miejsca w pamięci. Wykonujemy to w następujący sposób:
mov eax, dword p tr [ebx]
Rejestr EBX zawiera adres, pod którym znajduje się wartość — zosta
nie ona skopiowana do rejestru EAX.
Instrukcje arytmetyczne
Instrukcje arytmetyczne służą do wykonywania podstawowych działań
matematycznych, takich jak dodawanie, odejmowanie, mnożenie
i dzielenie.
18 | Asembler. Leksykon k eszonkowy
ADD (dodawanie)
Składnia: ADD operand1, operand2
Instrukcja ADDdodaje wartość do rejestru lub komórki pamięci.
Przykładowe użycie:
add eax, 124 ; dodaje 124 do wartości w rejestrze EAX
add ecx, ebx ; dodaje wartość z E B X do wartości w ECX
add zmienna, 25 ; dodaje 25 do wartości w zmiennej ”zmienna”
SUB (odejmowanie)
Składnia: SUB operand1, operand2
Odejmuje wartość drugiego operandu od pierwszego i zapisuje wynik
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.
IMUL (mnożenie ze znakiem)
Składnia:
IMUL wartość
IMUL wynik, wartość, wartość
IMUL wynik, wartość
Mnoży wartość z EAX z podaną wartością (IMUL wartość) lub mnoży
dwie wartości i wynik zachowuje w pierwszym operandzie (IMUL wynik,
wartość, wartość), lub mnoży rejestr z wartością (IMUL wynik, wartość).
DIV (dzielenie bez znaku)
Składnia: DIV dzielnik
Instrukcja ta dzieli wartość w rejestrze EAX przez wartość podaną jako
dzielnik. Dzielona jest zawsze wartość w EAX. Także wynik zostanie
umieszczony w tym rejestrze, natomiast reszta z dzielenia — w EDX.
mov eax, 32 ; EAX przyjmuje wartość 32
mov ecx, 2 ; ECXprzyjmuje wartość 2
d iv ecx ; podziel EAXprzez ECX
Rozdz ał 2. Podstawowe nstrukcje procesora | 19
IDIV (dzielenie ze znakiem)
Składnia: IDIV dzielnik
Działa podobnie jak instrukcja DIV, tyle że dzieli ze znakiem.
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
(zmi enna=zmienna-l).
Przykład:
dec eax ; zmniejsza wartość w EAX o 1
dec [zmienna] ; zmniejsza wartość zmiennej o 1
2.2. Instrukcje logiczne i przesunięć bitowych
Instrukcje logiczne
Instrukcje logiczne służą do wykonywania operacji logicznych na
bitach, takich jak koniunkcja (iloczyn logiczny), alternatywa (suma
logiczna), alternatywa wykluczająca i negacja (zaprzeczenie).
AND (koniunkcja)
Składnia: AND operand1, operand2
1 and 1 = 1
1 and 0 = 0
0 and 1 = 0
0 and 0 = 0
Dla przykładu wykonamy koniunkcję dla dwóch liczb binarnych:
11101010100 and 01010110000 = 01000010000
Przy ręcznym liczeniu najwygodniej napisać je jedna pod drugą i spraw
dzać według tabelki poszczególne bity.
20 | Asembler. Leksykon k eszonkowy
OR (alternatywa)
Składnia: OR operand1, operand2
1 or 1 = 1
1 or 0 = 1
0 or 1 = 1
0 or 0 = 0
Dla przykładu mamy dwie liczby binarne:
10100011100 or 01110010010 = 11110011110
A zatem bierzemy po kolei dwa odpowiadające sobie bity i spraw
dzamy według tabelki, jaki będzie dla nich wynik.
XOR (alternatywa wykluczająca)
Składnia: XOR operand1, operand2
1 xor 1 = 0
1 xor 0 = 1
0 xor 1 = 1
0 xor 0 = 0
Będziemy często używać tej instrukcji do czyszczenia zawartości ja
kiegoś rejestru (gdyż wynik operacji xor dla dwóch tych samych
wartości daje zero).
NOT (zaprzeczenie)
Składnia: NOT operand1
not 1 = 0
not 0 = 1
Krótko mówiąc, odwraca wszystkie bity w podanym operandzie
(zamiast 1 daje 0, zamiast 0 daje 1).
Instrukcje przesunięć bitowych
Instrukcje przesunięć bitowych służą do wykonywania operacji, które
zmieniają miejsce bitów w rejestrze. Ujmując to prościej, bity są prze
suwane w prawo lub lewo.
SAL/SHL (przesunięcie arytmetyczne/logiczne w lewo)
Składnia:
SAL operand1, operand2
SHL operand1, operand2
Rozdz ał 2. Podstawowe nstrukcje procesora | 21
Przesuwa w lewo bity pierwszego operandu o podaną liczbę bitów
w operandzie drugim. Starszy przesunięty bit zostaje zachowany
w fladze CF (ang. Carry Flag), a młodsze bity są wypełniane zerami.
Sposób przesuwania się bitów dla instrukcji SAL/SHL w 32-bitowym
rejestrze prezentuje rysunek 2.1.
Dla przykładu przesuniemy wartość w EAXo 1 bit w lewo:
mov eax, 1 ; do EAX dajemy wartość 1
shl eax, 1 ; EA X będzie równy 2 (10 binarnie),
; gdyżjedynka się przemieściła,
; a na je j miejscu mamy zero,
; co daje wartość 10b (2h).
Najmłodszy bit
EAX (32 bity) jest zerowany
1 < n
Carry Flag 31 15 1 0
(znacznik Starsze bity Młodsze bity
przeniesienia)
Rysunek 2.1. Rysunek prezentujący, jak przesuwają się bity dla instrukcji SAL/SHL
SAR/SHR (przesunięcie arytmetyczne/logiczne w prawo)
Składnia:
SAR operand1, operand2
SHR operand1, operand2
Instrukcja SARprzesuwa w prawo bity pierwszego operandu o poda
ną liczbę bitów w operandzie drugim. Najmłodszy przesunięty bit
zostaje zachowany w fladze CF, a najstarszy bit zostaje zachowany.
Instrukcja SHR działa podobnie, różni się tylko tym, że zeruje najstarszy
bit (nazywany również bitem znaku). Sposób przesuwania się bitów
dla instrukcji SAR został zaprezentowany na rysunku 2.2, a dla in
strukcji SHR— na rysunku 2.3.
Dla przykładu przesuniemy wartość w EAXo 1 bit w prawo:
mov eax, 1 ; do EAX idzie wartość 1
shr eax, 1 ; EA X będzie równy 0,
; fla g a C F zostanie ustawiona.
22 | Asembler. Leksykon k eszonkowy
Rysunek 2.2. Rysunek prezentujący, jak przesuwają się hity dla instrukcji SAR
Rysunek 2.3. Rysunek prezentujący, jak przesuwają się hity dla instrukcji SHR
ROL/RCL (rotacja w lewo bez użycia flagi CF/z użyciem flagi CF)
Składnia:
ROL operand1, operand2
RCL operand1, operand2
Pierwsza instrukcja przesuwa logicznie w lewo pierwszy operand
o liczbę bitów podaną w operandzie drugim. Nie używa flagi CF, więc
najmłodszy bit przyjmuje wartość najstarszego bitu. Druga instrukcja
przeprowadza tę samą czynność, ale przy użyciu flagi CF. Najmłodszy
bit przyjmuje wartość, która była w CF, a znacznik CF przyjmuje wartość
najstarszego bitu. Sposób obracania się bitów dla instrukcji ROL w 32-
bitowym rejestrze został przedstawiony na rysunku 2.4, a dla instrukcji
RCL— na rysunku 2.5.
Rysunek 2.4. Rysunek prezentujący, jak obracają się hity dla instrukcji ROL
Rozdz ał 2. Podstawowe nstrukcje procesora | 23
EAX (32 bity)
31 15 7 0
Starsze bity
--- -------
Młodsze bity
Carry Flag
(znacznik przeniesienia)
Rysunek 2.5. Rysunek prezentujący, jak obracają się bity dla instrukcji RCL
ROR/RCR (rotacja w prawo bez użycia flagi CF/z użyciem flagi CF)
Składnia:
ROR operandl, operand2
RCR operandl, operand2
Pierwsza instrukcja przesuwa logicznie w prawo pierwszy operand
o liczbę bitów podaną w operandzie drugim. Nie używa flagi CF,
więc najstarszy bit przyjmuje wartość najmłodszego bitu. Druga in
strukcja przeprowadza tę samą czynność, ale przy użyciu flagi CF.
Najstarszy bit przyjmuje wartość, która była w CF, a znacznik CF
przyjmuje wartość najmłodszego bitu. Sposób obracania się bitów
w 32-bitowym rejestrze dla instrukcji ROR został zaprezentowany na
rysunku 2.6, a dla instrukcji RCR— na rysunku 2.7.
EAX (32 bity)
31 15 7 0
Starsze bity Młodsze bity
■<--------------------
Carry Flag
(znacznik przeniesienia)
Rysunek 2.6. Rysunek prezentujący, jak obracają się bity dla instrukcji ROR
SHLD/SHRD (przesunięcie logiczne w lew o/w prawo podwójnego rejestru)
Składnia:
SHLD operandl, operand2, operand3
SHRD operandl, operand2, operand3
24 | Asembler. Leksykon k eszonkowy
EAX (32 bity)
31 15 7 0
Starsze bity Młodsze bity
------ —
Carry Flag
(znacznik przeniesienia)
Rysunek 27. Rysunek prezentujący, jak obracają się hity dla instrukcji RCR
Pierwsza instrukcja (SHLD) przesuwa w lewo bity w operandzie pierw
szym o liczbę bitów podaną w operandzie trzecim. Najstarsze bity
pierwszego operandu zostają wyrzucone (flaga CF przyjmuje wartość
ostatniego wyrzuconego bitu), a najmłodsze bity przyjmują wartości
najstarszych bitów z drugiego operandu. Uwaga! Bity drugiego ope-
randu pozostają niezmienione. SHRD przesuwa natomiast bity pierw
szego operandu o liczbę bitów podaną w operandzie trzecim. Naj
młodsze bity pierwszego operandu zostają wyrzucone, a najstarsze
bity przyjmują wartość najmłodszych bitów z operandu drugiego.
Bity drugiego operandu pozostają niezmienione. Sposób przesuwania
się bitów dla instrukcji SHLD został zaprezentowany na rysunku 2.8,
a dla instrukcji SHRD— na rysunku 2.9.
Przykład dla instrukcji SHLD:
mov ecx, 0 ; zerujemy ECX
mov eax, OFFFFFFFFh ; do EAX wartość OFFFFFFFFh
shld ecx, eax, 2 ; EA X będzie równy OFFFFFFFCh, gdyż
; dwa jeg o najmłodsze bity zostały zastąpione
; przez dwa najstarsze bity rejestru ECX,
; które miały wartość zero.
Przykład dla instrukcji SHRD:
mov ecx, 0 ; zerujemy ECX
mov eax, OFFFFFFFFh ; do EAX wartość OFFFFFFFFh
shrd ecx, eax, 2 ; EA X będzie równy 3FFFFFFFh, gdyż
; dwa jeg o najstarsze bity zostały
; zastąpione dwoma najmłodszymi bitami
; z rejestru ECX, które miały wartość zero.
Rozdz ał 2. Podstawowe nstrukcje procesora | 25
Rysunek 2.8. Rysunek prezentujący, jak przesuwają się bity dla instrukcji SHLD
Rysunek 2.9. Rysunek prezentujący, jak przesuwają się bity dla instrukcji SHRD
2.3. Instrukcje wywołania procedury i powrotu
Instrukcje związane z procedurami
Procedury to wydzielone części programu, które mogą być wielokrotnie
wywoływane, bez potrzeby kopiowania tego samego kodu w różne
miejsca. Tworzenie takich procedur (nazywanych też podprogramami)
jest możliwe przy użyciu instrukcji związanych z procedurami.
CALL (wywołanie funkcji/podprogramu)
Składnia: CALL nazwa_funkcji
26 | Asembler. Leksykon k eszonkowy
551. £ Poznaj Asemblera i dołącz do elity programistów D ow iedz się, jak jest zbudowany i jak działa procesor Poznaj język Asem bler i narzędzia u m ożliw iające tw orzenie w nim program ów N aucz się pisać w ydajny kod działający w system ach D O S i W indow s D A W ID F A R B A N IE C
Spis treści 1. Podstawowe informacje 7 1.1. Słowem wstępu 7 1.2. Architektura x86 — podstawowe informacje 10 1.3. Budowa programu dla podsystemu DOS (16-bitowego) 13 1.4. Budowa programu dla systemu Windows (32-bitowego) 15 2. Podstawowe instrukcje procesora 18 2.1. Instrukcja kopiowania i instrukcje arytmetyczne 18 2.2. Instrukcje logiczne i przesunięć bitowych 20 2.3. Instrukcje wywołania procedury i powrotu 26 2.4. Instrukcja porównania i instrukcje skoku 29 2.5. Instrukcje do operacji na łańcuchach znaków 30 3. Makroinstrukcje 35 3.1. Makroinstrukcje kontroli przepływu 35 3.2. Makroinstrukcje do tworzenia pętli 36 4. Praca z danymi 39 4.1. Adresowanie i wskaźniki 39 4.2. Zmienne i stałe 41 4.3. Praca ze strukturami 42 5. Programowanie w systemie Windows 45 5.1. Konsola w systemie Windows 45 5.2. Proste okno dialogowe 47 5.3. Odczyt myszki i klawiatury 51 5.4. Operacje na plikach i alokacja bloków pamięci 52 5.5. Tworzenie menu 55 5.6. Kontrolka przycisku (Button) 56 5.7. Kontrolka pola tekstowego (Edit) 57 5.8. Kontrolka wielokrotnego wyboru (CheckBox) 60 3
5.9. Kontrolka pojedynczego wyboru (RadioButton) 62 5.10. Kontrolka listy rozwijanej (ComboBox) 62 5.11. Kontrolka listy (ListBox) 64 5.12. Kontrolka paska postępu (ProgressBar) 65 5.13. Kontrolka widoku drzewa (TreeView) 66 5.14. Kontrolka widoku listy (ListView) 68 5.15. Kontrolka suwaka (TrackBar) 70 5.16. Kontrolka podpowiedzi (Tooltip) 71 5.17. Kontrolka do wprowadzania adresu IP (IPAddress) 78 5.18. Kontrolka tekstu o bogatym formatowaniu (RichEdit) 83 5.19. Kontrolki w nowym stylu (pliki .manifest) 85 5.20. Podpięcia do systemu Windows (Hooks) 87 5.21. Ekran powitalny (Splashscreen) 89 5.22. Ikona w zasobniku systemowym (Tray) 91 5.23. Podmiana procedury obsługi okna 93 5.24. Programowanie aplikacji wielowątkowych 95 5.25. Tworzenie bibliotek DLL 97 5.26. Pliki odwzorowane w pamięci 99 5.27. Pobieranie adresu IP lokalnego komputera 99 6. Dodatkowe zestawy instrukcji 102 6.1. Korzystanie z instrukcji koprocesora (FPU) 102 6.2. Korzystanie z rozszerzeń MMX i SSE 110 6.3. Nowe rozszerzenie —Advanced Vector Extensions (AVX) 118 7. Asembler 64-bitowy (x86-64) 120 7.1. Wstęp do Asemblera dla architektury 64-bitowej 120 8. Opis rozkazów procesorów z rodziny 80x86 125 8.1. Instrukcje na literę A 125 8.2. Instrukcje na literę B 128 8.3. Instrukcje na literę C 132 8.4. Instrukcje na literę D 137 8.5. Instrukcje na literę E 139 8.6. Instrukcje na literę H 139 8.7. Instrukcje na literę I 140 8.8. Instrukcje na literę J 143 8.9. Instrukcje na literę L 144 8.10. Instrukcje na literę M 148 8.11. Instrukcje na literę N 151 4 | Asembler. Leksykon k eszonkowy
8.12. Instrukcje na literę O 152 8.13. Instrukcje na literę P 154 8.14. Instrukcje na literę R 156 8.15. Instrukcje na literę S 160 8.16. Instrukcje na literę T 168 8.17. Instrukcje na literę V 169 8.18. Instrukcje na literę W 169 8.19. Instrukcje na literę X 170 9. Dyrektywy asemblera MASM 172 9.1. Etykiety kodu 172 9.2. Warunkowa kontrola przepływu 173 9.3. Alokacja danych 175 9.4. Przyrównania 177 9.5. Makra 177 9.6. Procedury 178 9.7. Rodzaj zestawu instrukcji procesora 179 9.8. Bloki powtórzeń 182 9.9. Zakres 183 9.10. Segmenty 184 9.11. Uproszczone segmenty 185 9.12. Łańcuchy znaków 187 9.13. Struktury i rekordy 188 9.14. Różne 189 Dodatki 192 A. Tablica kodów ASCII 192 B. Potęgi liczby dwa 193 C. Lista opkodów procesora 80x86 196 Skorowidz 202 Sp s treśc | 5
6 j Asembler. Leksykon k eszonkowy
Rozdział 1. Podstawowe informacje 1.1. Słowem wstępu Witaj! Zapraszam do lektury leksykonu języka Asembler. Niektórzy z pewnością się zastanawiają, dlaczego warto poznać właśnie Asembler. Mówi się, że programy pisane w tym języku są znane z tego, iż są małe i szybkie. I rzeczywiście takie właśnie są, a to dlatego, że mają taki kod, jaki my sami stworzymy, a nie taki, jaki zostanie przetłu maczony przez kompilator. Co ważne, nie posiadają również zbędnych bibliotek. Na jednym z wykładów z programowania niskopoziomowe- go usłyszałem kiedyś istotną uwagę: programista piszący w językach wysokiego poziomu żyje w swoim własnym świecie i nie ma pojęcia, co się tak naprawdę dzieje w środku. Dzięki programowaniu w Asemblerze możemy zrozumieć, jak działa procesor czy jak jest zbudowany pro gram, i nauczyć się wykorzystywać jeszcze inne aspekty niskopo- ziomowego programowania, o których programista zajmujący się je dynie pisaniem na przykład w Javie nie ma w ogóle pojęcia. Dlatego każdy szanujący się programista powinien poznać przynajmniej pod stawy Asemblera. Podstawowe pojęcia Asembler (wielką literą) — niskopoziomowy język programowania. asembler (małą literą) — program przeprowadzający proces asem- blacji, po którym otrzymujemy plik *.obj. Konsolidator (ang. linker) — program, który po przeprowadzeniu wcześniejszej asemblacji dokonuje konsolidacji, czyli łączenia. W wyni ku jego działania otrzymujemy plik wykonywalny *.exe. Odpluskwiacz (ang. debugger) — program do wyszukiwania błędów w programach. Przedstawia kod programu w Asemblerze i pozwala wykonywać go instrukcja po instrukcji, co ułatw ia analizę aplikacji i wykrywanie błędów. 7
Potrzebne narzędzia Podczas pisania leksykonu używałem asemblera MASM, który można pobrać za darmo ze strony http://masm32.com/, oraz środowiska pro gramistycznego WinAsm, które również jest dostępne bezpłatnie na stronie http://winasm.net/. Do szukania błędów i analizy naszych pro gramów może się przydać debugger — ja polecam Olly Debugger (http://ollydbg.de/). Na początku należy zainstalować pakiet MASM32, następnie środo wisko WinAsm (przy czym kolejność jest tu istotna), a później można jeszcze doinstalować wymieniony wcześniej Olly Debugger. Ważne jest, aby w środowisku WinAsm odpowiednio ustawić ścieżki. Aby to zrobić, należy wybrać w górnym menu Tools/Options/Files & Paths. Przykładowe ścieżki przedstawiam na rysunku 1.1. Options General [ Files & Paths pEditor Intellisense | KeyWords Colors Miscellaneous | Binary Path V*1asm32^in Li Indude Path yiasm32\[ndude LI Library Path Wasm32\Jb i Keyword File ^WinASM\
Idąc od lewej do prawej, pierwszy przycisk dokonuje asemblacji, drugi linkowania, trzeci wykonuje obie te czynności naraz, czwarty uruchamia gotowy program, a piąty kończy działanie programu. 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). Po niżej zamieszczam skrótowy opis: • System binarny — podstawą systemu są dwie cyfry: 0 i 1. W sys temie binarnym działa komputer i jest zapisywany kod pro gramu. 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 syste mie 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ść jedno stek 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). Rozdz ał 1. Podstawowe nformacje | 9
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. 1.2. Architektura x86 — podstawowe informacje Jeżeli chcesz programować w Asemblerze x86, musisz bliżej poznać tę architekturę. Poniżej zostaną opisane najważniejsze rejestry procesora. Wytłumaczę także, czym jest stos. Rejestry procesora Rejestry to komórki pamięci wewnątrz procesora służące mu do wy konywania różnych operacji. Warto wspomnieć, że operacje na reje strach 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. destina tion). 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 prze chowuje 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. 10 | Asembler. Leksykon k eszonkowy
EAX (A k u m u la to r) A X AH A L I I . 1 1 1 1 1 1 1111 i i i i i 31 15 7 0 --------r-------- S ta rs z e s ło w o M ło d s z e s ło w o EBX (R e je s tr bazowy) BX BH BL i i i i i i i i i i i i i i i i i i i i i i 31 L5 7 0 S ta rs z e sło w o M łodsz e sło w o ECX (L ic z n ik ) c x CH CL i ;............... ¡ m m i i i M M I I I i i i i i i i 31 15 7 0 S ta rs z e s ło w o M ło d s z e sło w o EDX ( R e je s tr danych) DX DH ...... DL I I I I 1 1 I I 1 1 1 1 1 1 1 L i i i 1 1 1 31 , 5 7 0 1 1-------- S ta rs z e s ło w o M ło d sze s ło w o Rysunek 1.3. Rejestry ogólnego przeznaczenia 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 intere sować, gdy będziemy debugować swoje programy): • 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. • PF (ang. Parity Flag) — znacznik parzystości. Jest ustawiany, gdy w wyniku wykonania działania liczba bitów o wartości 1 w mniej znaczącym bajcie jest parzysta. • AF (ang. Auxillary Flag) — znacznik przeniesienia pomocni czego. Jest ustawiany, gdy nastąpiło przeniesienie z bitu numer 3 na bit numer 4 lub pożyczenie z bitu numer 4. Rozdz ał 1. Podstawowe nformacje | 11
• ZF (ang. Zero Flag) — znacznik zerowy. Przyjmuje wartość 1, gdy wynikiem ostatnio wykonanego działania było zero. • SF (ang. Sign Flag) — znacznik zmiany znaku. Jest ustawiany, gdy najbardziej znaczący bit (czyli najstarszy bit, nazywany bitem znaku) w otrzymanym wyniku równa się jeden. • OF (ang. Overflow Flag) — znacznik nadmiaru/przepełnienia. Jest ustawiany, gdy w danym działaniu nastąpiło przeniesie nie na najstarszy bit lub pożyczenie z tego bitu. • TF (ang. Trap Flag) — jest ustawiany przy debugowaniu. Pro cesor wywołuje wtedy tylko jedną instrukcję, a następnie do chodzi do przerwania, co pozwala dołączonemu debuggerowi zbadać program. • IF (ang. Interrupt Flag) — zezwolenie na przerwanie. • DF (ang. Direction Flag) — znacznik kierunku. • IOPL (ang. I/O Privilege Level) — informuje o priorytecie dostępu do portów wejścia-wyjścia. CPL (ang. Current Priority Level) musi być większe lub równe od tego znacznika, aby program miał dostęp do portów wejścia-wyjścia. Dostępny tylko w pro cesorach 286 i nowszych. • NT (ang. Nested Task) — dostępny tylko w procesorach 286 i nowszych. • RF (ang. Resume Flag) — znacznik wznowienia; dostępny tylko w procesorach 386 i nowszych. • VM(ang. Virtual 8086 Mode) — włączenie trybu wirtualnego; dostępny tylko w procesorach 386 i nowszych. • AC (ang. Alignment Check) — dostępny tylko w procesorach 486SX i nowszych. • VIF (ang. Virtual Interrupt Flag) — dostępny tylko w procesorach Pentium i nowszych. • VIP (ang. Virtual Interrupt Pending) — dostępny tylko w pro cesorach Pentium i nowszych. • ID (ang. Identification) — odczyt rodzaju procesora; dostępny tylko w procesorach Pentium i nowszych. 12 | Asembler. Leksykon k eszonkowy
• Rejestry koprocesora: ST0...ST7 — do operacji na liczbach zmien noprzecinkowych. • Rejestry MMX (64-bitowe): mm0...mm7. • Rejestry SSE (128-bitowe): xmm0...xmm7. • EIP — rejestr zawierający adres aktualnie wykonywanej instrukcji. To już wszystkie rejestry, które będą nas interesowały. Umiejscowienie znaczników w rejestrze EFLAGS zostało zaprezentowane na rysunku 1.4. 3lbo|29|28|27|26|25|24|23|22|212019181716N14i:14um £■7e.u mzy . Z - _¡f ZT££ £ 3 S B Rysunek 1.4. Umiejscowienie znaczników w rejestrze EFLAGS 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 pierw sza. Instrukcja PUSH odkłada wartość na stos, natomiast POP zdejmuje i zapisuje do rejestru/pamięci. 1.3. Budowa programu dla podsystemu DOS (16-bitowego) W programowaniu dla podsystemu DOS korzystamy z rejestrów 16- bitowych oraz rejestrów segmentowych. Z kolei do wykonywania różnych czynności, takich jak na przykład wyświetlanie tekstu czy operacje na plikach, korzystamy z przerwań DOS oraz BIOS. Istnieją tutaj dwa rodzaje programów: programy typu COM (zajmują jeden segment, tj. 64 kB) oraz programy typu EXE. Warto również zaznaczyć, że programy 16-bitowe (DOS-owe) nie uruchomią się już na nowych, 64-bitowych systemach operacyjnych. Rozdz ał 1. Podstawowe nformacje | 13
Budowa programu dla podsystemu DOS Budowa programu typu COM jest następująca: .model tin y ; m odel pam ięci .data ; dane .code .startu p ; instrukcje .e x it end Natomiast budowa programu typu EXE wygląda tak: .model small ; model pam ięci .stack 100h ; stos .data ; dane .code s t a r t : mov a x , @data ; pobierz adres segmentu danych mov ds, ax ; umieść g o w rejestrze DS (Data Segment) ; instrukcje end s ta rt Gotowe szablony aplikacji DOS-owych zawiera środowisko WinAsm. Wystarczy wybrać z górnego menu File/New Project i przejść na za kładkę DOS. Przerwania Jak już wspomniałem we wstępie, programowanie dla DOS-a opiera się na przerwaniach. Ogólna zasada wywoływania przerwań polega na umieszczeniu w odpowiednim rejestrze numeru funkcji, a następnie wywołaniu odpowiedniego przerwania za pomocą instrukcji INT. Dla przykładu poniżej zamieszczam program typu EXE wyświetlający tekst na konsoli: .model small .stack 100h .data msg db "H ello W o rld !",1 3 ,1 0 ,"$ "; napis musi się kończyć znakiem $ .code s t a r t : mov ax, @data ; pobierz adres segmentu danych mov ds, ax ; i umieść g o w rejestrze DS mov dx, o ffs e t msg ; pobierz adres napisu do rejestru DX 14 | Asembler. Leksykon k eszonkowy
mov ah, 9 ; funkcja 09h w rejestrze AH oznacza ”wypisz tekst na standardowe wyjście” in t 21h ; wywołanie przerwania DOS o numerze 21h mov ax, 0C07h ;funkcja 0Ch = ”odśwież bufor i czytaj ze standardowego wejścia' in t 21h ; czeka na naciśnięcie klawisza mov ax, 4C00h ;funkcja wyjścia in t 21h ; wywołanie przerwania DOS o numerze 21h end s ta rt Obecnie mało kto pisze programy dla DOS-a w Asemblerze, ale po stanowiłem o tym wspomnieć, gdyż czasem może się to przydać (choćby na zajęciach na niektórych uczelniach w Polsce). 1.4. Budowa programu dla systemu Windows (32-bitowego) Przy programowaniu dla systemu DOS korzysta się z rejestrów 16-bitowych oraz przerwań, natomiast w programowaniu dla sys temu Windows (32-bitowego) będziemy korzystać z 32-bitowych re jestrów procesora oraz funkcji WinAPI. Jeżeli chodzi o pamięć, w pod systemie DOS były segmenty, tutaj natomiast mamy jeden typ pamięci — płaski (ang. flat). Oznacza to, że pamięć tworzy dużą, ciągłą prze strzeń o rozmiarze 4 GB. Budowa programu dla systemu Windows: .386 ; zestaw instrukcji .model f l a t , s td c a ll ; model pam ięci i sposób wywoływaniafunkcji option casemap:none ; rozróżnianie małych i dużych liter ; pliki nagłówkowe in cl ude windows.inc i ncl ude kernel32. inc includ e user32.inc ; biblioteki in cl u d elib u se r3 2 .lib in cl u d elib k e rn e l3 2 .lib .data ; dane zainicjowane .d a ta ? ; dane niezainicjowane .code s ta r t: ; początek etykiety głównej ; instrukcje end s ta r t ; koniec etykiety głównej Rozdz ał 1. Podstawowe nformacje | 15
Program „Witaj, świecie!" Poniżej przedstawiam kod programu wyświetlającego okno infor macyjne poprzez wywołanie funkcji MessageBox. .386 .model f l a t , std c a ll option casemap:none i nclude windows.i nc in cl ude kernel 32.inc includ e user32.inc in cl udelib u se r3 2 .lib in cl udelib kernel 3 2 .lib .data MsgCaption db "In fo rm a cja ",0 MsgBoxText db "W ita j, ś w ie c ie !",0 .d a ta ? .code s ta r t: invoke MessageBox, NULL, addr MsgBoxText, addr MsgCaption, MB_OK invoke Ex itPro cess, NULL end s ta rt W powyższym kodzie najpierw mamy określony zestaw instrukcji (.386), następnie model pamięci i sposób wywoływania funkcji, póź niej zaś dyrektywę rozkazującą asemblerowi odróżniać małe i duże litery. Dalej zostają dołączone odpowiednie pliki nagłówkowe i bibliote ki. Niżej w sekcji danych zostały zadeklarowane dwa ciągi znaków zakończone zerem (w DOS-ie znakiem kończącym był znak $, tutaj jest to zero). W sekcji kodu pojawiło się wywołanie funkcji MessageBox wraz z podaną listą argumentów, która wyświetla okno informacyjne (funkcja pochodzi z biblioteki user32dll). Niżej znajduje się wywołanie funkcji ExitProcess, która kończy pracę programu (funkcja pochodzi z biblioteki kernel32.dll). Zapewne zadajesz sobie teraz pytanie, skąd wziąć opisy tych wszystkich funkcji oraz informacje, z jakich bibliotek pochodzą. Dokładną doku mentację można znaleźć na stronach MSDN oraz w pliku WIN32.HLP (Win32 Programmer's Reference), który można znaleźć w sieci. Funkcje WinAPI zawsze zwracają wartość w rejestrze EAX. Funkcje, których wyniki są 64-bitowe, w 32-bitowym systemie zwracają w parze EDX: EAX. 16 | Asembler. Leksykon k eszonkowy
Szablon aplikacji dla systemu Windows zawiera nasze środowisko WinAsm. Wystarczy wybrać z górnego menu File/New Project, przejść na zakładkę Bare Bone i wskazać EXE. Pseudoinstrukcja INVOKE Na koniec kilka słów o pseudoinstrukcji INVOKE. Jak zapewne zauwa żyłeś, w powyższym kodzie wywoływaliśmy funkcję, podając jej na zwę i argumenty po przecinku. Standardowo w Asemblerze się tak nie robi — przeważnie odkłada się argumenty na stos przy użyciu instrukcji PUSH i wywołuje funkcję za pomocą instrukcji CALL. Jednak makroasembler MASM ma pseudoinstrukcję, która pozwala wywo ływać funkcję w ten prosty i czytelny sposób. Dla porównania poni żej przedstawię wywołanie tej samej funkcji z użyciem INVOKE i bez. Wywołanie funkcji MessageBox z użyciem pseudoinstrukcji INVOKE: invoke MessageBox, NULL, addr MsgText, addr MsgCaption, MB_OK Wywołanie tej samej funkcji bez użycia pseudoinstrukcji INVOKE: push MB_OK push o ffs e t MsgCaption push o ffs e t MsgText push NULL c a ll MessageBox Korzystanie z INVOKE jest warte polecenia, gdyż dzięki temu kod jest bardziej przejrzysty i łatwiej się ustrzec błędu przy podawaniu ar gumentów funkcji. Ten rozdział był małym wprowadzeniem do programowania w Asem blerze dla systemu Windows. Poznałeś w nim budowę prostego pro gramu. W dalszych rozdziałach opiszę najważniejsze instrukcje, gdyż programowanie w Asemblerze to przede wszystkim instrukcje pro cesora, a nie tylko wywoływanie funkcji WinAPI. Rozdz ał 1. Podstawowe nformacje | 17
Rozdział 2. Podstawowe instrukcje procesora 2.1. Instrukcja kopiowania i instrukcje arytmetyczne Instrukcja kopiowania (MOV) Zacznijmy od najczęściej spotykanej instrukcji — MOV. Składnia: MOV cel, źródło To chyba najczęściej spotykana instrukcja. Kopiuje wartość z operandu źródłowego do operandu celu, przy czym pierwsza wartość pozostaje niezmieniona. Na przykład: mov eax, 25 ; EAXprzyjmuje wartość 25 mov ecx, eax ; skopiuj do ECX wartość z EAX mov zmienna, eax ; skopiuj do zmiennej wartość z EAX Dlaczego nie jest MOV zmienna, zmienna? Ponieważ procesor nie potrafi wykonać czegoś takiego. Musimy posłużyć się rejestrem pomocniczym (najpierw skopiować wartość do rejestru, a potem do zmiennej) lub odłożyć wartość na stos (instrukcja PUSH), po czym przy użyciu instrukcji POP zdjąć ją ze stosu i umiejscowić w drugiej zmiennej. Oprócz kopiowania wartości stałej do rejestru/zmiennej czy kopio wania wartości między rejestrami możemy skopiować wartość z da nego miejsca w pamięci. Wykonujemy to w następujący sposób: mov eax, dword p tr [ebx] Rejestr EBX zawiera adres, pod którym znajduje się wartość — zosta nie ona skopiowana do rejestru EAX. Instrukcje arytmetyczne Instrukcje arytmetyczne służą do wykonywania podstawowych działań matematycznych, takich jak dodawanie, odejmowanie, mnożenie i dzielenie. 18 | Asembler. Leksykon k eszonkowy
ADD (dodawanie) Składnia: ADD operand1, operand2 Instrukcja ADDdodaje wartość do rejestru lub komórki pamięci. Przykładowe użycie: add eax, 124 ; dodaje 124 do wartości w rejestrze EAX add ecx, ebx ; dodaje wartość z E B X do wartości w ECX add zmienna, 25 ; dodaje 25 do wartości w zmiennej ”zmienna” SUB (odejmowanie) Składnia: SUB operand1, operand2 Odejmuje wartość drugiego operandu od pierwszego i zapisuje wynik 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. IMUL (mnożenie ze znakiem) Składnia: IMUL wartość IMUL wynik, wartość, wartość IMUL wynik, wartość Mnoży wartość z EAX z podaną wartością (IMUL wartość) lub mnoży dwie wartości i wynik zachowuje w pierwszym operandzie (IMUL wynik, wartość, wartość), lub mnoży rejestr z wartością (IMUL wynik, wartość). DIV (dzielenie bez znaku) Składnia: DIV dzielnik Instrukcja ta dzieli wartość w rejestrze EAX przez wartość podaną jako dzielnik. Dzielona jest zawsze wartość w EAX. Także wynik zostanie umieszczony w tym rejestrze, natomiast reszta z dzielenia — w EDX. mov eax, 32 ; EAX przyjmuje wartość 32 mov ecx, 2 ; ECXprzyjmuje wartość 2 d iv ecx ; podziel EAXprzez ECX Rozdz ał 2. Podstawowe nstrukcje procesora | 19
IDIV (dzielenie ze znakiem) Składnia: IDIV dzielnik Działa podobnie jak instrukcja DIV, tyle że dzieli ze znakiem. 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 (zmi enna=zmienna-l). Przykład: dec eax ; zmniejsza wartość w EAX o 1 dec [zmienna] ; zmniejsza wartość zmiennej o 1 2.2. Instrukcje logiczne i przesunięć bitowych Instrukcje logiczne Instrukcje logiczne służą do wykonywania operacji logicznych na bitach, takich jak koniunkcja (iloczyn logiczny), alternatywa (suma logiczna), alternatywa wykluczająca i negacja (zaprzeczenie). AND (koniunkcja) Składnia: AND operand1, operand2 1 and 1 = 1 1 and 0 = 0 0 and 1 = 0 0 and 0 = 0 Dla przykładu wykonamy koniunkcję dla dwóch liczb binarnych: 11101010100 and 01010110000 = 01000010000 Przy ręcznym liczeniu najwygodniej napisać je jedna pod drugą i spraw dzać według tabelki poszczególne bity. 20 | Asembler. Leksykon k eszonkowy
OR (alternatywa) Składnia: OR operand1, operand2 1 or 1 = 1 1 or 0 = 1 0 or 1 = 1 0 or 0 = 0 Dla przykładu mamy dwie liczby binarne: 10100011100 or 01110010010 = 11110011110 A zatem bierzemy po kolei dwa odpowiadające sobie bity i spraw dzamy według tabelki, jaki będzie dla nich wynik. XOR (alternatywa wykluczająca) Składnia: XOR operand1, operand2 1 xor 1 = 0 1 xor 0 = 1 0 xor 1 = 1 0 xor 0 = 0 Będziemy często używać tej instrukcji do czyszczenia zawartości ja kiegoś rejestru (gdyż wynik operacji xor dla dwóch tych samych wartości daje zero). NOT (zaprzeczenie) Składnia: NOT operand1 not 1 = 0 not 0 = 1 Krótko mówiąc, odwraca wszystkie bity w podanym operandzie (zamiast 1 daje 0, zamiast 0 daje 1). Instrukcje przesunięć bitowych Instrukcje przesunięć bitowych służą do wykonywania operacji, które zmieniają miejsce bitów w rejestrze. Ujmując to prościej, bity są prze suwane w prawo lub lewo. SAL/SHL (przesunięcie arytmetyczne/logiczne w lewo) Składnia: SAL operand1, operand2 SHL operand1, operand2 Rozdz ał 2. Podstawowe nstrukcje procesora | 21
Przesuwa w lewo bity pierwszego operandu o podaną liczbę bitów w operandzie drugim. Starszy przesunięty bit zostaje zachowany w fladze CF (ang. Carry Flag), a młodsze bity są wypełniane zerami. Sposób przesuwania się bitów dla instrukcji SAL/SHL w 32-bitowym rejestrze prezentuje rysunek 2.1. Dla przykładu przesuniemy wartość w EAXo 1 bit w lewo: mov eax, 1 ; do EAX dajemy wartość 1 shl eax, 1 ; EA X będzie równy 2 (10 binarnie), ; gdyżjedynka się przemieściła, ; a na je j miejscu mamy zero, ; co daje wartość 10b (2h). Najmłodszy bit EAX (32 bity) jest zerowany 1 < n Carry Flag 31 15 1 0 (znacznik Starsze bity Młodsze bity przeniesienia) Rysunek 2.1. Rysunek prezentujący, jak przesuwają się bity dla instrukcji SAL/SHL SAR/SHR (przesunięcie arytmetyczne/logiczne w prawo) Składnia: SAR operand1, operand2 SHR operand1, operand2 Instrukcja SARprzesuwa w prawo bity pierwszego operandu o poda ną liczbę bitów w operandzie drugim. Najmłodszy przesunięty bit zostaje zachowany w fladze CF, a najstarszy bit zostaje zachowany. Instrukcja SHR działa podobnie, różni się tylko tym, że zeruje najstarszy bit (nazywany również bitem znaku). Sposób przesuwania się bitów dla instrukcji SAR został zaprezentowany na rysunku 2.2, a dla in strukcji SHR— na rysunku 2.3. Dla przykładu przesuniemy wartość w EAXo 1 bit w prawo: mov eax, 1 ; do EAX idzie wartość 1 shr eax, 1 ; EA X będzie równy 0, ; fla g a C F zostanie ustawiona. 22 | Asembler. Leksykon k eszonkowy
Rysunek 2.2. Rysunek prezentujący, jak przesuwają się hity dla instrukcji SAR Rysunek 2.3. Rysunek prezentujący, jak przesuwają się hity dla instrukcji SHR ROL/RCL (rotacja w lewo bez użycia flagi CF/z użyciem flagi CF) Składnia: ROL operand1, operand2 RCL operand1, operand2 Pierwsza instrukcja przesuwa logicznie w lewo pierwszy operand o liczbę bitów podaną w operandzie drugim. Nie używa flagi CF, więc najmłodszy bit przyjmuje wartość najstarszego bitu. Druga instrukcja przeprowadza tę samą czynność, ale przy użyciu flagi CF. Najmłodszy bit przyjmuje wartość, która była w CF, a znacznik CF przyjmuje wartość najstarszego bitu. Sposób obracania się bitów dla instrukcji ROL w 32- bitowym rejestrze został przedstawiony na rysunku 2.4, a dla instrukcji RCL— na rysunku 2.5. Rysunek 2.4. Rysunek prezentujący, jak obracają się hity dla instrukcji ROL Rozdz ał 2. Podstawowe nstrukcje procesora | 23
EAX (32 bity) 31 15 7 0 Starsze bity --- ------- Młodsze bity Carry Flag (znacznik przeniesienia) Rysunek 2.5. Rysunek prezentujący, jak obracają się bity dla instrukcji RCL ROR/RCR (rotacja w prawo bez użycia flagi CF/z użyciem flagi CF) Składnia: ROR operandl, operand2 RCR operandl, operand2 Pierwsza instrukcja przesuwa logicznie w prawo pierwszy operand o liczbę bitów podaną w operandzie drugim. Nie używa flagi CF, więc najstarszy bit przyjmuje wartość najmłodszego bitu. Druga in strukcja przeprowadza tę samą czynność, ale przy użyciu flagi CF. Najstarszy bit przyjmuje wartość, która była w CF, a znacznik CF przyjmuje wartość najmłodszego bitu. Sposób obracania się bitów w 32-bitowym rejestrze dla instrukcji ROR został zaprezentowany na rysunku 2.6, a dla instrukcji RCR— na rysunku 2.7. EAX (32 bity) 31 15 7 0 Starsze bity Młodsze bity ■<-------------------- Carry Flag (znacznik przeniesienia) Rysunek 2.6. Rysunek prezentujący, jak obracają się bity dla instrukcji ROR SHLD/SHRD (przesunięcie logiczne w lew o/w prawo podwójnego rejestru) Składnia: SHLD operandl, operand2, operand3 SHRD operandl, operand2, operand3 24 | Asembler. Leksykon k eszonkowy
EAX (32 bity) 31 15 7 0 Starsze bity Młodsze bity ------ — Carry Flag (znacznik przeniesienia) Rysunek 27. Rysunek prezentujący, jak obracają się hity dla instrukcji RCR Pierwsza instrukcja (SHLD) przesuwa w lewo bity w operandzie pierw szym o liczbę bitów podaną w operandzie trzecim. Najstarsze bity pierwszego operandu zostają wyrzucone (flaga CF przyjmuje wartość ostatniego wyrzuconego bitu), a najmłodsze bity przyjmują wartości najstarszych bitów z drugiego operandu. Uwaga! Bity drugiego ope- randu pozostają niezmienione. SHRD przesuwa natomiast bity pierw szego operandu o liczbę bitów podaną w operandzie trzecim. Naj młodsze bity pierwszego operandu zostają wyrzucone, a najstarsze bity przyjmują wartość najmłodszych bitów z operandu drugiego. Bity drugiego operandu pozostają niezmienione. Sposób przesuwania się bitów dla instrukcji SHLD został zaprezentowany na rysunku 2.8, a dla instrukcji SHRD— na rysunku 2.9. Przykład dla instrukcji SHLD: mov ecx, 0 ; zerujemy ECX mov eax, OFFFFFFFFh ; do EAX wartość OFFFFFFFFh shld ecx, eax, 2 ; EA X będzie równy OFFFFFFFCh, gdyż ; dwa jeg o najmłodsze bity zostały zastąpione ; przez dwa najstarsze bity rejestru ECX, ; które miały wartość zero. Przykład dla instrukcji SHRD: mov ecx, 0 ; zerujemy ECX mov eax, OFFFFFFFFh ; do EAX wartość OFFFFFFFFh shrd ecx, eax, 2 ; EA X będzie równy 3FFFFFFFh, gdyż ; dwa jeg o najstarsze bity zostały ; zastąpione dwoma najmłodszymi bitami ; z rejestru ECX, które miały wartość zero. Rozdz ał 2. Podstawowe nstrukcje procesora | 25
Rysunek 2.8. Rysunek prezentujący, jak przesuwają się bity dla instrukcji SHLD Rysunek 2.9. Rysunek prezentujący, jak przesuwają się bity dla instrukcji SHRD 2.3. Instrukcje wywołania procedury i powrotu Instrukcje związane z procedurami Procedury to wydzielone części programu, które mogą być wielokrotnie wywoływane, bez potrzeby kopiowania tego samego kodu w różne miejsca. Tworzenie takich procedur (nazywanych też podprogramami) jest możliwe przy użyciu instrukcji związanych z procedurami. CALL (wywołanie funkcji/podprogramu) Składnia: CALL nazwa_funkcji 26 | Asembler. Leksykon k eszonkowy