Úroveň strojového kódu 32-bitový procesor Intel Pentium Štruktúra 32-bitových procesorov Intel Pentium Základné inštrukcie Vetvenia a cykly Práca so zásobníkom Adresovanie pamäte Pravidlá pre assemblerové procedúry v Lazaruse Praktické programovanie assemblerových procedúr Autor: Peter Tomcsányi, Niektoré práva vyhradené v zmysle licencie Creative Commons http://creativecommons.org/licenses/by-nc-sa/3./
Štruktúra 32-bitových procesorov Intel Pentium EAX EBX ECX EDX ESI EDI EBP ESP Všeobecné registre 31 15 7 15 AH AX AL BH BX BL CH CX CL DH DX DL SI DI BP SP 31 EFLAGS EIP CS SS DS ES FS GS Segmentové registre Register príznakov Adresa nasledujúcej inštrukcie Pomocný smerník do zásobníka Adresa vrcholu zásobníka
Štruktúra 32-bitových procesorov Intel Pentium Register príznakov: 31 3 29 28 27 26 25 24 23 22 21 2 19 18 17 16 ID VIP VIF AC VM RF NT IOPL OF DF IF TF SF ZF AF PF 15 14 13 12 11 1 9 8 7 6 5 4 3 2 1 Niektoré príznaky sú výstupom z ALU - po vykonaní aritmetickej alebo logickej operácie nimi ALU oznamuje niektoré vlastnosti výsledku: CF - Carry flag je N+1-vý bit N-bitovej aritmetickej operácie, teda ak je 1, tak sa výsledok operácie s číslami bez znamienka "nezmestil" do N bitov, v prípade neznamienkovej operácie to znamená pretečenie. Dá sa nastavovať aj programovo špeciálnymi inštrukciami. PF - Parity Flag dopĺňa najnižší bajt výsledku do nepárneho počtu jednotiek AF - Auxiliary Carry Flag obsahuje prenos z dolných štyroch bitov ZF - Zero Flag je nastavený na 1 práve vtedy ak bol výsledok operácie nula SF - Sign Flag obsahuje kópiu najvyššieho bitu výsledku, teda je pre nezáporné výsledky a 1 pre záporné výsledky OF - Overflow Flag je nastavený na 1 práve vtedy ak výsledok operácie s číslami so znamienkom "nezmestil" do N bitov - pretečenie Iné príznaky umožňujú programátorovi nastaviť alebo zistiť isté nastavenie alebo režim práce procesora DF - Direction Flag určuje z ktorej strany sa vykonávajú reťazcové inštrukcie IF - Interrupt Enable Flag určuje, či sú dovolené prerušenia TF - Trap Flag prikazuje procesoru vykonať za každou inštrukciou ladiace prerušenie IOPL - Input-Output Privilege Level určuje najnižšiu úroveň oprávnenia pre vykonávanie vstupných a výstupných inštrukcií 1 CF
Základné inštrukcie procesora Intel Pentium Presuny údajov MOV EAX,EBX MOV EAX, 5 MOV EBX,AL MOVSX EBX,AL MOVZX EBX,AL XCHG AL,AH EAX:=EBX Nerovnaké dĺžky Aritmetické inštrukcie Logické inštrukcie OR EAX,EBX EAX EBX Výsl. 111111111111111111111 1111111111111111111 11111111111111111111111111111 ADD EAX,EBX SUB BH,6 ADC EAX,EBX MUL CL CMP EBX,5 EAX:=EAX+EBX EAX:=EAX+EBX+ CF AX:=AL*CL (neznamienkovo) Vypočíta EBX-5 ale výsledok nikam neuloží Väčšinou po nej nasleduje podmienený skok AND AX,15 Nechá len 4 najspodnejšie bity TEST AL,7 1 OR 1 = 1 Vypočíta AL AND 7 ale výsledok nikam neuloží Väčšinou po nej nasleduje podmienený skok
Odovzdávanie parametrov a výsledku assemblerových procedúr/funkcií Prvé tri parametre vhodnej dĺžky a typu sa odovzdávajú v registroch EAX, EDX a ECX (v tomto poradí), ostatné parametre sa odovzdávajú v zásobníku 8-bitové a 16-bitové hodnoty sa odovzdávajú v príslušnej menšej časti registra (AL, DL, CL alebo AX, DX, CX) V príslušnom registri je hodnota parametra okrem parametrov označených var alebo out a okrem polí, množín a recordov- vtedy je v danom registri uložená adresa parametra 32-bitový výsledok sa odovzdáva v registri EAX, 16-bitový v AX a 8-bitový v AL Pozn: pravidlá platia pre volanie typu register, volanie typu Pascal odovzdáva všetky parametre v zásobníku.
Používanie registrov v assemblerových procedúrach Registre EAX, ECX a EDX možno meniť (Ale pozor: ak obsahuje parameter tak jeho zmenou si zničíte hodnotu alebo adresu parametra) Registre EBX, ESI a EDI treba zachovať - môžu sa používať len keď sa ich pôvodné hodnoty na začiatku uložia do zásobníka a pred koncom sa z neho vyberú Registre EBP a ESP sa nemajú vôbec nemeniť (pokiaľ neviete čo presne tým spôsobíte) Príznak DF registra EFLAGS je vždy pri vstupe vynulovaný a pri výstupe musí byť taktiež vynulovaný Zásobník musí byť na konci presne v tom stave ako na začiatku
Prvý pokus Naprogramujte funkciu, ktorá na vstupe dostane dve 32-bitové celé čísla so znamienkom A a B a jej výsledkom bude hodnota výrazu 2*A+4*B funkcia musí začať slovom asm, nie begin prvý parameter sa prenesie v EAX druhý parameter sa prenesie v EDX function pocitaj(a,b:integer):integer; asm ADD EDX,EDX // v EDX je teraz 2*B ADD EAX,EDX // v EAX je A+2*B ADD EAX,EAX // v EAX je 2*A+4*B end; integer je 32-bitové celé číslo so znamienkom Pre programovanie v strojovom kóde je typické, že sa pri jednoduchom aritmetickom výpočte nepoužívajú inštrukcie násobenia lebo ich použitie spotrebuje viac registrov a zneprehľadní kód
Rotácie a posuny ROL SHL ROR CF N-1 CF SAL N-1 RCL RCR CF N-1 CF N-1 CF SHR SAR N-1 N-1 CF CF N-1 N-1 CF (bit N-1 zostáva na mieste aj sa skopíruje do bitu N-2)
Vetvenie programu Namiesto štruktúr Pascalu sa program v strojovom kóde vetví pomocou podmienených skokov, ktoré typicky (ale nie vždy) nasledujú po operácii porovnania. porovnanie if EAX = 5 then <príkaz>; CMP EAX,5 JNE @1 <príkaz> @1: podmienený skok je opačný než pascalovský operátor lebo hovorí: ak sa EAX nerovná 5 tak preskoč <príkaz1> Pri nerovnostiach musíme vedieť, či sú čísla chápané znamienkovo (v dvojkovom doplnku) alebo neznamienkovo. Kompilátor Pascalu to zistí z deklarácie premenných (viď slajdy Celočíselné typy v Delphi), v strojovom kóde na to ale musí myslieť programátor. znamienkovo väčší if EAX > (zn.) EBX then <príkaz1> else <príkaz2>; CMP EAX,EBX JLE @1 <príkaz1> JMP @2 @1:<príkaz2> @2: nepodmienený skok Skoč ak je znamienkovo menší alebo rovný Teda zase opačný skok
Jednoduché cykly while AL < (nezn.) <príkaz>; @2:CMP AL,DL JAE @1 <príkaz> JMP @2 @1: DL do repeat <príkazy> until AX <= (zn.) 18; @1:<príkazy> CMP AX,18 JG @1 Vždy používame opačné skoky Pri nerovnostiach záleží, či ide o čísla so znamienkom alebo bez znamienka
Cykly typu For for i:=1 to 1 do <príkaz>; MOV ECX,1 @1:<príkaz> LOOP @1 for i:=1 to EBX do <príkaz>; MOV ECX,EBX JECXZ @2 @1:<príkaz> LOOP @1 @2: Tento prístup funguje len ak je horná hranica konštanta alebo ak určite vieme, že je väčšia než nula LOOP @1 znamená: DEC ECX JNZ @1 Teda zníži ECX o jednu a ak výsledok nie je nula, tak skočí na @1 Vo všeobecnosti (napr. keď horná hranica je výsledok predošlého výpočtu), musíme pridať test na nulu Keď je výsledok predošlej operácie číslo so znamienkom (teda môže byť aj záporné), tak musíme dať ešte zložitejší test (skúste ako cvičenie)
Práca so zásobníkom Zásobník je údajová štruktúra LIFO - Last In, First Out Má definované operácie PUSH (pridaj do zásobníka) a POP (vyber zo zásobníka) Zásobník je vhodná dátová štruktúra pre niektoré typy algoritmov Načo je zásobník v strojovom kóde? Na ukladanie návratových adries podprogramov Na ukladanie lokálnych premenných Na ukladanie medzivýsledkov aritmetických výpočtov Implementácia zásobníka v procesoroch Intel Pentium Je uložený v časti pamäti Adresa jeho vrcholu je uložená v registri ESP Rastie smerom do nižších adries
Implicitné použitie zásobníka @Pocitaj: ADD EAX,EAX ADD EAX,1 RET @F: MOV EAX,1 CALL @Pocitaj MOV EBX,EAX MOV EAX,2 CALL @Pocitaj ADD EAX,EBX RET function Pocitaj(X:Integer):Integer; begin Result:=2*X+1; end; function F:Integer; begin Result:=Pocitaj(1)+Pocitaj(2); end; Volanie podprogramu - do zásobníka sa uloží obsah EIP a do EIP sa uloží adresa podprogramu. Návrat z podprogramu - EIP sa vyberie zo zásobníka
Explicitné použitie zásobníka Programátor môže používať zásobník na uloženie akýchkoľvek údajov: Medzivýsledky pri výpočte zložitých výrazov Uchovanie registrov keď ich dočasne treba na niečo iné PUSH EAX Ulož EAX do zásobníka POP EBX Vyber EBX zo zásobníka PUSHFD Ulož EFLAGS do zásobníka POPFD Vyber EFLAGS do zásobníka
Narábanie s bitmi registra EFLAGS Niektoré bity registra EFALGS sa dajú meniť špeciálnymi inštrukciami: Je to napríklad bit CF: STC - Nastav CF na 1 CLC - Nastav CF na CMC - Neguj CF alebo bit IF STI - Nastav IF na 1 (teda povoľ prerušenia) CLI - Nastav IF na (teda zakáž prerušenia)
Ukážka assemblerovej funkcie (1) Naprogramujte assemblerovú funkciu: function Pocet1(x:Cardinal):Byte; Jej výsledkom je počet jedničiek v dvojkovom zápise čísla x. function Pocet1(x:Cardinal):Byte; asm mov dl, // v dl budeme sčitovať jedničky mov ecx,32 // cyklus - 32-krát @1: shr eax,1 // najvyšší bit prejde do CF jnc @2 // if cf=1 then dl:=dl+1 inc dl @2: loop @1 // koniec cyklu = dec ecx; jnz @1 mov al,dl // výsledok sa musí dostať do al end;
Ukážka assemblerovej funkcie (2) Optimalizovaná verzia: vieme priamo pripočítať cf k eax nemusíme robiť 32-krát, stačí kým nebude v EAX nula function Pocet1(x:Cardinal):Byte; asm mov dl, @1: shr eax,1 adc dl, cmp eax, jne @1 mov al,dl end;
Ukážka assemblerovej funkcie (3) Naprogramujte assemblerovú funkciu: function ObrateneCislo(x:Byte):Byte; Jej výsledkom je číslo, ktoré vznikne obrátením zápisu x. function ObrateneCislo(x:Byte):Byte; asm mov ecx,8 @1: shr al,1 rcl dl,1 loop @1 mov al,dl end;
Adresovanie pamäte Keď je operand inštrukcie v operačnej pamäti, hovoríme mu pamäťový operand Aby vedel procesor nájsť pamäťový operand, musí poznať jeho adresu Pamäťové operandy zodpovedajú premenným vo vyšších programovacích jazykoch Preto tvorcovia strojového kódu navrhujú také spôsoby adresovania, aby sa pomocou nich dali adresovať všetky druhy premenných, ktoré poznáme z vyšších programovacích jazykov.
Priama adresa MOV EAX,[1284] Nepriama adresa MOV EDX,[EBX] Indexovaná adresa Adresovanie pamäte Zhrnutie (1) MOV [1246+EAX*4],ECX Bázovaná adresa MOV EAX,[EBP+1] Indexovaná a bázovaná adresa (najzložitejší možný prípad) MOV EDX,[EBP+1+ECX*4] Jeden register (EAX, EBX, ECX,EDX, ESI, EDI, ESP alebo EBP) Konštanta (kladná alebo záporná) Druhý register Násobiaci faktor (len 1, 2, 3 alebo 4)
Adresovanie pamäte Zhrnutie (2) Najviac jeden operand smie byť v pamäti MOV [EAX+2],[EBX+4] Niekedy kompilátor nevie aký dlhý je operand: INC [EDX+8] INC BYTE PTR [EDX+8] INC WORD PTR [EDX+8] INC DWORD PTR [EDX+8] nie je jasná dĺžka operandu operand je jeden bajt operand je dvojbajt operand je štvorbajt
Ukážka assemblerovej funkcie (4) Naprogramujte assemblerovú funkciu: type Pole = array[1..1] of Integer; function MaxPrvok(A:Pole):Integer; Jej výsledkom je hodnota najväčšieho prvku v poli A. type Pole = array[1..1] of Integer; function MaxPrvok(A:Pole):Integer; asm mov edx,[eax] mov ecx,9 @1: add eax,4 cmp edx,[eax] jge @2 mov edx,[eax] @2: loop @1 mov eax,edx end;