KATEDRA INFORMATIKY ˇ I´RODOVEˇDECKA ´ FAKULTA PR UNIVERZITA PALACKE´HO
ASSEMBLER ALESˇ KEPRT
´N VY´VOJ TOHOTO UCˇEBNI´HO TEXTU JE SPOLUFINANCOVA ´ LNI´M FONDEM A STA ´ TNI´M ROZPOCˇTEM CˇESKE´ REPUBLIKY EVROPSKY´M SOCIA
Olomouc, verze 2.4.2008
Abstrakt
Tento text ma´ za cı´l poslouzˇit jako ucˇebnice programova´nı´ v assembleru. Popisuje 32bitovy´ assembler procesoru˚ Intel (rˇady oznacˇovane´ jako IA32 cˇi x86) s cı´lem umozˇnit cˇtena´rˇu˚m cˇi studentu˚m za´kladnı´ pochopenı´ principu˚, na ktery´ch funguje assembler a potazˇmo mikroprocesory v soucˇasny´ch pocˇ´ıtacˇ´ıch. Ve vy´uce je assembler obvykle soucˇa´stı´ cvicˇenı´ neˇktere´ho z kurzu˚ v rˇadeˇ „Operacˇnı´ syste´my“.
Cı´lova´ skupina
Text je prima´rneˇ urcˇen pro studenty oboru Aplikovana´ informatika uskutecˇnˇovane´ho v kombinovane´ formeˇ na Prˇ´ırodoveˇdecke´ fakulteˇ Univerzity Palacke´ho v Olomouci a da´le pro studenty oboru˚ Informatika a Aplikovana´ informatika uskutecˇnˇovane´ho v prezencˇnı´ formeˇ tamte´zˇ. Vsˇichni jmenovanı´ studenti majı´ programova´nı´ v assembleru jako povinny´ prˇedmeˇt sve´ho bakala´rˇske´ho studia.
Pozna´mka Tato publikace obsahuje rˇadu obra´zku˚ (technicky´ch diagramu˚) popisujı´cı´ch ru˚zne´ detaily ty´kajı´cı´ se procesoru˚ rˇady x86. Veˇtsˇina teˇchto jich je prˇevzata z publikace [IA32], kterou vydala spolecˇnost Intel jako u´plny´ manua´l ke svy´m procesoru˚m. Vsˇechny svazky je mozˇno volneˇ sta´hnout z internetu.
Obsah 1
2
3
4
Motivace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.1
Procesor, strojovy´ ko´d a assembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
1.2
Co se naucˇ´ıme a procˇ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
´ vodnı´ kru˚cˇky s assemblerem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . U
11
2.1
Strategie programova´nı´ v assembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.2
Assembler a vysˇsˇ´ı programovacı´ jazyky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.3
Struktura programu v inline assembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.4
Nasˇe prvnı´ instrukce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
2.5
Prvnı´ cvicˇenı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.6
Dalsˇ´ı za´kladnı´ konstrukty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.7
Pra´ce s ru˚zneˇ velky´mi cˇ´ısly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.7.1
Zu´zˇenı´ na mensˇ´ı typ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
2.7.2
Rozsˇ´ırˇenı´ na veˇtsˇ´ı typ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
2.8
Popis instrukcı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.9
Podmı´neˇne´ skoky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
2.9.1
Instrukce jecxz imm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
2.10 Za´sady strukturovane´ho programova´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
Registry a adresova´nı´ pameˇti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.1
Registry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.2
Vy´beˇr registru˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.3
Adresovacı´ rezˇimy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.4
Datove´ typy a prˇetypova´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
3.5
Programovy´ za´sobnı´k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.5.1
Instrukce push reg/mem/imm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.5.2
Instrukce pop reg/mem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
3.6
Pameˇt’ove´ modely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
3.7
Mozˇnosti pra´ce s poli a pointery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
3.8
Dalsˇ´ı pozna´mky k pra´ci s pameˇtı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.8.1
Instrukce loop imm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
Prˇ´ıznaky a podmı´neˇne´ vykona´va´nı´ ko´du . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
4.1
Prˇ´ıznaky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
4.1.1
Sezna´menı´ s prˇ´ıznaky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
4.1.2
Aritmeticke´ prˇ´ıznaky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
4.1.3
ˇ ´ıdicı´ prˇ´ıznaky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . R
39
4.2
Podmı´neˇne´ skoky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
4.3
Dalsˇ´ı instrukce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
4.3.1
Instrukce cmp op1,op2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.2
Instrukce test op1,op2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.3
Instrukce bt op1,op2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.4
Instrukce adc op1,op2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.5
Instrukce sbb op1,op2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.6
Instrukce pro explicitnı´ zmeˇnu prˇ´ıznaku˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.7
Instrukce nemeˇnı´cı´ prˇ´ıznaky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
4.3.8
Bitove´ rotace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
4.4
Vı´cebitove´ operace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
4.5
Vyhy´ba´nı´ se podmı´neˇny´m instrukcı´m (optimalizace) . . . . . . . . . . . . . . . . . . . . . . . . . .
42
4.6
Veˇtvenı´ ko´du . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
4.6.1
Konstrukce if–then–else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
4.6.2
Konstrukce switch–case–break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
Cykly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
4.7.1
Konstrukce repeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
4.7.2
Konstrukce do–while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
4.7.3
Konstrukce while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
4.7.4
Konstrukce for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46
Za´sobnı´k a podprogramy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
5.1
Programovy´ za´sobnı´k . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
5.1.1
Instrukce push reg/mem/imm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
5.1.2
Instrukce pop reg/mem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
5.1.3
Instrukce call reg/mem/imm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
5.1.4
Instrukce ret / ret imm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
5.1.5
Vy´znam za´sobnı´ku prˇi vola´nı´ podprogramu˚ . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
5.2
Cvicˇenı´ s rekurzı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
5.3
Externı´ assembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
5.3.1
Historicke´ souvislosti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
5.4
Microsoft Macro Assembler (MASM) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
5.5
Kostra programu v assembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
5.6
Globa´lnı´ promeˇnne´ a konstanty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
5.6.1
Definice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
5.6.2
Export a import symbolu˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
Podprogramy, procedury a funkce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.7.1
Vysveˇtlenı´ pojmu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.7.2
Definice a export procedury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.7.3
Import procedury do jazyka C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
5.7.4
Import C funkce do assembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
4.7
5
5.7
5.7.5
Na´vratova´ hodnota . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
5.7.6
Prˇeda´va´nı´ parametru˚ prˇi vola´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
5.7.7
Pouzˇitı´ prˇedany´ch parametru˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
5.7.8
Loka´lnı´ promeˇnne´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
5.7.9
Vnorˇene´ funkce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
64
Konstanty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
Prostrˇedky makroassembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
6.1
Direktivy, pseudoinstrukce a makra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
6.2
Export/import a hlavicˇkove´ soubory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
6.3
Vy´razy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69
6.4
Definice vlastnı´ch typu˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69
6.5
Podmı´neˇny´ prˇeklad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
6.6
FP cˇ´ısla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
6.7
Bezejmenna´ na´veˇsˇtı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
6.8
Prˇ´ıkazy pro blokove´ podmı´nky a opakova´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
6.8.1
Podmı´neˇne´ vykona´nı´ bloku
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
6.8.2
Opakova´nı´ bloku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
6.8.3
Opera´tory podmı´neˇne´ho vykona´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
Procedury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
6.9.1
Definice a vola´nı´ procedury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
6.9.2
Uchova´nı´ meˇneˇny´ch registru˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
6.9.3
Loka´lnı´ promeˇnne´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
A Dalsˇ´ı te´mata assembleru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
A.1 Co bylo ve stare´m online textu navı´c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
A.2 Doporucˇene´ volby ve vlastnostech projektu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
B Historie MASM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77
´ vod do jazyka C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C U
78
´ vod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.1 U
78
C.2 Datove´ typy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
C.3 Umı´steˇnı´ ko´du a dat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
C.4 Vola´nı´ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
C.5 Hlavicˇkove´ soubory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
C.6 Neˇktere´ uzˇitecˇne´ funkce CRT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
5.8 6
6.9
C.6.1
Psanı´ na obrazovku . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
C.6.2
Alokace pameˇti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
80
D Seznam obra´zku˚ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
E Seznam tabulek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
82
1
Motivace
Studijnı´ cı´le: Prostudova´nı´m kapitoly zı´ska´ student motivaci k dalsˇ´ımu studiu. Dozvı´ se, co to assembler je, procˇ se ucˇ´ı a jaka´ je jeho pozice v dzˇungli programovacı´ch jazyku˚. Klı´cˇova´ slova: assembler, strojovy´ ko´d, instrukce, vysˇsˇ´ı a nizˇsˇ´ı jazyky Potrˇebny´ cˇas: 25 minut. V u´vodu kurzu assembleru je vhodne´ veˇnovat jisty´ cˇas vysveˇtlenı´, co na´s vlastneˇ motivuje ucˇit se tento programovacı´ jazyk. Rˇada z va´s se s nı´m zrˇejmeˇ doposud „na vlastnı´ ku˚zˇi“ vu˚bec nesetkala a toto mu˚zˇe by´t pocit’ova´no jako jisty´ signa´l, zˇe assembler nebude azˇ tak du˚lezˇity´m jazykem. Skutecˇneˇ, v dnesˇnı´ dobeˇ se v assembleru prˇ´ılisˇ neprogramuje. Jedna´ se o historicky´ programovacı´ jazyk, ktery´ je z dnesˇnı´ho pohledu pomeˇrneˇ zvla´sˇtnı´ a pro tvorbu programu˚, jake´ se dnes beˇzˇneˇ deˇlajı´, je prˇekonany´ cˇi zcela nevhodny´. Ve vy´uce informatiky ma´ vsˇak vy´znamne´ postavenı´ pra´veˇ pro svou zvla´sˇtnost a historickou povahu. Assembler je stejneˇ stary´ jako sa´m mikroprocesor a pochopı´me-li, jak funguje assembler, pochopı´me tı´m soucˇasneˇ, jak funguje samotny´ mikroprocesor. Prvotnı´ motivacı´ k ucˇenı´ assembleru tedy je snaha o pochopenı´ principu˚, na ktery´ch „opravdu“ stojı´ pocˇ´ıtacˇove´ programy. At’uzˇ napı´sˇete va´sˇ program v ktere´mkoliv jazyce, at’uzˇ pouzˇijete prˇi vasˇ´ı pra´ci ktere´koliv programovacı´ paradigma, va´sˇ program nakonec vzˇdy vykona´va´n neˇjaky´m mikroprocesorem a ma´ podobu posloupnosti primitivnı´ch instrukcı´, ktere´ odpovı´dajı´ programu zapsane´m v assembleru.
1.1
Procesor, strojovy´ ko´d a assembler
Pru˚vodce studiem V minulosti bylo mnoho knizˇnı´ho papı´ru zbytecˇneˇ popsa´no na te´ma prvnı´ch pocˇ´ıtacˇu˚ a prvnı´ch mikroprocesoru˚. Kazˇde´ takove´ prvenstvı´ vsˇak trvalo jen do okamzˇiku, kdy byly po letech odtajneˇny ty cˇi ony vojenske´ za´znamy. Pochopitelneˇ, nic tak du˚lezˇite´ho nenı´ jako prvnı´ pouzˇito v civilnı´ sfe´rˇe a verˇejneˇ.
Mikroprocesory, cˇi jednodusˇeji procesory, v pocˇ´ıtacˇ´ıch i mnoha jiny´ch elektronicky´ch zarˇ´ızenı´ch jsou nejcˇasteˇji von Neumannova cˇi Harvardske´ho typu (podle prˇ´ıslusˇne´ pocˇ´ıtacˇove´ architektury). Tyto typy procesoru˚ se vyznacˇujı´ tı´m, zˇe procesor vykona´va´ program, ktery´ je ulozˇen v pameˇti. Procesor tedy zna´ cˇi umı´ vykona´vat urcˇite´ prˇ´ıkazy – rˇ´ıka´me jim odborneˇ instrukce. Kazˇda´ instrukce je prˇitom z lidske´ho pohledu jedno cˇi neˇkolik cˇ´ısel ulozˇeny´ch za sebou v pameˇti. Kazˇdy´ pocˇ´ıtacˇovy´ program je tedy de facto jen pole cˇ´ısel. Pru˚vodce studiem Vı´te jaky´ je rozdı´l mezi CPU a mikroprocesorem? CPU je centra´lnı´ zpracovacı´/procesnı´ jednotka, hlavnı´ mozek pocˇ´ıtacˇe. Mikroprocesor je jednou z mnoha mozˇny´ch implementacı´ CPU. Pocˇ´ıtacˇe a CPU existovaly jizˇ prˇed vyna´lezem mikroprocesoru˚. Vyna´lez mikroprocesoru vsˇak vy´razneˇ redukoval velikost i cenu pocˇ´ıtacˇu˚ a dnes je beˇzˇne´, zˇe kazˇdy´ pocˇ´ıtacˇ obsahuje mnoho mikroprocesoru˚. CPU je naopak v kazˇde´m pocˇ´ıtacˇi obvykle pouze jedna, i kdyzˇ v poslednı´ dobeˇ roste obliba vı´ceprocesorovy´ch pocˇ´ıtacˇu˚ cˇi vı´ceja´drovy´ch procesoru˚, kde pojem „procesor“ cˇi „ja´dro“ se vztahuje pra´veˇ k pocˇtu CPU. Jak je videˇt, pojmy CPU a procesor cˇasto sply´vajı´ cˇi by´vajı´ zameˇnˇova´ny a z hlediska softwaru z toho nevznika´ za´sadnı´ proble´m.
6
Instrukce je za´kladnı´ primitivnı´ prˇ´ıkaz, ktere´mu procesor rozumı´.
Tento zpu˚sob za´pisu programu v rˇecˇi pocˇ´ıtacˇe se nazy´va´ „strojovy´ ko´d“. Pro lidi to je jen posloupnost cˇ´ısel, jednicˇek a nul, cˇi jiny´ch symbolu˚, ktery´mi zobrazı´me pameˇt’ pocˇ´ıtacˇe do cˇloveˇku cˇitelne´ podoby. To, zˇe tato cˇ´ısla umı´me cˇ´ıst, samozrˇejmeˇ jesˇteˇ neznamena´, zˇe umı´me porozumeˇt tomu, co takovy´ program deˇla´. Kromeˇ cˇ´ıselne´ho za´pisu vytva´rˇejı´ vy´robci ke kazˇde´mu sve´mu procesoru take´ prˇedpis pro lidsky´ za´pis instrukcı´. Kazˇda´ instrukce ma´ neˇjake´ jme´no, obvykle zkratku z anglicke´ho slovesa. Prˇ´ıpadneˇ je doplneˇna´ o neˇjake´ dalsˇ´ı prˇedpony cˇi prˇ´ıpony. Namı´sto dlouhe´ rˇady cˇ´ısel pak program ve strojove´m ko´du ma´ podobu rˇady slovnı´ch zkratek, ktere´ zapisujeme na rˇa´dky pod sebe. Jako prˇ´ıklad si uved’me program pro vy´pocˇet absolutnı´ hodnoty cele´ho cˇ´ısla. V C++ mu˚zˇe vypadat naprˇ´ıklad takto: int a ; ... i f ( a <0) a=−a ; Prvnı´ rˇa´dek deklaruje promeˇnnou a samotny´ ko´d programu ma´ pak de´lku jednoho jedine´ho rˇa´dku, jehozˇ smysl je na prvnı´ pohled jasny´. Ve strojove´m ko´du tento program vypada´ takto: A1 78 71 41 00 83 F8 00 7D 02 F7 D8. Takto je zapsa´n v sˇestna´ctkove´ soustaveˇ, ma´ de´lku 12 bajtu˚. V tomto 12bajtove´m programu na´m cˇ´ısla na druhe´ azˇ pa´te´ pozici uda´vajı´, ze ktere´ promeˇnne´ absolutnı´ hodnotu pocˇ´ıta´me. Zˇe nevı´te, procˇ to jsou zrovna tato cˇ´ısla? Je to cˇ´ıslo pameˇt’ove´ bunˇky s nasˇ´ı promeˇnnou. Kdyzˇ programujeme ve strojove´m ko´du, tato cˇ´ısla musı´me skutecˇneˇ sami rˇesˇit a to deˇla´ programy velmi neprˇehledne´. Proto programova´nı´ ve strojove´m ko´du bylo mozˇne´ jen v da´vny´ch doba´ch, kdy pocˇ´ıtacˇe meˇly velmi male´ pameˇti. Na´sˇ program mu˚zˇeme namı´sto cˇ´ısel zapsat v instrukcˇnı´ch ko´dech takto: mov cmp jge neg
eax , dword p t r 417178 h eax , 0 4113 B4h eax
Prvnı´ slovo na kazˇde´m rˇa´dku je instrukce a za nı´m pak na´sleduje uprˇesneˇnı´ k instrukci. Jednou z du˚lezˇity´ch vlastnostı´ strojove´ho ko´du je, zˇe kazˇdy´ procesor ma´ svu˚j vlastnı´ strojovy´ ko´d. Rozdı´ly mezi jednotlivy´mi procesory prˇitom mohou by´t pomeˇrneˇ znacˇne´. Programa´toru˚m jsou vsˇak tyto rozdı´ly obvykle skryty dı´ky tomu, zˇe pouzˇ´ıvajı´ tzv. „vysˇsˇ´ı programovacı´ jazyky“. Pru˚vodce studiem Pojem „vy´sˇky“ jazyka souvisı´ s tı´m, zda je jazyk blizˇsˇ´ı cˇloveˇku (pak je nazy´va´n vysˇsˇ´ım), cˇi stroji (pak je nazy´va´m nizˇsˇ´ım cˇi nı´zkou´rovnˇovy´m). Ru˚zne´ programovacı´ jazyky prˇitom mu˚zˇeme povazˇovat za vysˇsˇ´ı cˇi nizˇsˇ´ı podle toho, z jake´ho u´hlu pohledu je hodnotı´me. Naprˇ´ıklad C++ je objektoveˇ orientovany´ jazyk, ktery´ je z mnoha hledisek vysˇsˇ´ım jazykem, avsˇak z pohledu pra´ce s pameˇtı´ a bezpecˇnosti jde o jazyk spı´sˇe nizˇsˇ´ı. Jak jizˇ mozˇna´ tusˇ´ıte, assembler je jednı´m z nejnizˇsˇ´ıch jazyku˚, protozˇe se velmi podoba´ strojove´mu ko´du. Slovo assembler se veˇtsˇinou pı´sˇe s maly´m a na zacˇa´tku, nebot’ obvykle nejde o jme´no neˇjake´ho u´plneˇ konkre´tnı´ho jazyka, ale pra´veˇ pouze o zkratku pro jisty´ zpu˚sob za´pisu strojove´ho ko´du se symbolicky´mi adresami. Slovo assembler je odvozeno od programu, ktery´ prˇeva´dı´ zdrojovy´ ko´d tohoto jazyka do strojove´ho ko´du (v anglicˇtineˇ: sloveso assemble, od neˇj odvozene´ podstatne´ jme´no assembly). Rˇ´ıkat jazyku na´zvem programu, ktery´ s nı´m pracuje, se mu˚zˇe zda´t matoucı´ cˇi chybne´, ale je to velmi zazˇite´ a nebudeme se tomu bra´nit.
7
Strojovy´ ko´d je jediny´m jazykem, ktere´mu procesor prˇ´ımo rozumı´.
Jazyk assembler mezi vysˇsˇ´ı programovacı´ jazyky nepatrˇ´ı, je to totizˇ jen jaka´si jina´ varianta strojove´ho ko´du, kde kromeˇ slovnı´ch na´zvu˚ instrukcı´ jesˇteˇ nahradı´me ostatnı´ zbyla´ cˇ´ısla. Assembler by´val v minulosti v cˇesˇtineˇ take´ nazy´va´n opisem „Jazyk symbolicky´ch adres“ a tento dnes jizˇ prakticky nepouzˇ´ıvany´ na´zev prˇesneˇ vystihuje podstatu assembleru: Adresy (nesrozumitelna´ cˇ´ısla) totizˇ assembler nahrazuje symbolicky´mi jme´ny. Podı´vejme se tedy znovu na prˇedchozı´ prˇ´ıklad a zapisˇme jej v assembleru: mov cmp jge neg skip :
eax , a eax , 0 skip eax
Acˇkoliv pro pochopenı´ tohoto programu sta´le musı´me zna´t vy´znam jednotlivy´ch instrukcı´ „zkratek“ v prvnı´m sloupci, takto zapsany´ program je jizˇ pro cˇloveˇka prˇehledny´. Pojmy strojovy´ ko´d a assembler cˇasto sply´vajı´, nebot’si odpovı´dajı´ te´meˇrˇ 1:1 a cˇloveˇk obvykle pı´sˇe program v assembleru proto, aby jej stroj prˇesneˇ tı´mto zpu˚sobem vykona´val ve strojove´m ko´du. Velmi cˇasto tedy tyto dva pojmy sply´vajı´ a neudeˇla´me chybu, budeme-li jej v dalsˇ´ım textu povazˇovat za jedno a tote´zˇ. Pru˚vodce studiem Mozˇna´ va´s nynı´ zajı´ma´, jak moc se lisˇ´ı strojove´ ko´dy cˇi assemblery jednotlivy´ch procesoru˚. Bohuzˇel, rozdı´ly jsou cˇasto obrovske´. Jme´na instrukcı´ by´vajı´ jina´ a za´pis jejich parametru˚ take´. Samotny´ princip fungova´nı´ procesoru˚ je vsˇak vzˇdy stejny´. Zajı´mavou pozici zde zaujı´majı´ jazyky modernı´ch vy´pocˇetnı´ch stroju˚, jako je naprˇ´ıklad bytecode jazyka Java. Je to strojovy´ jazyk, ke ktere´mu byl teprve pozdeˇji vytvorˇen hardwarovy´ procesor. Takovy´ procesor pak vlastneˇ prˇirozeneˇ vykona´va´ program v jazyku Java. Na zkouma´nı´, jak moc je takovy´ „pozpa´tku“ vytvorˇeny´ vy´pocˇetnı´ stroj podobny´ klasicky´m procesoru˚m, je vsˇak zde na zacˇa´tku studia prˇ´ılisˇ brzy. K te´to ota´zce se vra´tı´me pozdeˇji.
1.2
Co se naucˇ´ıme a procˇ
Zda´ se va´m, zˇe stacˇ´ı mı´t po ruce tabulku vysveˇtlujı´cı´ vy´znam jednotlivy´ch instrukcı´ a mu˚zˇeme smeˇle programovat v assembleru? Do jiste´ mı´ry ano. Zrˇejmeˇ va´s ale v na´sledujı´cı´ch kapitola´ch prˇekvapı´, jak primitivnı´ tyto instrukce jsou a jak ma´lo jedna takova´ instrukce umı´. Assembler naprˇ´ıklad nezna´ prˇ´ıkaz if. Stejneˇ tak nezna´ for, while cˇi procedury a funkce. Zˇa´dny´ z teˇchto prvku˚ celkem beˇzˇny´ch ve vysˇsˇ´ıch jazycı´ch ve skutecˇnosti procesor prˇ´ımo nezna´. Naucˇit se programovat v assembleru tedy kromeˇ ucˇenı´ dlouhe´ rˇady jmen a vy´znamu˚ instrukcı´ znamena´ take´ naucˇit se postupy, jak z teˇchto primitivnı´ch instrukcı´ sestavovat smysluplne´ programy. Jednı´m ze zpu˚sobu˚, jak v assembleru programovat, je naucˇit se pomocı´ jeho instrukcı´ sestavit ko´d odpovı´dajı´cı´ zmı´neˇny´m prˇ´ıkazu˚m if cˇi for, funkcı´m apod. To je taky zrˇejmeˇ nejvhodneˇjsˇ´ı zpu˚sob, jak s assemblerem zacˇ´ıt. Pru˚vodce studiem Je s podivem, zˇe klasicke´ ucˇebnice programova´nı´ v assembleru nejdou touto cestou. Namı´sto toho, aby vysveˇtlily, jak v assembleru udeˇlat program pomocı´ prvku˚, ktere´ cˇloveˇk zna´ z jiny´ch jazyku˚, prˇ´ılisˇ cˇasu veˇnujı´ vyjmenova´va´nı´ jednotlivy´ch instrukcı´ procesoru. K vysveˇtlenı´ „jak programovat“ je veˇnova´no naproste´ minimum prostoru a zka´za je doplneˇna tı´m, zˇe namı´sto u´loh, ve ktery´ch ma´ assembler rea´lny´ smysl, je tento procvicˇova´n na primitivnı´ch a nudny´ch prˇ´ıkladech.
8
V praxi se assembler pouzˇ´ıva´ jednak ve velmi specificky´ch situacı´ch, kde nenı´ mozˇne´ pouzˇ´ıt jiny´ programovacı´ jazyk (naprˇ´ıklad neˇktere´ cˇa´sti ja´dra operacˇnı´ho syste´mu), nebo tam, kde programy napsane´ v jiny´ch jazycı´ch nejsou dostatecˇneˇ rychle´. Acˇkoliv naprˇ´ıklad C++ zvla´dne 99% u´loh stejneˇ efektivneˇ nebo alesponˇ uspokojiveˇ dobrˇe (ve srovna´nı´ s assemblerem), naprˇ´ıklad vy´pocˇty v oblasti pocˇ´ıtacˇove´ grafiky jsou v assembleru obvykle vy´razneˇ rychlejsˇ´ı nezˇ v C++ cˇi jiny´ch jazycı´ch. Obecneˇ je program v assembleru rychlejsˇ´ı prˇi splneˇnı´ teˇchto dvou podmı´nek: 1. Pracujeme s velky´m polem cˇ´ısel. 2. S kazˇdy´m prvkem pole udeˇla´me stejnou operaci. Konkre´tnı´ prˇ´ıklady si uka´zˇeme v dalsˇ´ıch kapitola´ch. K procvicˇova´nı´ assembleru se vsˇeobecneˇ hodı´ u´lohy pracujı´cı´ s polem cˇ´ısel cˇi znaku˚. Nejcˇasteˇji budeme pracovat s textovy´mi rˇeteˇzci, tedy poli znaku˚. I takove´ programy jsou v assembleru obvykle rychlejsˇ´ı nezˇ v jiny´ch jazycı´ch, avsˇak tyto operace jsou celkoveˇ cˇasoveˇ nena´rocˇne´, takzˇe i kdyby byl program v assembleru trˇeba 2× rychlejsˇ´ı, usˇetrˇ´ıme tı´m v praxi neˇkolik mikrosekund, cozˇ je nepodstatne´. S poli se v assembleru prˇitom da´ pracovat pouze pomocı´ pointeru˚. Zde je zajı´mave´, zˇe zatı´mco v C++ mı´vajı´ studenti obvykle s pointery proble´my, dı´ky assembleru pak syste´m pointeru˚ v jazyku C++ pochopı´. Kromeˇ pra´ce s poli vyuzˇijeme assembler take´ k pochopenı´ principu vola´nı´ funkce (cˇi procedury), prˇeda´va´nı´ parametru˚ a souvisejı´cı´ch veˇcı´. Tyto zda´nliveˇ trivia´lnı´ operace jsou totizˇ v assembleru doslova odhaleny ve sve´ nahoteˇ, cozˇ na´m odhalı´ princip fungova´nı´ loka´lnı´ch promeˇnny´ch a programove´ho za´sobnı´ku, ktery´ zna´te z prˇedna´sˇek Operacˇnı´ syste´my cˇi Struktura pocˇ´ıtacˇu˚. Shrnutı´ V te´to u´vodnı´ kapitole jsme zı´skali motivaci k dalsˇ´ımu studiu assembleru. Vysveˇtlili jsme si, co to je assembler a procˇ se jej ma´me ucˇit. Za´rovenˇ jsme se sezna´mili s neˇktery´mi za´kladnı´mi pojmy, jako instrukce. Pojmy k zapamatova´nı´ • Strojovy´ ko´d • Assembler • Vysˇsˇ´ı a nizˇsˇ´ı jazyky Kontrolnı´ ota´zky 1. 2. 3. 4.
Co je to instrukce? Procˇ je assembler nizˇsˇ´ım programovacı´m jazykem? Jaky´ je hlavnı´ rozdı´l mezi strojovy´m ko´dem a assemblerem? Procˇ se u strojove´ho ko´du a assembleru velmi cˇasto pouzˇ´ıva´ pra´veˇ sˇestna´ctkova´ cˇ´ıselna´ soustava? 5. Z drˇ´ıveˇjsˇ´ıho studia byste meˇli zna´t jazyky Scheme a C++ nebo C#. Ktery´ z nich je vysˇsˇ´ı a procˇ? Cvicˇenı´ 1. Prˇeved’te prvnı´ cˇtyrˇi cˇ´ısla za´pisu programu v nasˇem prˇ´ıkladeˇ ze sˇestna´ctkove´ do desı´tkove´ soustavy.
´ koly k textu U
9
1. Pro dalsˇ´ı studium assembleru je velmi vhodne´ ovla´dat dvojkovou a sˇestna´ctkovou cˇ´ıselnou soustavu. Zopakujte si je. 2. Zopakujte si take´ algoritmy pro prˇevod mezi desı´tkovou, dvojkovou a sˇestna´ctkovou soustavou. To va´m usˇetrˇ´ı mnoho cˇasu prˇi pra´ci s assemblerem.
ˇ esˇenı´ R 1. 161 120 113 65. Vsˇimneˇte si, zˇe sˇestna´ctkova´ cˇ´ısla jsou vzˇdy dvojciferna´ a reprezentujı´ hodnotu jednoho bajtu. Kazˇda´ cifra mu˚zˇe naby´vat jedne´ z sˇestna´cti hodnot (10 cˇ´ısel + 6 prvnı´ch pı´smen abecedy). Prˇevod do desı´tkove´ soustavy udeˇla´me pomocı´ vzorce: 16× prvnı´ cifra + druha´ cifra. Pro prˇevod opacˇny´m smeˇrem je dobre´ zna´t zpameˇti na´sobky cˇ´ısla 16, dı´ky cˇemuzˇ velmi rychle zjistı´me prvnı´ cifru. Druhou cifru pak snadno dopocˇ´ıta´me tak, aby soucˇet byl roven pu˚vodnı´mu cˇ´ıslu.
10
´ vodnı´ kru˚cˇky s assemblerem U
2
Studijnı´ cı´le: V te´to kapitole zacˇneme programovat v assembleru. Jelikozˇ je to slozˇity´ jazyk, nebudeme la´tku probı´rat linea´rneˇ a neˇktere´ detaily odlozˇ´ıme na pozdeˇji. Vyzkousˇ´ıme si napsat prvnı´ maly´ progra´mek v assembleru a na konci kapitoly si prˇipomeneme za´sady strukturovane´ho programova´nı´. Tato teorie na´m prˇi dalsˇ´ı pra´ci hodneˇ pomu˚zˇe. Klı´cˇova´ slova: instrukce, operandy, registry, vysˇsˇ´ı jazyky, strukturovane´ programova´nı´ Potrˇebny´ cˇas: 55 minut.
2.1
Strategie programova´nı´ v assembleru
Aby va´m assembler nezlomil vaz, je velmi du˚lezˇite´ sezna´mit se hned na zacˇa´tku se spra´vnou strategiı´, jak v assembleru programovat. Ma´me-li rˇesˇit neˇjaky´ u´kol v assembleru, nejprve mu musı´me rozumeˇt a musı´me umeˇt jej algoritmicky vyrˇesˇit (na u´rovni slovnı´ho popisu algoritmu). Namı´sto slovnı´ho popisu rˇesˇenı´ cˇasto poslouzˇ´ı prˇ´ımo rˇesˇenı´ v jine´m programovacı´m jazyce, naprˇ´ıklad C++ nebo C#. V tom prˇ´ıpadeˇ se samozrˇejmeˇ musı´me omezit na ty konstrukty, ktere´ z teˇchto jazyku˚ umı´me prˇeve´st do assembleru. Obra´ceneˇ, pokud nevı´me, jak zadanou u´lohu rˇesˇit, nevyrˇesˇ´ıme ji ani v assembleru. Je-li naprˇ´ıklad zada´na u´loha „Zmeˇnˇte textovy´ rˇeteˇzec na tenty´zˇ rˇeteˇzec pozpa´tku a udeˇlejte to prˇ´ımo v mı´steˇ pu˚vodnı´ho rˇeteˇzce.“, pak tuto u´lohu bud’ umı´me vyrˇesˇit v jine´m jazyku a v assembleru mu˚zˇeme postupovat stejneˇ, nebo ji vyrˇesˇit neumı´me a pak se studium programovacı´ho jazyka spı´sˇe meˇnı´ ve studium algoritmizace a dokud spra´vny´ algoritmus nesestavı´me, lopota s assemblerem je zbytecˇna´. U zada´nı´ slozˇiteˇjsˇ´ıch u´loh proto bude uvedeno i uka´zkove´ rˇesˇenı´ v jine´m jazyce.
2.2
Assembler a vysˇsˇ´ı programovacı´ jazyky
Jak jsme videˇli vy´sˇe, na´sˇ prvnı´ program v assembleru nebyl obvykly´ „Hello World“1 , ale mnohem jednodusˇsˇ´ı program na vy´pocˇet absolutnı´ hodnoty cele´ho cˇ´ısla. Hello World v assembleru by pro na´s totizˇ na u´vod byl poneˇkud slozˇity´ – assembler totizˇ neumı´ pracovat prˇ´ımo s textem a hlavneˇ nema´ zˇa´dny´ prˇ´ıkaz pro vy´pis na obrazovku. Pra´veˇ proto, zˇe v assembleru jsou i zda´nliveˇ jednoduche´ veˇci velmi slozˇite´ a pracne´, nasˇe studium zacˇneme na tzv. inline assembleru. To je assembler, ktery´ vpisujeme do vysˇsˇ´ıho programovacı´ho jazyka. V nasˇem prˇ´ıpadeˇ to bude jazyk C++. Strucˇny´ u´vod do jazyka C++ pro programa´tory v C# najdete v prˇ´ıloze C. Kromeˇ C++ lze pouzˇ´ıt i jazyk C, Turbo Pascal nebo jine´ starsˇ´ı jazyky. Naopak veˇtsˇina soucˇasny´ch jazyku˚, vcˇetneˇ C#, Visual Basicu a Javy, vkla´da´nı´ assembleru neumozˇnˇuje. V dalsˇ´ım textu bude cˇasto zminˇova´n jazyk C++, avsˇak to, co o neˇm bude napsa´no, cˇasto platı´ i o dalsˇ´ıch vysˇsˇ´ıch jazycı´ch. Proto si za heslo C++ obvykle mu˚zˇete dosazovat va´sˇ oblı´beny´ jazyk. Pru˚vodce studiem Nejcˇasteˇjsˇ´ı chyba, kterou zacˇa´tecˇnı´ci v assembleru deˇlajı´, je, zˇe se u´lohy snazˇ´ı rˇesˇit ru˚zny´mi krkolomny´mi zpu˚soby, jakoby neznali C++. Prˇitom v assembleru by 90% ko´du meˇlo by´t napsa´no stejny´m zpu˚sobem jako v jiny´ch jazycı´ch, pouze s jinou syntaxı´. 1
Hello World = Ahoj sveˇte
11
Inline assembler se vkla´da´ do jine´ho jazyka.
2.3
Struktura programu v inline assembleru
Inline assembler je assembler vepsany´ do ko´du jine´ho jazyka, v nasˇem prˇ´ıpadeˇ to bude Visual C++ 2005. Jako prˇ´ıklad uved’me na´m zna´my´ vy´pocˇet absolutnı´ hodnoty, tentokra´t bude napsa´n jako funkce v C++ a vlastnı´ vy´pocˇet bude v assembleru: i n t abs ( i n t value ) { asm { mov eax , v a l u e cmp eax , 0 jge skip neg e a x skip : mov v a l u e , e a x } return value ; } Na prˇ´ıkladu vidı´me, zˇe ko´d assembleru je uzavrˇen do bloku s hlavicˇkou _asm. Cˇa´ra (podtrzˇ´ıtko) prˇed slovem asm ve starsˇ´ıch verzı´ch prˇekladacˇu˚ by´t nemusela, neˇktere´ prˇekladacˇe naopak vyzˇadujı´ dokonce dveˇ cˇa´ry. Da´le vidı´me, zˇe ko´d assembleru mu˚zˇe pouzˇ´ıvat promeˇnne´ z C++, naopak v assembleru nemu˚zˇeme prˇ´ımo definovat cele´ funkce cˇi vracet na´vratovou hodnotu. Ko´d programu se na´m o jeden rˇa´dek prodlouzˇil (oproti ko´du absolutnı´ hodnoty v prˇedchozı´ kapitole), du˚vodem je prˇeda´nı´ vy´sledku zpeˇt do promeˇnne´ value. Jednotlive´ rˇa´dky prˇedstavujı´ instrukce assembleru. Slovem instrukce prˇitom oznacˇujeme jak u´vodnı´ slovo na rˇa´dku, tak cely´ rˇa´dek. Parametry, ktere´ jsou za mezerou, nazy´va´me operandy Jak vidı´me, jednotlive´ instrukce majı´ ru˚zny´ pocˇet operandu˚ – obecneˇ jich mu˚zˇe by´t nula azˇ trˇi, ale u kazˇde´ instrukce jich musı´ by´t spra´vny´ pocˇet, aby program meˇl smysl. (Toto nasˇteˇstı´ kontroluje prˇekladacˇ.) Vy´pocˇetnı´ instrukce vzˇdy ulozˇ´ı vy´sledek do prvnı´ho operandu. U dvojoperandovy´ch instrukcı´ obvykle prvnı´ operand slouzˇ´ı za´rovenˇ jako vstupnı´ parametr operace, takzˇe provedenı´m operace prˇepı´sˇeme prvnı´ operand novou hodnotu a jeho pu˚vodnı´ hodnotu tak ztratı´me. Nynı´ jsme se tedy sezna´mili se syntaxı´ inline assembleru. Bohuzˇel, tato syntaxe se v jednotlivy´ch prˇekladacˇ´ıch assembleru lisˇ´ı. Stejneˇ tak se lisˇ´ı i syntaxe vkla´da´nı´ assembleru do vysˇsˇ´ıch jazyku˚. My se budeme drzˇet te´to syntaxe, kterou zavedli cˇi prˇijali prˇedevsˇ´ım Intel, Microsoft a Borland.
2.4
Nasˇe prvnı´ instrukce
Naucˇme se tedy prvnı´ instrukce. Zacˇneme samozrˇejmeˇ s teˇmi nejjednodusˇsˇ´ımi, jsou uvedeny v tabulce 1. Dodejme jen, zˇe velka´ a mala´ pı´smena se v assembleru standardneˇ nerozlisˇujı´. Lze to vsˇak zapnout, prˇ´ıpadneˇ lze zapnout cˇa´stecˇne´ rozlisˇova´nı´ u identifika´toru˚ sdı´leny´ch s cˇa´stmi programu napsany´mi v jine´m jazyce (to se hodı´ pra´veˇ prˇi spolupra´ci s C++). Vsˇimneˇte si, zˇe vsˇechny instrukce v tabulce jsou aritmeticke´ operace obsahujı´cı´ take´ prˇirˇazova´nı´. Toto jsou totizˇ te´meˇrˇ jedine´ instrukce, jejichzˇ vy´znam a pouzˇitı´ odpovı´da´ vysˇsˇ´ım programovacı´m jazyku˚m. Navı´c na´sobenı´ a deˇlenı´ je dosti komplikovane´ z hlediska operandu˚. K tomu se ale jesˇteˇ dostaneme pozdeˇji. Du˚vodem toho, zˇe ve vsˇech teˇchto za´kladnı´ch instrukcı´ch je prˇirˇazenı´, je zpu˚sob, jaky´m procesor pracuje. Zatı´mco pro cˇloveˇka je prˇirozeny´ naprˇ´ıklad vzorec a = b + c, procesor takto vy´pocˇet prove´st nemu˚zˇe. Meˇl by totizˇ proble´m, co deˇlat s mezivy´sledkem b+c v tom kra´tke´m okamzˇiku, nezˇ se ulozˇ´ı do a. Navı´c vsˇechny promeˇnne´ jsou ulozˇeny ve stejne´ (tedy spolecˇne´) pameˇti
12
Pojem instrukce ma´ v assembleru dva podobne´ vy´znamy. Parametry instrukcı´ nazy´va´me operandy.
C++
Assembler
= += −= *= *= /= /= ++ −− >>= >>= <<= &= |= ˆ= ∼= −
mov add sub mul imul div idiv inc dec shr sar shl and or xor not neg
operandy 2 2 2 1 1,2,3 1 1 1 1 2 2 2 2 2 2 1 1
na´zev instrukce move add subtract multiply (unsigned) multiply (signed) divide (unsigned) divide (signed) increment decrement shift right (unsigned) shift arithmetic right (signed) shift left (bitwise) and (bitwise) or (bitwise) xor (bitwise) not negate (zmeˇnı´ zname´nko cˇ´ısla na opacˇne´)
Tabulka 1: Nejjednodusˇsˇ´ı instrukce. pocˇ´ıtacˇe a v jednom okamzˇiku tedy lze prova´deˇt jen jednu operaci (cˇtenı´ cˇi za´pis jedne´ pameˇt’ove´ bunˇky). Zde jsme narazili na jednu du˚lezˇitou poucˇku: Procesor mu˚zˇe v jedne´ instrukci jen jednou adresovat pameˇt’. Jiny´mi slovy, jedna instrukce mu˚zˇe pameˇt’cˇ´ıst i zapisovat, ale jen na stejne´m mı´steˇ. Prˇ´ıkladem takove´ instrukce je inc, ktera´ prˇecˇte hodnotu z pameˇti, zvy´sˇ´ı o jednicˇku a ulozˇ´ı zpeˇt na stejne´ mı´sto. Rˇada dalsˇ´ıch instrukcı´ pameˇt’ bud’ jen prˇecˇte, nebo jen zapı´sˇe. Hodnoty prˇecˇtene´ z pameˇti mu˚zˇeme nasˇteˇstı´ docˇasneˇ ulozˇit v loka´lnı´ pameˇti uvnitrˇ procesoru. Ma´me tam k dispozici neˇkolik ma´lo pameˇt’ovy´ch buneˇk, ktere´ nazy´va´me registry. Registr je tedy neˇco jako promeˇnna´, ktera´ je vsˇak uvnitrˇ procesoru. Ve zdrojove´m ko´du assembleru rozlisˇ´ıme registry od promeˇnny´ch pomocı´ specia´lnı´ch na´zvu˚ – za´kladnı´ cˇtyrˇi registry pouzˇ´ıvane´ pro beˇzˇne´ vy´pocˇty se nazy´vajı´ eax, ebx, ecx a edx. Podrobneˇji se k registru˚m vra´tı´me pozdeˇji. Nynı´ tedy mu˚zˇeme napsat program pro vy´pocˇet soucˇtu a = b + c, pouzˇijeme k tomu operace z tabulky 1 a jeden registr. mov eax , b add eax , c mov a , eax Vsˇimneˇte si, zˇe vy´sledek strˇa´da´me do registru eax a teprve na konci vy´pocˇtu jej ulozˇ´ıme do promeˇnne´ a. Tento postup je v assembleru beˇzˇny´ – kazˇda´ pra´ce s pameˇtı´ je vzˇdy „drazˇsˇ´ı“, nezˇ pra´ce s registry. Registry navı´c nemajı´ ono omezenı´ jedne´ adresace pameˇti, takzˇe zatı´mco instrukce mov a,b nenı´ mozˇna´, protozˇe by meˇla dveˇ adresace pameˇti, tata´zˇ instrukce se dveˇma registry, naprˇ. mov eax,edx, funguje.
2.5
Prvnı´ cvicˇenı´
Se znalostı´ teˇchto za´kladnı´ch instrukcı´ uzˇ mu˚zˇete napsat va´sˇ prvnı´ vlastnı´ program. Napisˇte funkce pro vy´pocˇet obsahu a obvodu obde´lnı´ka. Vzorove´ rˇesˇenı´ obsahu obde´lnı´ka na´sleduje. i n t ObsahObdelnika ( i n t a , i n t b ) { 13
Nelze v jedne´ instrukci dvakra´t adresovat pameˇt’.
Registr je pameˇt’ova´ bunˇka uvnitrˇ procesoru.
asm { mov eax , a i m u l eax , b mov a , e a x } return a ; } Na´vratovou hodnotu vracı´me tak, zˇe vy´sledek ulozˇ´ıme do promeˇnne´ a a pouzˇijeme prˇ´ıkaz return v C++. Mu˚zˇeme take´ vyuzˇ´ıt znalosti toho, zˇe hodnoty z funkcı´ se nativneˇ vracejı´ v registru eax. Co to pro na´s znamena´? Kdyzˇ funkci deklarujeme jako vracejı´cı´ int, ale nenapı´sˇeme do nı´ zˇa´dny´ prˇ´ıkaz return, funkce vra´tı´ tu hodnotu, kterou necha´me v registru eax. (Tento princip je samozrˇejmeˇ podporˇen prˇekladacˇem C++, ktery´ beˇzˇneˇ u zapomenute´ho prˇ´ıkazu return hla´sı´ chybu. Nedeˇla´ to vsˇak u funkcı´ obsahujı´cı´ch ko´d v assembleru.) Vyzkousˇejte.
2.6
Dalsˇ´ı za´kladnı´ konstrukty
Kromeˇ za´kladnı´ch vy´pocˇetnı´ch instrukcı´ v tabulce 1 si uved’me take´ neˇkolik dalsˇ´ıch konstrukcı´, ktere´ fungujı´ stejneˇ cˇi podobneˇ jako v C++. Najdete je v tabulce 2. C++
Assembler
pozna´mka
// goto label: & * *(char*)& *(short*)& *(long*)&
; jmp label: offset [] byte ptr word ptr dword ptr
jednorˇa´dkova´ pozna´mka jump reference (adresa promeˇnne´) dereference (promeˇnna´ z adresy) (tvrde´) prˇetypova´nı´ na 1bajtovou hodnotu (tvrde´) prˇetypova´nı´ na 2bajtovou hodnotu (tvrde´) prˇetypova´nı´ na 4bajtovou hodnotu
Tabulka 2: Za´kladnı´ konstrukty.
2.7
Pra´ce s ru˚zneˇ velky´mi cˇ´ısly
Zastavme se chvı´li u prˇetypova´nı´, ktere´ ma´ v assembleru neˇkolik du˚lezˇity´ch odlisˇnostı´ od C++. Assembler prˇedevsˇ´ım u datovy´ch typu˚ rozlisˇuje jen jejich velikost. Je mu tedy jedno, kdyzˇ pomı´cha´te dva ru˚zne´ typy, pokud oba majı´ stejny´ pocˇet bajtu˚/bitu˚. Dı´ky tomu veˇtsˇinou vystacˇ´ıme se trˇemi typy uvedeny´mi v tabulce: byte, word a dword. Prˇetypova´nı´ pomocı´ ptr funguje jen pro pra´ci s pameˇtı´ (nelze pouzˇ´ıt u registru˚) a meˇli bychom ho pouzˇ´ıvat jen pro zmeˇnu typu z veˇtsˇ´ıho na mensˇ´ı. V opacˇne´m prˇ´ıpadeˇ totizˇ cˇteme cˇi zapisujeme vı´c bajtu˚, nezˇ ve skutecˇnosti ma´me k dispozici, cˇ´ımzˇ nejspı´sˇ dojde k pameˇt’ove´ chybeˇ. Tento fenome´n si mu˚zˇete vyzkousˇet na na´sledujı´cı´m prˇ´ıkladeˇ. Co tento program vypı´sˇe? (V C++ dosa´hneme stejne´ho efektu prˇ´ıkazem ∗(long∗)&b=512;. char b , c ; v o i d main ( ) { asm { mov word p t r b , 5 1 2 } p r i n t f ( ” b=%i c=%i \ n ” , b , c ) ; }
14
Trˇi za´kladnı´ typy: byte, word, dword.
V prˇ´ıpadeˇ cely´ch cˇ´ısel, se ktery´mi pracujeme i v assembleru, jde prˇi tzv. prˇetypova´nı´ de facto jen o rozsˇ´ırˇenı´ cˇi zu´zˇenı´ cˇ´ısla na jiny´ pocˇet bitu˚. V prˇ´ıpadeˇ rozsˇ´ırˇenı´ na vı´ce bitu˚ vzˇdy chceme, aby rozsˇ´ırˇena´ hodnota byla rovna hodnoteˇ pu˚vodnı´. V prˇ´ıpadeˇ zu´zˇenı´ toto pozˇadujeme pouze tehdy, pokud se hodnota do mensˇ´ıho pocˇtu bitu˚ vejde; v opacˇne´m prˇ´ıpadeˇ docha´zı´ k orˇeza´nı´. Jelikozˇ tyto operace jsou prova´deˇny prˇ´ımo v procesoru, assembler se chova´ stejneˇ jako vysˇsˇ´ı jazyky (naprˇ. prˇevodem cˇ´ısla −1000 na typ byte dostaneme vzˇdy cˇ´ıslo +24). 2.7.1
Zu´zˇenı´ na mensˇ´ı typ
Zu´zˇenı´ na mensˇ´ı typ prova´dı´me bez ohledu na to, zda je cˇ´ıslo zname´nkove´, cˇi nezname´nkove´. Prˇi pra´ci s registry jednodusˇe pouzˇijeme mensˇ´ı cˇa´st registru. Prˇi pra´ci s promeˇnnou pouzˇijeme opera´tor ptr (viz tabulka 2). Ma´me-li trˇi na´sledujı´cı´ promeˇnne´ char p1 ; s h o r t p2 ; i n t p4 ; pak mu˚zˇeme konverze prova´deˇt naprˇ´ıklad takto: mov mov mov mov mov mov
a l , b y t e p t r p2 a l , b y t e p t r p4 ax , word p t r p4 ebx , V e l k e´ Cˇ ´ı s l o p1 , b l p2 , bx
; b y t e <−− s h o r t ; b y t e <−− i n t ; word <−− i n t ; c h a r <−− word / dword ; s h o r t <−− dword
Vsˇimneˇte si, zˇe kdyzˇ ma´me pro prˇ´ıklad neˇjake´ velke´ cˇ´ıslo v registru ebx, jeho ulozˇenı´ do mensˇ´ı promeˇnne´ musı´me prove´st pomocı´ mensˇ´ıho jme´na te´hozˇ registru bl. Tote´zˇ platı´ pro 2bajtovy´ bx a samozrˇejmeˇ takte´zˇ pro eax, ecx a edx. U registru˚ esi a edi ma´me jen 2bajtovou verzi, ale ne 1bajtovou. 2.7.2
Rozsˇ´ırˇenı´ na veˇtsˇ´ı typ
U nezname´nkovy´ch cˇ´ısel prova´dı´me rozsˇ´ırˇenı´ doplneˇnı´m nul do vsˇech noveˇ prˇidany´ch bitu˚. Toto je velmi jednoducha´ operace a lze ji prove´st dveˇma zpu˚soby: • Instrukce movzx je obdobou instrukce mov umozˇnˇujı´cı´, aby prvnı´ operand byl veˇtsˇ´ı nezˇ druhy´. Vsˇechny vysˇsˇ´ı bity jsou doplneˇny nulami. • U registru˚ je mozˇno prove´st prˇevod dvoukrokoveˇ: Nejprve vynulujeme veˇtsˇ´ı registr, pak do jeho mensˇ´ı cˇa´sti ulozˇ´ıme mensˇ´ı hodnotu. Naprˇ´ıklad prˇirˇazenı´ eax ←− bl provedeme takto: mov eax , 0 mov a l , b l
; e a x <−− b l
U zname´nkovy´ch cˇ´ısel rozsˇ´ırˇenı´ prova´dı´me jinak. Du˚vodem je, zˇe pouzˇ´ıva´me doplnˇkovy´ ko´d, kde u za´porny´ch cˇ´ısel je jednicˇka v nejvysˇsˇ´ım bitu a hlavneˇ take´ ve vsˇech nepouzˇity´ch nejvysˇsˇ´ıch bitech. (Dokazˇte!) Algoritmus rozsˇ´ırˇenı´ je tedy da´n doplnˇkovy´m ko´dem: Kladna´ cˇ´ısla a nulu rozsˇirˇujeme stejneˇ jako u nezname´nkovy´ch doplneˇnı´m nuly, zatı´mco za´porna´ cˇ´ısla rozsˇirˇujeme doplneˇnı´m jednicˇky do vsˇech prˇidany´ch bitu˚. De facto tedy provedeme okopı´rova´nı´ nejvysˇsˇ´ıho bitu pu˚vodnı´ hodnoty do vsˇech prˇidany´ch bitu˚. Toto by nebylo u´plneˇ jednoduche´ udeˇlat, ma´me vsˇak k dispozici neˇkolik specia´lnı´ch instrukcı´. Od procesoru 80386 je k dispozici univerza´lnı´ instrukce movsx, ktera´ funguje podobneˇ jako movzx, ale vsˇechny vysˇsˇ´ı bity prvnı´ho operandu mı´sto nulova´nı´ nastavuje na hodnotu nejvysˇsˇ´ıho bitu druhe´ho operandu. Uved’me neˇkolik prˇ´ıkladu˚. 15
movsx movsx movsx movsx movsx movsx
p4 , a l p2 , a l p4 , ax eax , p1 ax , p1 eax , p2
; i n t <−− s b y t e ; s h o r t <−− s b y t e ; i n t <−− sword ; sdword <−− c h a r ; sword <−− c h a r ; sdword <−− s h o r t
Pro nezname´nkove´ konverze platı´ tote´zˇ (pouze pouzˇijeme instrukci movzx mı´sto movsx).
2.8
Popis instrukcı´
V dalsˇ´ıch kapitola´ch se budeme pru˚beˇzˇneˇ seznamovat s jednotlivy´mi instrukcemi procesoru, budeme si je prˇitom obvykle popisovat podrobneˇji, nezˇ dosud. U kazˇde´ instrukce bude uvedeno: • Pocˇet a typy operandu˚. Rozlisˇujeme prˇitom trˇi za´kladnı´ typy operandu˚: reg (registr), mem (adresa pameˇti) a imm (prˇ´ıma´ hodnota). Adresou pameˇti rozumı´me prˇ´ıpad, kdy operandem je cˇ´ıslo urcˇujı´cı´ adresu pameˇti. Prˇ´ımou hodnotou rozumı´me prˇ´ıpad, kdy operandem je cˇ´ıslo. • Popis instrukce. Z popisu je jasne´, co prˇesneˇ instrukce deˇla´, vcˇetneˇ prˇ´ıpadny´ch zvla´sˇtnostı´ a omezenı´. • Ovlivneˇne´ aritmeticke´ prˇ´ıznaky. Neˇktere´ instrukce po sobeˇ zanecha´vajı´ jiste´ „stopy“ v podobeˇ prˇ´ıznaku˚. Vliv instrukce na za´kladnı´ prˇ´ıznaky si vzˇdy uvedeme. (A to i prˇesto, zˇe v pocˇa´tcı´ch ani jesˇteˇ nebudeme rozumeˇt, k cˇemu je to dobre´.) Typy operandu˚ lze nejle´pe pochopit na prˇ´ıkladeˇ. Komenta´rˇ na kazˇde´m rˇa´dku popisuje, jake´ho typu jsou operandy v dane´m prˇ´ıkladeˇ. V za´vorce je uveden tenty´zˇ ko´d v jazyku C++ (fungoval by za prˇedpokladu, zˇe by v C++ bylo mozˇno prˇ´ımo pracovat s registry). mov mov mov mov mov mov mov mov mov
eax , 2 3 ; reg , imm eax , x ; reg , mem eax , [ x ] ; reg , mem eax , o f f s e t x ; reg , imm eax , dword p t r x ; reg , mem eax , ebx ; reg , r e g x ,23 ; mem , imm [ eax ] , 0 ; mem , imm a,b ; mem , mem
( eax = 23) ( eax = x ) ( eax = x ) ( e a x = &x ) ( e a x = ∗ ( i n t ∗)& x ) ( eax = ebx ) ( x = 23) (∗ eax = 0) − toto nelze !
Prˇ´ıklad take´ ukazuje vy´znam opera´toru˚ reference a dereference a fakt, zˇe pokud je operandem globa´lnı´ promeˇnna´, hranate´ za´vorky mu˚zˇeme vynechat. (Neˇktere´ alternativnı´ prˇekladacˇe assembleru vsˇak toto zjednodusˇenı´ nepodporujı´.) Na u´vod si prˇedstavı´me jednu instrukci, ktera´ se na´m bude velmi hodit v u´vodnı´ch cvicˇenı´ch – instrukci podmı´neˇne´ho skoku podle hodnoty registru ecx.
2.9
Podmı´neˇne´ skoky
Skoky deˇlı´me na nepodmı´neˇne´ a podmı´neˇne´. Nepodmı´neˇny´ skok odpovı´da´ prˇ´ıkazu goto zna´me´mu z vysˇsˇ´ıch jazyku˚. Podmı´neˇny´ skok je odlisˇny´ tı´m, zˇe se provede jen prˇi splneˇnı´ urcˇite´ podmı´nky. Pomocı´ podmı´neˇny´ch skoku˚ lze tedy dosa´hnout stejne´ho efektu, jako prˇi pouzˇitı´ prˇ´ıkazu if ve vysˇsˇ´ıch jazycı´ch. U vsˇech skokovy´ch instrukcı´ oznacˇujeme cı´l skoku stejny´m zpu˚sobem: Kdekoliv v programu vytvorˇ´ıme na´veˇsˇtı´ uvedenı´m jme´na a dvojtecˇky. Toto jme´no je pak parametrem skokove´ instrukce. Nejjednodusˇsˇ´ı skokovou instrukci si hned prˇedstavı´me, na dalsˇ´ı se podı´va´me azˇ pozdeˇji. 16
2.9.1
Instrukce jecxz imm
Instrukce jecxz provede skok na adresu danou operandem, pokud je registr ecx roven nule. Operand je prˇ´ıma´ adresa (cı´l skoku) a musı´ by´t v okolı´ +/− 128 bajtu˚ od mı´sta instrukce skoku. (Kontrolu, zˇe se nesnazˇ´ıme ska´kat moc daleko, zajistı´ kazˇdy´ solidnı´ prˇekladacˇ. Neˇktere´ vsˇak prˇi pokusu o prˇ´ılisˇ daleky´ skok vytvorˇ´ı chybny´ ko´d a bez varova´nı´.) Prˇ´ıznaky: neovlivnˇuje Z popisu instrukce jecxz je videˇt, zˇe jme´no na´veˇsˇtı´ uvedene´ jako parametr se fyzicky prˇelozˇ´ı na cˇ´ıslo – prˇ´ımou hodnotu (immediate value). Toto cˇ´ıslo je cˇ´ıslem pameˇt’ove´ bunˇky, kde je umı´steˇn prˇ´ıkaz programu na´sledujı´cı´ za oznacˇeny´m na´veˇsˇtı´m. Podmı´neˇne´ skoky neumozˇnˇujı´ ska´kat jiny´m zpu˚sobem nezˇ prˇes prˇ´ımou hodnotu. U nepodmı´neˇne´ho skoku ma´me daleko sˇirsˇ´ı mozˇnosti – mu˚zˇeme totizˇ ska´kat i na hodnoty registru˚, cˇili cı´lovou adresu skoku mu˚zˇeme vypocˇ´ıtat prˇi beˇhu programu. V prˇ´ıpadeˇ, zˇe potrˇebujeme opacˇny´ test a skok, kdyzˇ je ecx nenulove´, pomu˚zˇeme si opisem s nepodmı´neˇny´m skokem. jecxz pokracuj jmp p r y c ; s k o cˇ ´ı p r y cˇ , k d y zˇ j e e c x n e n u l o v e´ pokracuj :
2.10
Za´sady strukturovane´ho programova´nı´
V u´vodnı´m povı´da´nı´ o assembleru jsme se mj. dozveˇdeˇli, zˇe v assembleru, prˇestozˇe vypada´ na prvnı´ pohled zvla´sˇtneˇ, se programuje stejneˇ jako v jiny´ch imperativnı´ch jazycı´ch (C++, C# apod.). Tyto stejne´ rysy imperativnı´ho programova´nı´ lze pojmenovat jako strukturovane´ programova´nı´. Tento styl programova´nı´ prˇina´sˇ´ı prˇehlednost a snizˇuje chybovost programu˚, je proto vhodne´ jej dodrzˇovat. V C++ nebo C# je prˇitom povinny´ a nelze se mu vyhnout. V assembleru vsˇak lze programovat i jinak nezˇ strukturovaneˇ, proto je velmi du˚lezˇite´ si hned v u´vodu prˇipomenout, jak vlastneˇ spra´vneˇ psa´t programy, aby strukturovane´ byly. (Prˇehlednost a bezchybnost programu˚ prˇece je nasˇ´ım cı´lem.) Imperativnı´ programovacı´ jazyky vyjadrˇujı´ program jako posloupnost prˇ´ıkazu˚, ktere´ jsou vykona´va´ny postupneˇ po sobeˇ. Da´le ma´me k dispozici ru˚zne´ rˇ´ıdı´cı´ prˇ´ıkazy, ktery´mi lze porˇadı´ vykona´va´nı´ prˇ´ıkazu˚ zmeˇnit. Prˇ´ıkladem takove´ho rˇ´ıdı´cı´ho prˇ´ıkazu je dobrˇe zna´my´ prˇ´ıkaz if, ktery´ mu˚zˇe prˇeskocˇit cˇa´st ko´du podle toho, zda platı´, nebo neplatı´ neˇjaka´ podmı´nka. Dalsˇ´ım prˇ´ıkladem je prˇ´ıkaz goto, ktery´m lze prˇ´ımo prˇesunout vykona´va´nı´ prˇ´ıkazu˚ na jine´ mı´sto. Tyto dva prˇ´ıkazy se v minulosti obvykle pouzˇ´ıvaly dohromady takto: Program meˇl ocˇ´ıslovane´ rˇa´dky. Prˇ´ıkazem ve tvaru goto n prˇesˇlo vykona´va´nı´ na rˇa´dek n. Podmı´neˇny´ prˇ´ıkaz meˇl tvar if podmı´nka then n+ a prˇesˇel na rˇa´dek n v prˇ´ıpadeˇ, zˇe podmı´nka platila. Toto muselo stacˇit k psanı´ cely´ch programu˚. Strukturovane´ programova´nı´ prˇineslo do imperativnı´ho programova´nı´ porˇa´dek tı´m, zˇe zaka´zalo pouzˇ´ıvat prˇ´ıkaz skoku goto. Namı´sto toho jsou definova´ny trˇi za´kladnı´ stavebnı´ „kameny“, pomocı´ ktery´ch program skla´da´me: 1. Podmı´neˇny´ prˇ´ıkaz (if), ktery´ podle splneˇnı´, cˇi nesplneˇnı´ podmı´nky vykona´ jeden nebo druhy´ blok ko´du. Tento prˇ´ıkaz mu˚zˇe by´t v kombinaci s else, nebo bez – to nenı´ podstatne´. Hlavnı´ je, zˇe po otestova´nı´ podmı´nky program neska´cˇe nikam prycˇ, ale vykona´, nebo nevykona´ oznacˇeny´ blok ko´du, ktery´ prˇ´ımo na´sleduje za podmı´neˇny´m prˇ´ıkazem. 2. Prˇ´ıkaz cyklu (while), ktery´ opakuje blok ko´du tak dlouho, dokud je splneˇna podmı´nka. Toto je obdoba prˇedchozı´ho prˇ´ıkazu, rovneˇzˇ jde o jisty´ druh podmı´neˇne´ho vykona´nı´ ko´du. Nenı´ prˇitom podstatne´, zda je podmı´nka testova´na prˇed, nebo po provedenı´ bloku. 17
3. Podprogramy (procedury cˇi funkce), ktere´ mu˚zˇeme zavolat. Po provedenı´ ko´du podprogramu se rˇ´ızenı´ vracı´ zpeˇt na mı´sto vola´nı´ a vykona´va´nı´ programu pokracˇuje prˇ´ıkazem na´sledujı´cı´m za zavola´nı´m podprogramu. Nenı´ prˇitom podstatne´, zda podprogram vracı´ neˇjakou hodnotu. Prˇ´ınos te´to trojice pravidel se uka´zal tak velky´, zˇe noveˇ vznikajı´cı´ jazyky dokonce ani jinak nezˇ strukturovaneˇ programovat neumeˇjı´. Rˇada teˇchto jazyku˚ prˇitom podporuje i prˇ´ıkaz nestrukturovane´ho skoku goto, avsˇak lze jı´m skocˇit jen na jine´ mı´sto te´zˇe funkce. Naopak assembler jakozˇto jazyk historicky´ nevynucuje strukturovane´ programova´nı´. Prˇ´ıkazy if nebo while zde vu˚bec nejsou a nestrukturovany´m skokem mu˚zˇeme ska´kat bez omezenı´ na libovolne´ mı´sto programu. Tato mozˇnost ska´ka´nı´ kamkoliv mu˚zˇe dosti zkomplikovat prvnı´ kroky zacˇ´ınajı´cı´ho programa´tora, proto je vhodne´ na podobne´ mozˇnosti radeˇji zapomenout a snazˇit se dodrzˇovat za´sady strukturovane´ho programova´nı´ z vlastnı´ vu˚le. Assembler ma´ jesˇteˇ jednu zvla´sˇtnost pramenı´cı´ ze stejne´ho du˚vodu: Acˇkoliv zde existujı´ procedury, ktere´ lze zavolat a vra´tit se z nich tak, jak to popisuje strukturovane´ programova´nı´, ma´ to od strukturovany´ch jazyku˚ jednu zvla´sˇtnost: Na konci kazˇde´ procedury musı´me explicitneˇ uve´st instrukci ret. V opacˇne´m prˇ´ıpadeˇ procedura „neskoncˇ´ı “, jak bychom cˇekali, ale program pokracˇuje dalsˇ´ım prˇ´ıkazem v pameˇti – cˇili obvykle zacˇne vykona´vat na´sledujı´cı´ proceduru. Zatı´m na´s procedury nemusejı´ tra´pit, protozˇe budeme pouzˇ´ıvat pomoc jazyka C++. Pozdeˇji si toto pravidlo znovu prˇipomeneme. Pru˚vodce studiem Nutnost explicitneˇ ukoncˇovat procedury a mozˇnost nestrukturovaneˇ ska´kat na libovolne´ mı´sto programu jsou vlastnosti vyply´vajı´cı´ z technicke´ realizace programu v pameˇti. Nejen hardwarove´ procesory, ale i modernı´ umeˇle vytvorˇene´ assemblery, naprˇ. nativnı´ jazyk objektoveˇ orientovane´ platformy .NET, majı´ tyto vlastnosti. To, zˇe programa´tor nebude nestrukturovaneˇ ska´kat do nepatrˇicˇny´ch mı´st sve´ho programu, musı´ zajistit prˇekladacˇ konkre´tnı´ho vysˇsˇ´ıho jazyka, naprˇ. C#.
Shrnutı´ V te´to kapitole jsme zacˇali programovat v inline assembleru, ktery´ vkla´da´me do ko´du jazyka C++. Sezna´mili jsme se s rˇadou novy´ch pojmu˚, jako operand a adresova´nı´ pameˇti. Prˇedstavili jsme si sadu aritmeticky´ch instrukcı´. Tyto majı´ jme´na v podobeˇ zkratek z anglicky´ch sloves a bohuzˇel si je budete muset zapamatovat zpameˇti. Bez dobre´ znalosti na´zvu˚ instrukcı´ se totizˇ programuje velmi pomalu. Naucˇili jsme se take´ vracet hodnoty z funkcı´, pouzˇili jsme to prˇi vy´pocˇtu obsahu a obvodu obde´lnı´ka. V za´veˇru kapitoly jsme si zopakovali za´sady strukturovane´ho programova´nı´ a vysveˇtlili si jeho dopad na programova´nı´ v assembleru. Pojmy k zapamatova´nı´ • • • • •
Inline assembler Instrukce a operand Adresova´nı´ pameˇti Registry Strukturovane´ programova´nı´
Kontrolnı´ ota´zky 1. Ktere´ trˇi programove´ konstrukty jsou za´kladnı´mi kameny strukturovane´ho programova´nı´? 2. Kam se ukla´da´ vy´sledek aritmeticky´ch operacı´ (naprˇ. kdyzˇ provedeme soucˇet pomocı´ instrukce add)? 18
3. Snad u´plneˇ nejza´kladneˇjsˇ´ı instrukcı´ je prˇirˇazenı´ hodnoty. Jak se jmenuje? 4. Co je to operand? 5. Procˇ prˇirˇazenı´ a = b nelze prove´st v jedne´ instrukci? Cvicˇenı´ 1. V sekci byl uveden program pro vy´pocˇet obsahu obde´lnı´ka. Napisˇte jesˇteˇ program pro vy´pocˇet obvodu obde´lnı´ka. (Obvod je soucˇtem de´lek stran, cˇili O = 2(a + b).) 2. Napisˇte program pro vy´pocˇet obsahu troju´helnı´ka dle de´lky za´kladny a jı´ prˇ´ıslusˇne´ vy´sˇky. (Vzorec je S = a·v2 a .) 3. Podmı´neˇny´mi skoky nelze ska´kat na hodnotu registru, ale jen na prˇ´ımou hodnotu. Ukazˇte, jak lze toto omezenı´ vyrˇesˇit cˇi obejı´t.
´ koly k textu U 1. Pokud jste tak jesˇteˇ neudeˇlali, spust’te si Visual Studio 2005 a oveˇrˇte, zˇe va´m spra´vneˇ funguje, vcˇetneˇ assembleru. Zalozˇte novy´ projekt typu „C++ / Win32 Console Application“ a vyzkousˇejte uka´zkove´ programy z te´to kapitoly. 2. Pro vy´meˇnu dvou hodnot existuje instrukce xchg, ktera´ funguje podobneˇ jako mov, ale nastavuje oba svoje operandy na hodnotu opacˇne´ho operandu. Jak byste vymeˇnili hodnoty dvou registru˚, kdybyste nemeˇli k dispozici xchg, ani dalsˇ´ı registr, promeˇnnou cˇi za´sobnı´k? Na´poveˇda: Pouzˇijte tento algoritmus: 1. A+=B, 2. B–=A, 3. A+=B, 4. B=0–B. Realizujte tento algoritmus v assembleru. 3. Lze prˇedchozı´ algoritmus pouzˇ´ıt i pro prˇ´ıpad, kdy potrˇebujeme vymeˇnit hodnotu mezi registrem a promeˇnnou cˇi mezi dveˇma promeˇnny´mi? Zdu˚vodneˇte. 4. Napisˇte program pro vy´pocˇet obsahu troju´helnı´ka dle vzorce S = a · va . Promeˇnne´ a a va budou typu unsigned int. 5. Prˇedchozı´ program upravte na typ unsigned short. 6. Prˇedchozı´ program upravte na typ unsigned char. 7. Napisˇte program pro vy´pocˇet aritmeticke´ho pru˚meˇru trˇ´ı cˇ´ısel (dle vzorce (a + b + c)/3).
ˇ esˇenı´ R 1. V programu vyuzˇijeme vracenı´ hodnoty v registru eax a tento registr pouzˇijeme take´ pro samotny´ vy´pocˇet. Pro na´sobenı´ dveˇma pak vyuzˇijeme instrukci shl, cozˇ je pro tento u´cˇel nejelegantneˇjsˇ´ı rˇesˇenı´. i n t ObvodObdelnika ( i n t a , i n t b ) { asm { mov eax , a add eax , b s h l eax , 1 } } Alternativneˇ bychom mohli pouzˇ´ıt naprˇ. instrukci add eax,eax, naopak pouzˇitı´ prave´ho na´sobenı´ nenı´ prˇ´ılisˇ vhodne´, protozˇe jde o velmi na´rocˇnou operaci. (O na´rocˇnosti na´sobenı´ a deˇlenı´ jesˇteˇ bude rˇecˇ v jedne´ z dalsˇ´ıch kapitol.)
19
2. Tentokra´t budeme potrˇebovat opravdove´ na´sobenı´, takzˇe pouzˇijeme imul. Pro deˇlenı´ dveˇma na´m ale postacˇ´ı sar (prˇ´ıpadneˇ shr – v tomto prˇ´ıpadeˇ funguje obojı´ stejneˇ). i n t O b s a h T r o j u h e l n i k a ( i n t a , i n t va ) { asm { mov eax , a imul eax , va s a r eax , 1 } } 3. Pomu˚zˇeme si nepodmı´neˇny´m skokem, ktery´ umı´ ska´kat na hodnotu registru. Dejme tomu, zˇe chceme udeˇlat podmı´neˇny´ skok na adresu v registru edx prˇi ecx rovne´m nule (cˇili jakoby jecxz edx). Program, ktery´ toto deˇla´, mu˚zˇe vypadat naprˇ´ıklad takto: jecxz p l a t i jmp k o n e c plati : jmp edx konec :
20
3
Registry a adresova´nı´ pameˇti
Studijnı´ cı´le: V te´to kapitole se zameˇrˇ´ıme na data – vysveˇtlı´me si, jak fungujı´ registry a jak se pracuje s globa´lnı´mi promeˇnny´mi. Prˇedstavı´me a vysveˇtlı´me pojmy adresace pameˇti a pameˇt’ove´ modely. Naopak loka´lnı´ promeˇnne´ odlozˇ´ıme na na´sledujı´cı´ kapitolu. Klı´cˇova´ slova: registr, adresace pameˇti, prˇ´ıme´ adresova´nı´, neprˇ´ıme´ adresova´nı´ Potrˇebny´ cˇas: 80 minut.
3.1
Registry
Jak jizˇ vı´me z u´vodnı´ kapitoly, kazˇdy´ procesor ma´ vnitrˇnı´ pameˇt’ove´ bunˇky zvane´ registry. Ty, se ktery´mi jsme se jizˇ setkali (eax, ebx, ecx, edx) jsou za´kladnı´ pracovnı´ registry nejrozsˇ´ırˇeneˇjsˇ´ı rˇady procesoru˚ x86 ve 32bitove´m rezˇimu a fungujı´ takto na vsˇech procesorech typu 80386 nebo noveˇjsˇ´ıch. Podı´vejme se nynı´ na dalsˇ´ı detaily k registru˚m. Pru˚vodce studiem Prvnı´ ota´zka, ktera´ va´s asi napadne, je: „Kolik teˇch registru˚ vlastneˇ v procesoru je?“ Celkovy´ pocˇet registru˚ se v jednotlivy´ch procesorech lisˇ´ı, pomeˇrneˇ mnoho z nich vsˇak pouzˇ´ıva´ jen operacˇnı´ syste´m k rˇ´ızenı´ beˇhu pocˇ´ıtacˇe. Pro na´s vsˇak budou podstatne´ jen vy´pocˇetnı´ registry a registr prˇ´ıznaku˚, o ktery´ch bude rˇecˇ da´le v te´to kapitole. A ty jsou pocˇ´ınaje procesorem 80386 sta´le stejne´.
Pru˚vodce studiem Jelikozˇ registr nenı´ v pameˇti, nelze vytvorˇit pointer na registr. Pointer totizˇ vzˇdy ukazuje neˇkam do pameˇti.
Kazˇdy´ registr ma´ velikost 32 bitu˚, umı´ tedy ulozˇit azˇ 32bitove´ cˇ´ıslo (tedy v rozsahu zhruba 0 azˇ 4 miliardy bez zname´nka, cˇi -2 azˇ +2 miliardy se zname´nkem). Je to vy´hoda, protozˇe na´m to zjednodusˇuje pra´ci s procesorem. V neˇktery´ch syste´movy´ch registrech jsou neˇktere´ bity nevyuzˇite´ nebo naopak navı´c, to na´s vsˇak nemusı´ tra´pit. Jak jizˇ vı´me, za´kladnı´ cˇtverˇice registru˚ se jmenuje eax, ebx, ecx, edx. Historicke´ procesory meˇly pouze jeden takovy´ registr jme´nem A, jako akumula´tor. Pozdeˇji se logicky prˇida´valy dalsˇ´ı registry pojmenovane´ dle abecedy. Kazˇdy´ z nich ma´ i mnemotechnickou pomu˚cku pro zapamatova´nı´ jeho vy´znamu (dle pı´smen a–b–c–d), ale tı´m se nebudeme tra´pit, nebot’ tyto registry jsou si ve veˇtsˇineˇ prˇ´ıpadu˚ rovnocenne´. Pı´smeno e na zacˇa´tku na´zvu urcˇuje, zˇe jde o 32bitovy´ registr. Vynecha´me-li jej, dostaneme 16bitovou verzi te´hozˇ registru (ax, bx, cx, dx). Jde tedy sta´le o jeden a tenty´zˇ registr, pouze pomocı´ pı´smena e urcˇ´ıme, zda jej chceme pouzˇ´ıt cely´, nebo jen jeho prvnı´ (dolnı´) polovinu. K hornı´ polovineˇ 32bitove´ho registru se takto jednodusˇe nedostaneme, mu˚zˇete ale velmi elegantneˇ vymeˇnit dolnı´ a hornı´ polovinu registru – slouzˇ´ı k tomu instrukce rol reg,16 (prvnı´m operandem je na´zev registru, naprˇ. eax). Pru˚vodce studiem Du˚vodem toho, zˇe kazˇdy´ registr ma´ ru˚zna´ jme´na podle toho, s kolika jeho bity (a ktery´mi) chceme pracovat, je historicky´. Dnesˇnı´ procesory jsou odvozene´ od 32bitove´ho 80386, ten od 16bitove´ho 8086 a ten zas od 8bitove´ho 8080 a jeho prˇedchu˚dce 8008. Designe´rˇi
21
Kazˇdy´ registr ma´ 32 bitu˚.
Obra´zek 1: Procesor Intel 8008 (rok 1972).
Obra´zek 2: Procesor Intel 8080 (rok 1974).
Intelu se s kazˇdou novou generacı´ procesoru˚ snazˇili vycha´zet z te´ sta´vajı´cı´, takzˇe vlastneˇ „nabalovali“ dalsˇ´ı prvky ke sta´vajı´cı´ architekturˇe a stejneˇ tomu bylo i u registru˚.
Registry mu˚zˇeme take´ pouzˇ´ıvat jako 8bitove´, tentokra´t mu˚zˇeme dokonce pouzˇ´ıvat dolnı´ dva bajty (cˇili obeˇ poloviny 16bitove´ho registru) tak, zˇe prˇ´ıponu x nahradı´me prˇ´ıponou h (high – vysˇsˇ´ı) nebo l (low – nizˇsˇ´ı). Vsˇechny mozˇnosti pojmenova´nı´ registru eax ukazuje obra´zek 3, u registru˚ ebx, ecx a edx to platı´ stejneˇ. 31
0 eax ax ah al
Obra´zek 3: Prˇehled registru eax.
Pru˚vodce studiem Vsˇimneˇte si, zˇe dolnı´ cˇa´st registru je na obra´zcı´ch vzˇdy znacˇena vpravo a hornı´ je vlevo. Toto odpovı´da´ arabske´mu za´pisu cˇ´ısel, ktery´ se vsˇeobecneˇ pouzˇ´ıva´ (jednotky, cˇili dolnı´ cˇa´st je tam take´ vpravo). S tı´mto za´pisem zprava doleva se setka´me i v dalsˇ´ıch kapitola´ch.
Dalsˇ´ı dva registry, ktere´ lze pouzˇ´ıt prˇi beˇzˇne´ pra´ci, se jmenujı´ esi a edi. Mu˚zˇete je opeˇt pouzˇ´ıvat k libovolne´mu u´cˇelu. Jejich nestandardnı´ jme´na pocha´zejı´ z historicky´ch procesoru˚, kde slouzˇily pouze pro adresova´nı´ pameˇti (si = source index, di = destination index). Tyto registry ale neumozˇnˇujı´ pracovat s 8bitovy´mi cˇa´stmi. Take´ dalsˇ´ı dva adresovacı´ registry ebp a esp majı´ stejne´ vlastnosti, jsou vsˇak pouzˇ´ıva´ny k jiny´m u´cˇelu˚m, takzˇe prˇi beˇzˇny´ch vy´pocˇtech pra´ci je veˇtsˇinou vyuzˇ´ıvat nemu˚zˇeme. (O tom se jesˇteˇ dozvı´te vı´ce pozdeˇji.) 22
esi a edi jsou indexove´ registry.
Na za´veˇr zmı´nı´me jesˇteˇ dva syste´move´ registry: V registru eip je udrzˇova´na adresa pra´veˇ vykona´vane´ho ko´du. Postupneˇ se zvysˇuje, jak procesor prova´dı´ jednotlive´ instrukce, nebo se zmeˇnı´ prˇi vola´nı´ procedur atp. Tento registr ale nemu˚zˇeme prˇ´ımo cˇ´ıst ani zapisovat instrukcemi jako mov, zmeˇnit ho vsˇak mu˚zˇeme velmi snadno pomocı´ podmı´neˇny´ch cˇi nepodmı´neˇny´ch skoku˚ (skok jmp imm je totizˇ tote´zˇ jako mov eip,imm). Registr eflags je prˇ´ıznakovy´ registr, jeho 32 bitu˚ slouzˇ´ı jako prˇ´ıznaky ru˚zne´ho deˇnı´ v procesoru cˇi jako konfigurace jeho chova´nı´. O prˇ´ıznacı´ch budeme mluvit pozdeˇji.
3.2
Vy´beˇr registru˚
K urcˇity´m operacı´m se doporucˇuje pouzˇ´ıvat prˇednostneˇ urcˇite´ konkre´tnı´ registry. Nenı´ to sice technicky nutne´, ale zprˇehlednˇuje to program. Snazˇte se prˇi pra´ci s registry dodrzˇet tato doporucˇenı´: • Prˇi kopı´rova´nı´ a dalsˇ´ı pra´ci s bloky pameˇti pouzˇ´ıvejte esi jako pointer cˇtenı´ a edi jako pointer za´pisu, ebx pak prˇednostneˇ jako trˇetı´ registr (je-li potrˇeba). Nelze-li rozlisˇit cˇtenı´ a za´pis, pouzˇijte opeˇt prˇednostneˇ tyto trˇi registry v uvedene´m porˇadı´. Pokud vasˇe funkce nema´ loka´lnı´ promeˇnne´, ani vstupnı´ parametry, mu˚zˇete jako cˇtvrty´ v rˇadeˇ pouzˇ´ıt registr ebp. • Pro indexaci polı´ pouzˇijte prˇednostneˇ ebx. Obvykle se pak scˇ´ıta´ esi+ebx cˇi edi+ebx. • Pro vsˇechny ostatnı´ operace kromeˇ adresace pameˇti pouzˇ´ıvejte esi, edi a ebp azˇ jako poslednı´ v rˇadeˇ. • Jako pocˇ´ıtadlo cyklu˚ (pocˇet opakova´nı´) pouzˇijte ecx, pro dvojı´ do sebe vnorˇene´ smycˇky pouzˇijte ecx prˇednostneˇ pro vnitrˇnı´ smycˇky a pro vneˇjsˇ´ı pouzˇijte ktery´koliv jiny´ registr, promeˇnnou nebo u delsˇ´ıho ko´du opeˇt ecx chra´neˇny´ ulozˇenı´m na za´sobnı´k. • Pro matematicke´ operace pouzˇijte eax, jako druhy´ pak edx. • Pracujete-li pouze s bajty (typ char apod.), nebojte se vyuzˇ´ıvat kazˇdou cˇa´st registru samostatneˇ. Cˇili naprˇ. mı´sto al a bl pouzˇijte radeˇji al a ah, usˇetrˇ´ıte tak cely´ jeden registr. • Potrˇebujete-li jesˇteˇ dalsˇ´ı registry, pomocı´ instrukce rol reg32,16 mu˚zˇete vymeˇnit spodnı´ a hornı´ polovinu registru. Tı´mto zpu˚sobem zı´ska´te dvojna´sobny´ pocˇet 16bitovy´ch registru˚. • Promeˇnne´ pouzˇ´ıvejte, azˇ kdyzˇ jsou vsˇechny registry obsazene´. • Za´sobnı´k a push–pop pouzˇ´ıvejte pouze tehdy, kdyzˇ uzˇ nelze pouzˇ´ıt ani promeˇnne´. (Naprˇ´ıklad prˇi ukla´da´nı´ dat dynamicky´ch rozmeˇru˚.) • Segmentove´ registry pro ukla´da´nı´ dat pouzˇ´ıvat nelze (v MS-DOSu ano, ale je to poneˇkud pomale´). • Pro zkusˇeneˇjsˇ´ı: Jako pocˇ´ıtadla cyklu˚ mu˚zˇete pouzˇ´ıt ecx rozdeˇleny´ na dveˇ nebo i trˇi cˇa´sti. Vnitrˇnı´ smycˇku pocˇ´ıtejte pomocı´ cl nebo cx. Mu˚zˇete mı´t i dveˇ vnorˇene´ vnitrˇnı´ smycˇky a pocˇ´ıtat je pomocı´ cl a ch. Vneˇjsˇ´ı smycˇku pak pocˇ´ıtejte ve zbytku registru, tj. ch, pokud je volny´, nebo hornı´ polovinu ecx, pokud je cela´ spodnı´ polovina obsazena´ vnitrˇnı´ smycˇkou/smycˇkami.
3.3
Adresovacı´ rezˇimy
V prˇedchozı´ kapitole jsme si vysveˇtlili mozˇne´ kombinace operandu˚ u instrukce mov, nynı´ si je rozebereme podrobneˇji. Instrukce ve tvaru mov cı ´l,zdroj je pro na´s snadno srozumitelny´m za´stupcem dalsˇ´ıch instrukcı´ pracujı´cı´ch s pameˇtı´ (vcˇetneˇ vsˇech aritmeticky´ch instrukcı´ z tabulky 23
eip ukazuje na aktua´lnı´ pozici vykona´va´nı´ ko´du.
1). Kazˇdy´ z operandu˚ prˇitom mu˚zˇe by´t v ru˚zne´m adresovacı´m rezˇimu, vsˇechny vsˇak musejı´ mı´t stejnou velikost (stejny´ pocˇet bitu˚). Nejjednodusˇsˇ´ı dva typy operandu˚ nepracujı´ s pameˇtı´: Prˇ´ıma´ hodnota (imm – immediate) je cˇ´ıselna´ konstanta. Pouzˇitı´ prˇ´ımy´ch hodnot je velmi jednoduche´ a u veˇtsˇiny instrukcı´ bez omezenı´. Prˇ´ıklad: mov a,23 – zde druhy´m operandem je prˇ´ıma´ hodnota. Z principu veˇci vyply´va´, zˇe nikdy nejsou vsˇechny operandy prˇ´ımy´mi hodnotami (takova´ instrukce by nemeˇla smysl). Registr (reg – register) je registr procesoru. Registry majı´ vy´sadnı´ postavenı´ a pracuje se s nimi velmi pohodlneˇ, nebot’ veˇtsˇina instrukcı´ umozˇnˇuje bez omezenı´ pouzˇ´ıvat vsˇechny registry jako jako prvnı´ i druhy´ operand. Prˇ´ıklad: mov eax,ebx – zde oba operandy jsou registry. Dalsˇ´ı typy operandu˚ slouzˇ´ı k pra´ci s pameˇtı´ (hovorˇ´ıme o adresova´nı´, vsˇechny dohromady znacˇ´ıme zkratkou mem). Vı´me-li prˇesnou pozici v pameˇti, se kterou pracujeme (nejspı´sˇe jde o globa´lnı´ promeˇnnou), pak pouzˇijeme jejı´ adresu cˇi jme´no deklarovane´ promeˇnne´. Instrukce ve tvaru mov a,23 ma´ prvnı´ operand v tomto tvaru – do strojove´ho ko´du se prˇelozˇ´ı jako mov [123456],23, kde mı´sto 123456 se dosadı´ adresa pameˇti, kde je promeˇnna´ a. Vsˇimneˇte si, zˇe a je tote´zˇ jako [a] – prˇekladacˇ si sa´m doplnı´ hranate´ za´vorky tam, kde je jen jme´no promeˇnne´. Pokud by hranate´ za´vorky nedoplnil, z operandu by se vlastneˇ stala prˇ´ıma´ hodnota. Pokud prˇesnou adresu nevı´me, naprˇ. prˇi pra´ci s pointery, poli cˇi objekty, pouzˇijeme neprˇ´ıme´ adresova´nı´. Nejprve adresu vypocˇ´ıta´me cˇi nacˇteme do neˇktere´ho registru, pak se na ni neprˇ´ımo odkazujeme opeˇt pomocı´ hranaty´ch za´vorek, naprˇ. mov eax,[ebx] nacˇte do registru eax hodnotu z pameˇt’ove´ho mı´sta, na ktere´ ukazuje ebx (prˇedpokla´da´ se tedy, zˇe ebx je pointer). Vsˇimneˇte si zde, zˇe assembler opravdu nerozlisˇuje mezi cˇ´ısly a pointery – obe´ lze libovolneˇ ukla´dat do stejny´ch registru˚ a pouze pomocı´ pouzˇitı´ hranaty´ch za´vorek urcˇ´ıme, co ma´ s registrem kazˇda´ instrukce deˇlat. Dalsˇ´ı variantou je indexovane´ adresova´nı´, to je vhodne´ pro pra´ci s poli. Prˇ´ıkladem je mov eax,pole[ebx] – pole je zde na´zev globa´lnı´ho pole a ebx urcˇuje pozici v tomto poli (posunutı´ od zacˇa´tku pole). Identicky je mozˇno pouzˇ´ıt registr pro oznacˇenı´ pocˇa´tku a prˇ´ımou hodnotu pro posunutı´ – instrukce ma´ tvar mov eax,ebx[pole] a je zcela totozˇna´ s prˇedchozı´ variantou. Za´pis adres proto by´va´ zvykem psa´t unifikovaneˇ jako soucˇet v hranaty´ch za´vorka´ch v podobeˇ mov eax,[ebx+pole]. Tento za´pis take´ prˇesneˇ vystihuje, co procesor deˇla´: nejprve secˇte adresu pole a hodnotu registru a pak z te´to adresy prˇecˇte hodnotu a ulozˇ´ı ji do registru eax. Indexovane´ adresova´nı´ je jen rozsˇ´ırˇenı´m neprˇ´ıme´ho adresova´nı´ a je mozˇne´ pouzˇ´ıt take´ dva registry (naprˇ. mov eax,[ebx+ecx]). Pru˚vodce studiem Syntaxe adresova´nı´ pole je zde podobna´ jako v C++, assembler vsˇak pocˇ´ıta´ indexy pole vzˇdy po bajtech, tedy bez ohledu na velikost jednotlivy´ch prvku˚ pole.
Mozˇna´ nynı´ duma´te, procˇ a jak ten jinak celkem hloupy´ procesor doka´zˇe v jedne´ instrukci prova´deˇt tak slozˇite´ vy´pocˇty adres. Du˚vodem je, zˇe vy´pocˇty adres patrˇ´ı k nejcˇasteˇjsˇ´ım operacı´m, ktere´ program prova´dı´, takzˇe je vhodne´ pra´veˇ tyto vy´pocˇty co nejvı´ce zrychlit. Proto ma´ procesor vestaveˇnou schopnost skutecˇneˇ velmi rychle prove´st vyhodnocenı´ adresy pameˇti. Povolene´ samozrˇejmeˇ nejsou vsˇechny typy vy´pocˇtu˚, ale mozˇnosti jsou pomeˇrneˇ bohate´: Mu˚zˇete pouzˇ´ıt libovolnou kombinaci soucˇa´stı´ ze vzorce reg + reg ∗ scale + imm
24
Obra´zek 4: Mozˇnosti vy´pocˇtu efektivnı´ adresy (neprˇ´ıme´ adresova´nı´). [IA32] Symbolicky lze toto sche´ma vyja´drˇit takto: base + index ∗ scale + displacement
Prˇesna´ definice mozˇne´ho tvaru neprˇ´ıme´ adresy je na obra´zku 4. Displacement je libovolna´ prˇ´ıma´ hodnota urcˇujı´cı´ pevnou adresu pameˇti (naprˇ. globa´lnı´ promeˇnnou) cˇi pevny´ index v poli (po bajtech). Base se obvykle pouzˇ´ıva´ prˇi pra´ci s loka´lnı´mi promeˇnny´mi, kde urcˇuje pozici dna za´sobnı´ku v aktua´lnı´ funkci. Index je dalsˇ´ı registr pouzˇity´ pro adresova´nı´ pole – je to jednodusˇe index bunˇky v poli. Scale je na´sobı´cı´ faktor prˇi pra´ci s poli – uda´va´ velikost jedne´ bunˇky pole. Mu˚zˇe vsˇak naby´vat jen hodnot 1, 2, 4 nebo 8; ma´me-li jinou velikost buneˇk v poli, musı´me vy´pocˇet prove´st rucˇneˇ. Vy´pocˇty adres se prova´deˇjı´ beˇhem fa´ze deko´dova´nı´ instrukce, cˇili v idea´lnı´m prˇ´ıpadeˇ trvajı´ „nula“ cyklu˚ procesoru (0T). Jedna´ se tedy o nejrychlejsˇ´ı mozˇny´ vy´pocˇet. Pomocnı´kem je zde instrukce lea, ktera´ vy´sledek vy´pocˇtu adresy druhe´ho operandu ulozˇ´ı do prvnı´ho operandu. Toho lze pouzˇ´ıt naprˇ´ıklad pro velmi rychle´ na´sobenı´ devı´ti: mov eax , a l e a eax , [ eax +8∗ eax ] V registru eax je nynı´ devı´tina´sobek hodnoty promeˇnne´ a. Rychlejsˇ´ı zpu˚sob, jak dosa´hnout te´hozˇ, neexistuje. Jako dalsˇ´ı prˇ´ıklad si uved’me funkci, ktera´ prˇijme dva parametry: pointer na pole a index prvku, ktery´ ma´ prˇecˇ´ıst z pole a vra´tit. V C++ je program jednoduchy´: i n t CtiPrvekPole ( i n t ∗ pole , i n t index ) { return pole [ index ] ; } Pokuste se sami udeˇlat tote´zˇ v assembleru. Vzorove´ rˇesˇenı´ na´sleduje. i n t CtiPrvekPole ( i n t ∗ pole , i n t index ) { asm { mov eax , p o l e mov ebx , i n d e x mov eax , [ e a x + ebx ∗ 4 ] } } Vsˇimneˇte si take´, zˇe v poslednı´ instrukci pouzˇ´ıva´me registr eax pro urcˇenı´ adresy a za´rovenˇ do neˇj zapisujeme vy´sledek operace. Tento postup funguje, protozˇe procesor nejprve vypocˇte neprˇ´ımou adresu ve druhe´m operandu, pak z nı´ prˇecˇte hodnotu a azˇ potom zapı´sˇe vy´sledek do eax. 25
Instrukce lea vypocˇte adresu operandu.
Pru˚vodce studiem Hranate´ za´vorky jsou u kazˇde´ho prˇ´ıstupu do pameˇti. Mu˚zˇeme si to prˇedstavit tak, zˇe cela´ pameˇt’je jedno velke´ pole, do ktere´ho prˇistupujeme. Nejprve vypocˇteme index (cˇ´ıslo bunˇky) v tomto poli, a pak hranaty´mi za´vorkami s polem pracujeme. Indexy prˇitom mu˚zˇeme bud’ vypocˇ´ıta´vat rucˇneˇ pomocı´ instrukcı´ jako mov a add, nebo pouzˇ´ıt rozsˇ´ırˇene´ mozˇnosti neprˇ´ıme´ho adresova´nı´.
Pro dalsˇ´ı procvicˇenı´ adresace si nejprve vytvorˇte pole s prvky o de´lce 7 bajtu˚. struct T { char a , b , c , d , e , f , g ; }; Nynı´ v assembleru napisˇte funkci, ktera´ prˇijme dva parametry uda´vajı´cı´ pointer na pole a cˇ´ıslo prvku. Funkce vra´tı´ v registrech edx:eax hodnotu dane´ho prvku pole. T CtiPrvekPole (T ∗ pole , i n t index ) ; Dodejme jesˇteˇ, zˇe s loka´lnı´mi promeˇnny´mi se ve skutecˇnosti pracuje jinak nezˇ s globa´lnı´mi. Jejich adresa se totizˇ meˇnı´ prˇi jednotlivy´ch vola´nı´ch (vzpomenˇte na rekurzi), takzˇe nemohou by´t prˇelozˇeny jako displacement. Nasˇteˇstı´, inline assembler umozˇnˇuje nacˇ´ıst a ulozˇit beˇzˇnou loka´lnı´ promeˇnnou opeˇt pouzˇitı´m jejı´ho na´zvu jako operandu. Jak se to prˇesneˇ prˇelozˇ´ı, to si vysveˇtlı´me pozdeˇji spolu s dalsˇ´ımi taji vola´nı´ funkcı´. Pru˚vodce studiem Zkratky reg, mem, imm a take´ r/m (registr nebo pameˇt’) pouzˇ´ıva´me v popisu jednotlivy´ch instrukcı´ k zdokumentova´nı´, jake´ho typu mohou by´t jednotlive´ operandy u dane´ instrukce. Zˇa´dna´ instrukce totizˇ nefunguje u´plneˇ s libovolny´m operandem a pomocı´ teˇchto znacˇek si mu˚zˇeme snadno oveˇrˇit, jake´ ma´me u kazˇde´ instrukce mozˇnosti.
S globa´lnı´mi konstantami se pracuje stejneˇ jako s globa´lnı´mi promeˇnny´mi. Neˇktere´ prˇekladacˇe (vcˇetneˇ Visual C++) sice konstanty ukla´dajı´ do specia´lnı´ sekce pameˇti, kde je hardwaroveˇ blokova´n za´pis, jejich adresa je vsˇak opeˇt stejna´ beˇhem cele´ho beˇhu programu, takzˇe se stejneˇ jako globa´lnı´ promeˇnne´ mohou prˇekla´dat na displacement.
3.4
Datove´ typy a prˇetypova´nı´
V prˇedchozı´ kapitole jsme se jizˇ sezna´mili se dveˇma opera´tory offset a dword ptr. Nynı´ si osveˇtlı´me, jak prˇesneˇ fungujı´. Assembler a potazˇmo procesor na datove´ typy prˇ´ılisˇ nehledı´, jedna´ se tedy o vylozˇeneˇ slabeˇ typovany´ jazyk. Platı´ zde jednoducha´ poucˇka: Procesor nezajı´majı´ datove´ typy, zajı´ma´ ho jen pocˇet bajtu˚ (cˇili de´lka). Typova´nı´ si popı´sˇeme opeˇt na prˇ´ıkladu instrukce mov. U kazˇde´ho pouzˇitı´ instrukce s operandy musı´ by´t jasneˇ uvedena velikost operandu˚. (Azˇ na vy´jimky musı´ by´t vsˇechny neadresovacı´ operandy stejneˇ velke´.) Je-li neˇktery´m operandem registr (samozrˇejmeˇ bez hranaty´ch za´vorek), pak velikost tohoto registru je dana´ jeho jme´nem. Pouzˇijeme-li soucˇasneˇ vı´ce registru˚ ru˚zny´ch velikostı´, je to u veˇtsˇiny instrukcı´ chyba (naprˇ. mov eax,bl pouzˇ´ıt nelze). Pouzˇijeme-li adresova´nı´ pameˇti v jednom operandu a za´rovenˇ registr v jine´m operandu, prˇekladacˇ bohuzˇel toto odmı´tne prˇelozˇit, acˇkoliv dle jme´na registru by mohl vyvodit i velikost druhe´ho operandu. 26
Assembler typy stejne´ velikosti da´le nerozlisˇuje.
Velikost operandu˚ vsˇak prˇekladacˇ umı´ vyvodit z typu promeˇnne´. Naprˇ. instrukce mov a,0 ma´ za operandy promeˇnnou a cˇ´ıslo. Cˇ´ıslo nema´ velikost, takzˇe velikost obou operandu˚ je urcˇena typem promeˇnne´ a. Tabulka 3 ukazuje seznam za´kladnı´ch typu˚ zna´my´ch v assembleru a jim odpovı´dajı´cı´ typy v C/C++. V tabulce jsou rozlisˇeny zname´nkove´ a nezname´nkove´ typy, v praxi vsˇak azˇ na ojedineˇle´ prˇ´ıpady nema´ smysl toto rozlisˇovat, protozˇe registry procesoru majı´ velikost 1, 2 cˇi 4 bajty a stacˇ´ı na´m tedy za´kladnı´ typy byte, word a dword. Typ assembleru
velikost
byte word dword qword
1 2 4 8
unsigned char unsigned short unsigned int, unsigned long, vsˇechny typy pointeru˚ unsigned long long
sbyte sword sdword sqword
1 2 4 8
char short int, long long long
Odpovı´dajı´cı´ typ C/C++
Tabulka 3: Datove´ typy assembleru a jim odpovı´dajı´cı´ typy C/C++. Pouzˇijeme-li za operand promeˇnnou, ktera´ nema´ velikost odpovı´dajı´cı´ neˇktere´mu za´kladnı´mu typu, program nepu˚jde prˇelozˇit (naprˇ. mov pole,0). Stejneˇ zle dopadneme u instrukcı´ bez uvedenı´ velikosti (naprˇ. mov [eax],0). V teˇchto prˇ´ıpadech je trˇeba explicitneˇ uve´st velikost neˇktere´ho z operandu˚ pra´veˇ pomocı´ opera´toru ptr (jak jsme si uka´zali jizˇ v tabulce 2 na straneˇ 14). Spra´vny´ tvar instrukce pak je naprˇ. mov typ ptr pole,0 nebo mov typ ptr[eax],0. Za typ dosadı´me dword cˇi jiny´ typ, dle potrˇeby. Uva´dı´me-li v instrukcı´ch pouze jme´na promeˇnny´ch, prˇekladacˇ je vzˇdy musı´ prˇeve´st do neˇjake´ho jine´ho tvaru, protozˇe samotny´ na´zev promeˇnne´ v assembleru nic neznamena´. Kazˇde´ uvedenı´ pouhe´ho jme´na se prˇekla´da´ na tvar typ ptr jme ´no nebo offset jme ´no. Tyto dveˇ varianty majı´ prˇesneˇ opacˇny´ vy´znam (jako dereference a reference), takzˇe je samozrˇejmeˇ nezbytne´ rozumeˇt, podle cˇeho prˇekladacˇ zvolı´ variantu, kterou pouzˇije. Nasˇteˇstı´ to nenı´ slozˇite´: Je-li promeˇnna´ pouzˇita v instrukci skoku cˇi vola´nı´ funkce, pak je jme´no prˇelozˇeno do tvaru offset jme ´no, zatı´mco ve vsˇech ostatnı´ch prˇ´ıpadech do tvaru typ ptr jme ´no. ´ loha znı´: V poli bajtu˚ prˇekopı´rujte Pouzˇitı´ opera´toru ptr si uka´zˇeme na prˇ´ıkladu pra´ce s polem. U prvek cˇ. 2 a 3 na pozici 7 a 9. Nejprve zkuste u´lohu vyrˇesˇit sami. Dveˇ vzorova´ rˇesˇenı´ na´sledujı´. mov mov mov mov mov
eax , o f f s e t p o l e bl , [ eax + 2 ] [ eax + 7 ] , b l bl , [ eax + 3 ] [ eax + 9 ] , b l
Toto rˇesˇenı´ je jednoduche´ a dobrˇe srozumitelne´. Nynı´ si ukazˇme jesˇteˇ trosˇku zajı´maveˇjsˇ´ı rˇesˇenı´. mov bx , word p t r [ o f f s e t p o l e + 2 ] mov b y t e p t r [ o f f s e t p o l e + 7 ] , b l mov b y t e p t r [ o f f s e t p o l e + 9 ] , bh Analyzujme tento ko´d: Pomocı´ offset pole zı´ska´me adresu pole, pak se pomocı´ +2 posuneme na bunˇku cˇ.2. Pomocı´ hranate´ za´vorky provedeme adresaci, prvnı´ instrukce mov tedy nacˇte obsah pameˇti do registru bx. To je dvojbajtovy´ registr, cˇili nacˇetli jsme dveˇ bunˇky pole soucˇasneˇ. Dalsˇ´ı dva rˇa´dky je podobny´m zpu˚sobem zapı´sˇ´ı na pozice 7 a 9. Tyto instrukce vyzˇadujı´ pouzˇitı´ opera´toru ptr, jelikozˇ hranata´ za´vorka pro prˇ´ıstup do pameˇti ctı´ typ pole, tj. prˇekladacˇ 27
ze za´pisu offset pole uvnitrˇ hranate´ za´vorky odvodı´, zˇe cely´ vy´raz ma´ mı´t stejna´ typ jako pole a to nenı´ word, ktery´ my potrˇebujeme Opera´tor offset pouzˇijeme tehdy, chceme-li zı´skat adresu jme´na. At’ uzˇ jde o jme´no promeˇnne´, funkce, na´veˇsˇtı´, cˇi neˇcˇeho jine´ho, opera´torem offset zı´ska´me jeho adresu. Pochopitelneˇ jde ve skutecˇnosti jen o cˇa´st cele´ adresy, a to konkre´tneˇ offset. Tento opera´tor pouzˇ´ıva´me nejcˇasteˇji pro zı´ska´nı´ adresy pole (offset pole) cˇi adresy funkce (offset funkce), kterou pozdeˇji mu˚zˇeme zavolat prˇes registr. Rozdı´l mezi opera´tory offset a ptr mu˚zˇeme uka´zat take´ na vola´nı´ funkce. Z na´sledujı´cı´ch dvou rˇa´dku˚ ko´du je jen jeden spra´vny´:
Instrukce call vola´ funkci.
call offset printf c a l l dword p t r p r i n t f Ktery´ je spra´vny´? To za´lezˇ´ı na tom, je-li printf funkce, cˇi promeˇnna´ typu pointer na funkci. Visual Studio 2005/2008 pouzˇ´ıva´ obeˇ varianty, prˇesneˇji rˇecˇeno jednu z nich podle nastavenı´ prˇekladacˇe. Ve vy´chozı´m nastavenı´ (kdyzˇ si zalozˇ´ıte projekt a nebudete nic meˇnit) je dobrˇe druhy´ rˇa´dek, nebot’vsˇechny funkce knihovny CRT se volajı´ odkazem prˇes tabulku adres a printf je tedy vlastneˇ promeˇnna´ typu pointer na funkci. Toto chova´nı´ ale nenı´ intuitivnı´ pro nasˇe pokusy v assembleru, proto je vhodne´ nastavenı´ prˇekladacˇe prˇepnout: V nastavenı´ projektu (Klikneˇte na tucˇny´ rˇa´dek v Solution Exploreru a zvolte Properties) zvolte statickou CRT knihovnu (C/C++ Code Generation / Run Time Library → Multi-threaded Debug). Potom mu˚zˇete volat printf pomocı´ obycˇejne´ho call printf (a tote´zˇ platı´ i pro dalsˇ´ı funkce CRT).
3.5
Programovy´ za´sobnı´k
Kazˇdy´ procesor podporuje kromeˇ prˇ´ıme´ho a neprˇ´ıme´ho prˇ´ıstupu do pameˇti take´ programovy´ za´sobnı´k. V te´to sekci se sezna´mı´me s neˇkolika za´kladnı´mi instrukcemi pro jeho pouzˇ´ıva´nı´ a pozdeˇji se k za´sobnı´ku jesˇteˇ vra´tı´me v souvislosti s vola´nı´m funkcı´ a organizacı´ loka´lnı´ch promeˇnny´ch prˇi rekurzivnı´ch vy´pocˇtech. Pra´veˇ tam se totizˇ za´sobnı´k s vy´hodou vyuzˇije. Jak jizˇ vı´me, na za´sobnı´k ukazuje registr esp. Posledneˇ vlozˇena´ hodnota (vrchol za´sobnı´ku) je prˇ´ımo na adrese [esp], prˇedposlednı´ je na adrese [esp+4], atd. Na´sledujı´cı´ hodnota pak bude vlozˇena na [esp-4]. Z toho vyply´va´, zˇe vrchol za´sobnı´ku mu˚zˇeme velmi jednodusˇe prˇecˇ´ıst instrukcı´ mov (naprˇ. mov eax,[esp]). 3.5.1
Instrukce push reg/mem/imm
Push vlozˇ´ı hodnotu na za´sobnı´k. Bez ohledu na velikost operandu, na za´sobnı´k se vzˇdy vlozˇ´ı 4 bajty. Vlozˇenı´ probı´ha´ takto: Nejprve se esp snı´zˇ´ı o 4, potom se do [esp] zapı´sˇe hodnota operandu instrukce.
Instrukce push vlozˇ´ı hodnotu na za´sobnı´k.
Prˇ´ıznaky: Neovlivnˇuje 3.5.2
Instrukce pop reg/mem
Vyzvedne hodnotu ze za´sobnı´ku a ulozˇ´ı ji do operandu (registru cˇi pameˇti). Vyzvednutı´ probeˇhne takto: Nejprve je prˇecˇtena hodnota z [esp] a je ulozˇena do operandu. Potom se esp zvy´sˇ´ı o 4. Prˇ´ıznaky: Neovlivnˇuje
28
Instrukce pop vyzvedne hodnotu ze za´sobnı´ku.
3.6
Pameˇt’ove´ modely
Pojem pameˇt’ovy´ model popisuje organizaci pameˇti.2 Pameˇt’ove´ modely se lisˇ´ı u jednotlivy´ch procesoru˚, ale lisˇ´ı se take´ v ru˚zny´ch operacˇnı´ch syste´mech. Pameˇt’kazˇde´ho procesu je obvykle rozdeˇlena na trˇi cˇa´sti: ko´d, data a za´sobnı´k. Data a za´sobnı´k prˇitom mohou sply´vat, nebo by´t oddeˇleny. Pro vysˇsˇ´ı jazyky je vsˇak cˇasto nutne´, aby sply´valy, takzˇe za´sobnı´k a data jsou na stejne´m mı´steˇ pameˇti. V syste´mu Windows, se ktery´m pracujeme, se pouzˇ´ıva´ jediny´ pameˇt’ovy´ model: Nazy´va´ se flat a ma´ velmi jednoduchou strukturu, kde kazˇdy´ proces ma´ vlastnı´ linea´rnı´ pameˇt’. Linea´rnı´ pameˇt’, jak uzˇ sa´m na´zev napovı´da´, je nestrukturovany´ blok pameˇti, se ktery´m se bez jaky´chkoliv omezenı´ mu˚zˇe pracovat jako s polem. Soucˇasne´ verze Windows navı´c oddeˇlujı´ pameˇt’jednotlivy´ch procesu˚, takzˇe chyba v programu neovlivnı´ ostatnı´ programy. Procesy spolu naopak sdı´lejı´ ko´d knihoven, prˇitom ale samozrˇejmeˇ neveˇdı´, zˇe cˇa´st jejich pameˇti je sdı´lena´ s jiny´mi procesy. (Takto sdı´lena´ pameˇt’je vzˇdy prˇ´ıstupna´ pouze pro cˇtenı´.) Linea´rnı´ organizace pameˇti prˇedevsˇ´ım znamena´, zˇe ma´me-li pointer na prvnı´ prvek pole a prˇicˇteme k neˇmu naprˇ. cˇ´ıslo 20000, dostaneme vzˇdy pointer na dvaca´ty´tisı´cı´ prvek (pokud takovy´ existuje). Nelinea´rnı´ organizace pameˇti se pouzˇ´ıvala v minulosti (prˇed Windows 95) a posouva´nı´ pointeru˚ na jine´ prvky pole tam bylo slozˇiteˇjsˇ´ı. Pameˇt’ovy´ model flat se take´ nazy´va´ small (anglicky maly´). Tyto dva na´zvy jsou identicke´. Maly´ je proto, zˇe pointer ma´ stejnou velikost jako registr, cˇili umozˇnˇuje adresovat jen 4GB (232 bajtu˚) pameˇti. Naproti tomu jine´ pameˇt’ove´ modely (naprˇ. large – velky´) pouzˇ´ıvajı´ pointery veˇtsˇ´ı, nezˇ je jeden registr. Umozˇnˇujı´ tak adresovat vı´c nezˇ 4GB, ale pra´ce v teˇchto modelech je slozˇiteˇjsˇ´ı a nebudeme se jı´ zaby´vat. Ve Windows se tyto modely nepouzˇ´ıvajı´, pro adresaci pameˇti nad 4GB je vhodneˇjsˇ´ı pouzˇ´ıt 64bitove´ Windows, takzˇe registry jsou 64bitove´ a opeˇt pracujeme v rezˇimu flat/small. Pru˚vodce studiem V 16bitovy´ch syste´mech, jako je Windows 3.1 cˇi MS-DOS, je model small omezen na 64KB pameˇti. Veˇtsˇ´ı modely tam tedy byly daleko cˇasteˇji pouzˇ´ıva´ny nezˇ dnes ve Windows. Kromeˇ modelu large byl oblı´beny´ take´ model compact, kde data jsou adresova´na dveˇma registry, ale ko´d je jako v modelu small omezen na 64KB. Tento hybridnı´ prˇ´ıstup byl vy´hodny´ z hlediska rychlosti programu˚ i sˇetrˇenı´ pameˇti (adresy ko´du jsou kratsˇ´ı, takzˇe cely´ program je kratsˇ´ı).
3.7
Mozˇnosti pra´ce s poli a pointery
Pra´ci s poli a pointery jsme si jizˇ vysveˇtlili v prˇedchozı´m textu, neusˇkodı´ na´m vsˇak shrnout vsˇechny zna´me´ poznatky. V assembleru je prˇedevsˇ´ım nutno pecˇliveˇ rozlisˇovat mezi poli a pointery, navı´c mezi globa´lnı´mi a loka´lnı´mi. O poli hovorˇ´ıme tehdy, ma´me-li v pameˇti prˇ´ımo za sebou vı´c promeˇnny´ch stejne´ho typu, ktere´ nemajı´ samostatna´ jme´na. Namı´sto pojmenova´va´nı´ jednotlivy´ch promeˇnny´ch je pojmenujeme vsˇechny jako celek. Pole jako celek nenı´ datovy´m typem, nelze tedy uve´st pole jako vstupnı´ parametr (ani na´vratovy´ typ) neˇjake´ funkce. Toto „nelze“ je trˇeba bra´t doslova – vsˇimneˇte si na´sledujı´cı´ dveˇ deklarace funkcı´: void funkce1 ( i n t a [ ] ) ; v o i d f u n k c e 2 ( i n t ∗b ) ; 2 Toto platı´ pro sekvencˇnı´ procesory. Prˇi paralelnı´m zpracova´nı´ popisuje pameˇt’ovy´ model i dalsˇ´ı vlastnosti pameˇti, ktere´ se u sekvencˇnı´ho zpracova´nı´ nemusejı´ rˇesˇit.
29
Pole a pointer nenı´ tote´zˇ.
Na prvnı´ pohled by se mohlo zda´t, zˇe promeˇnna´ a je pole a promeˇnna´ b je pointer. To je vsˇak omyl! Ve skutecˇnosti jsou obeˇ deklarace zcela totozˇne´ a obeˇ promeˇnne´ jsou pointery. Pole prosteˇ jako parametr prˇedat nejde. Prˇi pra´ci s polem v assembleru stacˇ´ı pamatovat si neˇkolik jednoduchy´ch pravidel, jak zı´skat adresu prvnı´ho prvku pole. To se lisˇ´ı podle toho, zda ma´me k dispozici opravdu prˇ´ımo pole, nebo pouze pointer na pole. Zohlednit musı´me take´, zda se jedna´ o globa´lnı´, cˇi loka´lnı´ promeˇnnou. (Parametry funkcı´ samozrˇejmeˇ rˇadı´me mezi loka´lnı´ promeˇnne´.) Globa´lnı´ pole – Globa´lnı´ promeˇnne´ jsou vzˇdy na stejne´m mı´steˇ pameˇti. Chceme-li pracovat prˇ´ımo s globa´lnı´m polem, naprˇ. s deklaracı´ int pole[20]; v globa´lnı´m prostoru, pak opera´torem offset mu˚zˇeme prˇ´ımo zı´skat adresu prvnı´ho prvku, naprˇ: mov eax,offset pole. Globa´lnı´ pointer – Opeˇt platı´, zˇe globa´lnı´ promeˇnna´ ma´ vzˇdy stejnou adresu. Tentokra´t vsˇak musı´me nacˇ´ıst jejı´ obsah a tı´m teprve zı´ska´me adresu onoho pole. Naprˇ. prˇi deklaraci int *pointer; tedy pouzˇijeme prˇ´ıkaz mov eax,dword ptr [pointer]. Existuje i kratsˇ´ı za´pis mov eax,pointer. Pozor: Jde o dva za´pisy totozˇne´ instrukce (to prvnı´ je skutecˇna´ podoba instrukce, to druhe´ je pomu˚cka, kterou nabı´zı´ prˇekladacˇ). Loka´lnı´ pointer – Tentokra´t je pointer loka´lnı´ promeˇnnou, jeho adresa v pameˇti se tedy meˇnı´ prˇi jednotlivy´ch vola´nı´ch nasˇ´ı funkce. Prˇekladacˇ zde vsˇak opeˇt nabı´zı´ uzˇitecˇnou pomu˚cku ve tvaru mov eax,pointer. Prˇ´ıstup k loka´lnı´ promeˇnne´ je tedy dı´ky te´to pomu˚cce stejny´ jako u globa´lnı´ promeˇnne´. Fyzicky se instrukce prˇelozˇ´ı na mov eax,[ebp+x], kde za x se dosadı´ offset promeˇnne´ vzhledem ke dnu za´sobnı´ku. Toto ale zatı´m nebudeme prozkouma´vat. Loka´lnı´ pole – Nejprve du˚lezˇitou pozna´mku: Jak vı´me, vstupnı´ parametr deklarovany´ jako int pole[] je pointer, nikoliv pole! Pokud tedy nejde o tento prˇ´ıpad a my ma´me opravdu pole jakozˇto loka´lnı´ promeˇnnou, pak jeho adresa je opeˇt vztazˇena ke dnu za´sobnı´ku. K nacˇtenı´ adresy prvnı´ho prvku pole pouzˇijeme instrukci lea, cˇili naprˇ. lea eax,[pole]. Alternativneˇ je mozˇno vynechat hranate´ za´vorky a napsat lea eax,pole. Jakmile zı´ska´me adresu prvnı´ho prvku pole a ma´me ji ulozˇenou v neˇktere´m registru, vsˇechna slozˇita´ pra´ce je hotova. Da´le jizˇ jen pouzˇ´ıva´me tento registr a opera´tor prˇ´ıstupu do pameˇti [ ], naprˇ. mov ebx,[eax] nacˇte 4bajtovou hodnotu do registru ebx, zatı´mco mov bl,[eax] nacˇte 1bajtovou hodnotu do registru bl. (Jak vı´me, 4bajtove´ registry pouzˇ´ıva´me pro beˇzˇna´ cˇ´ısla typu int, 1bajtove´ obvykle pro znaky.) V neˇktery´ch prˇ´ıpadech prˇ´ıstupu do pameˇti musı´me prˇidat jesˇteˇ opera´tor ptr na urcˇenı´ typu. Naprˇ. vynulova´nı´ prvku pole nelze prove´st instrukcı´ mov [eax],0, protozˇe prˇekladacˇ zde vu˚bec netusˇ´ı, kolik bajtu˚ vlastneˇ chceme vynulovat. Prˇed hranatou za´vorku tedy musı´me prˇidat urcˇenı´ velikosti, naprˇ. mov dword ptr[eax],0. Prˇekladacˇe assembleru bohuzˇel co se ty´cˇe datovy´ch typu˚ nejsou zrovna dokonale´ a toto urcˇenı´ velikosti vyzˇadujı´ neˇkdy i v situacı´ch, kdy bychom ocˇeka´vali, zˇe velikost je z deklarace promeˇnne´ zrˇejma´. Tyto situace je potrˇeba se naucˇit rozpoznat – kdyzˇ prˇekladacˇ zahla´sı´ chybu nekompatibilnı´ch operandu˚ a je to u instrukce s hranatou za´vorkou, nejspı´sˇe tam chybı´ urcˇenı´ velikosti pomocı´ ptr.
3.8
Dalsˇ´ı pozna´mky k pra´ci s pameˇtı´
V za´veˇru kapitoly nakousneme neˇkolik dalsˇ´ıch ota´zek, ktere´ se pra´ce s pameˇtı´ ty´kajı´. Stejneˇ jako vysˇsˇ´ı jazyky, i assembler umozˇnˇuje pracovat se strukturovany´mi datovy´mi typy (struct), take´ se sjednoceny´mi typy (union) a vy´cˇtovy´mi typy (enum). Teˇmito veˇcmi se zatı´m ale nebudeme tra´pit.
30
K loka´lnı´mu poli se dostaneme pomocı´ instrukce lea.
Assembler umozˇnˇuje take´ objektoveˇ orientovane´ programova´nı´ (OOP), pra´ce je to ovsˇem podstatneˇ me´neˇ snadna´ nezˇ v C++. Kdyzˇ se OOP zacˇ´ınalo rozsˇirˇovat, rˇada prˇekladacˇu˚ assembleru prˇisˇla s jeho podporou. V praxi se vsˇak tyto prvky assembleru nikdy prˇ´ılisˇ nepouzˇ´ıvaly a programa´torˇi se za´jmem o OOP prˇesˇli na nove´ vysˇsˇ´ı jazyky. Existenci podpory OOP v assembleru tedy berme za kuriozitu. Neˇktere´ operace nad poli, jako je kopı´rova´nı´ polı´, maza´nı´ pole cˇi vyhleda´va´nı´ v poli, se pouzˇ´ıvajı´ tak cˇasto, zˇe dostaly i vlastnı´ instrukce. Tyto instrukce jsou v assembleru procesoru˚ Intel dokonce jizˇ od da´vny´ch cˇasu˚ 8bitovy´ch procesoru˚. V soucˇasny´ch procesorech se vsˇak jizˇ nepouzˇ´ıvajı´, nebot’je paradoxneˇ rychlejsˇ´ı napsat naprˇ´ıklad kopı´rova´nı´ pole pomocı´ nacˇ´ıta´nı´ hodnot z jednoho pole do registru˚ a jejich na´sledne´ ukla´da´nı´ do druhe´ho pole. Prˇestozˇe tedy tyto instrukce na rychlosti programu neprˇidajı´, pozdeˇji se k nim jesˇteˇ vra´tı´me a neˇktere´ se naucˇ´ıme pouzˇ´ıvat. Zatı´m je ale potrˇebovat nebudeme. Protozˇe v u´loha´ch k procvicˇenı´ budete potrˇebovat napsat ko´d pro opakova´nı´ (neboli smycˇku, cyklus), bude se va´m hodit instrukce loop, ktera´ va´m tuto pra´ci usnadnı´. 3.8.1
Instrukce loop imm
Tato instrukce slouzˇ´ı k opakova´nı´ ko´du. Dekrementuje registr ecx a pokud je vy´sledek nenulovy´, skocˇ´ı na adresu danou operandem. Pozor: Instrukce je omezena na blı´zke´ skoky (±128 bajtu˚), neˇktere´ starsˇ´ı prˇekladacˇe nehla´sı´ prˇi prˇekrocˇenı´ tohoto limitu chybu. Prˇ´ıznaky: neovlivnˇuje Obvykle´ pouzˇitı´: mov ecx ,< p o cˇ e t o p a k o v a´ n ´ı > opakuj : . . . z d e l i b o v o l n y´ k o´ d . . . loop opakuj Instrukce loop
je ekvivalentem ke dvojici instrukcı´ dec ecx, jnz , ovsˇem s tı´m rozdı´lem, zˇe loop neovlivnˇuje prˇ´ıznaky. Shrnutı´ V te´to kapitole jsme se podrobneˇji sezna´mili s registry a adresova´nı´m pameˇti. Zameˇrˇili jsme se prˇedevsˇ´ım na obecne´ registry vyuzˇitelne´ prˇi beˇzˇny´ch vy´pocˇtech. Dozveˇdeˇli jsme se, zˇe acˇkoliv dnes jizˇ jsou jednotlive´ registry mezi sebou veˇtsˇinou zameˇnitelne´, z historicky´ch du˚vodu˚ a pro lepsˇ´ı prˇehlednost programu˚ by´va´ doporucˇova´no pouzˇ´ıvat urcˇite´ registry sta´le k teˇm u´cˇelu˚m, ke ktery´m byly urcˇeny v historicky´ch verzı´ch procesoru˚. Ve druhe´ cˇa´sti kapitoly jsme se naucˇili pouzˇ´ıvat prˇ´ıme´ a neprˇ´ıme´ adresova´nı´ pameˇti. Na rozdı´l od vysˇsˇ´ıch jazyku˚, kde vy´pocˇty efektivnı´ch adres v ra´mci neprˇ´ıme´ho adresova´nı´ zajisˇt’uje prˇekladacˇ a programa´tor je te´to pra´ce usˇetrˇen, v assembleru to patrˇ´ı k za´kladnı´m dovednostem, ktere´ programa´tor musı´ ovla´dat. Pojmy k zapamatova´nı´ • • • • • • •
Registr Prˇ´ıma´ hodnota Offset Neprˇ´ıme´ adresova´nı´ Programovy´ za´sobnı´k Pameˇt’ovy´ model Pole a pointer 31
Instrukce loop slouzˇ´ı k opakova´nı´ ko´du.
• Instrukce lea • Instrukce loop Kontrolnı´ ota´zky 1. 2. 3. 4. 5. 6. 7.
K cˇemu slouzˇ´ı registr eip? Ktere´ registry jsou indexove´ a co to znamena´? Jaky´m zpu˚sobem mu˚zˇeme vyuzˇ´ıt 32bitove´ registry, kdyzˇ potrˇebujeme jen 8 bitu˚? Vysveˇtlete, co deˇla´ opera´tor offset. Vysveˇtlete, co deˇla´ opera´tor []. K cˇemu slouzˇ´ı opera´tor ptr ve spojenı´ s opera´torem []? Popisˇte smysl instrukce lea, uved’te vhodny´ prˇ´ıklad pouzˇitı´.
Cvicˇenı´ 1. Napisˇte obdobu CRT funkce memcpy pro kopı´rova´nı´ bloku pameˇti. Funkci implementujte tı´mto zpu˚sobem (pomocı´ pole): v o i d memcpy1 ( v o i d ∗ d e s t , v o i d ∗ s r c , i n t c o u n t ) { char ∗ d e s t = ( char ∗ ) d e s t ; char ∗ s r c = ( char ∗ ) s r c ; f o r ( i n t i = 0 ; i 0) { ∗ dest = ∗ src ; d e s t ++; s r c ++; c o u n t −−; } } 3. Napisˇte program, ktery´ secˇte hodnoty vsˇech prvku˚ v poli. i n t c s o u c e t ( i n t ∗ pole , i n t delka ) { int soucet = 0; f o r ( i n t i = 0 ; i
´ koly k textu U
32
1. Napisˇte funkci clearmem, ktera´ vynuluje blok pameˇti. V C++ tato funkce vypada´ takto: v o i d clearmem ( v o i d ∗p , i n t c o u n t ) { f o r ( i n t i =0 ; i
ˇ esˇenı´ R ˇ esˇenı´ je pomeˇrneˇ 1. Jedna´ se jednu ze za´kladnı´ch u´loh na procvicˇenı´ adresova´nı´ pameˇti. R snadne´ – vzpomeneme si prˇitom na spra´vne´ pouzˇ´ıva´nı´ registru˚: pouzˇijeme ecx jako pocˇ´ıtadlo opakova´nı´, esi jako adresu cˇtenı´, edi jako adresu za´pisu a ebx jako index pole. (Fungovalo by to i s jiny´mi registry, toto je jen konvence pro prˇehlednost.) 33
v o i d asm memcpy1 ( v o i d ∗ d e s t , v o i d ∗ s r c , i n t c o u n t ) { asm { mov e s i , s r c mov e d i , d e s t mov ecx , c o u n t mov ebx , 0 opakuj : mov a l , [ e s i +ebx ] mov [ e d i +ebx ] , a l i n c ebx loop opakuj } } Drobnou u´pravou lze dosa´hnout i rˇesˇenı´ se trˇemi registry – ecx totizˇ mu˚zˇeme pouzˇ´ıt jako pocˇ´ıtadlo a za´rovenˇ index v poli: v o i d asm memcpy1b ( v o i d ∗ d e s t , v o i d ∗ s r c , i n t c o u n t ) { asm { mov e s i , s r c mov e d i , d e s t mov ecx , c o u n t dec e s i dec e d i opakuj : mov a l , [ e s i + e c x ] mov [ e d i + e c x ] , a l loop opakuj } } 2. Druha´ varianta zada´nı´ vede k jine´mu zajı´mave´mu rˇesˇenı´, kdy indexy do pole nepouzˇ´ıva´me vu˚bec a namı´sto toho prˇ´ımo posouva´me registry cˇtenı´ a za´pisu. v o i d asm memcpy2 ( v o i d ∗ d e s t , v o i d ∗ s r c , i n t c o u n t ) { asm { mov e s i , s r c mov e d i , d e s t mov ecx , c o u n t opakuj : mov a l , [ e s i ] inc e s i mov [ e d i ] , a l inc edi loop opakuj } } ´ loha ma´ opeˇt mnoho variant rˇesˇenı´. Uka´zˇeme si jednu z nich: 3. U i n t asm soucet ( i n t ∗ pole , i n t delka ) { asm { mov e s i , p o l e mov ecx , d e l k a mov eax , 0 opakuj : add eax , [ e s i ] 34
add e s i , 4 loop opakuj } } Pozna´mka: Vy´sledek zde vracı´me prˇ´ımo v registru eax. Mohli bychom take´ pouzˇ´ıt prˇ´ıkaz C/C++ return, ale kdyzˇ jej vynecha´me, tak prˇekladacˇ automaticky prˇedpokla´da´, zˇe vy´sledek pro vra´cenı´ je prˇipraven v registru eax. Na to samozrˇejmeˇ myslı´me uzˇ v na´vrhu pouzˇitı´ registru˚ – do registru eax pru˚beˇzˇneˇ ukla´da´me mezisoucˇty, takzˇe pro vra´cenı´ hodnoty pak jizˇ nemusı´me nic dalsˇ´ıho deˇlat. 4. Mu˚zˇeme pouzˇ´ıt sar. jecxz nula s a r ecx , 3 1 jecxz kladne ; p r o z a´ p o r n e´
; s k o k p rˇ i n u l e ; o k o p ´ı r u j e m e n e j v y sˇ sˇ ´ı b i t do c e l e´ h o r e g i s t r u ; s k o k p r o k l a d n e´ ( e c x =0) n e s k a´ cˇ e m e ( e c x =−1)
Takte´zˇ je mozˇno pouzˇ´ıt shr. jecxz nula ; s k o k p rˇ i n u l e s h r ecx , 3 1 ; posuneme n e j v y sˇ sˇ ´ı b i t do n e j n i zˇ sˇ ´ı h o dec e c x ; n y n ´ı bude 0 p r o z a´ p o r n e´ , −1 p r o k l a d n e´ j e c x z z a p o r n e ; s k o k p r o z a´ p o r n e´ ( e c x =0) ; p r o k l a d n e´ n e s k a´ cˇ e m e ( e c x =−1)
35
4
Prˇ´ıznaky a podmı´neˇne´ vykona´va´nı´ ko´du
Studijnı´ cı´le: V te´to kapitole se sezna´mı´me s prˇ´ıznaky a uka´zˇeme si, kdy a jak je mu˚zˇeme pouzˇ´ıvat. Naucˇ´ıme se v assembleru psa´t programy obsahujı´cı´ podmı´neˇne´ prˇ´ıkazy pro veˇtvenı´ a opakova´nı´ cˇa´stı´ vy´pocˇtu. Tyto programove´ konstrukty se v assembleru pı´sˇ´ı velmi odlisˇneˇ od vysˇsˇ´ıch programovacı´ch jazyku˚, proto jim je veˇnova´na cela´ samostatna´ kapitola. Klı´cˇova´ slova: prˇ´ıznak, flag, podmı´neˇny´ skok Potrˇebny´ cˇas: 95 minut.
4.1 4.1.1
Prˇ´ıznaky Sezna´menı´ s prˇ´ıznaky
Konstrukce jazyka assembler, jak vı´me, odpovı´da´ strojove´mu ko´du prˇ´ıslusˇne´ho procesoru. Na rozdı´l od veˇtsˇiny vysˇsˇ´ıch jazyku˚ jde tedy o jazyk, jehozˇ podoba je silneˇ ovlivneˇna faktem, zˇe procesory jsou (veˇtsˇinou) rˇesˇeny hardwaroveˇ. Jednou ze za´kladnı´ch vlastnostı´ hardwaru je, zˇe tam lze velmi jednodusˇe realizovat paralelnı´ vykona´va´nı´ vı´ce jednoduchy´ch operacı´. Jednı´m z programovy´ch konstruktu˚, ktere´ toto vyuzˇ´ıvajı´, jsou prˇ´ıznaky (anglicky flags). Kazˇdy´ prˇ´ıznak je jednobitovy´ registr v procesoru. Vsˇechny prˇ´ıznaky jsou pak obvykle sdruzˇeny do jednoho veˇtsˇ´ıho registru, kde zaujı´majı´ jednotlive´ jeho bity. Naprˇ. v procesorech x86 jsou vsˇechny prˇ´ıznaky umı´steˇny ve 32bitove´m registru eflags. Tento mu˚zˇeme libovolneˇ cˇ´ıst, ale meˇnit jeho hodnotu lze jen omezeneˇ – neˇktere´ z prˇ´ıznaku˚ jsou totizˇ z bezpecˇnostnı´ch du˚vodu˚ chra´neˇne´ a mu˚zˇe je meˇnit jen operacˇnı´ syste´m. Prˇ´ıznaky mu˚zˇeme rozdeˇlit do dvou skupin: ˇ ´ıdicı´ prˇ´ıznaky ovlivnˇujı´ (rˇ´ıdı´) chova´nı´ procesoru. Nastavit je azˇ na vy´jimky mu˚zˇe jen opeR racˇnı´ syste´m. Cˇtenı´ jejich hodnot je povoleno vsˇem, ovsˇem nenı´ to veˇtsˇinou k uzˇitku. Aritmeticke´ prˇ´ıznaky jsou naopak urcˇeny prima´rneˇ ke cˇtenı´, nikoliv k nastavova´nı´ hodnot. Jejich hodnoty nastavuje sa´m procesor beˇhem vykona´va´nı´ ko´du. Na´s budou samozrˇejmeˇ zajı´mat prˇedevsˇ´ım prˇ´ıznaky aritmeticke´. Ty jsou nastavova´ny dle vy´sledku aritmeticky´ch i rˇady dalsˇ´ıch instrukcı´. Pomocı´ dalsˇ´ıch instrukcı´ mu˚zˇeme pak hodnoty teˇchto prˇ´ıznaku˚ zjisˇt’ovat a rˇ´ıdit se jejich hodnotami v dalsˇ´ım vy´pocˇtu pomocı´ instrukcı´ podmı´neˇny´ch skoku˚ (a nejen jich). Na neˇktere´ z teˇchto prˇ´ıznaku˚ a instrukcı´ jsme jizˇ drˇ´ıve narazili, naprˇ. hned v prvnı´ kapitole u prˇ´ıkladu vy´pocˇtu absolutnı´ hodnoty. Ukazˇme si znovu tento prˇ´ıklad: i n t abs ( i n t hodnota ) { asm { mov eax , h o d n o t a cmp eax , 0 jge skip neg eax skip : } } Algoritmus podmı´neˇne´ho vykona´nı´ ko´du je vzˇdy dvoukrokovy´: Nejprve provedeme neˇjakou instrukci, ktera´ mj. nastavı´ aritmeticke´ prˇ´ıznaky. V nasˇem prˇ´ıkladeˇ je to instrukce cmp. Pak instrukcı´ podmı´neˇne´ho skoku otestujeme vybrany´ prˇ´ıznak cˇi prˇ´ıznaky. V prˇ´ıpadeˇ, zˇe testovane´ prˇ´ıznaky jsou nastaveny na jednicˇku, procesor provede skok na adresu uvedenou v operandu. V opacˇne´m prˇ´ıpadeˇ skok neprobeˇhne a vy´pocˇet pokracˇuje norma´lnı´m zpu˚sobem na na´sledujı´cı´ instrukci. 36
Seznam vsˇech prˇ´ıznaku˚ procesoru x86 a struktura registru eflags je zobrazena na obra´zku 5. Popis, jak jednotlive´ instrukce ovlivnˇujı´ jednotlive´ prˇ´ıznaky je v tabulce 4 a tabula 5 pro u´plnost uva´dı´ seznam instrukcı´, ktere´ prˇ´ıznaky nijak neovlivnˇujı´ (i kdyzˇ neˇktere´ z nich prˇ´ıznaky cˇtou).
Obra´zek 5: Prˇ´ıznaky procesoru rˇady x86 (Pentium 3). [IA32]
4.1.2
Aritmeticke´ prˇ´ıznaky
Aritmeticke´ prˇ´ıznaky, jak jejich na´zev napovı´da´, jsou ovlivneˇny vy´sledky aritmeticky´ch operacı´. Nynı´ se s kazˇdy´m z nich podrobneˇ sezna´mı´me. CF (Carry Flag) je nastaven na jednicˇku, kdyzˇ dojde k prˇenosu z nejvysˇsˇ´ıho bitu. Prˇi scˇ´ıta´nı´ (add) je to tehdy, kdyzˇ je vy´sledek veˇtsˇ´ı nezˇ nejveˇtsˇ´ı mozˇne´ cˇ´ıslo. Prˇi odcˇ´ıta´nı´ (sub, cmp) je to tehdy, kdyzˇ je vy´sledek naopak mensˇ´ı nezˇ nejmensˇ´ı mozˇne´ cˇ´ıslo. CF se nastavuje take´ prˇi dalsˇ´ıch instrukcı´ch, naprˇ. bitovy´ch posunech, se ktery´mi se sezna´mı´me pozdeˇji. Pozor vsˇak na to, zˇe instrukce inc a dec tento prˇ´ıznak neovlivnˇujı´(!). Tento prˇ´ıznak ma´ jesˇteˇ druhou znacˇku B (Below), protozˇe prˇi porovna´va´nı´ (cmp) signalizuje prˇ´ıpad, zˇe prvnı´ hodnota je mensˇ´ı nezˇ druha´ (nezname´nkoveˇ). Prˇ´ıklady (scˇ´ıta´nı´ a odcˇ´ıta´nı´ 8bitovy´ch hodnot): 2+5=7, CF=0 250+10=4, CF=1 5-2=3, CF=0 10-250=16, CF=1 PF (Parity Flag) je nastaven na jednicˇku prˇi sude´ pariteˇ a na nulu prˇi liche´ pariteˇ. (Parita je pocˇet jednicˇkovy´ch bitu˚.) Pozor na to, zˇe tento prˇ´ıznak sleduje pouze dolnı´ch osm bitu˚.
37
Instrukce
OF
SF
ZF
AF
PF
CF
ADC M M M M M M ADD M M M M M M AND 0 M M M 0 BSF/BSR ? ? M ? ? ? BT/BTS/BTR/BTC ? ? ? ? M CMP M M M M M M CMPS M M M M M M DEC M M M M M DIV ? ? ? ? ? ? IDIV ? ? ? ? ? ? IMUL M ? ? ? ? M INC M M M M M MUL M ? ? ? ? M NEG M M M M M M OR 0 M M ? M 0 POPF R R R R R R RCL/RCR 1 M M RCL/RCR count ? M ROL/ROR 1 M M ROL/ROR count ? M SAHF R R R R R SAL/SAR/SHL/SHR 1 M M M M M SAL/SAR/SHL/SHR n ? M M M M SBB M M M M M M SCAS M M M M M M SHLD/SHRD ? M M ? M M SUB M M M M M M TEST 0 M M ? M 0 XOR 0 M M ? M 0 M = modifikuje dle vy´sledku, R = nastavuje cely´ registr 0/1 = nastavuje na 0/1, ? = nedefinovana´ hodnota Tabulka 4: Instrukce ovlivnˇujı´cı´ prˇ´ıznaky. BSWAP, CALL, CBW, CWD, CDQ, CWDE, ENTER, INT, Jcc, JCXZ, JECXZ, JMP, LEA, LEAVE, LODS*, LOOP*, MOV*, NOP, NOT, POP* (kromeˇ POPF), PUSH*, REP*, RET, SETcc, STOS*, XCHG Tabulka 5: Instrukce neovlivnˇujı´cı´ prˇ´ıznaky. Jeho smysl je dnes te´meˇrˇ nulovy´, meˇl vsˇak velke´ vyuzˇitı´ v minulosti, kdy se 8bitova´ parita pouzˇ´ıvala pro sledova´nı´ chyb prˇi komunikaci. Prˇ´ıklady: 0=00b: P=1 1=01b: P=0 2=10b: P=0 3=11b: P=1 257=100000001b: P=0, protozˇe se zapocˇ´ıta´va´ jen dolnı´ch 8 bitu˚ AF (Auxiliary Carry Flag) je doplnˇkovy´ prˇ´ıznak prˇenosu. Nastavuje se na jednicˇku prˇi prˇenosu z dolnı´ poloviny bajtu (ze cˇtvrte´ho do pa´te´ho bitu). Tento prˇ´ıznak interneˇ pouzˇ´ıvajı´
38
instrukce pro pra´ci s BCD cˇ´ısly, cˇili opeˇt jde o prˇ´ıznak, jehozˇ prakticke´ vyuzˇitı´ je velmi male´. (Neplet’te si vsˇak tento prˇ´ıznak s podmı´neˇny´mi skoky s pı´smenem A – instrukce ja/jna/jae/jnae s nı´m nemajı´ nic spolecˇne´ho.) ZF (Zero Flag) je prˇ´ıznak nuly. Nastavuje se na jednicˇku, kdyzˇ je vy´sledkem operace nula. Je vedle CF zrˇejmeˇ nejuzˇitecˇneˇjsˇ´ım prˇ´ıznakem. Tento prˇ´ıznak ma´ jesˇteˇ druhou znacˇku E (Equal), protozˇe prˇi porovna´va´nı´ hodnot (cmp) signalizuje rovnost operandu˚. SF (Sign Flag) je prˇ´ıznak zname´nka. Obsahuje vzˇdy kopii nejvysˇsˇ´ıho bitu vy´sledku (tam je zname´nko). SF je 1 pro za´porna´ cˇ´ısla a 0 pro kladna´ cˇ´ısla a nulu. OF (Overflow Flag) je prˇ´ıznak aritmeticke´ho prˇetecˇenı´. Jde o pomeˇrneˇ komplikovany´ flag poma´hajı´cı´ prˇi sledova´nı´ prˇetecˇenı´ u vy´pocˇtu˚ se zname´nkovy´mi hodnotami. OF=1 kdyzˇ dosˇlo k prˇenosu do nejvysˇsˇ´ıho bitu vy´sledku, ale nikoliv z nejvysˇsˇ´ıho bitu, nebo opacˇneˇ. OF=0 ve vsˇech ostatnı´ch prˇ´ıpadech. Zjednodusˇeneˇ lze OF popsat takto: OF=1 kdyzˇ v operaci dosˇlo k prˇetecˇenı´ zname´nkove´ hodnoty - cˇili zname´nkovy´ vy´sledek je mimo rozsah platny´ch hodnot. Posledneˇ uvedeny´ prˇ´ıznak OF je zcela klı´cˇovy´, stojı´ na neˇm totizˇ porovna´va´nı´ zname´nkovy´ch cˇ´ısel a podmı´neˇne´ skoky typu G–L (veˇtsˇ´ı–mensˇ´ı), se ktery´mi pracujeme velmi cˇasto. Programa´tor si nasˇteˇstı´ obvykle nemusı´ prˇesneˇ pamatovat, jak tento prˇ´ıznak funguje, pokud jej umı´ pouzˇ´ıvat. Rozdı´l mezi CF a OF si jesˇteˇ ukazˇme na dvou konkre´tnı´ch prˇ´ıkladech obycˇejne´ho scˇ´ıta´nı´, viz tabulka 6. operace
vy´sledek
255+1 127+1
0 128
CF
OF
1 0
0 1
Tabulka 6: Srovna´nı´ rozdı´lu mezi OF a CF prˇi scˇ´ıta´nı´.
4.1.3
ˇ ´ıdicı´ prˇ´ıznaky R
Rˇ´ıdicı´ prˇ´ıznaky azˇ na vy´jimky v beˇzˇny´ch programech nepotrˇebujeme. Neˇkdy se vsˇak mohou hodit, proto si neˇkolik za´kladnı´ch z nich prˇedstavı´me. TF (Trap Flag) je prˇ´ıznak zastavenı´ (doslova „pasti“). Pouzˇ´ıva´ se ke krokova´nı´ programu˚ (naprˇ. kla´vesami F10/F11 ve Visual Studiu). Je-li nastaven na 1, procesor se po kazˇde´ instrukci prˇerusˇ´ı. Obvykle je tedy tento prˇ´ıznak nulova´n, ale nespole´hejte na to. IF (Interrupt Enable Flag) je prˇ´ıznak povolujı´cı´ prˇerusˇenı´. Je-li nulova´n, rˇ´ıka´me, zˇe prˇerusˇenı´ je maskova´no, a vsˇechna tzv. maskovatelna´ prˇerusˇenı´ jsou ignorova´na. Tento prˇ´ıznak mu˚zˇe nastavit jen operacˇnı´ syste´m. DF (Direction Flag) je prˇ´ıznak smeˇru posunu adres blokovy´ch instrukcı´. Ve vy´chozı´m stavu je nulova´n. Vy´znam tohoto prˇ´ıznaku si vysveˇtlı´me pozdeˇji, jakmile se sezna´mı´me s algoritmy a instrukcemi, ktere´ jej pouzˇ´ıvajı´. IOPL (I/O Privilege Level) je jediny´m 2bitovy´m prˇ´ıznakem. Jeho hodnota v rozsahu 0–3 urcˇuje opra´vneˇnı´ procesu. Ve Windows majı´ vsˇechny procesy opra´vneˇnı´ 3 (nejnizˇsˇ´ı), kromeˇ ja´dra syste´mu a neˇktery´ch ovladacˇu˚, ktere´ beˇzˇ´ı v u´rovni opra´vneˇnı´ 0 (nejvysˇsˇ´ı). Tento prˇ´ıznak nemu˚zˇe prˇ´ımo nastavit ani operacˇnı´ syste´m, podrobneˇji je syste´m opra´vneˇnı´ procesu˚ popsa´n v [Kep07].
39
4.2
Podmı´neˇne´ skoky
Instrukce podmı´neˇny´ch skoku˚ se souborneˇ oznacˇujı´ jcc. Na rozdı´l od instrukce skoku jmp, podmı´neˇne´ skoky jsou provedeny jen prˇi splneˇnı´ (platnosti) urcˇite´ podmı´nky. Podmı´nku ke skokove´ instrukci nezada´va´me v operandu, ale je prˇ´ımo soucˇa´stı´ jme´na instrukce.3 Kromeˇ instrukce jecxz, kterou jsme si prˇedstavili jizˇ v kapitole 2.9.1 na straneˇ 17 (a jejı´ 16bitove´ sestry jcxz), se vsˇechny ostatnı´ podmı´nky vztahujı´ k prˇ´ıznaku˚m. Seznam vsˇech instrukcı´ podmı´neˇny´ch skoku˚ je uveden v tabulka´ch 7 a 8. instrukce
prˇ´ıznaky
popis
ja/jnbe jae/jnb jb/jnae jbe/jna jc jnc jz/je jnz/jne jnp/jpo jp/jpe jcxz jecxz
(CF or ZF) = 0 CF=0 CF=1 (CF or ZF) = 1 CF = 1 CF = 0 ZF = 1 ZF = 0 PF = 0 PF = 1 cx = 0 ecx = 0
Veˇtsˇ´ı / nenı´ mensˇ´ı nebo roven Veˇtsˇ´ı nebo roven / nenı´ mensˇ´ı Mensˇ´ı / nenı´ veˇtsˇ´ı nebo roven Mensˇ´ı nebo roven / nenı´ veˇtsˇ´ı Prˇenos Nenı´ prˇenos Nula / roven Nenı´ nula / nenı´ roven Nenı´ parita / parita licha´ (odd) Parita / parita suda´ (even) Registr cx je nulovy´ Registr ecx je nulovy´
Tabulka 7: Nezname´nkove´ podmı´neˇne´ skoky. instrukce
prˇ´ıznaky
popis
jg/jnle jge/jnl jl/jnge jle/jng jno jo js jns
((SF xor OF) or ZF) = 0 (SF xor OF) = 0 (SF xor OF) = 1 ((SF xor OF) or ZF) = 0 OF = 0 OF = 1 SF = 1 SF = 0
Veˇtsˇ´ı / nenı´ mensˇ´ı nebo roven Veˇtsˇ´ı nebo roven / nenı´ mensˇ´ı Mensˇ´ı / nenı´ veˇtsˇ´ı nebo roven Mensˇ´ı nebo roven / nenı´ veˇtsˇ´ı Nenı´ prˇetecˇenı´ Je prˇetecˇenı´ Je zname´nko (je za´porny´) Nenı´ zname´nko (je neza´porny´)
Tabulka 8: Zname´nkove´ podmı´neˇne´ skoky. Bez ohledu na slozˇitost testu˚ (nejvı´c prˇ´ıznaku˚ testujeme v instrukcı´ch jg/jng) jsou vsˇechny podmı´neˇne´ skoky stejneˇ rychle´. Obvykle se snazˇ´ıme programy psa´t tak, aby v nich bylo co nejme´neˇ pojmenovany´ch na´veˇsˇtı´ (labelu˚).
4.3
Dalsˇ´ı instrukce
Nynı´ se podı´va´me na neˇkolik dalsˇ´ıch instrukcı´, ktere´ se ty´kajı´ prˇ´ıznaku˚. Instrukce cmp, test a bt slouzˇ´ı k porovna´va´nı´ a testova´nı´ hodnot. Instrukce adc a sbb prˇicˇ´ıtajı´ resp. odecˇ´ıtajı´ kromeˇ druhe´ho operandu i carry flag. Sada instrukcı´ cl a st explicitneˇ meˇnı´ hodnoty jednotlivy´ch prˇ´ıznaku˚. 3
Toto je specificka´ vlastnost procesoru˚ rˇady x86. Na neˇktery´ch jiny´ch procesorech jsou podmı´nky zada´va´ny v operandech.
40
4.3.1
Instrukce cmp op1,op2
(Compare) Provede odecˇtenı´ op1–op2 bez ulozˇenı´ vy´sledku. Dle hodnoty vy´sledku se nastavı´ prˇ´ıznaky. (Tato instrukce se chova´ stejneˇ jako sub, ale neprˇepisuje prvnı´ operand vy´sledkem operace.) Prˇ´ıznaky: Nastavuje AF, CF, OF, PF, SF, ZF dle vy´sledku operace. 4.3.2
Instrukce test op1,op2
Provede op1 and op2 bez ulozˇenı´ vy´sledku. Dle hodnoty vy´sledku se nastavı´ prˇ´ıznaky. (Tato instrukce se chova´ stejneˇ jako and, ale neprˇepisuje prvnı´ operand vy´sledkem operace.) Prˇ´ıznaky: Nastavuje CF, OF, PF, SF, ZF dle vy´sledku operace. Stav AF je nedefinova´n. Pozna´mka: Pomocı´ and cˇi test lze prove´st efektivnı´ test, zda je hodnota registru nulova´. Stacˇ´ı dany´ registr dosadit za prvnı´ i druhy´ operand instrukce, vy´sledkem je pak opeˇt tata´zˇ hodnota a za´rovenˇ je nastaven prˇ´ıznak ZF dle vy´sledku, tedy podle hodnoty tohoto registru. (Tato operace hodnotu registru nezmeˇnı´, proto je mozˇno pouzˇ´ıt and i test. Naopak pro promeˇnne´ to takto udeˇlat nejde.) 4.3.3
Instrukce bt op1,op2
Hodnotu bitu cˇ´ıslo op2 v op1 prˇenese do CF. (Bity jsou cˇ´ıslova´ny od nuly.) Prˇ´ıznaky: nastavuje CF dle vy´sledku operace. 4.3.4
Instrukce adc op1,op2
(Add with carry) Soucˇet op1+op2+CF ulozˇ´ı do op1. Nastavuje vsˇechny prˇ´ıznaky, stejneˇ jako add. Pouzˇ´ıva´ se pro implementaci vı´cebitovy´ch operacı´. Prˇ´ıznaky: Nastavuje AF, CF, OF, SF, PF, ZF dle vy´sledku operace. 4.3.5
Instrukce sbb op1,op2
(Subtract with borrow) Rozdı´l op1–op2–CF ulozˇ´ı do op1. Nastavuje vsˇechny prˇ´ıznaky, stejneˇ jako sub. Pouzˇ´ıva´ se pro implementaci vı´cebitovy´ch operacı´. Prˇ´ıznaky: Nastavuje AF, CF, OF, SF, PF, ZF dle vy´sledku operace. 4.3.6
Instrukce pro explicitnı´ zmeˇnu prˇ´ıznaku˚
Instrukce pro explicitnı´ zmeˇnu prˇ´ıznaku˚ mu˚zˇeme pouzˇ´ıt pro nastavenı´ neˇktery´ch vybrany´ch prˇ´ıznaku˚ samostatneˇ. Vsˇechny tyto instrukce shrnuje tabulka 9. Vy´znamy zkratek jsou clear (cl), set (st), complement (cm). Jak vidı´me v tabulce, pouze prˇ´ıznaky CF, DF a IF umozˇnˇujı´ explicitnı´ nastavenı´. 4.3.7
Instrukce nemeˇnı´cı´ prˇ´ıznaky
Rˇ´ıdicı´ instrukce prˇ´ıznaky nikdy nemeˇnı´ (pokud nedojde k neˇjake´ chybeˇ). Z teˇch, ktere´ uzˇ zna´me, to jsou vsˇechny varianty cˇi typy prˇirˇazenı´, skoku˚, vola´nı´ podprogramu˚ a na´vratu˚ z neˇj.
41
instrukce
popis
clc stc cmc cld std cli sti
Nuluje CF (CF = 0) Nastavı´ CF (CF = 1) Invertuje CF (CF = 1 - CF) Nuluje DF (DF = 0) Nastavı´ DF (DF = 1) Nuluje IF (IF = 0) Nastavı´ IF (IF = 1)
Tabulka 9: Instrukce pro explicitnı´ zmeˇnu prˇ´ıznaku˚. 4.3.8
Bitove´ rotace
Instrukce pro bitove´ rotace rol (rotate left) a ror (rotate right) fungujı´ podobneˇ jako instrukce bitovy´ch posunu˚ shl a shr, jenzˇe bity rotujı´ „kolem dokola“ a nikam se neztra´cejı´. Proto naprˇ´ıklad jednora´zovou nebo i postupnou rotacı´ o 32 bitu˚ ma´me vzˇdy zpeˇt pu˚vodnı´ hodnotu. Stejneˇ jako u bitovy´ch posunu˚ se rotujı´cı´ bit dostane take´ do CF. Dalsˇ´ı rotacˇnı´ instrukce jsou rcl (rotate with carry left) a rcr (rotate with carry right), ktere´ fungujı´ obdobneˇ, ale k registru prˇida´vajı´ jesˇteˇ CF a rotujı´ mı´sto 8/16/32 bitu˚ 9/17/33 bitu˚. Tyto instrukce umozˇnˇujı´ snadneˇji prova´deˇt neˇktere´ operace s CF, ktere´ by jinak vyzˇadovaly delsˇ´ı ko´d.
4.4
Vı´cebitove´ operace
Pomocı´ prˇ´ıznaku prˇenosu CF (carry) lze snadno implementovat vı´cebitove´ operace. Zkusı´me pro procvicˇenı´ naimplementovat 32bitovy´ soucˇet pomocı´ 16bitovy´ch registru˚. Pouzˇijeme prˇi tom vy´sˇe uvedenou instrukci adc. mov add mov mov adc mov
bx , word p t r A bx , word p t r B word p t r C , bx bx , word p t r A+2 bx , word p t r B+2 word p t r C+2 , bx
Vsˇimneˇte si take´, zˇe hodnotu CF nastavenou ve druhe´m rˇa´dku pouzˇijeme azˇ v 5.rˇa´dku. Instrukce mov totizˇ prˇ´ıznaky neovlivnˇuje, takzˇe toho zde s vy´hodou vyuzˇijeme.
4.5
Vyhy´ba´nı´ se podmı´neˇny´m instrukcı´m (optimalizace)
Zajı´ma´-li na´s rychlost vy´pocˇtu, uprˇednostnˇujeme prˇ´ıpad neplatne´ podmı´nky (neskocˇit je rychlejsˇ´ı nezˇ skocˇit) nebo se podmı´neˇny´m skoku˚m prˇ´ımo vyhy´ba´me. Pro prˇ´ıklad si uved’me ko´d, ktery´ zjistı´, zda je hodnota v registru eax suda´, cˇi licha´, a nastavı´ dle toho registr ebx na jednicˇku (licha´), cˇi nulu (suda´). Za´kladnı´ implementace mu˚zˇe vypadat naprˇ´ıklad takto: mov ebx , 0 t e s t eax , 1 j z konec mov ebx , 1 konec : Tento algoritmus vsˇak lze prˇepsat tak, aby nepouzˇ´ıval podmı´neˇny´ skok. Prˇitom mu˚zˇeme zave´st i dalsˇ´ı optimalizace. 42
xor shr adc rcl
ebx , ebx eax , 1 ebx , 0 eax , 1
Prvnı´ rˇa´dek pomocı´ instrukce xor vynuluje registr. Nulova´nı´ pomocı´ xor je rychle´ a zabı´ra´ i me´neˇ pameˇti (usˇetrˇ´ıme 4 bajty s cˇ´ıslem). Potom posuneme eax doprava, cˇ´ımzˇ dojde k prˇenosu nejnizˇsˇ´ıho bitu do CF. Pak mu˚zˇeme prˇicˇ´ıst CF k ebx a na za´veˇr pomocı´ rotace doleva prˇes carry vra´tı´me eax do pu˚vodnı´ho stavu. Navı´c, nebude-li hodnota eax jizˇ potrˇeba, mu˚zˇeme poslednı´ dva rˇa´dky nahradit elegantnı´m rcl ebx,1. Rotace a posuny jsou lepsˇ´ı nezˇ scˇ´ıta´nı´, protozˇe cˇ´ıslo ve druhe´m operandu nezabı´ra´ 4 bajty.
4.6
Veˇtvenı´ ko´du
Jak uzˇ vı´me, veˇtvenı´ ko´du, ktere´ ve vysˇsˇ´ıch jazycı´ch prova´dı´me prˇedevsˇ´ım pomocı´ prˇ´ıkazu if, v assembleru prova´dı´me pomocı´ podmı´neˇny´ch skoku˚. Zastavme se nynı´ podrobneˇji u tohoto te´matu. 4.6.1
Konstrukce if–then–else
Prˇ´ıkaz if se ve vysˇsˇ´ıch jazycı´ch pouzˇ´ıva´ ve dvou varianta´ch: i f A then B i f A then B else C Nejprve se vyhodnotı´ predika´t (podmı´nka) A. Pokud je vy´sledek true, provede se B. Je-li vy´sledek false, provede se C. Blok ko´du C je prˇitom nepovinny´. V assembleru se tato konstrukce implementuje dle te´to sˇablony: ... A ... jcc neplati ... B ... jmp k o n e c neplati : ... C ... konec : Zkra´cena´ verze bez bloku C pak vypada´ takto: ... A ... jcc neplati ... B ... neplati : Pouzˇitı´ sˇablony si uka´zˇeme na na´sledujı´cı´m prˇ´ıkladu: Funkce akceptuje dveˇ cˇ´ısla a a b a vypı´sˇe pozdrav, kdyzˇ a je kladne´ a b je sude´. Zvla´dnete-li vyrˇesˇit tuto u´lohu sami, zkuste to! v o i d t e s t a b c ( i n t a , i n t b , c o n s t char ∗ p o z d r a v ) { i f ( a>0 && b%2==0) p r i n t f ( p o z d r a v ) ; } Vzorove´ rˇesˇenı´ na´sleduje. void t e s t a b ( in t a , in t b , const char ∗ pozdrav ) { c o n s t c h a r ∗ p o z d r a v = ” Ahoj ” ; asm { 43
cmp a , 0 jng konec test b ,1 jnz konec push p o z d r a v c a l l dword p t r p r i n t f add esp , 4 konec : } }
4.6.2
Konstrukce switch–case–break
Prˇ´ıkaz switch provede jeden z bloku˚ ko´du dle hodnoty vy´razu. Tento prˇ´ıkaz se jmenuje i zapisuje v ru˚zny´ch jazycı´ch ru˚zneˇ, proto se tentokra´t budeme odkazovat prˇ´ımo na syntaxi C/C++. s w i t c h (A) { c a s e B1 : . . . C1 . . . break ; c a s e B2 : . . . C2 . . . break ; default : } Jednou z mozˇnostı´, jak tuto konstrukci realizovat, je pouzˇ´ıt stejny´ postup jako u prˇ´ıkazu if. Tuto variantu nebudeme da´le zkoumat, je naprosto trivia´lnı´. Dalsˇ´ı mozˇnostı´ je implementace pomocı´ tabulky skoku˚. Tuto variantu take´ nynı´ nebudeme zkoumat, protozˇe v inline assembleru ji nelze jednodusˇe realizovat (vyzˇaduje totizˇ pomocne´ pole, ktere´ musı´me deklarovat v assembleru, cozˇ ale inline assembler neumı´).
4.7
Cykly
V te´to sekci se podı´va´me, jak lze v assembleru realizovat cykly. Vycha´zı´me prˇitom z prˇedpokladu, zˇe studenti znajı´ neˇjaky´ vysˇsˇ´ı programovacı´ jazyk a nenı´ du˚vod v assembleru nepouzˇ´ıt neˇktere´ jizˇ naucˇene´ vzory programova´nı´, proto se podı´va´me, jak v assembleru realizujeme cykly v podobeˇ klasicky´ch vysokou´rovnˇovy´ch prˇ´ıkazu˚ repeat, while, for a do–while. Neˇktere´ tyto prˇ´ıkazy lze v assembleru vy´hodneˇ realizvoat take´ pomocı´ maker, tuto alternativu budeme diskutovat v kapitole A.1 na straneˇ 76. 4.7.1
Konstrukce repeat
Prˇ´ıkaz repeat se ve vysˇsˇ´ıch jazycı´ch pouzˇ´ıva´ pro opakova´nı´ cˇa´sti ko´du, kde pocˇet opakova´nı´ je prˇedem dany´. Tvar pseudoko´du je na´sledujı´cı´: r e p e a t A: B A je cˇ´ıslo, pocˇet opakova´nı´. Mu˚zˇe to by´t jak konstanta, tak hodnota vypocˇ´ıtana´, ale beˇhem vykona´va´nı´ ko´du, ktery´ se opakuje, se jizˇ neda´ zmeˇnit. B je blok ko´du. V assembleru toto implementujeme nejsna´ze pomocı´ instrukce loop: 44
mov ecx , A zacatek : ...B... loop z a c a t e k Jak vidı´me, uvnitrˇ opakovane´ho bloku ma´me v registru ECX prˇ´ıstupne´ pocˇ´ıtadlo opakova´nı´, ktere´ ma´ prˇi jednotlivy´ch pru˚chodech hodnotu A azˇ 1. Po skoncˇenı´ opakova´nı´ platı´ ecx = 0. Alternativneˇ lze pouzˇ´ıt opis instrukce loop, kdy pomocı´ kombinace dec a jnz provedeme stejnou operaci. mov ecx , A zacatek : ...B... dec e c x jnz za cate k Vy´hodou tohoto rˇesˇenı´ je, zˇe mı´sto registru ecx mu˚zˇeme pouzˇ´ıt libovolny´, ktery´ ma´me volny´. Je-li pocˇet opakova´nı´ maly´, mu˚zˇe to by´t i neˇktery´ mensˇ´ı registr. Naopak u instrukce loop nenı´ volba registru mozˇna´. 4.7.2
Konstrukce do–while
Cyklus typu do–while se lisˇ´ı od repeat tı´m, zˇe pocˇet opakova´nı´ nemusı´ by´t prˇedem zna´m. Prˇ´ıkaz ma´ tento tvar: do B w h i l e A Kde B je opeˇt blok ko´du, ktery´ se ma´ opakovat, zatı´mco A je podmı´nka, jejı´zˇ platnost je testova´na na konci kazˇde´ho pru˚chodu cyklem. Cyklus tedy probeˇhne minima´lneˇ jednou na zacˇa´tku, a pak probı´ha´ znovu tak dlouho, dokud platı´ podmı´nka A. Realizace v assembleru je stejna´ jako druha´ varianta repeat – na konci cyklu testujeme libovolnou podmı´nku a dokud platı´, podmı´neˇny´m skokem se vracı´me zpeˇt na zacˇa´tek. Samotne´ testova´nı´ podmı´nky je prˇitom analogicke´ rˇesˇenı´ prˇ´ıkazu if, viz vy´sˇe. Mu˚zˇeme tedy testovat i slozˇenou podmı´nku apod. 4.7.3
Konstrukce while
Cyklus typu while je obdobou prˇedchozı´ho, tentokra´t se vsˇak platnost podmı´nky testuje na zacˇa´tku cyklu. Prˇ´ıkaz ma´ tvar: w h i l e A: B Azˇ na porˇadı´ je prˇ´ıkaz stejny´ jako do–while. Tı´m, zˇe podmı´nku testujeme na zacˇa´tku prˇed provedenı´m B, je docı´leno toho, zˇe prˇi neplatnosti podmı´nky se cyklus neprovede ani jednou. Implementace je obdobou prˇ´ıkazu if: Nejprve otestujeme podmı´nku a pokud neplatı´, prˇeskocˇ´ıme blok B. Pouzˇijeme proto stejnou sˇablonu, pouze na konec bloku B prˇida´me nepodmı´neˇny´ skok na zacˇa´tek prˇ´ıkazu. zacatek : ... A ... j c c konec ... B ... jmp z a c a t e k konec : 45
Na jednu veˇc je trˇeba da´t pozor: Podmı´nku musı´me testovat obra´ceneˇ, protozˇe pokud bude splneˇn test, ska´cˇeme na konec. Tzn. musı´me test postavit tak, aby podmı´nka v assembleru byla splneˇna pra´veˇ tehdy, kdyzˇ nenı´ splneˇna podmı´nka A, a obra´ceneˇ. 4.7.4
Konstrukce for
Cyklus typu for je zobecneˇnı´m while cˇi do–while (podle toho, zda je prova´deˇn test prˇed prvnı´m pru˚chodem). Ko´d tohoto typu nenı´ pro assembler prˇ´ılisˇ vhodny´, protozˇe nenı´ zcela prˇehledny´ a neprˇina´sˇ´ı zˇa´dny´ velky´ prˇ´ınos oproti jednodusˇsˇ´ım prˇ´ıkazu˚m vy´sˇe. Prˇesto se na neˇj podı´va´me. Prˇ´ıkaz for ma´ v ru˚zny´ch jazycı´ch ru˚znou podobu, my pouzˇijeme za´kladnı´ model: for A = D to H B end B je opeˇt blok ko´du, ktery´ se bude opakovat (teˇlo cyklu). A je pocˇ´ıtadlo cyklu, P je pocˇa´tecˇnı´ hodnota A, H je hornı´ hodnota A. A je nejprve inicializova´no na D, potom se cyklus opakuje tak dlouho, nezˇ je A rovno H. mov A, D zacatek : cmp A, H jg konec ... B ... inc A jmp z a c a t e k konec : Tento vzor mu˚zˇeme libovolneˇ upravit, naprˇ. pro odecˇ´ıta´nı´ pocˇ´ıtadla nebo u´plneˇ volne´ u´pravy pocˇ´ıtadla po kazˇde´m cyklu. Shrnutı´ V te´to kapitole jsme se veˇnovali prˇ´ıznaku˚m. Aritmeticke´ prˇ´ıznaky, ktere´ jsou jejich hlavnı´ skupinou, slouzˇ´ı k implementaci rozhodovacı´ch prˇ´ıkazu˚. Prˇ´ıznaky jsou (azˇ na pa´r vy´jimek) jediny´m zpu˚sobem, jak lze veˇtvit ko´d. Jelikozˇ assembler nema´ ani nejbeˇzˇneˇjsˇ´ı prˇ´ıkazy strukturovany´ch jazyku˚ jako if nebo while, je v praxi potrˇeba pouzˇ´ıvat prˇ´ıznaky pomeˇrneˇ cˇasto. Prˇedstavili jsme si proto instrukce podmı´neˇny´ch skoku˚, ktere´ s prˇ´ıznaky pracujı´, a take´ jsme probrali realizaci beˇzˇny´ch konstrukcı´ pro rozhodovacı´ a opakovacı´ prˇ´ıkazy zna´me´ ze strukturovany´ch jazyku˚. Dalsˇ´ı skupina, syste´move´ prˇ´ıznaky, slouzˇ´ı k nastavenı´ cˇinnosti procesoru. Jejich pouzˇitı´ v praxi je daleko me´neˇ cˇaste´ nezˇ u prˇ´ıznaku˚ aritmeticky´ch. Pojmy k zapamatova´nı´ • • • • • • • • •
ˇr´ıdı´cı´ prˇ´ıznaky aritmeticke´ prˇ´ıznaky CF (carry flag) PF (parity flag) AF (auxiliary carry flag) ZF (zero flag) SF (sign flag) OF (overflow flag) TF (trap flag) 46
• • • • • •
IF (interrupt enable flag) DF (direction flag) IOPL (I/O privilege level) podmı´neˇny´ skok veˇtvenı´ ko´du opakova´nı´ ko´du (cyklus)
Kontrolnı´ ota´zky 1. Ktere´ dveˇ kategorie prˇ´ıznaku˚ rozlisˇujeme? Charakterizujte je. Cvicˇenı´ 1. Napisˇte funkci strlen zjisˇt’ujı´cı´ de´lku rˇeteˇzce. (V jazyce C/C++ je de´lka rˇeteˇzce da´na pozicı´ bina´rnı´ nuly, ktera´ jej ukoncˇuje. Do de´lky nenı´ tato nula zahrnuta.) i n t s t r l e n ( c o n s t char ∗ t e x t ) { i n t len =0; w h i l e ( t e x t [ l e n ] ) l e n ++; return len ; } Jedna´ se o velmi jednoduchou funkci, takzˇe se snazˇte najı´t neˇjake´ elegantnı´ rˇesˇenı´. 2. Napisˇte funkci, ktera´ zjistı´, zda je v poli vı´c kladny´ch nebo za´porny´ch cˇ´ısel. Funkce bude vracet: -1 kdyzˇ prˇevla´dajı´ za´porna´, 0 kdyzˇ je jich stejneˇ, +1 kdyzˇ prˇevla´dajı´ kladna´. i n t KladnaZaporna C ( i n t ∗ pole , i n t delka ) { int pocitadlo = 0; f o r ( i n t i = 0 ; i 0) p o c i t a d l o ++; e l s e i f ( p o l e [ i ] <0) p o c i t a d l o −−; } i f ( p o c i t a d l o >0) r e t u r n + 1 ; i f ( p o c i t a d l o <0) r e t u r n −1; return 0; } 3. Stejnou u´lohu rˇesˇte bez pouzˇitı´ porovna´vacı´ instrukce cmp a take´ bez odcˇ´ıta´nı´. 4. Instrukce cdq prova´dı´ zname´nkove´ rozsˇ´ırˇenı´ eax na edx:eax. Jak byste ji nahradili pomocı´ jiny´ch instrukcı´ s vyuzˇitı´m prˇ´ıznaku˚?
´ koly k textu U 1. Napisˇte funkci, ktera´ v rˇeteˇzci zmeˇnı´ vsˇechna velka´ pı´smena na mala´. Funkce bude meˇnit text v mı´steˇ (cˇili nebude alokovat novou pameˇt’). Pı´smena abecedy jsou ’A’ azˇ ’Z’ a ’a’ azˇ ’z’. Znaky v apostrofech jsou litera´ly, ktere´ mu˚zˇete pouzˇ´ıvat jako cˇ´ısla. (Toto platı´ pro C/C++ i assembler.) Cˇeske´ znaky neuvazˇujte, rˇesˇte pouze anglickou abecedu. v o i d t o l o w e r ( char ∗ t e x t ) { f o r ( i n t i = 0 ; t e x t [ i ] ; i ++) { i f ( t e x t [ i ]>= ’A ’ && t e x t [ i ]<= ’Z ’ ) t e x t [ i ] += ’ a ’− ’A ’ ; } }
47
2. Napisˇte obdobnou funkci jako v prˇedchozı´m u´kolu, ale znaky meˇnˇte tak, zˇe kazˇde´ sude´ pı´smeno abecedy bude velke´ a kazˇde´ liche´ bude male´. 3. Sestavte sˇablonu pro prˇ´ıkaz for. (Vasˇ´ım u´kolem je napsat vzorovy´ prˇ´ıklad v assembleru, na ktere´m uka´zˇete, jak lze zapsat ko´d ekvivalentnı´ konstrukci for zna´me´ z jazyku˚ C/C++.) 4. Sestavte sˇablonu pro prˇ´ıkaz do–while. (Je to obdoba cyklu typu while, kde se podmı´nka testuje na konci cyklu, tj. poprve´ azˇ po prvnı´m pru˚chodu.) 5. Napisˇte funkce pro soucˇet, rozdı´l, soucˇin a podı´l dvou zname´nkovy´ch 64bitovy´ch cˇ´ısel. Na´poveˇda: Pouzˇijte klasicke´ algoritmy ze za´kladnı´ sˇkoly („scˇ´ıta´nı´ pod sebou“ atd.) s 4bajtovou cˇ´ıselnou soustavou (tj. kazˇde´ 4 bajty prˇedstavujı´ jednu cifru cˇ´ısla a vy ma´te udeˇlat trivia´lnı´ dvojciferny´ soucˇet, rozdı´l, soucˇin a podı´l). 6. Napisˇte funkci pro soucˇin dvou 1024bitovy´ch nezname´nkovy´ch cˇ´ısel. Vstupnı´mi parametry budou trˇi pointery – dva ukazujı´cı´ na 1024bitove´ cˇinitele a trˇetı´ ukazujı´cı´ na 2048bitovy´ buffer pro vy´sledek. Pro jednoduchost se mu˚zˇete omezit na pouze nezname´nkova´ cˇ´ısla. ´ lohu lze rˇesˇit klasicky´m algoritmem ze za´kladnı´ sˇkoly „na´sobenı´ pod seNa´poveˇda: U ˇ bou“. Resˇenı´ v C++ je zde jine´ nezˇ v assembleru, nebot’ v C++ nemu˚zˇeme pouzˇ´ıvat CF a rozsˇ´ırˇenı´ vy´sledku do 2 registru˚. Na´sledujı´cı´ vzorove´ rˇesˇenı´ uka´zka je tedy jen v pseudo–C++. v o i d M u l t i p l y ( u n s i g n e d ∗ a , u n s i g n e d ∗b , u n s i g n e d ∗ r e s u l t ) { f o r ( i n t i = 0 ; i <128; i ++) { f o r ( i n t j = 0 ; j <128; j ++) { ( unsigned long long ) r e s u l t [ i + j ] = b [ i ] ∗ a [ j ] ; } } } 7. Napisˇte CRT funkci memmove, ktera´ je obdobou memcpy, ale funguje i v prˇ´ıpadeˇ prˇekry´vajı´cı´ch se bufferu˚. Tato funkce tedy ma´ rˇesˇit i prˇ´ıpad, kdy cı´lovy´ buffer zacˇ´ına´ za zacˇa´tkem zdrojove´ho a prˇekry´va´ jeho konec. V tom prˇ´ıpadeˇ totizˇ memcpy prˇepı´sˇe konec zdrojove´ho bufferu daty z jeho zacˇa´tku. Rˇesˇenı´ je vsˇak jednoduche´: Nejprve zjistı´me, zda je cı´lovy´ buffer za, nebo prˇed zdrojovy´m. Pokud bude za nı´m, budeme kopı´rovat pozpa´tku. 8. Napisˇte funkci strmove, ktera´ bude mı´t stejny´ vy´znam jako memmove, ale bude fungovat pro rˇeteˇzce. Tato funkce tedy v prˇ´ıpadeˇ, zˇe cı´lovy´ buffer je prˇed zdrojovy´m, funguje stejneˇ jako strcpy. V opacˇne´m prˇ´ıpadeˇ ale nejprve zjistı´ de´lku rˇeteˇzce, a pak jej okopı´ruje pozpa´tku. 9. Jak vı´me, rozsˇ´ırˇenı´ hodnoty na veˇtsˇ´ı pocˇet bitu˚ u zname´nkovy´ch cˇ´ısel prova´dı´me instrukcı´ movsx. Tato instrukce se vsˇak objevila azˇ v procesoru 386, starsˇ´ı procesory meˇly jen dveˇ instrukce omezene´ na konkre´tnı´ dvojice registru˚ (cbw rozsˇirˇuje ax←−al, cwd rozsˇirˇuje dx:ax←−al. Nynı´ si ale prˇedstavme, zˇe zˇa´dnou takovou pomocnou instrukci nema´me. Jaky´m na´hradnı´m zpu˚sobem provedete rozsˇ´ırˇenı´ 16bitove´ zname´nkove´ hodnoty na 32bitovou? Napisˇte prˇ´ıslusˇny´ program. Na´poveˇda: Mohou se hodit bitove´ posuny. 10. Napisˇte funkci pro soucˇet cˇ´ısel v poli. Prvky pole budou typu int, vy´sledek typu long long (64bitovy´ se zname´nkem). V te´to funkci vyuzˇijete aritmeticke´ instrukce pracujı´cı´ s prˇ´ıznakem prˇenosu. (Toto jste uzˇ jednou deˇlali, nynı´ ale musı´te vy´sledek spocˇ´ıtat bez orˇeza´nı´ tj. 64bitoveˇ.)
48
long long s o u c e t p o l e ( i n t ∗ pole , i n t delka ) ;
ˇ esˇenı´ R 1. Jedna´ se o trivia´lnı´ funkci, ktera´ jednodusˇe najde nulu v poli znaku˚ a vracı´ jejı´ index. ˇ esˇenı´ mu˚zˇe vypadat trˇeba takto: R int s t r l e n ( const char ∗ t e x t ) { asm { mov eax , t e x t mov ecx , 0 dalsi : cmp b y t e p t r [ eax + e c x ] , 0 j z konec inc ecx jmp d a l s i konec : mov eax , e c x } } Vzpomenˇme si vsˇak, zˇe je vhodne´ vyhy´bat se podmı´neˇny´m skoku˚m. Pra´veˇ zde lze jeden ze skoku˚ usˇetrˇit, stejneˇ tak mu˚zˇeme usˇetrˇit jeden registr, protozˇe k posouva´nı´ po znacı´ch v poli na´m bude stacˇit i jeden. int s t r l e n ( const char ∗ t e x t ) { asm { mov eax , t e x t dec eax ; posuneme s e j e d e n z n a k p rˇ e d z a cˇ a´ t e k t e x t u dalsi : i n c eax ; posuneme s e na d a l sˇ ´ı z n a k cmp b y t e p t r [ eax ] , 0 jnz d a l s i sub eax , t e x t ; ma´me a d r e s u konce , o d e cˇ t e m e od n ´ı a d r e s u z a cˇ a´ t k u } } ˇ esˇenı´ je pomeˇrneˇ jednoduche´. Vyuzˇijeme take´ toho, zˇe v assembleru lze test na kladnou– 2. R za´pornou–nulovou hodnotu prove´st pomocı´ jedine´ho pouzˇitı´ cmp (zatı´mco v C/C++ to jsou dva prˇ´ıkazy if). i n t KladnaZaporna ( i n t ∗ pole , i n t delka ) { asm { mov e s i , p o l e mov ecx , d e l k a mov eax , 0 opakuj : cmp dword p t r [ e s i ] , 0 je d a l s i jg kladne ; z a´ p o r n e´ dec eax jmp d a l s i ; k l a d n e´ 49
kladne : i n c eax dalsi : add e s i , 4 loop opakuj cmp eax , 0 j e konec mov eax , 1 jg konec mov eax , −1 konec : } } 3. Tuto u´lohu lze samozrˇejmeˇ rˇesˇit ru˚zny´mi zpu˚soby. Naprˇ´ıklad lze vyuzˇ´ıt prˇ´ıznaku zname´nka SF. Instrukci cmp mu˚zˇeme nahradit naprˇ. instrukcı´ test cˇi and. i n t KladnaZaporna2 ( i n t ∗ pole , i n t delka ) { asm { mov e s i , p o l e mov ecx , d e l k a mov eax , 0 opakuj : mov bl , [ e s i ] and bl , b l je d a l s i jns kladne ; z a´ p o r n e´ dec eax jmp d a l s i ; k l a d n e´ kladne : i n c eax dalsi : add e s i , 4 loop opakuj and eax , eax j e konec mov eax , 1 jns konec mov eax , −1 konec : } } 4. Hodnotu nejvysˇsˇ´ıho bitu (tj. zname´nko) nejprve prˇesuneme do CF, a potom pomocı´ odecˇtenı´ 0–CF jej prˇeneseme do vsˇech bitu˚ registru. xor edx , edx b t eax , 3 1 sbb edx , 0
50
5
Za´sobnı´k a podprogramy
Studijnı´ cı´le: V te´to kapitole se naucˇ´ıme pouzˇ´ıvat programovy´ za´sobnı´k a externı´ assembler, cˇ´ımzˇ se dostaneme k cele´ rˇadeˇ dalsˇ´ıch prostrˇedku˚ v inline assembleru nedostupny´ch. Te´ma programove´ho za´sobnı´ku studujeme proto, abychom pochopili, jak v procesoru ve skutecˇnosti funguje vola´nı´ podprogramu˚ a rekurze a externı´ assembler je na´strojem, ktery´ k tomu budeme potrˇebovat. Pro studenty s veˇtsˇ´ım za´jmem o assembler bude externı´ assembler zajı´mavy´ i tı´m, zˇe teprve s nı´m lze vyuzˇ´ıt plnou sˇka´lu programovy´ch konstruktu˚ tohoto jazyka. (Pokrocˇilejsˇ´ı te´mata z externı´ho assembleru prˇitom budou te´matem i na´sledujı´cı´ kapitoly.) Klı´cˇova´ slova: za´sobnı´k, podprogram, vola´nı´, externı´ assembler, MASM, parametry vola´nı´ Potrˇebny´ cˇas: 120 minut.
5.1
Programovy´ za´sobnı´k
Jak jizˇ vı´te z kurzu funkciona´lnı´ho programova´nı´, rekurze je velmi mocny´ programa´torsky´ na´stroj. A rekurze je take´ jednı´m ze za´kladnı´ch prvku˚ procesoru˚, cˇi prˇesneˇji kazˇdy´ procesor pracuje s programovy´m za´sobnı´kem dı´ky neˇmuzˇ umozˇnˇuje rekurzi pomeˇrneˇ snadno pouzˇ´ıvat. Za´sobnı´k je datova´ struktura typu LIFO (last in first out – co poslednı´ ulozˇ´ıme, to prvnı´ prˇecˇteme), kterou mu˚zˇeme velmi snadno pouzˇ´ıt k ulozˇenı´ aktua´lnı´ho stavu (operace push) a pozdeˇji k jeho obnovenı´ (operace pop). Procesor nativneˇ pouzˇ´ıva´ jeden za´sobnı´k, ma´me proto k dispozici dveˇ jednoduche´ instrukce s jednı´m operandem: push a pop. Programovy´ za´sobnı´k ma´ podobu pole 4bajtovy´ch cˇ´ısel a nic jine´ho, nezˇ 4bajtove´ hodnoty na neˇj ukla´dat nejde. Implementace za´sobnı´ku pomocı´ pole je prˇitom velmi jednoducha´. Procesor pouzˇ´ıva´ registr esp (ktery´ jsme nena´padneˇ jizˇ zmı´nili v prˇedchozı´ sekci) jako ukazatel na vrchol za´sobnı´ku. Hodnotou esp je tedy vzˇdy adresa posledneˇ ulozˇene´ho prvku v za´sobnı´ku. Prˇecˇtenı´ hodnoty na vrcholu za´sobnı´ku je tedy mozˇne´ naprˇ´ıklad takto: mov eax,[esp]. (Pouzˇili jsme zde poprve´ hranatou za´vorku pro prˇecˇtenı´ hodnoty z pointeru. Je to opera´tor dereference, byl jizˇ v tabulce 2 a podrobneˇji se s nı´m sezna´mı´me da´le v te´to kapitole.) Dodejme jesˇteˇ, zˇe registr esp nema´ nejnizˇsˇ´ı dva bity, jeho hodnota je tedy vzˇdy na´sobkem cˇtyrˇ. Toto je ale vlastnost, se kterou bychom nemeˇli mı´t proble´my, protozˇe registr esp nebudeme nesystematicky meˇnit. Du˚lezˇiteˇjsˇ´ı vlastnostı´ za´sobnı´ku je, zˇe roste smeˇrem dolu˚ a obvykle je umı´steˇn v pameˇti za globa´lnı´mi promeˇnny´mi programu. Pokud tedy budete na za´sobnı´k ukla´dat prˇ´ılisˇ mnoho hodnot, mohlo by dojı´t k prˇepsa´nı´ globa´lnı´ch promeˇnny´ch. Toto ale by´val proble´m spı´sˇe v MS-DOSu, protozˇe modernı´ procesory umeˇjı´ s podporou operacˇnı´ho syste´mu kontrolovat a zjistit naplneˇnı´ za´sobnı´ku drˇ´ıve, nezˇ dojde k jeho prˇetecˇenı´ a porusˇenı´ neˇjaky´ch dat. 5.1.1
Instrukce push reg/mem/imm
Instrukce push ulozˇ´ı na za´sobnı´k hodnotu operandu: Nejprve se snı´zˇ´ı hodnota esp o 4, pak se do [esp] ulozˇ´ı operand. Operandem mu˚zˇe by´t prˇ´ıma´ hodnota (imm), adresa pameˇti (mem) nebo registr (reg). Jelikozˇ je za´sobnı´k nativneˇ 4bajtovy´, ukla´da´me na neˇj vzˇdy 4bajtove´ hodnoty. Existujı´ i 16bitove´ instrukce jako push ax, ale na za´sobnı´ku se vzˇdy objevı´ 4 bajty. Prˇ´ıznaky: neovlivnˇuje
51
Registr esp ukazuje na vrchol za´sobnı´ku.
5.1.2
Instrukce pop reg/mem
Instrukce pop vyzvedne ze za´sobnı´ku hodnotu a ulozˇ´ı ji do operandu: Hodnota z [esp] se ulozˇ´ı do operandu a hodnota esp se zvy´sˇ´ı o 4. Operandem mu˚zˇe by´t registr nebo adresa pameˇti. Prˇ´ıznaky: neovlivnˇuje 5.1.3
Instrukce call reg/mem/imm
Instrukce call zavola´ podprogram (proceduru cˇi funkci): Ulozˇ´ı na za´sobnı´k aktua´lnı´ hodnotu eip a prˇeda´ rˇ´ızenı´ na adresu danou operandem. Prˇ´ıznaky: neovlivnˇuje 5.1.4
Instrukce ret / ret imm
Instrukce ret bez operandu ukoncˇ´ı prova´deˇnı´ podprogramu a vra´tı´ rˇ´ızenı´ zpeˇt do volajı´cı´ho programu: Vyzvedne ze za´sobnı´ku hodnotu a ulozˇ´ı ji do eip. Tato instrukce ma´ jesˇteˇ variantu s operandem, ktera´ po nastavenı´ eip jesˇteˇ zvy´sˇ´ı esp o hodnotu uvedenou v operandu, cˇ´ımzˇ ze za´sobnı´ku uklidı´ parametry vola´nı´ funkce. (Toto ale mohou pouzˇ´ıvat jen jazyky, ktere´ nepodporujı´ promeˇnlivy´ pocˇet parametru˚ u procedur a funkcı´, jako naprˇ. Turbo Pascal. Naopak C++ pouzˇ´ıva´ jen ret bez operandu.) Prˇ´ıznaky: neovlivnˇuje Pru˚vodce studiem Pozor! ret je ve skutecˇnosti pseudoinstrukce, tj. nejde o skutecˇnou instrukci, ale jen dohodnuty´ za´pis tva´rˇ´ıcı´ se jako instrukce. Konkre´tneˇ u te´to pseudoinstrukce lze tento fakt zanedbat, nebot’ ret je pouze zkra´ceny´m za´pisem instrukcı´ retn a retf, ktere´ se lisˇ´ı pouze typem pameˇt’ove´ho modelu. Poslednı´ pı´smeno na´zvu znamena´ n jako near pro blı´zky´ na´vrat, cˇi f jako far pro vzda´leny´ na´vrat. V nasˇem programova´nı´ ve Windows se ret vzˇdy prˇelozˇ´ı jako retn; druha´ varianta by se mohla pouzˇ´ıt naprˇ. v MS–DOSu cˇi pro na´vrat ze syste´move´ho vola´nı´, kdy je potrˇeba obnovit ze za´sobnı´ku take´ segmentovy´ registr. Typ instrukce na´vratu musı´ prˇesneˇ odpovı´dat typu vola´nı´ – zajı´mave´ prˇitom je, zˇe blı´zke´ i vzda´lene´ vola´nı´ se prova´dı´ stejneˇ pojmenovanou instrukcı´ call, protozˇe typ vola´nı´ pozna´ prˇekladacˇ dle hodnoty operandu te´to instrukce. Na´vratova´ instrukce ale zˇa´dny´ takovy´ operand nema´ a nepotrˇebuje, a tak musı´ mı´t dva na´zvy. Prˇi beˇzˇne´m programova´nı´ jsou vsˇechna vola´nı´ stejne´ho typu a tak prˇekladacˇ sa´m doplnı´ spra´vnou variantu na´vratove´ instrukce.
5.1.5
Vy´znam za´sobnı´ku prˇi vola´nı´ podprogramu˚
Za´sobnı´k ma´ klı´cˇovy´ vy´znam prˇi vola´nı´ podprogramu˚. Zavola´nı´ podprogramu funguje v teˇchto krocı´ch: 1. Volajı´cı´ vlozˇ´ı na za´sobnı´k postupneˇ vsˇechny parametry (push). 2. Volajı´cı´ zavola´ volane´ho (call). 3. Volany´ vytvorˇ´ı ra´mec vola´nı´ podprogramu (tzv. „entry code“ cˇi „prolog“): (a) Ulozˇ´ı ebp (push ebp) – ten doposud ukazoval na loka´lnı´ promeˇnne´ volajı´cı´ho. (b) Nastavı´ ebp na esp (mov ebp,esp)– takzˇe bude ukazovat na vlastnı´ loka´lnı´ promeˇnne´. 52
(c) Snı´zˇ´ı esp tak, aby vytvorˇil na za´sobnı´ku mı´sto pro sve´ loka´lnı´ promeˇnne´ (sub esp,x). 4. Volany´ ulozˇ´ı aktua´lnı´ stavy vsˇech pracovnı´ch registru˚, ktere´ bude beˇhem vy´pocˇtu meˇnit (push). 5. Volany´ vykona´ vlastnı´ ko´d podprogramu. 6. Volany´ obnovı´ stavy vsˇech pracovnı´ch registru˚, ktere´ drˇ´ıve ulozˇil (pop). 7. Volany´ uklidı´ ra´mec vola´nı´ podprogramu (tzv. „exit code“ cˇi epilog): (a) Vra´tı´ esp na hodnotu ebp, cˇ´ımzˇ uklidı´ sve´ loka´lnı´ promeˇnne´ (mov esp,ebp). (b) Vyzvedne pu˚vodnı´ ebp ze za´sobnı´ku (pop ebp). 8. Volany´ vra´tı´ rˇ´ızenı´ do nadrˇazene´ho bloku (ret). 9. Volajı´cı´ ze za´sobnı´ku uklidı´ parametry vola´nı´ (push nebo add esp). Podoba za´sobnı´ku prˇi vola´nı´ je zobrazena na obra´zku 6. Tento prˇ´ıklad platı´ pro prˇ´ıpad volacı´ konvence C. Prˇesna´ podoba za´sobnı´ku vsˇak za´visı´ na neˇkolika faktorech (naprˇ´ıklad take´ na pouzˇitı´ loka´lnı´ch promeˇnny´ch, o ktery´ch jsme zatı´m nemluvili), u neˇktery´ch se jesˇteˇ zastavı´me pozdeˇji. ... Parametr n ... Parametr 1 Na´vratova´ adresa Pu˚vodnı´ ebp Prvnı´ loka´lnı´ promeˇnna´ ... Poslednı´ loka´lnı´ promeˇnna´ ...
←− ebp+8 ←− ebp ←− ebp–4 ←− esp
Obra´zek 6: Data na za´sobnı´ku v okamzˇiku vola´nı´ podprogramu. [Kep07]
Dalsˇ´ı pozna´mky: • V prˇ´ıpadeˇ, zˇe podprogram nepouzˇ´ıva´ loka´lnı´ promeˇnne´, nemusı´ vytva´rˇet ra´mec (stack frame), takzˇe lze vynechat entry code a exit code. • Povinnost ukla´dat registry, ktere´ chceme meˇnit, lze take´ prˇevelet na volajı´cı´ho. Ten ale nemu˚zˇe veˇdeˇt, ktere´ registry bude volany´ potrˇebovat, takzˇe musı´ prˇi kazˇde´m vola´nı´ ukla´dat vsˇechny. Proto je tato varianta me´neˇ efektivnı´. • Prˇi vkla´da´nı´ inline assembleru do cizı´ho ko´du nemusı´me registry EAX, EBX, ECX, EDX, ESI a EDI ukla´dat, protozˇe Visual C++ to deˇla´ za na´s. Je vsˇak potrˇeba myslet na to, zˇe v jine´m prˇekladacˇi se mu˚zˇe inline assembler chovat jinak. • Vstupnı´ parametry vola´nı´ lze ukla´dat na za´sobnı´k naprˇ´ıklad postupneˇ zleva doprava – tak to deˇla´ naprˇ. Turbo Pascal. Parametry jsou pak ale na za´sobnı´ku pozpa´tku, protozˇe za´sobnı´k roste dolu˚. • Rovneˇzˇ za u´klid vstupnı´ch parametru˚ mu˚zˇe odpovı´dat volajı´cı´ nebo volany´. Prˇednostneˇ pouzˇ´ıva´me model jazyka C, kde parametru˚ mu˚zˇe by´t promeˇnlivy´ pocˇet a jen volajı´cı´ vı´, kolik jich ve skutecˇnosti bylo. Proto je po sobeˇ uklı´zı´ on sa´m. Naopak Turbo Pascal pouzˇ´ıva´ k u´klidu instrukci ret s operandem, takzˇe za u´klid zodpovı´da´ volany´.
53
5.2
Cvicˇenı´ s rekurzı´
Nynı´ zna´me princip fungova´nı´ za´sobnı´ku a mu˚zˇeme si jej vyzkousˇet na rekurzi. Mohli bychom jako u´vodnı´ prˇ´ıklad pouzˇ´ıt trˇeba vy´pocˇet faktoria´lu, ale nevy´hodou je, zˇe faktoria´ly jsou velmi velka´ cˇ´ısla – jizˇ pro pomeˇrneˇ male´ vstupnı´ hodnoty se vy´sledek nevejde do 32bitove´ promeˇnne´. Proto mı´sto na´sobenı´ budeme cˇ´ısla v rˇadeˇ 1..n scˇ´ıtat. Vypocˇ´ıta´me tak cˇa´stecˇny´ soucˇet cˇ´ıselne´ rˇady 1 + 2 + · · · + n. V C++ je ko´d velmi jednoduchy´: i n t SoucetRady ( i n t n ) { r e t u r n ( n <=1) ? n : n+ S o u c e t R a d y ( n − 1 ) ; } K realizaci rekurze v assembleru na´m vsˇak jesˇteˇ chybı´ neˇjaky´ na´stroj pro osˇetrˇenı´ limitnı´ho prˇ´ıpadu rekurze (v tomto prˇ´ıpadeˇ ukoncˇenı´ rekurze pro n ≤ 1). Nejjednodusˇsˇ´ı instrukcı´, kterou k tomu lze pouzˇ´ıt, je podmı´neˇny´ skok jecxz. ˇ esˇenı´ R Zde je vzorove´ ˇresˇenı´: i n t SoucetRady ( i n t n ) { asm { mov ecx , n j e c x z k o n e c ; s k o k p r o n==0 sub ecx , 1 j e c x z k o n e c ; s k o k p r o n==1 push e c x c a l l SoucetRady pop e c x add n , eax konec : } return n; } Projdeˇte si tento program prˇ´ıkaz po prˇ´ıkazu. Meˇli byste jej snadno pochopit.
5.3
Externı´ assembler
Nynı´ musı´me od vy´kladu la´tky odbocˇit k cˇisteˇ technicke´mu te´matu. La´tka probı´rana´ zde a v na´sledujı´cı´ch sekcı´ch jizˇ v inline assembleru nejde deˇlat a je tedy nejvysˇsˇ´ı cˇas prˇejı´t k externı´mu assembleru, cozˇ je termı´n oznacˇujı´cı´ pra´ci s plnohodnotny´m prˇekladacˇem assembleru a soubory neobsahujı´cı´mi C/C++. Zatı´m jsme sice da´vali prˇednost inline assembleru pro jeho jednoduchost, pouze externı´ assembler na´m vsˇak umozˇnı´ vytva´rˇet v assembleru cele´ podprogramy a take´ promeˇnne´, konstanty cˇi makra. 5.3.1
Historicke´ souvislosti
V dalsˇ´ı pra´ci budeme opeˇt pouzˇ´ıvat prˇekladacˇ od Microsoftu, ktery´ jakozˇto vy´robce operacˇnı´ho syste´mu MS–DOS (a pozdeˇji MS–Windows) od zacˇa´tku vyvı´jel a poskytoval prˇekladacˇ MASM (Microsoft Macro Assembler) pro procesory x86 a svoje operacˇnı´ syste´my. V assembleru je napsa´n i samotny´ operacˇnı´ syste´m MS-DOS a zejme´na v 80.letech, kdy pocˇ´ıtacˇe nebyly tak 54
rychle´ jako dnes, patrˇil assembler mezi nejdu˚lezˇiteˇjsˇ´ı programovacı´ jazyky. (Na drtive´ veˇtsˇineˇ tehdejsˇ´ıch pocˇ´ıtacˇu˚, ktere´ dlouhou dobu u´speˇsˇneˇ konkurovaly dnes prˇevazˇujı´cı´m pocˇ´ıtacˇu˚m PC meˇl assembler dokonce zcela dominantnı´ roli, na PC take´ patrˇil mezi hlavnı´ jazyky, meˇl vsˇak i konkurenci.) Na zacˇa´tku 90.let 20.stoletı´ se objevovaly i konkurencˇnı´ prˇekladacˇe, v tehdejsˇ´ım Cˇeskoslovensku byl v oblibeˇ zejme´na TASM (Turbo Assembler) firmy Borland, ktery´ byl doda´va´n jako soucˇa´st Turbo Pascalu a Turbo C. Tento prˇekladacˇ se navı´c snazˇ´ı by´t kompatibilnı´ s MASM, takzˇe prˇechod programa´toru˚ mezi nimi dveˇma je pomeˇrneˇ snadny´. Beˇhem 90.let dosˇlo na PC k ması´vnı´mu na´stupu vysˇsˇ´ıch programovacı´ch jazyku˚ na jedne´ straneˇ a vzniku neˇkolika nekomercˇnı´ch projektu˚ s cı´lem vytvorˇit konkurencˇnı´ prˇekladacˇe. Zpocˇa´tku byl velmi oblı´beny´ naprˇ´ıklad NASM (Netwide Assembler). Microsoft se na konci 90.let opeˇt zapojil do te´to „bitvy“, kdyzˇ uvolnil MASM k pouzˇ´ıva´nı´ zdarma a zacˇal jej doda´vat spolu s Visual Studiem a Visual C++. Encyklopedie Wikipedia [Wiki] k tomu uva´dı´, zˇe MASM od zacˇa´tku byl a dodnes je cˇ´ıslem 1 mezi assemblery na platformeˇ x86 a MS–DOS/Windows. (Odhad, kdo dnes zaujı´ma´ dalsˇ´ı prˇednı´ mı´sta v oblı´benosti, [Wiki], ani jine´ dostupne´ zdroje neuva´deˇjı´. Mu˚zˇeme tedy pouze z historicky´ch souvislostı´ odhadnout, zˇe to jsou pra´veˇ zmı´neˇne´ NASM a/nebo TASM.) MASM je vysˇsˇ´ı assembler (high level), nebot’poskytuje podporu maker a sˇiroke´ spektrum jizˇ zabudovany´ch vysˇsˇ´ı prˇ´ıkazu˚, ktere´ napodobujı´ chova´nı´ vysˇsˇ´ıch programovacı´ch jazyku˚ (jako if, while, funkce s parametry apod.). Pro srovna´nı´: Schopnosti maker jsou v MASM na daleko vysˇsˇ´ı u´rovni, nezˇ v jazyce C, ovsˇem trˇeba konkurencˇnı´ NASM jako svou hlavnı´ devizu uva´dı´ pra´veˇ jesˇteˇ lepsˇ´ı schopnosti maker ve srovna´nı´ s MASM. Zmı´neˇny´ NASM je take´ asi nejoblı´beneˇjsˇ´ım konkurentem MASM, jeho vy´hodou je pouzˇitelnost i na jiny´ch operacˇnı´ch syste´mech, zatı´mco MASM je mozˇno spousˇteˇt jen ve Windows (a prˇ´ıpadneˇ v emula´torech Win32, starsˇ´ı verze pak take´ v MS-DOSu a IBM OS/2). Za´jemci o historii najdou dalsˇ´ı pozna´mky o MASM v prˇ´ıloze B.
5.4
Microsoft Macro Assembler (MASM)
Jak bylo rˇecˇeno, MASM je nynı´ soucˇa´stı´ Visual Studia a Visual C++. Najdete jej jako ml.exe v adresa´rˇi VC/bin a ma´ stejne´ cˇ´ıslo verze jako prˇ´ıslusˇna´ edice prˇekladacˇe C/C++. Programy v externı´m assembleru se nejcˇasteˇji pı´sˇ´ı tak, zˇe se zalozˇ´ı projekt C/C++ a prˇida´ se do neˇj jeden nebo vı´ce souboru˚ s prˇ´ıponou .asm. K dispozici pak ma´me jak plnohodnotny´ assembler, tak knihovnu CRT (C Runtime library), ktera´ mu˚zˇe by´t k uzˇitku a jejı´ prˇ´ıtomnost nema´ (resp. nemeˇla by mı´t) zˇa´dny´ negativnı´ dopad. Ota´zkou vlastnı´ch preferencı´ pak je, zda spousˇteˇcı´ funkci main() napı´sˇeme do assembleru, nebo ji necha´me v C/C++. Popsany´ postup je obecneˇ pouzˇ´ıvany´ a platı´ i pro konkurencˇnı´ prostrˇedı´, ne jen Visual Studio a MASM. V doba´ch MSDOSu by´vala obvykla´ i tvorba cely´ch programu˚ bez CRT, ale vzhledem k tomu, jak slozˇity´ ko´d je nutny´ k inicializaci a spusˇteˇnı´ programu ve Windows a jak slozˇite´ je volat funkce Windows API, ve Windows se CRT v programech beˇzˇneˇ necha´va´. CRT je navı´c jako DLL soubor distribuova´no prˇ´ımo s Windows, nenı´ trˇeba jej mı´t u kazˇde´ho programu znovu. (Ko´d vykona´vany´ prˇed a po vola´nı´ main(), ktery´ norma´lneˇ nevidı´me, je obvykle take´ soucˇa´stı´ CRT a obsahuje mj. inicializaci struktur pouzˇ´ıvany´ch v CRT. Program prˇi spusˇteˇnı´ startuje pra´veˇ zde a pak teprve vola´ nasˇi funkci main().) Zde je potrˇeba da´t pozor na dveˇ du˚lezˇite´ veˇci: 1. V projektu musı´ zu˚stat alesponˇ jeden C/C++ soubor, jinak linker neprˇida´ CRT knihovnu. 2. Visual Studio sice zna´ .asm soubory, ma´ pro neˇ ikonu apod., ale nechce je automaticky kompilovat pomocı´ MASM. U kazˇde´ho .asm souboru je tedy potrˇeba nastavit kompilaci rucˇneˇ. Kompilace se ve Visual Studiu nastavuje takto: Prˇidejte do projektu novy´ soubor (v menu Project – Add New Item..., prˇ´ıpadneˇ v Solution Exploreru kontextove´ menu a vybrat Add – New Item...), zvolte si neˇjake´ jme´no a prˇidejte prˇ´ıponu .asm. Potom na soubor klikneˇte v 55
Solution Exploreru a v kontextove´m menu zvolte Properties. Otevrˇe se okno vlastnostı´ souboru (Property Pages), kde je trˇeba nastavit na´sledujı´cı´ (viz take´ obra´zek 7): • Na karteˇ General nastavit Tool na Custom Build Step. • Na karteˇ Custom Build Step – General nastavit: – Command line na ml /Zd /Zi /c /Cx /coff /Fo”$(OutDir)\$(InputName).obj” ”$(InputPath)” – Outputs na $(OutDir)\$(InputName).obj
Obra´zek 7: Konfigurace prˇekladu externı´m assemblerem (Visual Studio 2008)
Pru˚vodce studiem Pozor! V projektu nesmeˇjı´ by´t dva stejneˇ pojmenovane´ soubory, protozˇe by nebylo mozˇno je oba zkompilovat do jednoho adresa´rˇe. Prˇi kompilaci se ztra´cı´ pu˚vodnı´ prˇ´ıpona, takzˇe ma´te-li v projektu .cpp a .asm lisˇ´ıcı´ se jen prˇ´ıponou, nebude to fungovat.
Prˇ´ıkazem ml /? je mozˇno zobrazit seznam vsˇech podporovany´ch prˇepı´nacˇu˚ na prˇ´ıkazove´ ˇra´dce. Vysveˇtlenı´ na´mi pouzˇity´ch je zde: • /Zd prˇipojı´ debug info s cˇ´ısly rˇa´dku˚ • /Zi prˇipojı´ debug info se jme´ny vsˇech symbolu˚ • /c pouze kompiluje, ale nelinkuje • /Cx u verˇejny´ch a externı´ch symbolu˚ rozlisˇuje velikost pı´smen • /coff kompiluje do forma´tu COFF 56
• /Fo nastavı´ jme´no vy´stupnı´ho souboru Tzv. „debug info“ jsou ladicı´ informace, ktere´ doka´zˇe pak Visual Studio vyuzˇ´ıt k pohodlne´mu ladeˇnı´. Cˇ´ısla rˇa´dku˚ slouzˇ´ı ke krokova´nı´ v beˇzˇ´ıcı´m programu, tzv. „symboly“ jsou vsˇechny pojmenovane´ prvky ve zdrojove´m ko´du, ktere´ se prˇekla´dajı´ do ko´du. Vsˇechny tyto informace jsou ulozˇeny prˇ´ımo ve vy´stupnı´m .obj souboru, jeho forma´t COFF je standardnı´m forma´tem .obj souboru˚ pouzˇ´ıvany´ ve Windows. Pru˚vodce studiem Prˇekladacˇ assembleru ve Visual Studiu 2008 (ML.EXE verze 9.00) ma´ chybu zpu˚sobujı´cı´, zˇe v okneˇ Error List se nezobrazujı´ spra´vneˇ vsˇechny chyby nalezene´ ve zdrojove´m ko´du beˇhem prˇekladu. Microsoft doporucˇuje pouzˇ´ıt ML.EXE z prˇedchozı´ edice Visual Studia 2005 (ML.EXE verze 8.00), kde se chyba nevyskytuje. Acˇkoliv je tento postup dosti nestandardnı´, je skutecˇneˇ funkcˇnı´: Pouzˇ´ıva´te-li Visual Studio 2008, okopı´rujte si do neˇj tedy (obvykle do cesty ”c:\Program Files\Microsoft Visual Studio 9.0\VC\bin\ml.exe”) funkcˇnı´ soubor z Visual Studia 2005 (obvykle ”c:\Program Files\Microsoft Visual Studio 8\VC\bin\ml.exe”). [bug report autora tohoto ucˇebnı´ho textu, viz Microsoft Connect feedback ID 331784] V edici Visual C++ 2008 Express prˇekladacˇ assembleru dokonce vu˚bec nenı´ [Microsoft Connect feedback ID 291199], opeˇt by vsˇak mohl pomoci vy´sˇe uvedeny´ postup. Microsoft k Express edici uva´dı´, zˇe v na´sledujı´cı´ verzi (pravdeˇpodobneˇ jizˇ v ra´mci SP1) tam prˇekladacˇ assembleru prˇida´.
5.5
Kostra programu v assembleru
V assembleru ma´me pomeˇrneˇ hodneˇ mozˇnostı´, jak psa´t zdrojovy´ ko´d. Pro zacˇa´tek a hlavneˇ pro u´cˇely vy´uky budeme pouzˇ´ıvat tuto jednotnou kostru programu, do ktere´ budeme vpisovat nasˇe programy – viz obra´zek 8. .486 .model f l a t , c
; budeme p o u zˇ ´ı v a t p r o c e s o r 486 ; p a m eˇ t’o v y´ model f l a t , j a z y k C
.const
; z a cˇ a´ t e k k o n s t a n t
; deklarace konstant .data
; z a cˇ a´ t e k i n i c i a l i z o v a n y´ c h d a t
; d e k l a r a c e promeˇ nny´ ch .data?
; z a cˇ a´ t e k n e i n i c i a l i z o v a n y´ c h d a t
; d e k l a r a c e promeˇ nny´ ch .code
; z a cˇ a´ t e k ko´ du
; ko´ d programu end
; konec souboru Obra´zek 8: Kostra souboru v externı´m assembleru. 57
Na zacˇa´tku souboru ma´me direktivu .486 nastavujı´cı´ typ procesoru. Pojem direktiva znamena´, zˇe jde o neˇjaky´ pokyn pro prˇekladacˇ (tedy ne o instrukci). Cˇ´ıslo v te´to direktiveˇ omezuje podporovane´ instrukce na procesor typu 486 a starsˇ´ı, toto omezenı´ se pouzˇ´ıva´ pro zajisˇteˇnı´, zˇe program bude minima´lneˇ na uvedene´m procesoru fungovat. Podobny´ch direktiv ma´ MASM hodneˇ, pro nasˇe programova´nı´ je .486 vyhovujı´cı´ (v podstateˇ je to tote´zˇ jako .386), pro pokrocˇilejsˇ´ı programa´tory se prˇ´ıpadneˇ mu˚zˇe hodit take´ direktiva .686, ktera´ povoluje azˇ Pentium Pro/Pentium II (od verze MASM 6.12). Dalsˇ´ı direktiva .model flat,c nastavuje pameˇt’ovy´ model. Ve 32bitovy´ch operacˇnı´ch syste´mech na platformeˇ x86 (Windows 9x/NT, Linux apod.) se pouzˇ´ıva´ jen model flat, takzˇe jej zde uvedeme. Za cˇa´rkou je vysˇsˇ´ı programovacı´ jazyk, se ktery´m budeme assembler kombinovat. Toto lze i vynechat, ale na´m se samozrˇejmeˇ spolupra´ce s jazykem C bude hodit. Model jazyka C prˇedepisuje assembleru prˇedevsˇ´ım dveˇ veˇci: • Zpu˚sob prˇeda´va´nı´ parametru˚ prˇi vola´nı´ funkcı´. • Zpu˚sob dekorova´nı´ verˇejny´ch jmen. Prˇeda´va´nı´ parametru˚ prˇi vola´nı´ funkcı´ si vysveˇtlı´me pozdeˇji. Dekorova´nı´ jmen je nutne´ k tomu, aby byl program v jazyce C vu˚bec prˇelozˇitelny´, nebot’historicky se pouzˇ´ıval prˇedevsˇ´ım prˇeklad prˇes assembler v textove´ formeˇ. Kazˇde´ symbolicke´ jme´no (promeˇnna´, funkce atp.) tedy musı´ by´t neˇjak dekorova´no, aby se nekrylo s klı´cˇovy´m slovem assembleru, protozˇe jinak by nesˇlo pouzˇ´ıvat jme´na jako mov, eax apod. Prˇekladacˇe C na platformeˇ x86 obvykle dekorujı´ vsˇechna jme´na prˇida´nı´m cˇa´ry (podtrzˇ´ıtka) na zacˇa´tek na´zvu. Tı´m, zˇe uvedeme .model ne ˇco,c v assembleru, zajistı´me konzistentnı´ pojmenova´va´nı´ symbolu˚ v assembleru a C souborech v ra´mci nasˇeho projektu a mu˚zˇeme je pak libovolneˇ kombinovat. Pozor! Je potrˇeba dodrzˇet porˇadı´ prvnı´ch dvou direktiv. MASM podle neˇj totizˇ urcˇuje adresovacı´ rezˇim procesoru. Toto nema´ zˇa´dny´ logicky´ du˚vod, je to jen historicka´ konvence: Nastavı´meli nejprve procesor na 386 cˇi vysˇsˇ´ı a pak teprve pameˇt’ovy´ model, prˇekladacˇ emituje ko´d ve 32bitove´m segmentu, v opacˇne´m prˇ´ıpadeˇ prˇekladacˇ emituje ko´d v 16bitove´m segmentu. Jelikozˇ procesory 386 a vysˇsˇ´ı podporujı´ 16 i 32bitove´ instrukce v 16 i 32bitovy´ch ko´dovy´ch segmentech a nenı´ zˇa´dna´ direktiva, ktera´ by je rozlisˇila, MASM pouzˇ´ıva´ pro urcˇenı´ adresovacı´ho rezˇimu pra´veˇ porˇadı´ teˇchto dvou direktiv.
5.6
Globa´lnı´ promeˇnne´ a konstanty
Pru˚vodce studiem Pro jme´na symbolu˚ platı´ na´sledujı´cı´ pravidla: Prvnı´m znakem mu˚zˇe by´t pı´smeno (anglicke´ abecedy) nebo jeden z teˇchto cˇtyrˇ znaku˚: @, , $, ?. Pro dalsˇ´ı znaky platı´ tote´zˇ a navı´c tam lze pouzˇ´ıt i cˇ´ısla. Maxima´lnı´ de´lka symbolu˚ je 247 znaku˚ (u´daj berte spı´sˇe s rezervou, u MASM 5 platı´ jen 31 znaku˚ a vsˇeobecneˇ u historicky´ch prˇekladacˇu˚ se pouzˇ´ıval princip ignorova´nı´ koncu˚ jmen – to platı´ i pro prˇekladacˇe jiny´ch jazyku˚ (jako C apod.)). Jme´no symbolu nesmı´ by´t stejne´ jako klı´cˇove´ slovo assembleru (tj. jme´na instrukcı´, registru˚, direktiv apod.). Tato pravidla platı´ pro vsˇechny symboly, ne jen promeˇnne´.
5.6.1
Definice
Zdrojovy´ soubor assembleru obsahuje v libovolne´m porˇadı´ konstanty, promeˇnne´ a ko´d, ktere´ umist’ujeme pomocı´ prˇ´ıslusˇny´ch direktiv do svy´ch segmentu˚. Ko´d je vzˇdy v sekci .code, data jsou trˇ´ı typu˚: • V sekci .const jsou konstanty. Ve Windows je tato sekce hardwaroveˇ chra´neˇna proti za´pisu. 58
• V sekci .data? jsou promeˇnne´ s nulovou pocˇa´tecˇnı´ hodnotou. Tyto promeˇnne´ se neukla´dajı´ do souboru s programem, pouze majı´ vymezenou pameˇt’a prˇi startu programu se tento u´sek pameˇti vynuluje. • V sekci .data jsou inicializovane´ promeˇnne´, ktere´ se ukla´dajı´ do souboru s programem. Na konci souboru vzˇdy musı´ by´t direktiva end. (Tato direktiva nema´ na zacˇa´tku tecˇku. MASM ma´ z historicky´ch du˚vodu˚ rˇadu direktiv s tecˇkou a jine´ pro zmeˇnu bez tecˇky na zacˇa´tku, nehledejte v tom zˇa´dnou logiku.) Globa´lnı´ promeˇnne´ a konstanty zakla´da´me uvedenı´m jme´na na zacˇa´tku rˇa´dku (vsˇimneˇte si, zˇe v assembleru se vzˇdycky pı´sˇe jako prvnı´ noveˇ definovane´ jme´no, v jazycı´ch typu C je tomu naopak), za ktere´ pı´sˇeme definici. p1 p2 p4
byte 7 word 4000 dword ?
;1 bajt ;2 bajty ;4 bajty
V prˇ´ıkladu jsme zalozˇili trˇi promeˇnne´ o de´lce 1, 2 a 4 bajty. Kazˇde´ promeˇnne´ prˇitom musı´me uve´st hodnotu nebo otaznı´k jako vyja´drˇenı´ nedefinovane´ hodnoty. Otaznı´ky uva´dı´me jen v sekci .data?, zatı´mco sekce .const a .data musı´ mı´t vsˇechny hodnoty uvedene´. Na rozdı´l od vysˇsˇ´ıch jazyku˚ v assembleru takto definujeme i pole, kde hodnoty oddeˇlujeme cˇa´rkami. U polı´ se mu˚zˇe hodit specia´lnı´ direktiva dup, ktera´ zopakuje definice hodnot (dup = duplicate). U te´to direktivy si vsˇimneˇte pomeˇrneˇ dost netradicˇnı´ zpu˚sob za´pisu. pole1 pole2 pole3 pole4
byte byte byte byte
1 , 2 , 3 , 4 , 5 ; p o l e 5 p r v k u˚ ? , ? , ? , ? , ? ; p o l e 5 p r v k u˚ b e z u v e d e n ´ı h o d n o t 5 dup ( ? ) ; p o l e 5 p r v k u˚ pomocı´ dup 2 dup ( 3 dup ( 4 , 5 ) , 6 ) ; p o l e h o d n o t 4 , 5 , 4 , 5 , 4 , 5 , 6 , 4 , 5 , 4 , 5 , 4 , 5 , 6
Stejny´m zpu˚sobem definujeme i rˇeteˇzce, prˇicˇemzˇ v assembleru se nedoplnˇuje koncova´ nula automaticky jako v jazyce C – doplnı´me ji tam tedy rucˇneˇ. Assembler take´ nepouzˇ´ıva´ escape ko´dy jako \r\n pro ukoncˇenı´ rˇa´dku, opeˇt je trˇeba zadat rucˇneˇ ko´dy 13,10. Prˇ´ıklad na´sleduje, za´rovenˇ ukazuje mozˇnost vı´cerˇa´dkovy´ch definic polı´: Kdyzˇ je na konci rˇa´dku cˇa´rka, seznam hodnot pokracˇuje na dalsˇ´ım rˇa´dku. t 1 b y t e ” H e l l o World ” , 13 ,10 ,0 Jesˇteˇ jedna pozna´mka k otaznı´ku˚m v definicı´ch hodnot: Jejich pouzˇ´ıva´nı´ nenı´ vylozˇeneˇ omezeno na sekci .data?, nicme´neˇ sekce .data a .const se ukla´dajı´ do vy´sledne´ho ”exe” souboru, zatı´mco .data? ne. Pouzˇijete-li naopak v sekci .data? neotaznı´kove´ definice, ve skutecˇnosti tam budete mı´t nuly (pameˇt’globa´lnı´ch promeˇnny´ch se nuluje prˇi startu programu). Cˇ´ısla lze zada´vat v desı´tkove´ soustaveˇ, v sˇestna´ctkove´ soustaveˇ s prˇ´ıponou h cˇi ve dvojkove´ soustaveˇ s prˇ´ıponou b. (Je mozˇno pouzˇ´ıt i osmicˇkovou soustavu cˇi u´plneˇ prˇedefinovat vy´chozı´ cˇ´ıselnou soustavu, ale to se nebudeme ucˇit.) 5.6.2
Export a import symbolu˚
Pojmy export a import oznacˇujı´ vza´jemne´ zprˇ´ıstupneˇnı´ symbolu˚ mezi soubory v projektu. Export/import symbolu˚ funguje neza´visle na programovacı´m jazyce, je to za´lezˇitost linkeru. Pro pochopenı´ linkeru a principu linkova´nı´ je dobre´ si alesponˇ strucˇneˇ popsat, jak probı´ha´ vytvorˇenı´ ”exe” souboru ze zdrojovy´ch textu˚ programu. Ma´me-li prˇ´ıslusˇne´ (kompatibilnı´) prˇekladacˇe, je teoreticky mozˇne´ psa´t jednotlive´ soubory programu v ru˚zny´ch jazycı´ch. Uzˇ vı´me, zˇe mu˚zˇeme libovolneˇ kombinovat C, C++ a Assembler, tote´zˇ vsˇak platı´ o Pascalu cˇi Fortranu a dalsˇ´ıch klasicky´ch procedura´lnı´ch jazycı´ch. Podmı´nkou 59
je mı´t vhodny´ prˇekladacˇ. Prˇekladacˇ prˇekla´da´ programy po jednotlivy´ch souborech a ke kazˇde´mu zdrojove´mu souboru „vyrobı´“ samostatny´ vy´stup – je to vzˇdy soubor se stejny´m jme´nem a prˇ´ıponou ”.obj” cˇi ”.o”. Visual Studio tyto soubory da´va´ do adresa´rˇe Debug cˇi Release, kde potom najdete i hotovy´ program. V okamzˇiku, kdy prˇekladacˇe prˇ´ıslusˇny´ch programovacı´ch jazyku˚ vyrobily ”obj” soubory, prˇicha´zı´ ke slovu linker – program, ktery´ vsˇechny obj soubory propojı´ (odtud na´zev linker – anglicky „spojovacˇ“) neboli „slinkuje“. Vy´stupem linkeru je ”exe” soubor, prˇ´ıpadneˇ jiny´ vhodny´ spustitelny´ soubor (naprˇ. v Linuxu se nevyra´bı´ ”exe”, ale jiny´ typ souboru˚). Na cele´m procesu kompilace si vsˇimneˇte, zˇe prˇekladacˇe jazyku˚ se nedı´vajı´ do ostatnı´ch souboru˚ v projektu, starajı´ se jen o ten jeden. Ani kdybyste napsali cely´ program v jednom jazyce, prˇekladacˇ si nebude ostatnı´ch souboru˚ v projektu vsˇ´ımat a vzˇdy kompiluje jen jeden. Z toho pak prˇ´ımo plyne zpu˚sob, jaky´m je zajisˇteˇno propojenı´ jednotlivy´ch souboru˚: Kazˇdy´ soubor projektu musı´ explicitneˇ definovat, ktere´ sve´ symboly chce zverˇejnit a pod jaky´m jme´nem (tedy co chce „exportovat“) a naopak ktere´ dodatecˇne´ (de facto nezna´me´ ci cizı´) symboly potrˇebuje, aby program fungoval (tedy co potrˇebuje „importovat“). Linker pak projde vsˇechny soubory a propojı´ mezi nimi exporty a importy. Pozna´mka: Principia´lneˇ nenı´ zaka´za´no, aby vı´ce souboru˚ exportovalo stejneˇ pojmenovany´ symbol. Kdyzˇ pak ale jiny´ soubor bude tento symbol chtı´t importovat, linker mu da´ jeden z teˇch dvou stejneˇ pojmenovany´ch a vy nedoka´zˇete ovlivnit, ktery´ to bude. Jednotlive´ linkery tyto situace rˇesˇ´ı ru˚zneˇ (bud’ berou vzˇdy prvnı´, nebo vzˇdy poslednı´, takzˇe za´visı´ na porˇadı´ souboru˚ v projektu; nebo majı´ mozˇnost dalsˇ´ıch specia´lnı´ch nastavenı´, ktery´mi to lze upravit), je dobre´ se stejneˇ pojmenovany´m exportu˚m vyhnout. Export se v assembleru prova´dı´ velmi jednodusˇe direktivou public s uvedenı´m symbolu k exportu. Prˇekladacˇ prˇitom nevyzˇaduje neˇjake´ specia´lnı´ porˇadı´ v ko´du, obvykle se vsˇechny exporty uva´deˇjı´ na zacˇa´tek souboru pro prˇehlednost cˇi ke kazˇde´mu exportovane´mu symbolu. Prˇ´ıklad na´sleduje. public public public public
MojePromenna MojeProcedura MojeKonstanta MujLabel
Exportovat se takto da´ jaky´koliv symbol, ktery´ je neˇkde v pameˇti prˇi beˇhu programu, tedy trˇeba jen mı´sto ke skoku (label). Platı´ to i pro procedury (zakla´da´nı´ procedur si popı´sˇeme v na´sledujı´cı´ sekci). Import funguje podobneˇ jako export, avsˇak jelikozˇ MASM je typovany´ assembler, u kazˇde´ho importovane´ho symbolu musı´me uve´st typ. (Pozor! Pro potrˇeby linkeru se skutecˇneˇ exportujı´ jen symboly, tedy jme´na a jejich adresy, zatı´mco jejich typy cˇi jake´koliv dalsˇ´ı informace soucˇa´stı´ exportu nejsou!) Definice importu je patrna´ z na´sledujı´cı´ch prˇ´ıkladu˚. e x t e r n C i z i I n t e g e r : dword e x t e r n CiziZnak : byte e x t e r n C i z i P r o c e d u r a : proc V tabulce je opeˇt uveden i import procedury. Mu˚zˇeme takto importovat naprˇ´ıklad printf a pak jej zavolat pomocı´ call printf. Je vsˇak potrˇeba se jesˇteˇ naucˇit prˇeda´vat parametry prˇi vola´nı´ a to se naucˇ´ıme za okamzˇik. Pru˚vodce studiem MASM z historicky´ch du˚vodu˚ umozˇnˇuje oznacˇovat importy direktivami extern i extrn (v tom druhe´m chybı´ jedno e). Obeˇ direktivy deˇlajı´ prˇesneˇ tote´zˇ, je tedy jedno, ktery´ za´pis budete pouzˇ´ıvat.
60
5.7 5.7.1
Podprogramy, procedury a funkce Vysveˇtlenı´ pojmu
Pojmy podprogram, procedura a funkce oznacˇujı´ tote´zˇ: Jedna´ se cˇa´st ko´du, kterou mu˚zˇeme zavolat, prˇ´ıpadneˇ s neˇjaky´mi parametry, a po skoncˇenı´ vola´nı´ pokracˇuje vykona´va´nı´ ko´du na pu˚vodnı´m mı´steˇ. Podprogram je tedy jistou obdobou softwarove´ho prˇerusˇenı´ – vykona´va´nı´ ko´du je prˇerusˇeno a po vykona´nı´ podprogramu vy´pocˇet pokracˇuje na mı´steˇ, kde byl prˇerusˇen. Pru˚vodce studiem Pta´te se, procˇ vlastneˇ ma´ tento pojem trˇi na´zvy? Pu˚vodneˇ se pouzˇ´ıval pouze pojem podprogram a forma´lneˇ vzato bychom prˇi pra´ci s assemblerem meˇli pouzˇ´ıvat pouze tento termı´n. Procedury a funkce byly zavedeny ve strukturovany´ch jazycı´ch. Majı´ neˇktere´ dobrˇe zna´me´ vlastnosti a navza´jem se lisˇ´ı prˇedevsˇ´ım tı´m, zˇe procedura nevracı´ na´vratovou hodnotu, zatı´mco funkce ano. Neˇktere´ pozdeˇjsˇ´ı jazyky tyto dva pojmy opeˇt slily (naprˇ. C), protozˇe vracenı´ cˇi nevracenı´ hodnoty nenı´ rozhodujı´cı´. Makro assemblery podprogramu˚m pro zmeˇnu rˇ´ıkajı´ obvykle procedury a majı´ k tomu prˇ´ıslusˇna´ klı´cˇova´ slova. (Z hlediska modernı´ho assembleru je take´ nepodstatne´, zda podprogram vracı´ cˇi nevracı´ hodnotu.)
5.7.2
Definice a export procedury
Naucˇ´ıme se nejprve pouzˇ´ıvat cˇisty´ assembler bez maker, ktery´ prˇ´ımo procedury nezna´. Chcemeli mı´t mozˇnost neˇco zavolat, oznacˇ´ıme si prˇ´ıslusˇny´ rˇa´dek ko´du na´veˇsˇtı´m (jme´no a dvojtecˇka). Chceme-li, aby tento symbol byl videˇt z jiny´ch souboru˚ projektu, musı´me jej explicitneˇ exportovat direktivou public. Pro srovna´nı´: V jazyce C jsou vsˇechny symboly exportova´ny implicitneˇ (a lze to u jednotlivy´ch soucˇa´stı´ zaka´zat oznacˇenı´m static). Prˇ´ıklad na´sleduje. public VratJednicku VratJednicku : mov eax , 1 ret Jak uzˇ vı´me z kapitoly 2.10, na konci procedury musı´ by´t instrukce ret a na´vratovou hodnotu vracı´me v registru eax. Tato procedura tedy jednodusˇe vzˇdy vracı´ cˇ´ıslo 1. Pozor! Assembler standardneˇ nerozlisˇuje velikost pı´smen, prˇi kompilaci s projektem C/C++ vsˇak ma´me nastaveno, aby exportovane´ symboly velikost pı´smen ctily dle definice (viz prˇedchozı´ sekce, konfigurace prˇekladu v MASM). Direktivu public lze uva´deˇt i k neexistujı´cı´m symbolu˚m, proto kdyzˇ v public uvedete jinou velikost pı´smen, nezˇ u symbolu v programu, tak vlastneˇ oznacˇujete export neexistujı´cı´ho symbolu. 5.7.3
Import procedury do jazyka C
V C/C++ si proceduru zprˇ´ıstupnı´me pomocı´ prototypu. (Exportova´nı´m jsme symbol zverˇejnili v ra´mci projektu, v kazˇde´m jednotlive´m C/C++ souboru jej vsˇak musı´me importovat pomocı´ prototypu. Princip, zˇe „vsˇechno je videˇt vsˇude“, ktery´ zna´me trˇeba z C# a jiny´ch modernı´ch jazyku˚, zde tedy ani zdaleka neplatı´.) Nejprve verze pro jazyk C: i n t VratJednicku ( void ) ; A nynı´ tote´zˇ v jazyce C++: e x t e r n ”C” i n t V r a t J e d n i c k u ( ) ; 61
Typ
Registr
byte word dword qword
al ax eax edx:eax (registrovy´ pa´r)
Tabulka 10: Registry pouzˇ´ıvane´ pro vracenı´ hodnot z procedur. Jak vidno, rozdı´l je v tom, zˇe v jazyce C++ musı´me prˇed kazˇdou takovou deklaraci prˇidat extern ”C” a naopak v C musı´me u funkcı´ bez parametru˚ psa´t do za´vorek slovo void. Na´sˇ program pak mu˚zˇeme zavolat a otestovat (vypsat vra´cene´ cˇ´ıslo na obrazovku apod.). Vsˇimneˇte si, zˇe z assembleru se exportuje jen jme´no a nic vı´c. Forma´t vstupnı´ch parametru˚ cˇi typ na´vratove´ hodnoty tedy nejsou soucˇa´stı´ exportnı´ch informacı´. Toto je klasicka´ vlastnost jak assembleru, tak jazyka C. Udeˇla´me-li prˇi psanı´ prototypu˚ chybu, prˇekladacˇ ji nezjistı´. (Mu˚zˇeme dokonce zameˇnit funkci a promeˇnnou, opeˇt to ve veˇtsˇineˇ prˇekladacˇu˚ nejde zjistit.) 5.7.4
Import C funkce do assembleru
Podobny´m zpu˚sobem jako vola´nı´ assemblerovske´ procedury z C lze take´ volat C funkce z assembleru. Jelikozˇ C vsˇe exportuje implicitneˇ, na straneˇ C nenı´ trˇeba deˇlat zˇa´dne´ u´pravy ko´du. V assembleru pak pouze importujeme prˇ´ıslusˇny´ symbol direktivou extern. Dı´ky direktiveˇ .model ne ˇco,c se prˇekladacˇ navı´c sa´m postara´ o dekorova´nı´ importovany´ch jmen. Prˇ´ıklad na´sleduje. e x t e r n V r a t D v o j k u : proc Nynı´ mu˚zˇete zkusit napsat v C funkci VratDvojku(), ktera´ vra´tı´ dvojku a z procedury VratJednicku ji zavolat pomocı´ instrukce call. Pak znovu otestujte proceduru VratJednicku, nynı´ by meˇla vracet dvojku. 5.7.5
Na´vratova´ hodnota
Kazˇda´ procedura mu˚zˇe mı´t na´vratovou hodnotu. Jak uzˇ jsme uvedli vy´sˇe, prˇi spolupra´ci C a assembleru se typ na´vratove´ hodnoty nehlı´da´ a za´visı´ jen na nasˇem napsa´nı´ prototypu. V kapitole 2.5 na straneˇ 14 vı´me, zˇe hodnota typu int se vracı´ v registru eax. Tabulka 10 ukazuje, v jaky´ch registrech se vracejı´ jednotlive´ datove´ typy assembleru. Pro zajı´mavost dodejme, zˇe hodnoty typu˚ s plovoucı´ rˇa´dovou cˇa´rkou se vracejı´ na FPU za´sobnı´ku a veˇtsˇ´ı datove´ struktury lze vracet jen tak, zˇe volajı´cı´ poskytne prˇedem buffer pro zapsa´nı´ vy´sledku. (Psa´t takove´ vola´nı´ v assembleru je tedy dosti komplikovane´.) 5.7.6
Prˇeda´va´nı´ parametru˚ prˇi vola´nı´
Nynı´ se dosta´va´me k pomeˇrneˇ slozˇite´ veˇci: prˇeda´va´nı´ parametru˚. Ve vysˇsˇ´ıch jazycı´ch se s parametry obvykle pracuje velmi jednodusˇe a chovajı´ se jako loka´lnı´ promeˇnne´. V assembleru se take´ parametry vola´nı´ a loka´lnı´ promeˇnne´ chovajı´ stejneˇ, ale jejich pouzˇ´ıva´nı´ v obycˇejne´m assembleru bez neˇjaky´ch pomu˚cek od prˇekladacˇe je slozˇiteˇjsˇ´ı. Pozdeˇji se naucˇ´ıme pouzˇ´ıvat pseudoinstrukce, ktere´ vsˇe zjednodusˇsˇ´ı, nejprve se ale musı´me naucˇit, jak veˇci doopravdy fungujı´ (proto prˇece assembler studujeme). O prˇeda´va´nı´ parametru˚ byla rˇecˇ jizˇ na zacˇa´tku te´to kapitoly v souvislosti s organizacı´ za´sobnı´ku a take´ na prˇedna´sˇce. Nynı´ tedy jizˇ zna´me´ veˇci jen prˇevedeme do praxe. Standardneˇ v assembleru pouzˇ´ıva´me volacı´ konvenci C, jiny´mi variantami se nynı´ zaby´vat nebudeme. Vola´nı´ si prˇedvedeme na prˇ´ıkladu vypsa´nı´ forma´tovane´ho textu s cˇ´ıslem na obrazovku. 62
.const
; z a cˇ a´ t e k k o n s t a n t
cislo dword 37 p o z d r a v b y t e ” H e l l o World ! ” , 0 f o r m a t b y t e ” c i s l o : %i , p o z d r a v : %s ” , 1 3 , 1 0 , 0 ; z a cˇ a´ t e k ko´ du
.code e x t e r n p r i n t f : proc
public t e s t u j testuj : push o f f s e t p o z d r a v push dword p t r c i s l o push o f f s e t f o r m a t call printf add esp , 1 2 ret Prˇ´ıklad ukazuje vola´nı´ printf se trˇemi parametry (forma´tovacı´ rˇeteˇzec, cˇ´ıslo a dalsˇ´ı rˇeteˇzec). (Pozor! Pro vyzkousˇenı´ tohoto prˇ´ıkladu je nutno ve Visual Studiu prˇepnout na statickou CRT knihovnu (tedy ne DLL).) Parametry pozpa´tku (zprava doleva) vlozˇ´ıme na za´sobnı´k instrukcı´ push, vsˇimneˇte si prˇitom, zˇe mu˚zˇeme na za´sobnı´k prˇ´ımo ukla´dat i hodnoty promeˇnny´ch (nebot’ ma´me CISC procesor). Du˚lezˇite´ take´ je uveˇdomit si, zˇe u rˇeteˇzcu˚ ukla´da´me na za´sobnı´k jejich adresu (offset), zatı´mco u integeru˚ jejich hodnoty (dword ptr). Pozor take´ na rozdı´ly mezi loka´lnı´mi a globa´lnı´mi promeˇnny´mi, pointery a poli. Po skoncˇenı´ vola´nı´ ma´ volajı´cı´ povinnost uklidit za´sobnı´k, tedy uve´st ho do stavu prˇed vola´nı´m push. To samozrˇejmeˇ lze prove´st pomocı´ trˇ´ı vola´nı´ pop, nebo le´pe prˇ´ımy´m prˇicˇtenı´m prˇ´ıslusˇne´ hodnoty k registru esp. Programovy´ za´sobnı´k funguje na x86 tak, zˇe vsˇechny jeho bunˇky jsou stejneˇ velke´, dle typu ko´dove´ho segmentu. V nasˇem prˇ´ıpadeˇ tedy ma´ kazˇde´ bunˇka 4 bajty, proto pro uklizenı´ 3 hodnot ze za´sobnı´ku prˇicˇteme k esp cˇ´ıslo 3 · 4 = 12. Pozna´mka: Budete-li potrˇebovat volat kra´tce po sobeˇ funkce s podobny´mi parametry, mu˚zˇete hodnoty na za´sobnı´ku pouzˇ´ıt. Lze je totizˇ nechat i pro vı´ce vola´nı´, cˇi dokonce meˇnit pomocı´ mov [esp+n],ne ˇco apod. Mu˚zˇete vsˇak narazit na proble´m, zˇe zavolana´ funkce hodnotu na za´sobnı´ku zmeˇnı´ (je to prˇece jejı´ loka´lnı´ promeˇnna´, takzˇe ji ma´ pra´vo meˇnit), proto tento druh optimalizace nenı´ zrovna bezpecˇny´. 5.7.7
Pouzˇitı´ prˇedany´ch parametru˚
Podı´vejme se nynı´ na opacˇnou situaci: My chceme vytvorˇit funkci s parametry tak, aby byla zavolatelna´ z jazyka C. Jak na to jsme si uzˇ teoreticky popsali na zacˇa´tku te´to kapitoly, proto rovnou uvedeme prˇ´ıklad: public soucet soucet : ; entry push ebp mov ebp , e s p ; t eˇ l o mov eax , [ ebp + 8 ] add eax , [ ebp + 1 2 ] ; exit pop ebp ret 63
Funkce soucet secˇte dveˇ cˇ´ısla prˇedana´ jako parametry vola´nı´. Ve funkci jsou take´ vyznacˇeny tzv. „entry“ a „exit“ ko´dy nutne´ k pouzˇ´ıva´nı´ parametru˚ vola´nı´ a loka´lnı´ch promeˇnny´ch. Teoreticky by sˇlo k promeˇnny´m prˇistupovat i prˇ´ımo prˇes [esp+n] ale meˇli bychom proble´my s prˇ´ıstupem na za´sobnı´k v okamzˇiku vola´nı´ dalsˇ´ıch funkcı´ (kazˇde´ push posune esp, cˇ´ımzˇ minima´lneˇ vznika´ velky´ chaos ve zdrojove´m ko´du). Pozor! Jelikozˇ instrukce push/pop pracujı´ vzˇdy s 4bajtovy´mi bunˇkami, prˇeda´nı´ trˇeba 10 1bajtovy´ch promeˇnny´ch zabere ve skutecˇnosti 40 bajtu˚. 5.7.8
Loka´lnı´ promeˇnne´
Loka´lnı´ promeˇnne´ se v assembleru pouzˇ´ıvajı´ naprosto sporadicky. Jejich u´cˇel a smysl pouzˇitı´ je sice u´plneˇ stejny´ jako ve vysˇsˇ´ıch jazycı´ch, v assembleru ale ma´me k dispozici registry procesoru, se ktery´mi se le´pe pracuje a obvykle na´m stacˇ´ı. Pouzˇ´ıvat mu˚zˇeme minima´lneˇ 6 obecny´ch a indexovy´ch registru˚ eax, ebx, ecx, edx, esi a edi. Ozˇelı´me-li loka´lnı´ promeˇnne´ a stack frame, pak ma´me jesˇteˇ ebp. Je-li registru˚ nedostatek, cˇasto se hodı´ ukla´dat vı´ce hodnot do jednoho registru a pomocı´ bitovy´ch rotacı´ (instrukce rol a ror) si je zprˇ´ıstupnˇovat. Dolnı´ dva bajty za´kladnı´ch 4 registru˚ dokonce lze pouzˇ´ıvat i prˇ´ımo. Pouzˇitı´ loka´lnı´ch promeˇnny´ch opeˇt plyne z faktu˚ uvedeny´ch na zacˇa´tku te´to kapitoly. Entry ko´d doplnı´me o vytvorˇenı´ prostoru na za´sobnı´ku posunutı´m esp, exit ko´d pak musı´ esp obnovit na pu˚vodnı´ hodnotu. Pro prˇ´ıklad si uved’me u´pravu prˇedchozı´ho programu tak, aby se oba vstupnı´ parametry nejprve okopı´rovaly do loka´lnı´ch promeˇnny´ch, a azˇ pak se secˇetly. public soucet soucet : ; entry push ebp mov ebp , e s p sub esp , 8 ; p rˇ e n e s e m e p a r a m e t r y do l o k a´ l n ´ı c h promeˇ nny´ ch mov eax , [ ebp + 8 ] mov [ ebp −4] , eax mov eax , [ ebp + 1 2 ] mov [ ebp −8] , eax ; t eˇ l o mov eax , [ ebp −4] add eax , [ ebp −8] ; exit add esp , 8 pop ebp ret Pouzˇ´ıva´nı´ parametru˚ a loka´lnı´ch promeˇnny´ch tedy spocˇ´ıva´ ve vytvorˇenı´ ra´mce na za´sobnı´ku a odkazova´nı´m se prˇes ebp. Nejveˇtsˇ´ı bolı´stkou programa´tora v praxi je pak neexistence na´zvu˚, ko´d je neprˇehledny´ a je zde veˇtsˇ´ı riziko chyb. 5.7.9
Vnorˇene´ funkce
Assembler pochopitelneˇ umozˇnˇuje i definovat a pouzˇ´ıvat vnorˇene´ funkce, tedy funkce definovane´ uvnitrˇ jiny´ch funkcı´. Toto se hodı´ naprˇ´ıklad prˇi realizaci rekurzivnı´ch algoritmu˚, kdy se jaky´koliv algoritmus tva´rˇ´ı jako jedna funkce a vsˇechen ko´d, ktery´ se v neˇm pouzˇ´ıva´, tedy vcˇetneˇ prˇ´ıpadny´ch rekurzivnı´ch funkcı´, je umı´steˇn uvnitrˇ one´ jedne´ verˇejne´ funkce. Vy´hodou tohoto rˇesˇenı´ je, zˇe u vnitrˇnı´ch funkcı´ nemusı´me dodrzˇovat konvenci vola´nı´ C a mu˚zˇeme si 64
prˇeda´vat parametry v registrech dle aktua´lnı´ potrˇeby. Jiny´mi slovy: Vnorˇene´ funkce jsou jednı´m z na´stroju˚ optimalizace ko´du pro rychlost. Ve vysˇsˇ´ıch jazycı´ch vnorˇene´ funkce obvykle nejdou vytva´rˇet (naprˇ. C nebo C#), nebo jde o konstrukt vedoucı´ naopak ke zpomalenı´ programu (naprˇ. Pascal).
5.8
Konstanty
Konstanty (neple´st s promeˇnny´mi v segmentu .const) lze vytva´rˇet dveˇma zpu˚soby. Cˇ´ıselne´ konstanty vytva´rˇ´ıme velmi intuitivneˇ pomocı´ obycˇejne´ znacˇky =. Tyto konstrukce jsou velmi mocne´, nebot’je lze pouzˇ´ıvat pro makrovy´pocˇty, prˇedefinova´vat i pomocı´ sebe sama. Uved’me neˇkolik prˇ´ıkladu˚ jme´ no = 120 nula = 0 jme´ no = jme´ no ∗2
; zmeˇ nı´ me d e f i n i c i k o n s t a n t y
kromeˇ toho MASM umozˇnˇuje pomocı´ direktivy equ definovat obycˇejne´ litera´lnı´ konstanty. Tentokra´t se jedna´ o cˇisteˇ textovy´ konstrukt, ktery´ jednou nadefinovany´ jizˇ nelze nikdy zmeˇnit. (Chova´ se de facto stejneˇ jako #define v jazyce C.) jmeno equ 120 n u l a equ 0 jmeno equ jmeno ∗2 ; c h y b a : p rˇ e k l a d a cˇ t o t o c h a´ p e j a k o ”120 equ 120∗2”
Shrnutı´ Inline assembler vepsany´ do zdrojovy´ch ko´du jazyka C/C++ je jednoduchy´ zpu˚sob, jak assembler pouzˇ´ıt a prˇitom vynechat slozˇitosti spojene´ se syntaxı´ globa´lnı´ch konstruktu˚, ma´ to vsˇak jista´ omezenı´. Naprˇ´ıklad tı´mto zpu˚sobem nelze zakla´dat cele´ funkce a take´ nelze pouzˇ´ıvat prostrˇedky makroassembleru. V te´to kapitole jsme si podrobneˇ vysveˇtlili fungova´nı´ programova´nı´ za´sobnı´ku a jeho vy´znam prˇi vola´nı´ funkcı´ (cˇi obecneˇ rˇecˇeno podprogramu˚). Za u´cˇelem pra´ce se za´sobnı´kem jsme se naucˇili tzv. externı´ assembler, tedy „opravdovy´“ assembler, ktery´ se jizˇ nevpisuje do jiny´ch programovacı´ch jazyku˚. Pojmy k zapamatova´nı´ • • • • • • • • • • • • • •
programovy´ za´sobnı´k instrukce push a pop instrukce call a ret externı´ assembler MASM pameˇt’ovy´ model podprogram, procedura, funkce dekorova´nı´ jmen prˇeda´va´nı´ parametru˚ (prˇi vola´nı´) globa´lnı´ promeˇnna´ export a import symbolu˚ loka´lnı´ promeˇnna´ vnorˇena´ funkce konstanta
Kontrolnı´ ota´zky 65
1. Kolik programovy´ch za´sobnı´ku˚ v pocˇ´ıtacˇi je? Diskutujte, co by prˇineslo cˇi jake´ by byly proble´my, kdyby za´sobnı´ku˚ bylo me´neˇ cˇi vı´ce. 2. Co je to pameˇt’ovy´ model? Jaky´ ma´ prˇi programova´nı´ vy´znam a jak souvisı´ s operacˇnı´mi syste´my? 3. Vysveˇtlete motivaci k pouzˇitı´ a princip fungova´nı´ dekorova´nı´ jmen symbolu˚ a jejich exportu˚ a importu˚. 4. Veˇtsˇina beˇzˇny´ch programovacı´ch jazyku˚ neumozˇnˇuje pouzˇ´ıvat vnorˇene´ funkce. Pokuste se navrhnout, jak by se vnorˇena´ funkce dala implementovat v assembleru. Chceme prˇitom, aby vnorˇena´ funkce meˇla prˇ´ıstup k loka´lnı´m promeˇnny´m lexika´lneˇ nadrˇazene´ funkce (tedy k promeˇnny´m funkce, ve ktere´ je vepsana´). Cvicˇenı´ 1. Zkuste si prˇeve´st neˇktere´ vasˇe programy (z prˇedchozı´ch kapitol) do externı´ho assembleru. Procvicˇ´ıte si tak syntaxi.
´ koly k textu U 1. Napisˇte v externı´m assembleru proceduru pro soucˇet libovolne´ho pocˇtu cˇ´ısel. Poslednı´m argumentem vola´nı´ bude vzˇdy nula, podle toho bude urcˇeno, kolik cˇ´ısel se ma´ secˇ´ıst. int soucet ( int cislo ,
...);
2. Napisˇte v externı´m assembleru proceduru, ktera´ vypı´sˇe hodnoty prvku˚ v poli. Vstupnı´m parametrem bude velikost pole a adresa zacˇa´tku pole. Funkce hodnoty vypı´sˇe vola´nı´m printf. void v y p i s p o l e ( i n t pocet , i n t ∗ pole ) { f o r ( i n t i = 0 ; i
ˇ esˇenı´ R 1. Ukazˇme si naprˇ´ıklad za´pis strlen (tuto funkci jsme rˇesˇili ve cvicˇenı´ v minule´ kapitole). Jedna´ se o velmi jednoduchou funkci bez loka´lnı´ch promeˇnny´ch cˇi vola´nı´ jiny´ch funkcı´, takzˇe mu˚zˇeme zkusit ji realizovat bez vytva´rˇenı´ ra´mce. Navı´c si vystacˇ´ıme s jediny´m registrem, ve ktere´m potom take´ vra´tı´me vy´sledek. 66
public s t r l e n 2 e x t strlen2ext : mov eax , [ e s p + 4 ] dec eax ; posuneme s e j e d e n z n a k p rˇ e d z a cˇ a´ t e k t e x t u dalsi : i n c eax ; posuneme s e na d a l sˇ ´ı z n a k cmp b y t e p t r [ eax ] , 0 jnz d a l s i sub eax , [ e s p + 4 ] ; ma´me a d r e s u konce , o d e cˇ t e m e od n ´ı a d r e s u z a cˇ a´ t k u ret
67
6
Prostrˇedky makroassembleru
Studijnı´ cı´le: Tato kapitola poskytuje na´hled do mozˇnostı´ makroassembleru jakozˇto na´stroje pro zprˇehledneˇnı´ a zjednodusˇenı´ programova´nı´ v assembleru. Zde diskutovana´ te´mata jsou jizˇ jen doplneˇnı´m studia pro studenty, kterˇ´ı majı´ o problematiku a studium assembleru hlubsˇ´ı za´jem. Klı´cˇova´ slova: makro, direktiva, procedura, podmı´neˇny´ blok, opakova´nı´ bloku Potrˇebny´ cˇas: 80 minut. Pru˚vodce studiem Na programova´nı´ v assembleru existujı´ mezi odbornı´ky ru˚zne´ pohledy. Jedna skupina pouzˇ´ıva´ makroassemblery a tvorˇ´ı v nich high–level ko´d s tı´m, zˇe je kratsˇ´ı, prˇehledneˇjsˇ´ı a me´neˇ chybovy´. Druha´ skupina vsˇak oponuje, zˇe to uzˇ prˇece nenı´ opravdovy´ assembler a pouzˇ´ıva´ pouze cˇisty´ assembler bez maker. Dalsˇ´ı skupina zase odmı´ta´ typova´nı´ v assembleru a pouzˇ´ıva´ assembler bez datovy´ch typu˚, i kdyzˇ trˇeba s makry. Autor tohoto ucˇebnı´ho textu zcela jednoznacˇneˇ inklinuje k prvnı´ zmı´neˇne´ skupineˇ, nicme´neˇ v za´jmu studia vnitrˇnı´ho chova´nı´ CPU bylo nutne´ studentu˚m rˇadu uzˇitecˇny´ch maker ve vy´kladu la´tky zatajit. Veˇtsˇina programa´toru˚ patrˇ´ıcı´ do zmı´neˇne´ druhe´ skupiny nepouzˇ´ıva´ high–level konstrukce jednodusˇe z toho du˚vodu, zˇe je vu˚bec nezna´. Hlavnı´m u´skalı´m jsou totizˇ nekvalitnı´ ucˇebnice, ktere´ se obvykle pitvajı´ v detailech jednotlivy´ch instrukcı´, ale vu˚bec nerˇesˇ´ı, jak v assembleru tvorˇit veˇtsˇ´ı programove´ celky. Trˇetı´ zmı´neˇna´ skupina pak obvykle nepouzˇ´ıva´ typy, protozˇe jejich prˇekladacˇe assembleru jednodusˇe typy vu˚bec neumozˇnˇujı´ pouzˇ´ıvat. Naprˇ´ıklad NASM je velmi popula´rnı´m assemblerem v Linuxu, kde MASM od Microsoftu pouzˇ´ıt nelze. NASM je prˇekladacˇ s bohatou funkcionalitou maker, ale je beztypovy´, takzˇe programa´tory nutı´ pracovat bez typu˚ at’se jim to lı´bı´, nebo ne. Tato kapitola cˇerpa´ vy´hradneˇ z manua´lu k MASM 6 [Masm]. Dalsˇ´ım zdrojem mu˚zˇe by´t take´ seznam novinek v MASM 8 [Masm8].
6.1
Direktivy, pseudoinstrukce a makra
Direktivy, pseudoinstrukce a makra jsou prostrˇedky, ktery´mi na´m MASM poma´ha´ zjednodusˇit si zˇivot prˇi pra´ci s assemblerem. Assembler se tak de facto sta´va´ o neˇco vysˇsˇ´ım jazykem, nezˇ jen prˇepisem strojove´ho ko´du se symbolicky´mi adresami. Celou rˇadu konstrukcı´ makroassembleru jizˇ zna´me z prˇedchozı´ch kapitol, nynı´ se sezna´mı´me s neˇkolika dalsˇ´ımi. Nebudou to vsˇak vsˇechny, ktere´ MASM nabı´zı´, nebot’jich existuje opravdu velke´ mnozˇstvı´ a jejich zvla´dnutı´ je nad ra´mec nasˇeho studia.
6.2
Export/import a hlavicˇkove´ soubory
Mı´sto deklaracı´ public a extern mu˚zˇeme pouzˇ´ıt univerza´lnı´ direktivu externdef, ktera´ se pouzˇ´ıva´ stejneˇ jako extern, ale funguje jinak: Je-li symbol v souboru definova´n, externdef se chova´ jako public. Je-li symbol v souboru pouzˇ´ıva´n, ale nenı´ definova´n, externdef se chova´ jako extern. V ostatnı´ch prˇ´ıpadech se direktiva ignoruje. Tato direktiva se cˇasto pouzˇ´ıva´ spolecˇneˇ z tzv. include soubory, cozˇ jsou soubory specia´lneˇ urcˇene´ k definici verˇejny´ch soucˇa´stı´ programu. Pomocı´ direktivy include a uvedenı´ jme´na se include soubor prˇipojı´ na dane´ mı´sto zdrojove´ho ko´du assembleru. Include soubor ma´ stejny´ forma´t jako .asm soubory, ale prˇ´ıponu .inc (cozˇ nenı´ technicky nutne´, ale je to zvykem). V jazyce C se teˇmto souboru˚m rˇ´ıka´ „hlavicˇkove´ soubory“. (V assembleru je to tote´zˇ, pouze mı´sto #include pı´sˇeme jen include a jme´no souboru uva´dı´me bez uvozovek.) 68
6.3
Vy´razy
Do zdrojove´ho textu MASM lze psa´t libovolne´ vy´razy, ktere´ lze spocˇ´ıtat prˇi kompilaci. Viz prˇ´ıklad: add a , ebx+ e c x ; c h y b a ! t a k o v a´ i n s t r u k c e n e n ´ı add a , 1 2 0 ∗ 1 0 0 0 s h r 12 xor 10 ;OK Soucˇet dvou registru˚ uvedeny´ na prvnı´ rˇa´dku nenı´ mozˇno spocˇ´ıtat v cˇase prˇekladu, takzˇe prvnı´ rˇa´dek je chybny´. Slova shr a xor na druhe´m rˇa´dku vsˇak nejsou instrukce assembleru, ny´brzˇ aritmeticke´ opera´tory (s intuitivnı´m vy´znamem) a cela´ prava´ strana (vsˇe za cˇa´rkou mezi operandy) je tedy konstanta, kterou prˇekladacˇ spocˇ´ıta´ prˇi prˇekladu a dosadı´ jako prˇ´ımou hodnotu (immediate). Tabulka 11 shrnuje za´kladnı´ opera´tory MASM. Opera´tor
Popis
+, -, *, / +, shr, shl mod not, and, or, xor eq, ne lt, le gt, ge length size
klasicke´ bina´rnı´ aritmeticke´ opera´tory klasicke´ una´rnı´ aritmeticke´ opera´tory bitove´ posuny zbytek po celocˇ´ıselne´m deˇlenı´ klasicke´ bitove´ opera´tory pravdivostnı´ – rovnost, nerovnost pravdivostnı´ – mensˇ´ı nezˇ, mensˇ´ı nebo rovno pravdivostnı´ – veˇtsˇ´ı nezˇ, veˇtsˇ´ı nebo rovno pocˇet prvku˚ v poli velikost promeˇnne´ cˇi cele´ho pole v bajtech
Tabulka 11: Vybrane´ za´kladnı´ opera´tory MASM.
6.4
Definice vlastnı´ch typu˚
Definice vlastnı´ch typu˚ direktivou typedef ma´ stejny´ vy´znam jako stejnojmenna´ konstrukce v jazyce C a pouzˇ´ıva´ se nejcˇasteˇji k pojmenova´nı´ typovy´ch pointeru˚. Prˇ´ıklad na´sleduje. char typedef sbyte ; c h a r j e z n a m e´ n k o v y´ b a j t p c h a r t y p e d e f p t r c h a r ; p c h a r j e p o i n t e r na c h a r Pozna´mka: Jak je videˇt na prˇ´ıkladu, slovo ptr na´sledovane´ typem ma´ lehce odlisˇny´ vy´znam nezˇ na´mi jizˇ zna´me´ pouzˇitı´ opacˇne´, tj. typu na´sledovane´ho slovem ptr. Vlastnı´ typy take´ mu˚zˇeme definovat jako struktury pomocı´ struct cˇi unie pomocı´ union. Tyto konstrukce fungujı´ stejneˇ jako v jazyce C. Uka´zˇeme si jeden prˇ´ıklad za vsˇechny: dwb d w b dwb
union dword ? word ? byte ? ends
; e n d s = end s t r u c t u r e
Jme´na cˇlensky´ch polozˇek nemusejı´ by´t jednoznacˇna´ v ra´mci souboru (cozˇ mozˇna´ pu˚sobı´ jako samozrˇejma´ veˇc, ale v assemblerech je toto pomeˇrneˇ nova´ funkcionalita). Z slovem struct lze jesˇteˇ uve´st pozˇadovane´ zarovna´nı´ (alignment, opeˇt stejny´ vy´znam jako v jazyce C), aktua´lnı´ verze MASM podporuje zarovna´nı´ na 1, 2, 4, 8 a 16 bajtu˚. (Zarovna´nı´ struktur na vı´ce bajtu˚ zrychluje program, pouzˇ´ıva´te-li vsˇak jen 1, 2 a 4bajtove´ promeˇnne´, nema´ zarovna´nı´ na vı´ce nezˇ 4 bajty smysl. Aktua´lnı´ prˇekladacˇe C obvykle struktury zarovna´vajı´ na 8 bajtu˚, kvu˚li promeˇnny´m typu double apod.) 69
ZarovnanaStruktura s t r u c t 4 a1 b y t e ? ; z a cˇ ´ı n a´ na p o z i c i 0 a2 b y t e ? ; z a cˇ ´ı n a´ na p o z i c i 1 a3 dword ? ; z a cˇ ´ı n a´ na p o z i c i 4 Z a r o v n a n a S t r u k t u r a ends X Z a r o v n a n a S t r u k t u r a ; z a l o zˇ e n ´ı promeˇ nne´ X t y p u Z a r o v n a n a S t r u k t u r a mov eax , X.a3
; u k a´ z k a p rˇ ´ı s t u p u k p o l o zˇ c e s t r u k t u r y
V uka´zkove´m prˇ´ıkladeˇ je videˇt take´ intuitivnı´ zpu˚sob zakla´da´nı´ promeˇnny´ch strukturovany´ch typu˚ a prˇ´ıstup k jednotlivy´m polozˇka´m. Dalsˇ´ı mozˇnostı´ jsou bitova´ pole pomocı´ direktivy record cˇi typova´nı´ registru˚ procesoru pomocı´ assume, naprˇ. oznacˇ´ıme, zˇe neˇktery´ registr ma´ by´t cha´pa´n jako pointer na konkre´tnı´ datovy´ typ. Ve vsˇech instrukcı´ch odkazujı´cı´ch na pameˇt’tı´mto registrem se pak prˇedpokla´da´, zˇe jde o hodnotu (cˇi pole) dane´ho typu.
6.5
Podmı´neˇny´ prˇeklad
Podmı´neˇny´ prˇeklad funguje opeˇt podobneˇ jako v jazyce C, pouzˇ´ıva´me direktivy if, elseif, else a endif. (Podobnost assembleru s jazykem C samozrˇejmeˇ nenı´ na´hodna´.) Da´le je mozˇno pouzˇ´ıt ife pro prˇeklad prˇi nesplneˇnı´ podmı´nky, ifdef cˇi ifndef pro prˇeklad je-li definova´n cˇi naopak nedefinova´n neˇjaky´ symbol. Da´le ma´me k dispozici na´stroje pro hla´sˇenı´ chyb. Direktiva .err zahla´sı´ chybu danou jako argument vzˇdy, direktivy .erre, .errnz, .errdef, .errndef a dalsˇ´ı se chovajı´ stejneˇ jako prˇ´ıslusˇne´ vy´sˇe uvedene´ direktivy podmı´neˇne´ho prˇekladu, ale prˇi splneˇnı´ dane´ podmı´nky zahla´sı´ chybu danou jako jejich argument. Zahla´sˇenı´m chyby prˇeklad v dane´m mı´steˇ koncˇ´ı a da´le nepokracˇuje.
6.6
FP cˇ´ısla
Cˇ´ısla s plovoucı´ rˇa´dovou cˇa´rkou jsou jednı´m z mozˇna´ i tajemny´ch te´matu˚, ktery´m jsme se za´meˇrneˇ vyhy´bali. Du˚vodem je, zˇe historicky procesory x86 FP cˇ´ısla nepodporovaly prˇ´ımo, ale jen pomocı´ tzv. koprocesoru – samostatne´ho cˇipu. V instrukcˇnı´ sadeˇ byla dohodnuta´ znacˇka a kdyzˇ na ni CPU narazil, prˇedal instrukci k vykona´nı´ do koprocesoru nebo vyvolal vy´jimku, kdyzˇ koprocesor nebyl prˇ´ıtomen (cozˇ vzhledem k ceneˇ byla nejcˇasteˇjsˇ´ı situace). Instrukci pak mohl mı´sto koprocesoru vykona´vat trˇeba specia´lnı´ program (nebylo to rychle´, ale mozˇne´ na´hradnı´ rˇesˇenı´). Matematicky´ koprocesor se obvykle oznacˇuje FPU (Floating Point Unit). Pru˚vodce studiem Wikipedia [Wiki] uva´dı´, zˇe na pu˚vodnı´m IBM PC znamenal koprocesor rˇa´doveˇ 50na´sobne´ zrychlenı´ matematicky´ch vy´pocˇtu˚. U jiny´ch procesoru˚ je vy´kon FPU jednotky cˇasto jesˇteˇ mnohem vysˇsˇ´ı. (Intel se beˇhem historicke´ho vy´voje procesoru˚ vzˇdycky zameˇrˇoval spı´sˇe na optimalizace vy´pocˇtu˚ v ALU cˇi vektorove´ vy´pocˇty v MMX cˇi SIMD.)
FPU ma´ prˇ´ımy´ prˇ´ıstup do pameˇti, pouzˇ´ıva´ vsˇak vlastnı´ sadu registru˚, vlastnı´ registr prˇ´ıznaku˚ a vlastnı´ instrukce. Registru˚ je 8 a kazˇdy´ ma´ 80 bitu˚ (10 bajtu˚), vsˇechny vy´pocˇty jsou prova´deˇny na teˇchto 80bitovy´ch registrech. Prˇi pra´ci s pameˇtı´ umı´ FPU konvertovat hodnoty mezi svy´m 80bitovy´m forma´tem a kratsˇ´ım 32 a 64bitovy´m forma´tem, ktery´ se beˇzˇneˇji pouzˇ´ıva´ naprˇ´ıklad ve vysˇsˇ´ıch jazycı´ch. Osmice registru˚ je organizova´na jako za´sobnı´k, kde vrchol je st(0) cˇi 70
kra´tce st a poslednı´ registr je st(7). S registry je vsˇak kromeˇ za´sobnı´kovy´ch operacı´ mozˇno pracovat i prˇ´ımo. Instrukce koprocesoru zacˇ´ınajı´ pı´smenem f, takzˇe jsou ve zdrojove´m ko´du snadno rozpoznatelne´. Tyto instrukce nikdy nepracujı´ s prˇ´ımy´mi hodnotami (immediate) a azˇ na jednu specia´lnı´ instrukci neumeˇjı´ pracovat ani s beˇzˇny´mi registry CPU. Za´kladnı´ zpu˚sob pra´ce s FPU je pomocı´ za´sobnı´kovy´ch instrukcı´. Tento zpu˚sob je odvozen od beˇzˇne´ho zpu˚sobu implementace matematicky´ch vy´pocˇtu˚ v pocˇ´ıtacˇi, kdy se syste´m pouzˇ´ıvajı´cı´ za´sobnı´k osveˇdcˇil jako nejlepsˇ´ı (nebot’ nejle´pe koresponduje s lidsky´m zpu˚sobem za´pisu a cha´pa´nı´ matematicky´ch operacı´). Na´sledujı´cı´ prˇ´ıklad secˇte dveˇ cˇ´ısla. fld1 fldpi fadd
; v l o zˇ ´ı 1 na z a´ s o b n ´ı k ; v l o zˇ ´ı PI na z a´ s o b n ´ı k ; v y t a´ h n e z e z a´ s o b n ´ı k u 1 a PI , s e cˇ t e j e a v y´ s l e d e k u l o zˇ ´ı z p eˇ t na z a´ s o b n ´ı k
Koprocesor umı´ hardwaroveˇ velmi rychle pocˇ´ıtat i slozˇiteˇjsˇ´ı operace jako je sinus, kosinus cˇi druha´ odmocnina. Ma´ vsˇak pomeˇrneˇ hodneˇ instrukcı´ a pro na´s nenı´ rea´lne´ je probrat a naucˇit se (a zrˇejmeˇ by to ani nemeˇlo neˇjaky´ prakticky´ smysl).
6.7
Bezejmenna´ na´veˇsˇtı´
Jednı´m z klı´cˇovy´ch high–level prvku˚ jsou bezejmenna´ na´veˇsˇtı´. Definujı´ se pomocı´ konstrukce @@: a odkazovat se dajı´ jen nejblizˇsˇ´ı prˇedchozı´ pomocı´ @b (back) a nejblizˇsˇ´ı na´sledujı´cı´ pomocı´ @f (forward). Smyslem teˇchto konstrukcı´ je zbavit se cˇi alesponˇ minimalizovat vy´skyt pojmenovany´ch na´veˇsˇtı´. Naprˇ´ıklad cyklus mu˚zˇe vypadat takto: @@: ... ... ... l o o p @b
6.8 6.8.1
Prˇ´ıkazy pro blokove´ podmı´nky a opakova´nı´ Podmı´neˇne´ vykona´nı´ bloku
Dalsˇ´ım velice uzˇitecˇny´m prvkem jsou direktivy implementujı´cı´ rozhodovacı´ prˇ´ıkazy ve stylu strukturovane´ho programova´nı´. Konstrukce .if/.elseif/.else/.endif nahradı´ veˇtvenı´ ko´du podmı´neˇny´mi skoky. Pozor! Tyto prˇ´ıkazy jsou u´plneˇ odlisˇne´ od direktiv podmı´neˇne´ho prˇekladu se stejny´mi jme´ny bez tecˇky na zacˇa´tku. Zatı´mco direktivy podmı´neˇne´ho prˇekladu umozˇnˇuje cˇa´sti ko´du prˇi prˇekladu vynechat, tyto direktivy se prˇelozˇ´ı do podmı´neˇny´ch skoku˚ jcc a budou se vyhodnocovat azˇ prˇi beˇhu programu. Dalsˇ´ım du˚lezˇity´m rozdı´lem je, zˇe se zde pouzˇ´ıvajı´ opera´tory v podobeˇ jako ve vysˇsˇ´ıch jazycı´ch typu C, podrobneˇji si je prˇedstavı´me nı´zˇe v sekci 6.8.3. Direktiva vsˇak umı´ jen za´kladnı´ typy testu˚, ktere´ lze prˇ´ımo prˇeve´st na podmı´neˇne´ skoky. Pro prˇ´ıklad si uka´zˇeme prˇepis tohoto programu do assembleru: int a , b , c ; i f ( a<=2 | | b ! = d ) a = 2 ; e l s e a = 1 ; V assembleru tote´zˇ napı´sˇeme takto: cmp a , 2 j l e lab2 mov eax , b 71
cmp eax , d je lab1 lab2 : mov a , 2 jmp l a b 3 lab1 : mov a , 1 lab3 : Pomocı´ direktivy .if lze tote´zˇ napsat bez pojmenovany´ch na´veˇsˇtı´. . i f a<=2 mov a , 2 .else mov eax , b . i f eax ! = d mov a , 2 .else mov a , 1 .endif Z prˇ´ıkladu je videˇt, zˇe ko´d pro je sice bez pojmenovany´ch na´veˇsˇtı´ a je plneˇ strukturovany´, ale u slozˇiteˇjsˇ´ıch podmı´nek nenı´ optima´lnı´ co do efektivity ko´du, ani nenı´ prˇ´ılisˇ prˇehledny´, protozˇe slozˇene´ testy je trˇeba rozepsat na vı´c vnorˇeny´ch cˇi po sobeˇ jdoucı´ch testu˚. 6.8.2
Opakova´nı´ bloku
Po sezna´menı´ s .if zrˇejmeˇ nikoho neprˇekvapı´, zˇe MASM ma´ i direktivy .while a .repeat pro opakova´nı´ ko´du. Tyto direktivy pouzˇ´ıvajı´ opeˇt test podmı´nek pomocı´ specia´lnı´ch opera´toru˚ popsany´ch samostatneˇ v na´sledujı´cı´ sekci. Direktivy .while a .endw oznacˇujı´ blok ko´du, ktery´ se opakuje, dokud platı´ podmı´nka uvedena´ na zacˇa´tku bloku. Podmı´nka se poprve´ testuje jesˇteˇ prˇed prvnı´m vykona´nı´ bloku. Jde tedy o klasickou konstrukci while zna´mou z jazyka C. Direktivy .repeat a .until oznacˇujı´ blok ko´du, ktery´ se opakuje, dokud nezacˇne platit podmı´nka na konci bloku. Od while se tedy lisˇ´ı dveˇma vlastnostmi: Podmı´nka se poprve´ testuje azˇ po prvnı´m vykona´nı´ bloku a podmı´nka se testuje opacˇneˇ, tj. blok ko´du se opakuje, dokud podmı´nka neplatı´. U tohoto prˇ´ıkazu je mozˇno alternativneˇ pouzˇ´ıt za´veˇrecˇnou direktivu .untilcxz, ktera´ generuje mı´sto podmı´neˇny´ch skoku˚ instrukci loop (takzˇe vzˇdy snı´zˇ´ı hodnotu ecx o jednicˇku). Tato direktiva tedy nepotrˇebuje uva´deˇt neˇjakou dalsˇ´ı podmı´nku, ale je to mozˇne´ (pak se testuje dana´ podmı´nka a jesˇteˇ se prˇida´ loop). U opakovacı´ch konstrukcı´ je take´ k dispozici .break a .continue (vy´znam je zrˇejmy´ z na´zvu). Tyto dveˇ direktivy je take´ mozˇno kombinovat s direktivou .if a prove´st je tak jen prˇi platnosti urcˇite´ podmı´nky. Prˇ´ıklad na´sleduje. .while ! carry? ... . b r e a k . i f e c x ==0 ... .endw
6.8.3
Opera´tory podmı´neˇne´ho vykona´nı´
Na prˇ´ıkladech direktiv .if a .while bylo videˇt, zˇe pro urcˇenı´ podmı´nek se zde pouzˇ´ıvajı´ jine´ opera´tory, nezˇ jake´ jsme si uva´deˇli drˇ´ıve v tabulce. MASM zde pouzˇ´ıva´ relacˇnı´ opera´tory 72
stejne´ jako v jazyce C, tj. ==, ! =, >, >=, <, <=, & (bitovy´ test), ! (negace), && (logicky´ soucˇin) a || (logicky´ soucˇet). Da´le lze testovat hodnoty prˇ´ıznaku˚ pomocı´ slov carry?, zero?, overf low?, sign? a parity?. U aritmeticky´ch testu˚ se samozrˇejmeˇ rozlisˇujı´ zname´nkove´ a nezname´nkove´ hodnoty, stejneˇ jako to zna´me z klasicky´ch podmı´neˇny´ch skoku˚ (na neˇ se ostatneˇ tyto vysˇsˇ´ı konstrukce nakonec prˇelozˇ´ı). U operandu˚ je proto velmi du˚lezˇite´ hlı´dat nastavenı´ zname´nek. Zna´my´m opera´torem ptr lze typy uprˇesnit prˇ´ımo v testech, pro urcˇenı´ zname´nkovosti hodnoty lze ptr pouzˇ´ıt i u registru˚ procesoru(!). Za´veˇrem jesˇteˇ dodejme, zˇe prˇekladacˇ mu˚zˇe prˇi slozˇiteˇjsˇ´ıch testech pouzˇ´ıt registry procesoru k ulozˇenı´ pomocne´ hodnoty, naprˇ´ıklad promeˇnne´ atp. Pokud vsˇak lze podmı´nku vyhodnotit, bez pouzˇitı´ dalsˇ´ıch registru˚, tak samozrˇejmeˇ prˇekladacˇ vytvorˇ´ı tento jednodusˇsˇ´ı ko´d.
6.9
Procedury
6.9.1
Definice a vola´nı´ procedury
Jak vı´me, model C, ktery´ uva´dı´me na zacˇa´tku kazˇde´ho zdrojove´ho textu assembleru, zajisˇt’uje spra´vne´ dekorova´nı´ exportovany´ch i importovany´ch symbolu˚. Dalsˇ´ı velmi du˚lezˇita´ funkcionalita, kterou jı´m zı´ska´me, je mozˇnost definovat cˇi importovat high level procedury a volat je pomocı´ pseudoinstrukce invoke. Nejprve si na prˇ´ıkladu printf uka´zˇeme definici prototypu cizı´ funkce a jejı´ho zavola´nı´ z nasˇ´ı funkce (printf ma´ navı´c promeˇnlivy´ pocˇet parametru˚, takzˇe je to zvla´sˇt’zajı´mavy´ prˇ´ıklad). .data f b y t e ” z k u s e b n i c i s l o :% i a t e x t : %s ” , 1 3 , 1 0 , 0 t b y t e ” H e l l o World ! ” .code p r i n t f proto format : ptr sbyte , args : vararg public soucet s o u c e t proc invoke p r i n t f , o f f s e t f ,50 , o f f s e t t ret s o u c e t endp Direktiva proto definuje prototyp funkce, tj. importuje dany´ symbol a za´rovenˇ definuje typy parametru˚. Slovem vararg oznacˇujeme promeˇnlivy´ pocˇet parametru˚ (konkre´tneˇ printf ma´ jako prvnı´ parametr char*, pak mohou na´sledovat dalsˇ´ı parametry, viz dokumentaci CRT). Definice procedury touto direktivou automaticky zajistı´ i entry a exit ko´d. Entry ko´d je vlozˇen na zacˇa´tek procedury, exit ko´d pak ke kazˇde´ pseudoinstrukci ret. Znamena´ to, zˇe retmu˚zˇete uve´st kamkoliv do teˇla procedury, ne jen na jejı´ konec, ale ze stejne´ho du˚vodu nelze vytva´rˇet vnorˇene´ podprogramy. (Na´vrat z vnorˇene´ho podprogramu by meˇl taky exit ko´d, ktery´ by tam vadil. Potrˇebujete-li volat jiny´ (loka´lnı´) ko´d z takto definovane´ procedury, mu˚zˇete, ale prˇ´ıkaz na´vratu nesmı´ by´t rˇesˇen umı´steˇnı´m ret uvnitrˇ procedury.) Pru˚vodce studiem Jelikozˇ prˇekladacˇ generuje epilog ke kazˇde´mu pouzˇitı´ pseudoinstrukce ret, je cˇasto rozumneˇjsˇ´ı pouzˇ´ıt jen jedine´ ret v kazˇde´ procedurˇe a skocˇit pomocı´ nepodmı´neˇne´ho skoku
73
na toto mı´sto skocˇit. Naopak se vyvarujte ska´ka´nı´ na ret do jine´ procedury, protozˇe prˇesna´ podoba epilogu je v kazˇde´ procedurˇe odlisˇna´ (prˇesneˇji rˇecˇeno za´visı´ to na pocˇtu volacı´ch parametru˚, loka´lnı´ch promeˇnny´ch a registru˚ v klauzuli uses).
Volacı´ direktiva invoke umı´ i automaticke´ rozsˇirˇova´nı´ parametru˚ (naprˇ. prototyp definuje parametr 4bajtovy´, a vy vola´te s 1bajtovou hodnotou). Pouzˇ´ıva´ prˇitom registry eax a edx, takzˇe nenı´ mozˇno pouzˇ´ıvat rozsˇirˇova´nı´ a za´rovenˇ se snazˇit v teˇchto registrech prˇeda´vat hodnoty. (Pozna´mka autora: Prˇekladacˇ MASM se prˇi testech choval poneˇkud neprˇedvı´datelneˇ. Neˇkdy dokonce vyrobil docela necˇekany´ neplatny´ ko´d, kdyzˇ naprˇ´ıklad dword hodnotu ulozˇil na za´sobnı´k jako 6 bajtu˚.) Procedury lze volat take´ odkazem, naprˇ´ıklad ma´me-li sadu procedur o stejne´ signaturˇe, mu˚zˇeme v neˇjake´m registru spocˇ´ıtat adresu konkre´tnı´ho vola´nı´ a potom ji zavolat dle prototypu. Tato konstrukce je bohuzˇel trochu slozˇita´, vyzˇaduje definici pointeru na funkci pomocı´ typedef, pak teprve lze prˇetypovat registr prˇi vola´nı´ pomocı´ ptr. Alternativneˇ lze tote´zˇ udeˇlat pomocı´ direktivy assume, kterou lze registru natrvalo prˇirˇadit dany´ typ. (To se hodı´ spı´sˇe prˇi cˇasteˇjsˇ´ım pouzˇ´ıva´nı´.) 6.9.2
Uchova´nı´ meˇneˇny´ch registru˚
Prˇed seznam parametru˚ je mozˇno jesˇteˇ vlozˇit seznam pouzˇity´ch registru˚. Tento seznam je uveden slovem uses a jednotlive´ registry jsou oddeˇleny mezerami (tedy ne cˇa´rkami). Za cˇa´rkou pak na´sleduje seznam parametru˚. Prˇ´ıklad na´sleduje. m o j e f u n k c e proc u s e s e s i e d i , param1 : dword , param2 : dword ... ret m o j e f u n k c e endp Prˇipomenˇme, zˇe Visual C++ v souladu s protokolem fastcall definuje (a to bez ohledu na pouzˇitou volacı´ konvenci), zˇe kazˇda´ funkce mu˚zˇe meˇnit eax, ecx a edx, zatı´mco ostatnı´ registry je nutno obnovit do pu˚vodnı´ho stavu. Typicky tedy pouzˇ´ıva´me uses ebx esi edi (pokud tyto registry meˇnı´me) a ebp uchova´va´me a obnovujeme v ra´mci prologu/epilogu. 6.9.3
Loka´lnı´ promeˇnne´
Ve spojitosti s direktivou proc je mozˇno take´ pohodlneˇ vytva´rˇet pojmenovane´ loka´lnı´ promeˇnne´. Slouzˇ´ı k tomu direktiva local. Prˇ´ıklad na´sleduje. m o j e f u n k c e proc l o c a l a : dword , b : dword , c : dword , p o l e : dword : 1 0 0 ... m o j e f u n k c e endp Pozor! Direktiva local musı´ by´t na rˇa´dku prˇ´ımo na´sledujı´cı´m za direktivou proc a jednotlive´ promeˇnne´ se pı´sˇ´ı dohromady (na jeden rˇa´dek) oddeˇlene´ cˇa´rkami. Poslednı´ promeˇnna´ v prˇ´ıkladu ukazuje definice loka´lnı´ch polı´. Shrnutı´ V te´to kapitole jsme si velmi strucˇneˇ prˇedstavili mozˇnosti programova´nı´ s makry v makroassembleru MASM. Makra, direktivy a souvisejı´cı´ konstrukty jsou v assembleru prˇedevsˇ´ım na´stroji pro zprˇehledneˇnı´ a zjednodusˇenı´ programova´nı´, prˇitom prˇi rozumne´m pouzˇitı´ prˇina´sˇejı´ 74
i dosti du˚lezˇitou prˇehlednost. Prˇi me´neˇ rozumne´m pouzˇitı´ vsˇak makra mohou by´t i du˚vodem neprˇehlednosti v ko´du a je tedy cˇisteˇ ota´zkou, jak je programa´tor bude pouzˇ´ıvat. U slozˇiteˇjsˇ´ıch konstrukcı´ cˇasto musı´me obeˇtovat prˇehlednost a pouzˇ´ıva´me makra prˇedevsˇ´ım pro zjednodusˇenı´ zdrojove´ho ko´du (ve smyslu zkra´cenı´ na u´kor prˇehlednosti). S vyuzˇitı´m maker lze take´ naprogramovat rˇadu veˇcı´, ktere´ bez nich ze syntakticky´ch du˚vodu˚ v assembleru ani mnohy´ch jiny´ch jazycı´ch vu˚bec naprogramovat nejde. Pojmy k zapamatova´nı´ • • • • • • • • •
direktiva pseudoinstrukce makro hlavicˇkovy´ soubor vy´raz vlastnı´ (datovy´) typ podmı´neˇny´ prˇeklad bezejmenne´ na´veˇsˇtı´ loka´lnı´ promeˇnna´
75
A
Dalsˇ´ı te´mata assembleru
A.1
Co bylo ve stare´m online textu navı´c
Ve stare´m online studijnı´m textu byla jesˇteˇ rˇada pokrocˇilejsˇ´ıch te´mat. Zmı´neˇny´ materia´l je sta´le k dispozici na adrese http://www.keprt.cz/vyuka/, uved’me si zde alesponˇ strucˇny´ seznam te´mat (u kazˇde´ho je cˇ´ıslo kapitoly, ve ktere´ je diskutova´no): • 1. Kombinace operandu˚ u na´sobenı´ a deˇlenı´ • 1. Disassembler • 1. Uka´zka noc200 • 3. Externı´ assembler – podrobnosti k dalsˇ´ım prˇekladacˇu˚m C a taky TASM • 4. Prˇehled instrukcı´ dle kategoriı´ • 5. Dalsˇ´ı pseudoinstrukce a direktivy ($, org) • 5. Konstanty (=, equ, rept) • 5. Makra V 6.cˇa´sti je jesˇteˇ strucˇneˇ probra´n programovy´ model v syste´mu MS–DOS. • Segmentove´ registry • Segmentove´ direktivy • Pameˇt’ove´ modely DOSu (tiny, small, large, compact, medium) • Alokace pameˇti a dalsˇ´ı syste´move´ funkce • Vytva´rˇenı´ samostatny´ch EXE a COM souboru˚ • Cˇtenı´ parametru˚ z prˇ´ıkazove´ rˇa´dky
A.2
Doporucˇene´ volby ve vlastnostech projektu
Na´sleduje seznam doporucˇeny´ch voleb projektu ve Visual Studiu. Prvnı´ volba je du˚lezˇita´ prˇi vola´nı´ funkcı´ CRT z assembleru, dalsˇ´ı trˇi usnadnˇujı´ disassemblova´nı´ tı´m, zˇe vypnou vsˇelijake´ kontrolnı´ mechanizmy projevujı´cı´ se dodatecˇny´m ko´dem vlozˇeny´m prˇedevsˇ´ım na zacˇa´tek a konec kazˇde´ funkce. Poslednı´ volba je du˚lezˇita´ prˇi vola´nı´ funkcı´ Windows API (z jazyka C i assembleru). C/C++ / Code Generation / Run Time Library → Multi-threaded Debug – vypne vola´nı´ CRT funkcı´ prˇes DWORD – du˚lezˇite´! C/C++ / Code Generation / Basic Runtime Checks → Default – nekontroluje stack frame (prˇetecˇenı´ polı´ atp.), nekontroluje uninitialized variable C/C++ / Code Generation / Buffer Security Check → No – vypne kontrolu prˇetecˇenı´ bufferu (ma´ efekt jen ve funkcı´ch, kde jsou loka´lnı´ pole) C/C++ / Linker / General / Enable Incremental Linking → No – vypne vola´nı´ funkcı´ prˇes tabulku JMP skoku˚ General / Character Set → Use Multi–Byte Character Set – vypne unicode
76
B
Historie MASM
Studijnı´ cı´le: Tato prˇ´ıloha neslouzˇ´ı jako studijnı´ materia´l, pouze prˇina´sˇ´ı neˇkolik zajı´mavostı´ z historie vy´voje prˇekladacˇe MASM. Klı´cˇova´ slova: MASM, TASM, historie, Microsoft Potrˇebny´ cˇas: 5 minut. MASM je jeden z ma´la programu˚, ktery´ s na´mi byl po celou dobu existence pocˇ´ıtacˇu˚ PC. Na stra´nce [Harv] je povı´da´nı´ o vy´voji MASM v poslednı´ch 20 letech. Pro zajı´mavost: V 80.letech vycha´zel MASM jako komercˇnı´ produkt pro MS-DOS. Teprve ve verzi 5.00 se objevily pomocne´ direktivy .model cˇi .code (pro nastavova´nı´ segmentu˚ je dodnes k dispozici i jina´ hodneˇ slozˇita´ direktiva). Verze 5.10 prˇidala loka´lnı´ na´veˇsˇtı´ zacˇ´ınajı´cı´ @@ a podporu IBM OS/2. Poslednı´ verzı´ stary´ch cˇasu˚ byla 5.10B z roku 1989. Ta jesˇteˇ beˇzˇela v 640KB RAM a v neˇkolika pozdeˇjsˇ´ıch verzı´ch prˇekladacˇe jesˇteˇ by´vala prˇibalena jako masm386.exe. S teˇmito stary´mi verzemi je take´ kompatibilnı´ konkurencˇnı´ prˇekladacˇ Borland TASM. Soucˇasne´ verze MASM jsou odvozeny od verze 6.00, ktera´ vysˇla roku 1992 jesˇteˇ jako plneˇ komercˇnı´ vy´vojovy´ na´stroj. Tato verze se hodneˇ lisˇila od prˇedchozı´ch verzı´ (a potazˇmo TASM) a prˇidala zejme´na .if/.endif direktivy umozˇnˇujı´cı´ strukturovane´ programova´nı´ te´meˇrˇ bez pojmenovany´ch na´veˇsˇtı´. Tato verze beˇzˇela v DOSu na procesorech 286 a 2MB RAM. Te´hozˇ roku na´sledovaly dalsˇ´ı opravne´ verze ve formeˇ za´plat, naprˇ. 6.10 uzˇ beˇzˇela jen na 386 a 4MB RAM a meˇla prˇida´nu podporu tehdy nove´ho Visual C++ 1.0 (ktere´ zabı´ralo 50MB na disku, na tehdejsˇ´ı pomeˇry docela hodneˇ). Verze 6.10A byla poslednı´, ktera´ obsahovala integrovane´ vy´vojove´ prostrˇedı´. (Editor Programmer’s Workbench (PWB) je zna´my´ i ze stare´ho Microsoft C a Basicu.) Verze 6.11 vysˇla roku 1993. Za cenu 250 dolaru˚ to byla prvnı´ verze pro 32bitove´ Windows, s prˇibaleny´m emula´torem Win32 pro MS-DOS (ktery´ samotny´ byl velmi zajı´mavy´, ale skoncˇil v propadlisˇti deˇjin). Tato verze podporovala instrukce Pentium (.586). Verze 6.11 vznikla v dobeˇ betaverze prvnı´ho Windows NT a prˇechod na verzi jen pro NT byl zrˇejmeˇ poneˇkud zbrkly´. Prˇi uvedenı´ fina´lnı´ verze Windows NT na neˇm pak kvu˚li rozdı´lu˚m od beta verze tento prˇekladacˇ paradoxneˇ nefungoval, dokud Microsoft nevydal specia´lnı´ za´platu, ktera´ simuluje chova´nı´ beta verze na fina´lnı´ verzi NT. Verze 6.11C roku 1994 jako prvnı´ podporovala tvorbu VxD ovladacˇu˚ zarˇ´ızenı´ pro Windows 95. Verze 6.12 v roce 1997 prˇidala podporu Pentium Pro (.686 a .686p) a MMX instrukce (.MMX) a verze 6.13 te´hozˇ roku prˇidala jesˇteˇ AMD 3D instrukce (.K3D). V roce 1999 Intel vydal sadu maker pro podporu MMX a novy´ch SSE instrukcı´ v MASM 6.12 (zdarma). Verze 6.14 v roce 1999 prˇidala podporu SSE instrukcı´ a k nim 128bitovy´ datovy´ typ OWORD (octet word). V roce 2000 Microsoft vydal tzv. „Processor Pack“ jako za´platu pro tehdejsˇ´ı Visual Studio 6.0, ve ktere´ se objevil MASM 6.15 a po dlouhy´ch osmi letech za´plat take´ nova´ dokumentace k tomuto assembleru. Tato verze jizˇ byla uvolneˇna volneˇ ke stazˇenı´, MASM byl poprve´ zdarma. V roce 2002 se objevil MASM 7.00 jako soucˇa´st Visual Studia .NET. Internı´ cˇ´ıslo verze zu˚stalo 6.15, sˇlo tedy mozˇna´ jen o sladeˇnı´ cˇ´ısel verzı´ v balı´ku Visual Studia. Microsoft da´va´ MASM nynı´ do kazˇde´ edice Visual Studia (zatı´m to platı´ do verze 2008), MASM je tedy zdarma, ale je trˇeba si koupit Visual Studio (ktere´ stojı´ vı´c, nezˇ pu˚vodneˇ MASM). MASM prˇekladacˇ nepotrˇebuje ke spusˇteˇnı´ .NET Framework a lze jej provozovat i na Windows 98 (i kdyzˇ Visual Studio ne). V dobeˇ psanı´ tohoto textu Microsoft potvrdil, zˇe MASM bude opeˇt zdarma poskytova´n v ra´mci budoucı´ch verzı´ Visual C++ Express (pocˇ´ınaje za´platou 2008 SP1). Mezitı´m Borland svu˚j konkurencˇnı´ prˇekladacˇ TASM sta´le proda´va´, i kdyzˇ je pomeˇrneˇ obtı´zˇne´ na jejich webove´ stra´nce najı´t o neˇm aktua´lnı´ informace.
77
C
´ vod do jazyka C++ U
Studijnı´ cı´le: Tato prˇ´ıloha poskytne strucˇny´ u´vod do jazyka C++ pro programa´tory v C#. Vzhledem k podobnosti ru˚zny´ch programovacı´ch jazyku˚ bude stejneˇ dobrˇe srozumitelna´ i programa´toru˚m v Javeˇ, PHP a neˇktery´ch dalsˇ´ıch jazycı´ch. Klı´cˇova´ slova: C++ Potrˇebny´ cˇas: 20 minut.
C.1
´ vod U
Veˇtsˇina na´sledujı´cı´ch informacı´ platı´ stejneˇ i pro jazyk C, nebot’prvky, ve ktery´ch se tyto dva jazyky lisˇ´ı, prˇi studiu assembleru nepouzˇ´ıva´me. Prˇi pra´ci s assemblerem je cˇasto dokonce lepsˇ´ı omezit se jen na jazyk C, protozˇe se tı´m vyhneme neˇktery´ch proble´mu˚m zpu˚sobeny´m odlisˇny´m stylem dekorova´nı´ jmen v C++ (viz take´ kap. 5.5 na straneˇ 58 pro dalsˇ´ı informace o dekorova´nı´ jmen).
C.2
Datove´ typy
Za´kladnı´ datove´ typy ukazuje tabulka 12. Za´kladnı´ celocˇ´ıselny´ typ se jmenuje int. Jeho velikost se lisˇ´ı dle typu pocˇ´ıtacˇe a prostrˇedı´, protozˇe je stejna´ jako velikost beˇzˇne´ho registru cˇi datove´ sbeˇrnice pocˇ´ıtacˇe. V nasˇem prˇ´ıpadeˇ ma´ tedy velikost 4 bajty. typ char short int long long long
velikost
typ v assembleru
1 2 dle pocˇ´ıtacˇe 4 8
byte word – dword qword
Tabulka 12: Za´kladnı´ datove´ typy jazyka C++. Znaky jsou 1bajtove´, cˇili nepouzˇ´ıva´ se ko´d Unicode. Cˇ´ısla v plovoucı´ cˇa´rce fungujı´ stejneˇ jako v C# (ale v assembleru se pouzˇ´ıvajı´ jinak, takzˇe na´s azˇ tak zajı´mat nebudou). Typ string sice v C++ je, ale nejde o za´kladnı´ typ a neda´ se pouzˇ´ıvat v assembleru, takzˇe mı´sto neˇj pouzˇ´ıva´me na´hradnı´ rˇesˇenı´ dle starsˇ´ıho jazyka C. Ten totizˇ typ string nema´ vu˚bec a texty ukla´da´ do polı´ znaku˚. Takovy´ string prˇedevsˇ´ım nema´ nikde explicitneˇ ulozˇenu informaci o sve´ de´lce – skutecˇnou de´lku stringu zjistı´me tak, zˇe projdeme jeho znaky a najdeme nulu. Funkce vracejı´cı´ de´lku stringu tedy mu˚zˇe vypadat naprˇ. takto: i n t s t r l e n ( char ∗ t e x t ) { i n t i =0; w h i l e ( t e x t [ i ] ! = 0 ) i ++; return i ; } Parametr text je v te´to uka´zce pointer. Je to 4bajtova´ promeˇnna´, ve ktere´ je ulozˇena adresa pameˇti (prˇesneˇji offset – cˇ´ıslo pameˇt’ove´ bunˇky od zacˇa´tku pameˇti). Slovo „pointer“ prˇekla´da´me jako ukazatel – ukazuje totizˇ neˇkam do pameˇti. Pointery sice v jazyku C# jsou take´, ale v drtive´ veˇtsˇineˇ prˇ´ıpadu˚ je nepouzˇ´ıva´me, takzˇe je to pro na´s veˇc zcela nova´. Prˇi studiu assembleru se s nimi vsˇak velmi dobrˇe sezna´mı´me, jsou to skutecˇneˇ jen promeˇnne´ obsahujı´cı´ adresu. Na rozdı´l od assembleru, ktery´ nekontroluje u datovy´ch typu˚ nic jine´ho nezˇ velikost (tj. kolik bajtu˚ zabı´rajı´ v pameˇti), C++ u pointeru˚ take´ hlı´da´, na co ukazujı´. Hveˇzdicˇkou tedy oznacˇ´ıme, zˇe 78
promeˇnna´ je typu pointer, ale prˇed hveˇzdicˇku musı´me jesˇteˇ napsat, na jaky´ typ hodnot ukazuje. V assembleru se vsˇechny tyto „hveˇzdicˇkove´“ promeˇnne´ ale pouzˇ´ıvajı´ stejneˇ – nejsou to nic vı´c nezˇ 4bajtova´ cˇ´ısla.
C.3
Umı´steˇnı´ ko´du a dat
Ko´d v C++ je ulozˇen ve funkcı´ch. Acˇkoliv jazyk umozˇnˇuje pouzˇ´ıvat i trˇ´ıdy a metody, v assembleru se toto pouzˇ´ıt neda´, takzˇe pracujeme pouze z globa´lnı´mi funkcemi. V programu je tedy vynecha´na hlavicˇka „class Jme´no“ a vsˇechen ko´d je na globa´lnı´ u´rovni. Stejny´m zpu˚sobem umı´st’ujeme i promeˇnne´, ktere´ chceme sdı´let mezi funkcemi – hovorˇ´ıme pak o globa´lnı´ch promeˇnny´ch. Promeˇnne´ mohou by´t i loka´lnı´, pak fungujı´ stejneˇ jako v C#. Hlavicˇky funkcı´ jsou velmi podobne´ tomu, co zna´me ze C#. Pouze vynecha´me u´daj o viditelnosti (public/private).
C.4
Vola´nı´
Funkce vola´me podobneˇ jako v C#, pouze nema´me trˇ´ıdy a objekty, takzˇe vsˇechny funkce jsou globa´lneˇ prˇ´ıstupne´. C++ ma´ take´ dveˇ knihovny – CRT je knihovna funkcı´ jazyka C, ktere´ C++ obsahuje take´, knihovna STL obsahuje trˇ´ıdy, ktere´ ale azˇ na vy´jimky pouzˇ´ıvat nebudeme. Funkce CRT lze volat prˇ´ımo z assembleru, jednodusˇe prˇ´ıkazem call jmeno nebo invoke jmeno. Neˇktere´ verze knihovny CRT vsˇak pouzˇ´ıvajı´ neprˇ´ımy´ model, kdy zprˇ´ıstupnˇujı´ jen pointery na funkce, a ne prˇ´ımo funkce. Toto nema´ zˇa´dny´ vliv na funkcˇnost a v C/C++ to ani nejde nijak poznat. V assembleru ale v teˇchto prˇ´ıpadech bohuzˇel musı´me pouzˇ´ıt za´pis vola´nı´ call dword ptr jmeno cˇi invoke dword ptr jmeno. Parametry (obvykle) prˇeda´va´me konvencı´ C, tj. zprava doleva je ulozˇ´ıme na za´sobnı´k a volajı´cı´ za´sobnı´k musı´ po sobeˇ i uklidit.
C.5
Hlavicˇkove´ soubory
Funkce z knihoven lze v C++ pouzˇ´ıvat azˇ po „inkludova´nı´“ prˇ´ıslusˇne´ho hlavicˇkove´ho souboru, kde majı´ funkce deklaraci. Toto je veˇc, ktera´ v C# nema´ ekvivalent – tam co ma´me v projektu, to mu˚zˇeme pouzˇ´ıt prˇ´ımo. V C/C++ ale musı´me na zacˇa´tku zdrojove´ho souboru da´t prˇ´ıkazy ve tvaru #include <soubor>, ktery´mi do programu zahrneme („inkludujeme“) prˇ´ıslusˇny´ soubor. Informace o tom, ktera´ funkce je ve ktere´m hlavicˇkove´m souboru, jsou k nalezenı´ v helpu.
C.6 C.6.1
Neˇktere´ uzˇitecˇne´ funkce CRT Psanı´ na obrazovku
Za´kladnı´ veˇcı´, ktera´ se na´m bude hodit, je vy´pis na obrazovku. To mu˚zˇeme prove´st bud’ pomocı´ printf, nebo pomocı´ cout. Funkce printf je v CRT (#include <stdio.h>), ve Visual Studiu ji mu˚zˇeme v projektu zalozˇene´m prˇes wizard pouzˇ´ıvat prˇ´ımo. Prvnı´m parametrem te´to funkce je forma´tovacı´ rˇeteˇzec, kde znak procento signalizuje, zˇe na´sledujı´cı´ znak urcˇ´ı typ parametru. Dalsˇ´ı parametry jsou pak libovolne´ a dosazujı´ se v prˇesne´m porˇadı´ na mı´sta procent. Nejle´pe to pochopı´me na prˇ´ıkladech vypsa´nı´ za´kladnı´ch datovy´ch typu˚, viz tabulka 13. Jak je videˇt v tabulce 13, konec rˇa´dku se oznacˇuje stejneˇ jako v C# symbolem \n.
79
typ int char char* ru˚zne´
prˇ´ıklad printf(”hodnota = %i\n”, i); printf(”znak = %i\n”, c); printf(”text = %s\n”, s); printf(”vı ´c hodnot: %i %i %s\n”, a, b, text); Tabulka 13: Prˇ´ıklady pouzˇitı´ prˇ´ıkazu printf.
Druhou mozˇnostı´, jak neˇco vypsat na obrazovku, je objekt cout z knihovny STL. Jeho pouzˇitı´ je poneˇkud netradicˇnı´, ma´ totizˇ prˇekryty´ opera´tor <<. Vy´hodou je vsˇak jednodusˇsˇ´ı pouzˇitı´ nezˇ u funkce printf. cout << cokoliv << endl; Konstanta endl znacˇ´ı konec rˇa´dku. C.6.2
Alokace pameˇti
Opera´tor new nelze jednodusˇe zavolat z assembleru, proto pro alokaci pameˇti pouzˇ´ıva´me na´sledujı´cı´ funkce z knihovny CRT (#include <malloc.h>): void ∗ malloc ( i n t s i z e ) ; void f r e e ( void ∗ ) ; Prvnı´ funkce alokuje pameˇt’o dane´m pocˇtu bajtu˚ a vracı´ beztypovy´ pointer na ni. Druha´ funkce prˇijme tento pointer a pameˇt’ uvolnı´. Alokovanou pameˇt’ musı´te vzˇdy sami uvolnit, protozˇe jazyky C/C++ automatickou spra´vu pameˇti neprova´deˇjı´.
80
D
Seznam obra´zku˚ 1
Procesor Intel 8008 (rok 1972). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
2
Procesor Intel 8080 (rok 1974). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
3
Prˇehled registru eax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
4
Mozˇnosti vy´pocˇtu efektivnı´ adresy (neprˇ´ıme´ adresova´nı´). [IA32] . . . . . . . . . . . . . . . . . . . .
25
5
Prˇ´ıznaky procesoru rˇady x86 (Pentium 3). [IA32] . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
6
Data na za´sobnı´ku v okamzˇiku vola´nı´ podprogramu. [Kep07] . . . . . . . . . . . . . . . . . . . . . .
53
7
Konfigurace prˇekladu externı´m assemblerem (Visual Studio 2008) . . . . . . . . . . . . . . . . . . .
56
8
Kostra souboru v externı´m assembleru. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
81
E
Seznam tabulek 1
Nejjednodusˇsˇ´ı instrukce. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2
Za´kladnı´ konstrukty. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3
Datove´ typy assembleru a jim odpovı´dajı´cı´ typy C/C++. . . . . . . . . . . . . . . . . . . . . . . . .
27
4
Instrukce ovlivnˇujı´cı´ prˇ´ıznaky. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
5
Instrukce neovlivnˇujı´cı´ prˇ´ıznaky. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
6
Srovna´nı´ rozdı´lu mezi OF a CF prˇi scˇ´ıta´nı´. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
7
Nezname´nkove´ podmı´neˇne´ skoky. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
8
Zname´nkove´ podmı´neˇne´ skoky. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
9
Instrukce pro explicitnı´ zmeˇnu prˇ´ıznaku˚. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42
10
Registry pouzˇ´ıvane´ pro vracenı´ hodnot z procedur. . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
11
Vybrane´ za´kladnı´ opera´tory MASM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69
12
Za´kladnı´ datove´ typy jazyka C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78
13
Prˇ´ıklady pouzˇitı´ prˇ´ıkazu printf. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
80
82
Reference [Harv]
R. E. Harvey. Assemblers. http://ourworld.compuserve.com/homepages/r harvey/doc book.htm
[Hyd96]
Randall Hyde. The Art of Assembly Language. – MASM verze 1996. http://www.arl.wustl.edu/ lockwood/class/cs306/books/artofasm/toc.html
[Kep07]
Alesˇ Keprt. Operacˇnı´ syste´my. Univerzita Palacke´ho, 2007. Studijnı´ text pro distancˇnı´ vzdeˇla´va´nı´, dostupny´ studentu˚m na adrese http://www.keprt.cz/vyuka/.
[IA32]
IA-32 Intel Architecture Software Developer’s Manual. Intel 2006. (Ma´ neˇkolik svazku˚ a existuje ve verzı´ch pro jednotlive´ procesory Intel, ke stazˇenı´ na www.intel.com.)
[Joh04]
Peter L. B. Johnson (Ed.) Computer Engineering II – Laboratory Notes. University of Illinois, UrbanaChampaign, IL (USA). http://courses.ece.uiuc.edu/ece390/books/labmanual/inst-ref-general.html
[Masm]
Microsoft MASM 6.1 Programmer’s Guide. Microsoft, 1992.
[Masm8] New MASM Features [in Visual C++ 2005]. Microsoft, 2004. http://msdn2.microsoft.com/enus/library/xw102cyh(VS.80).aspx [Wiki]
Wikipedia, the free encyclopedia. http://en.wikipedia.org/
83