Archív

Archív pre kategóriu ‘Assembler x86’

Assembler – Inštrukcie CALL a RET

September 6th, 2009 admin Žiadne komentáre

Syntax:
CALL typ_volania operandy
RET

Inštrukcie CALL a RET používame na implementáciu podprogramov. Inštrukcia CALL má jeden, zvyčajne priamy operand (ako u inštrukcii skoku sa pripúšťa operand v registri alebo v pamäti), ktorým je adresa miesta v pamäti, kam sa odovzdá riadenie. Na rozdiel od inštrukcie JMP sa do zásobníka uloží hodnota registra IP (EIP) nasledujúcej inštrukcie po inštrukcii CALL.
Parameter typ volania je podobný ako u inštrukcie JMP, ak nie je uvedený, bude sa predpokladať typ volania near, a preto sa bude ukladať a meniť iba register IP (EIP). Pri typu volania far sa na zásobník okrem registra IP (EIP) uloží aj hodnota segmentového registra CS (adresa volania sa lak bude pozostávať z hodnoty pre register IP (EIP) a hodnoty pre register CS).
Návrat z podprogramu zabezpečuje inštrukcia RET, ktorá zo zásobníka vyberie hodnotu z vrcholu a tu uloží do registra IP (EIP), čím sa začnú vykonávať inštrukcie uložené za volajúcou inštrukciou CALL. Pre návrat z podprogramu, ktorý bol privolaný inštrukciou CALL far musíme obnoviť aj pôvodnú hodnotu registra CS, čo môžeme zabezpečiť použitím inštrukcie RETF, ktorá zo zásobníka najprv vyberie hodnotu a uloží ju do registra IP, a potom vyberie ešte jednu, ktorú uloží do registra CS.
Situáciu môžeme ešte trochu skomplikovať.
Inštrukcie RET alebo RETF môžu mať ako priamy operand počet položiek, ktoré sa majú zo zásobníka vybrať po naplnení registra IP (EIP) (alebo aj SK). Tento špeciálny tvar inštrukcie RET využijeme až neskôr, kde si povieme o možnostiach prepojenia vyšších programovacích jazykov a assembleru.

Ukážme si použitie inštrukcií CALL a RET na príklade:
Pomocou podprogramu sčítame dve čísla uložené v registroch EAX a EBX, výsledok súčtu uložíme v registri ECX (ignoruje pretečenie). Hodnoty uložené v registroch EAX a EBX musia byť zachované aj po návrate z podprogramu.
Vytvoríme podprogram sčítaj. Najskôr musíme vyriešiť odovzdávanie parametrov podprogramu. Vo vyšších programovacích jazykoch sa pre odovzdanie parametra používa zásobník, my si zatiaľ vystačíme s dohodnutými registrami. Akonáhle vykonáme inštrukciu pre sčítanie ADD, bude jedna hodnota operanda prepísaná výsledkom. Využijeme zásobník a hodnotu uloženú v danom registri si uložíme a neskôr opäť obnovíme.

scitaj:
push eax; uložíme hodnotu registra EAX na zásobník 
add eax, EBX; sčítame EAX a EBX, výsledok bude v EAX 
mov ecx, eax; výsledok z EAX uložíme do ECX 
pop eax; obnovíme pôvodnú hodnotu v registri EAX 
ret ; návrat z podprogramu.

Podprogram scitaj vyskúšame s hodnotami 4 a 8.

mov eax, 4; do EAX uložíme 4 
mov EBX, 8; EBX = 8 
call scitaj ; zavoláme podprogram scitaj 
;Výsledok bude uložený v registri ECX a bude JIRRA. 
; hodnota ECX = OxOOOOOOOC

Čo by sa stalo keby sme vynechali inštrukciu POP eax? Inštrukcia RET by tak predala riadenie na adresu, ktorá bola pôvodne uložená v registri EAX, čo by viedlo k pádu programu a v horšom prípade pádu systému. Podobne opomenutie inštrukcie RET by viedlo k vykonávanie inštrukcií dávno nepatria nášmu podprogramu, čo tiež ľahko (a celkom určite) vedie k pádu programu (to sa veľmi často využíva crackarmi a hackermi).
Podprogram scitaj možno v našom jednoduchom prípade zjednodušiť tak, že sa zaobídeme bez inštrukcií PUSH a POP:

scitaj: 
mov ecx, eax ; hodnotu registra EAX (prvý parameter) prekopírujete do registra ECX 
add ecx, EBX ; a sčítame s druhým parametrom v registri EBX, 
; výsledok bude v požadovanom registri ECX 
ret ; návrat z podprogramu.
VN:F [1.9.3_1094]
Rating: 0.0/10 (0 votes cast)
Categories: Assembler x86 Tags:

Assembler – inštrukcie PUSH/POP a ich varianty

September 6th, 2009 admin Žiadne komentáre

Syntax: PUSH ol

Pomocou inštrukcie PUSH môžeme na zásobník uložiť ľubovoľný 16-bitový alebo 32-bitový register alebo obsah pamäťového miesta (nepoužíva sa príliš často).

push eax; na zásobník uložíme register EAX

Inštrukciu push môžeme rozpísať inštrukciami:

sub esp, 4; zníž ESP o 4 
mov [ss: esp], eax; zapíš do zásobníka hodnotu registra EAX

alebo všeobecnejšie (pomocou operátora sizeof, požičaného z vyšších prog. jazykov):

push ol

môžeme zapísať ako

(E) SP = (E) SP-sizeof (ol)
ol -> SS: [(E) SP]

Inštrukcia POP presunie do operanda hodnotu (obsah) z vrcholu zásobníka a vrcholom zásobníka sa stane hodnota uložená na vyššej adrese. Inštrukcia POP môže mať rovnaký typ operandov ako inštrukcia PUSH.

Opäť možno inštrukciu POP prepísať pomocou inštrukcií MOV a ADD:

mov eax, [ss: esp]; načítaj z vrcholu zásobníka do registra EAX 
add esp, 4; "smaž" najvyššie dvojslovo zo zásobníka

Uveďme si niekoľko príkladov.

push eax; na zásobník ulož hodnotu uloženú v registri EAX 
push esi; na zásobník ulož hodnotu registra ESI 
pop eax; zo zásobníka vyber hodnotu a ulož ju v registri EAX 
pop esi; zo zásobníka vyber hodnotu a ulož ju v registri ESI

Pripomeňme si, že na zásobník nie je možné pomocou inštrukcie PUSH uložiť 8-bitové registre a ani nemožno priamo uložiť obsah registra IP (EIP) (instruction pointer, ukazovateľ na ďalšiu inštrukciu). Inštrukcie PUSH / POP ip alebo PUSH / POP EIP neexistujú (možno ich ale nahradiť inak, viď ďalej).

Instrukcie PUSHA/POPA a PUSHAD/POPAD

Syntax:
PUSHA
POPA

Niekedy potrebujeme uložiť všetky univerzálne registre na zásobník. Na tento účel je mikroprocesor vybavený inštrukciami PUSH a POPA, ktoré na zásobník uloží (PUSHA) alebo zo zásobníka obnoví (POPA) hodnoty všetkých univerzálnych 16-bitových registrov. Inštrukcie PUSH a POPA nemajú žiadny voliteľný operand.

Inštrukcie PUSH / POPA boli zavedené v predchodcovi procesoru 80386, a preto neukladá 32-bitové registre (nemôže, pretože neexistovali). Ak ich cheme uložiť alebo vybrať, tak musíme použiť “nové” inštrukcie PUSHAD a POP AD.

Poradie ukladaných registrov na zásobníka od vrcholu nadol je:
(E)AX, (E)CX, (E)DX, (E)BX, pôvodné (E)SP, (E)BP, (E)SI, (E)DI

pusha; uložíme na zásobník obsah všetkých univerzálnych registrov
; urobíme veľké zmeny (napríklad komplikovaný výpočet,; ktorý zmení väčšinu hodnôt univerzálnych registrov) 
popa; zase všetky registre obnovíme
VN:F [1.9.3_1094]
Rating: 9.0/10 (1 vote cast)
Categories: Assembler x86 Tags:

Assembler – LOOP, LOOPZ, LOOPZN, LOOPNE

September 6th, 2009 admin 1 komentár

V Assembleri bude implementácia cyklu nasledovná. Zvolíme register ECX pre riadiacu premennú I.

for_start: 
mov ecx, 0 ; naplníme register ECX nulou 
for_cyklus: ; na toto návestie sa budeme vracať 
... ; tu budeme vykonávať príkazy v FOR-cykle 
inc ecx ; ECX zvýšime o 1 
cmp ecx, 10 ; porovnáme ECX s 10 
jnz for_cyklus ; ak nie je 10, skočíme na for_cyklus 
for_skonci: ; ak už je 10, potom skončíme

Ukážme si aj variant s premennou I umiestnenou v pamäti.

for_start:
mov  dword   [i] , 0
for_cyklus: ; na toto návestie sa budeme vracať 
... ; tu budeme vykonávať príkazy vo FOR-cykle 
inc dword [i] ; ECX zvýšime o 1 
cmp dword [i], 10 ; srovnárne ECX s 10 
jnz for_cyklus ; ak nie 10, skočíme na for_cyklus 
for_skonci: ; ak už je 10, tak skončíme

Inštrukcia LOOP

Syntax: LOOP navestie_cyklu

Inštrukcia LOOP má dva operandy, podobne ako inštrukcia MUL. Prvý operand je pevne daný a je ním register CX (alebo ECX). Druhý, voliteľný operand je adresa návestia, kam sa bude odovzdávať konanie v prípade splnenej podmienky. Inštrukcia LOOP najskôr odpočíta jedničku od registra CX (ECX), a ak výsledok nie je nula, tak odovzdá riadenie programu na miesto určené svojim voliteľným operandom (cieľ skoku musí ležať v intervale + (alebo -) 128 bytov).

For-cyklus od 10 do 1 (vrátane) vytvorený inštrukciou LOOP by vyzeral takto:

for_start:
mov cx, 10  ; naplníme register CX 10 
for_cyklus:  ; na toto návestie sa budeme vracať 
...              ; tu budeme vykonávať príkazy v FOR-cykle 
loop for_cyklus ; ak CX nie je 0, skočíme na for_cyklus 
for_skonci: ; ak už je 0, potom skončíme

Inštrukcie LOOPZ a LOOPZN

Syntax:
LOOPZ navestie_cyklu
LOOPNZ navestie_cyklu

Inštrukcia LOOPZ rozširuje podmienku uskutočnenia skoku. Skok na návestie uvedené v inštrukcii sa uskutoční práve vtedy, keď hodnota v registri CX (ECX) nie je rovná nule a zároveň je nastavený príznak ZF (zero flag) na 1. Synonymum inštrukcie je LOOPE.

Inštrukciou LOOPZ ľahko vytvoríme cyklus s dodatočnou podmienkou. Rozšírme podmienku jedného priechodu cyklu z predchádzajúceho príkladu o test registra BX na číslo 3. Cyklus sa vykoná najviac desaťkrát, ale za podmienky, že hodnota v registri BX = 3. Inak sa predčasne ukončí.

for_start: 
mov cx, 10   ; naplníme register ECX 10 
for_cyklus: ; na toto návestie sa budeme vracať 
... ; tu budeme vykonávať príkazy v FOR-cykle 
... ; možná zmena registra BX 
cmp bx, 3 ; je BX = 3?
loopz for_cyklus ; ak CX neni 0 a BX = 3, skočíme na for_cyklus 
for_skonci: ; ak už je 0 alebo BX nie je 3, tak skončíme

Inštrukcia LOOPNZ funguje analogicky, ale druhá podmienka je negovaná. Skok sa uskutoční, ak nie je register CX (ECX) rovný nule a zároveň nie je nastavený príznak ZF (zero flag je nula). Jej synonymum je LOOPNE.

VN:F [1.9.3_1094]
Rating: 0.0/10 (0 votes cast)
Categories: Assembler x86 Tags: