You are on page 1of 15

Prvý blok

Prvý blok cvičení je navrhnutý tak, aby ste sa oboznámili s fungovaním procesoru
a základnými funkciami jazyka c++.Za úlohy prvého bloku môžete získať až 12 bodov.
Úlohy odovzdávajte priebežne, najneskôr však vo štvrtom týždni semestra. Po tomto
termíne už nebude možné odovzdať žiadnu z týchto úloh!
Nemusíte riešiť všetky, vyberte si tie, ktoré vás zaujmú tak, aby ste získali potrebný
počet 12 bodov. Celkove je tu 8 úloh za 18 bodov. Viac ako 12 nemôžete mať. Na
zápočet potrebujete aspoň 6 bodov.

Úloha 11 - procesor (1b)


Vložte do C-programu kúsok assemblerovského kódu, ktorým vypočítate súčet dvoch
premenných.
Tip: Ako základ môžete použiť tento program, ktorý inkrementuje premennú. Stačí
doplniť druhú premennú a vymeniť inštrukciu INC za ADD.
Zdroj:

• Asme..., Assem... Čo? Aký assembler?


• Výber strojových inštrukcií s popisom.
• Vzorový príklad (inkrementuje premennú). [verzia pre Microsoft Visual C,
Dev-C++ a Borland C]
• Vysvetlivky k vzorovému príkladu, syntax.
• Podrobný návod k assembleru x86 [seriál z časopisu PC REVUE]
• Podrobný popis architektúry x86 [sandpile.org]

Úloha 12 - procesor (1b)


Napíšte pomocou vloženého (embedded) asembleru C-program v ktorom použijete
vlastnú funkciu na násobenie dvomi bez použitia súčinu.
Tip: Stačí ak si spomeniete, že násobenie dvoma v binárnej sústave znamená posunúť
číslo o jedno miesto doľava. Ako základ môžete použiť opäť vzorový program z
predošlej úlohy, ktorý inkrementuje premennú. Stačí vymeniť inštrukciu INC za SHL.
Zdroj: viď úloha11

Úloha 13 - procesor (2b)


Napíšte pomocou vloženého (embedded) asembleru C-program s funkciou na prevod
číslice (0 - 15) na príslušný ASCII znak predstavujúci hodnotu v hexadecimálnej sústave.
Pre čísla 0 - 9 bude výstupom znak '0' - '9', pre čísla 10 - 15 znaky 'A' - 'F'.
Tip: Tento kus programu v C urobí to isté:
cislo = znak < 10 ? znak + '0' : znak + 55;
Ako základ môžete použiť znova tento program, ktorý inkrementuje premennú. Stačí
vymeniť inštrukciu INC za ADD a doplniť test (CMP) s nejakým podmieneným skokom.
Zdroj: viď úloha 11

Úloha 14 - procesor (3b)


Napíšte pomocou inlineasembleru a inštrukcie cpuid program, ktorý zistí, aký typ
procesora máte v počítači (Intel, AMD, Cyrix,...).
Tip: Inštrukcia cpuid očakáva v registri EAX číslo funkcie, v našom prípade 0. Výsledkom
sú tri registre, ktoré treba uložiť do poľa a vypísať ako reťazec.
Zdroj:

• viď úloha 11
• viac o inštrukcii CPUID
• error C2443: operandsizeconflict
• Ešte viac o inštrukcii cpuid [niekedy nefunguje]
• Popis inštrukcie cpuid [sandpile.org]

Úloha 15 - procesor (3b)


Napíšte pomocou vloženého (embedded) asembleru C-program v ktorom použijete
vlastnú funkciu na násobenie dvoch čísel pomocou súčtu.
Tip: Stačí ak si spomeniete, že násobenie dvoch čísel (napr. n*k)je v podstate
viacnásobný súčet jedného z činiteľov (pre n=5 to bude k+k+k+k+k) . Ako základ
môžete použiť opäť vzorový program z predošlej úlohy, ktorý inkrementuje
premennú. Treba vytvoriť cyklus, ktorý skončí po správnom počte súčtových operácií.
Využite niektorý z podmienených skokov z inštrukcií.
Zdroj: viď úloha 11

Úloha 16 (2b)
Napíšte program, ktorý zobrazí nasledovné dva vstupy: číslo 29127 a ASCII kód znaku
'F', v dvojkovej, desiatkovej a šestnástkovej sústave.
Tip: Použite čo najmenší počet premenných a využite možnosti pretypovania
a formátovacieho reťazca funkcie printf.
Zdroj:

• Horovčák, P. a Podlubný, I.: Úvod do programovania v jazyku C, 1998. Učebnica


prístupná on-line: <http://www.tuke.sk/podlubny/C/>
• "Dvojková číselná sústava", Wikipedia, TheFreeEncyclopedia.

Úloha 17 (2b)
Napíšte program, ktorý dokáže zapísať zadené číslo (stačí rozsah 1-100) rímskymi
číslicami.
Zdroj:

• "Rímske číslice." Wikipedia, TheFreeEncyclopedia.

Úloha 18 (4b)
Napíšte program na výpočet kontrolnej sumy (checksum) pre zadaný riadok tzv. IntelHex
súboru.
Definícia: Kontrolný súčet sa vypočíta ako dvojkový doplnok súčtu jednotlivých bajtov
od začiatku riadku až po kontrolný súčet.
Príklad:
Ak zadáte tento reťazec:
:10010000214601360121470136007EFE09D21901XX
Musí vám vyjsť namiesto XX kontrolný súčet 40.
Splnenie úlohy demonštrujte výpočtom kontrolných súm napr. pre tieto reťazce:
:100010000C9445000C9445000C9445000C944500xx
:100020000C9445000C9445000C9445000C944500xx
:100030000C9445000C9445000C9445000C944500xx
:100040000C9445000C9445000C9445000C944500xx

Asme..., Assem...
Čo? Aký assembler?
Assembler je strojový jazyk, čo znamená, že každá inštrukcia priamo hovorí stroju --
procesoru, čo má vykonať. Z toho vyplýva, že tento jazyk je vždy určený pre konkrétny
typ procesora, programy nie sú prenositeľné a závisia vždy na konkrétnej konfigurácii a
architektúre.
Na cvičeniach budeme písať veľmi jednoduché programy, ktoré využívajú len malú časť z
kompletného inštrukčného súboru procesorov rodiny x86. Všetky (okrem CPUID) by mali
fungovať aj na najstaršom procesore 8086 až po najnovšie Pentium 4.
Na nasledujúcom obrázku je nakreslená vnútorná štruktúra procesora 80386. Je to
základ, ktorý možno nájsť aj v jeho nasledovníkoch. Keďže prakticky všetko, čo procesor
robí sa odohráva v registroch, nájdeme ich tu hneď niekoľko.
Registre procesorov x386.
Predovšetkým je tu sada univerzálnych registrov A-D, ktoré vykonávajú prakticky všetku
prácu. Niektoré majú špeciálne určenie, ale vo väčšine inštrukcií môžete použiť ľubovoľný
z nich. V pôvodnom procesore 8086 boli tieto registre 16-bitové (AX, BX, CX a DX),
pričom každý z nich sa dal použiť aj ako 8-bitový (napr. AX = AH + AL). S nástupom 32-
bitových procesorov sa rozšírili aj tieto registre, takže dnes sú všetky 32-bitové, pričom
ale ostala zachovaná možnosť pristúpiť k spodnej polovici obsahu cez pôvodné označenie
(teda napr. 32-bitový register EAX má spodných 16 bitov označených ako AX, ktoré sa
dajú rozdeliť zasa na dve polovice AH a AL).
Okrem univerzálnych registrov sú v procesore dva indexové registre ESI a EDI, ktoré sa
používajú napríklad pri počítadláach. Ďalej tri ukazovacie registre EBP, ESP a EIP (EBP je
bázový pointer, ktorý sa používa pri výpočte adresy, ESP je zásobníkový - stack pointer,
ktorý ukazuje na vrchol zásobníka a EIP je inštrukčný, ktorý ukazuje na práve
vykonávanú inštrukciu).
Pri práci s pamäťou sa intenzívne využíva ďalšia skupina, tzv. segmentových registrov
(SS - stack segment, CS - code segment, DS - data segment, ES - extra segment, FS a
GS).
Významné postavenie má tzv. príznakový register (FLAGS), v ktorom má každý bit
špeciálny význam. Napríklad nultý, najnižší bit CF (Carry Flag) sa automaticky nastaví do
log. 1, ak pri aritmetickej operácii nastal prenos do vyššieho rádu. Jednotlivé bity tohoto
registra je možné testovať a vetviť tak program na základe rozličných podmienok.

Assembler procesorov x86


V tomto dokumente nájdete výber inštrukcií aj s popisom, ktoré budete potrebovať na
riešenie úloh 2.1 a - d. Zoznam nie je ani zďaleka kompletný, záujemcovia o hlbšie
štúdium môžu čerpať napríklad z knihy Rudolf Marek: Učíme seprogramovat v
jazyceAssembler pro PC. Vydavateľstvo Computer Press, Brno, 2005.

Podľa funkcie

• Presuny
• MOV
• Aritmeticko-logické
• ADD
• SUB
• INC
• DEC
• Posuny
• SHL
• SHR
• Skoky
• CMP
• JG
• JE
• JZ
• JMP
• Špeciálne
• CPUID
• NOP

MOV Move
MOV ciel,zdroj ; ciel <- zdroj

Operandy:
reg, reg
reg, mem
mem, reg
reg, imm
mem, imm
Príznaky:
O D I T S Z A P C
Popis: Inštrukcia presunie obsah z jedného miesta na druhé. Najčastejšie sa vyskytujúca
inštrukcia. Procesor vlastne stále len niečo niekam presúva.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


MOV ecx, 8 ; do registra ECX vlozimecislo 8
ADD eax, ecx ; vypocitame EAX = EAX + ECX

ADD Addition
ADD ciel,zdroj ; ciel = ciel + zdroj

Operandy:
reg, reg
reg, mem
mem, reg
reg, imm
mem, imm
Príznaky:
O D I T S Z A P C
+ - - - + + + + +
Popis: Inštrukcia spočíta oba operandy a výsledok uloží do prvého z nich.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


MOV ecx, 8 ; do registra ECX vlozimecislo 8
ADD eax, ecx ; vypocitame EAX = EAX + ECX

SUB Substraction
SUB ciel,zdroj ; ciel = ciel - zdroj

Operandy:
reg, reg
reg, mem
mem, reg
reg, imm
mem, imm
Príznaky:
O D I T S Z A P C
+ + + + + +
Popis: Inštrukcia odpočíta druhý operand od prvého a výsledok uloží do prvého z nich.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


MOV ecx, 8 ; do registra ECX vlozimecislo 8
SUB eax, ecx ; vypocitame EAX = EAX - ECX

INC Increment by 1
INC zdroj ; zdroj = zdroj + 1

Operandy:
reg
mem
Príznaky:
O D I T S Z A P C
+ + + + +
Popis: Inštrukcia zväčší obsah operandu o jednotku. Je to rýchlejšie ako ADD zdroj,1.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


INC eax ; teraz EAX = 22 + 1

DEC Decrement by 1
DEC zdroj ; zdroj = zdroj - 1

Operandy:
reg
mem
Príznaky:
O D I T S Z A P C
+ + + + +
Popis: Inštrukcia zmenší obsah operandu o jednotku. Je to rýchlejšie ako SUB zdroj,1.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


SUB eax ; teraz EAX = 22 - 1

SHL ShiftLogicalLeft
SHL zdroj,pocet ; zdroj = zdroj<<pocet

Operandy:
reg,CL
mem,CL
Príznaky:
O D I T S Z A P C
+ + +
Popis: Inštrukcia posunie register, alebo obsah pamäti o pocet miest doľava. Zľava Zprava sa doplní
nula a najvyšší bit "prepadne" do príznakového bitu CF (carryflag). Pozn.: pocet sa uloží v registri CL.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


SHL eax, 1 ; teraz EAX = 22<<1, teda 44.
SHR ShiftLogicalRight
SHR zdroj,pocet ; zdroj = zdroj>>pocet

Operandy:
reg,CL
mem,CL
Príznaky:
O D I T S Z A P C
+ + +
Popis: Inštrukcia posunie register, alebo obsah pamäti o pocet miest doprava. Na najvyšší bit príde
nula a najnižší bit "prepadne" do príznakového bitu CF (carryflag). Pozn.: pocet sa uloží v registri CL.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


SHR eax, 1 ; teraz EAX = 22>>1, teda 11.

CMP Compare
CMP ciel,zdroj ; ciel - zdroj

Operandy:
reg, reg
reg, mem
mem, reg
reg, imm
mem, imm
Príznaky:
O D I T S Z A P C
+ + + + + +
Popis: Inštrukcia odpočíta druhý operand od prvého ale výsledok neuloží nikam, iba nastaví príznaky
podobne ako inštrukcia SUB. Používa sa pri rozhodovaní, obvykle nasleduje inštrukcia podmieneného
skoku.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


MOV ecx, 8 ; do registra ECX vlozimecislo 8
CMP eax,ecx ; porovnaj EAX a ECX
JZ niekam ; ak surovnake, skoc niekam

JZ/JE JumpifZero / JumpifEqual


JZ navestie ; if (ZF==1) gotonavestie
JE navestie ; if (ZF==1) gotonavestie

Operandy:
lab
Príznaky:
O D I T S Z A P C

Popis: Inštrukcia vykoná skok na návestie ak je výsledok predošlej operácie nula. Presnejšie
povedané, ak ZF je nastavený skok sa vykoná. Nemusí to byť výsledok bezprostredne predošlej
inštrukcie. Obvykle sa používa v kombinácii s inštrukciou CMP. Vtedy to znamená, že oba jej operandy
boli rovnaké. Preto sa táto inštrukcia používa aj ako JE. Vygenerovaný kód je však rovnaký.

Skok samotný procesor zrealizuje veľmi jednoducho -- presunie novú adresu do registra IP
(instruction pointer).

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


MOV ecx, 8 ; do registra ECX vlozimecislo 8
CMP eax,ecx ; porovnaj EAX a ECX
JE niekam ; ak surovnake, skoc niekam
... ; tu pokracuje program ak EAX != ECX
niekam: ... ; tu pokracuje program ak EAX == ECX

JG/JNLE JumpifGreaterThan / JumpifNotLess Or Equal


JG navestie ; if (SF==OF && ZF==0) gotonavestie
JNLE navestie ; if (SF==OF && ZF==0) gotonavestie

Operandy:
lab
Príznaky:
O D I T S Z A P C

Popis: Inštrukcia vykoná skok na návestie ak výsledok niektorej predošlej operácie nastaví príznaky SF
a OF na rovnakú hodnotu a nie je to nula. Obvykle sa používa v kombinácii s inštrukciou CMP. Vtedy to
znamená, že prvý jej operand je väčší (preto JG). To ale môžeme interpretovať aj tak, že nie je menší
alebo rovný a preto sa táto inštrukcia používa aj ako JNLE. Vygenerovaný kód je však rovnaký.

Príklad:

MOV eax, 22 ; do registra EAX vlozimecislo 22


MOV ecx, 8 ; do registra ECX vlozimecislo 8
CMP eax,ecx ; porovnaj EAX a ECX
JG niekam ; ak je EAX > ECX, skoc niekam
... ; tu pokracuje program ak EAX <= ECX
niekam: ... ; tu pokracuje program ak EAX > ECX

JMP Jump
JMP ciel ; goto ciel

Operandy:
ciel
Príznaky:
O D I T S Z A P C

Popis: Inštrukcia vykoná skok na návestie.

Skok samotný procesor zrealizuje veľmi jednoducho -- presunie novú adresu do registra IP
(instruction pointer).

Príklad:

JMP niekam ; skoc niekam


... ; toto preskocime
niekam: ... ; tu pokracuje program

CPUID CPU Identification


CPUID ; ebx,ecx,edx = VendorString

Operandy:

Príznaky:
O D I T S Z A P C

Popis: Inštrukcia uloží do registrov xxx, xxx a xxx 12 znakový reťazec, jednoznačne identifikujúci
výrobcu procesora.

Príklad:

MOV eax, 0 ; do registra EAX vlozime 0


CPUID ; volanie funkcie CPUid

Podrobnejšie o inštrukcii CPUID...

NOP No Operation
NOP ; nerobimnic

Operandy:
žiadne
Príznaky:
O D I T S Z A P C
- - - - - - - - -
Popis: Inštrukcia, ktorá nerobí nič. Používa sa ako malé oneskorenie, doladenie časových slučiek a
pod.

Príklad:
OUT 44, AX ; na port 44 zapisem obsah registra AX
NOP ; chvilupockam, lebo viem, ze je pomaly
OUT 44, BX ; na port 44 zapisemdruhecislo z registra BX

Vzorovýpríklad
/*
***************************************************************************
***** */
/*
*/
/* Architekturapocitacov - Uloha 2.1
*/
/*
*/
/* Program inkrementuje v assembleripremennu
*/
/*
*/
/* Autor: Richard Balogh <balogh@elf.stuba.sk>
*/
/* Historia:
*/
/* 2.3.2006 zakladna verzia funkcna
*/
/* 6.3.2006 verzia pre oba kompilatory
*/
/* Prenositelnost:
*/
/* Zalezi na syntaxi prekladaca!
*/
/*
***************************************************************************
***** */

#include<stdio.h>

staticintiCislo,iVysledok;// Niektore verzie potrebujuglobalne premenne

intmain(intargc,char*argv[])
{
iCislo=27;
iVysledok=0;
printf("\nCislo: %d Vysledok: %d",iCislo,iVysledok);

#ifdef __GNUC__ // Tato cast sa preklada len v Dev-C++


(gcc)

asm(".intel_syntaxnoprefix \n"// Prepneme z AT&T syntaxe na na Intel

"moveax,_iCislo \n"// iCislo -> EAX


"inceax \n"// EAX ++
"mov _iVysledok,eax \n"// EAX ->iVysledok

".att_syntax \n");// Damevsetko do povodneho stavu

#elif _MSC_VER // Tato cast sa preklada iba v MS


Visual C++

__asm{// zaciatok bloku asm


MOV EAX,iCislo// do EAX vloz hodnotu premennej iCislo (z pamate)
INC EAX // pripocitaj 1
MOV iVysledok,EAX// do premennej iVysledokvlozvysledok z registra EAX
}// koniec bloku asm

#endif

printf("\nCislo: %d Vysledok: %d",iCislo,iVysledok);

printf("\n\nStlac ENTER a skoncime...");


scanf("?");
return(0);
}

Vysvetlivky k vzorovému
príkladu
Aby sme sa nemuseli príliš detailne zaoberať štruktúrou programu v assembleri, t.j. čo
všetko okrem nášho algoritmu musí obsahovať, aby sa vôbec dal spustiť v danom
operačnom systéme, budeme používať tzv. vložený (embedded) assembler. To znamená,
že to bude len akýsi fragment kódu vložený do štandartného C-programu.

Microsoft Visual C++


Vo Visual C sa vkladá assembler medzi krútené zátvorky uvedené kľúčovým
slovom __asm { } . Nasledujú jednotlivé inštrukcie, prípadne s komentárom. Premenné
z C-programu, môžete používať priamo. Je však potrebné mať na pamäti, že do registrov
môžete presúvať len premenné zodpovedajúcej veľkosti. Nemôžete teda napr. do registra
EAX presunúť premennú typu double. Výsledok práce programu odovzdáte C-programu
tiež jednoducho cez premenné.
Príklad:
__asm { // zaciatok bloku asm
MOV EAX, iCislo // do EAX vloz hodnotu premennej
iCislo (z pamate)
INC EAX // pripocitaj 1
MOV iVysledok,EAX // do premennej iVysledokvlozvysledok
z registra EAX
} // koniec bloku asm

Microsoft Visual C++ Ladenie


Pri práci s assemblerom možno oceníte možnosť spustenia programu krok za krokom,
pričom môžete sledovať zmeny v jednotlivých registroch. Debugovanie sa spustí po
preložení programu z menu postupnosťou Debug>>StartDebug>> Step Into. V programe
sa pohybujete vpred klávesou F10 (Step Over). Vaše okno môže vyzerať napr. takto:
Obrazovka vývojového prostredia Visual C++ pri krokovaní.

DEV-C++ (gcc)
Kompilátor gcc má trocha odlišnú syntax pre vložený assembler. Je to vlastne
funkcia asm ("retazec") , kde samotné inštrukcie musíme písať ako textový reťazec.
Pretože takýto blok sa priamo podsunie prekladaču assembleru, musí vložený kúsok
vyzerať presne rovnako ako zdrojový kód pre assembler. To znamená, že každá
inštrukcia musí končiť \n. Nepríjemná komplikácia je, že gcc používa inú syntax (tzv.
AT&T), dosť odlišnú od Intel syntaxe. Napríklad operandy zdroj, cieľ sa uvádzajú v
opačnom poradí. Našťastie sa to dá prenaučiť príkazom .intel_syntaxnoprefix.
Premenné z C-programu, môžete používať priamo, len musíte pridať znak podtržítko pred
názov premennej. Premenné z C-programu, ktoré chcete používať v assembleri však
musia byť zadefinované ako globálne.
Podobne ako vo Visual C však musíte mať na pamäti, že do registrov môžete presúvať
len premenné zodpovedajúcej veľkosti. Nemôžete teda napr. do registra EAX presunúť
premennú typu double. Výsledok práce programu odovzdáte C-programu tiež jednoducho
cez premenné.
Príklad:
asm(".intel_syntaxnoprefix \n" // Prepneme z AT&T syntaxe na na Intel

"moveax,_iCislo \n" // iCislo -> EAX


"inceax \n" // EAX ++
"mov _iVysledok,eax \n" // EAX ->iVysledok

".att_syntax \n"); // Damevsetko do povodneho stavu

CPUID
Firma Intel po uvedení MMX inštrukcií zaviedla aj jednu novú inštrukciu (cpuid), ktorá
umožnila rozpoznať svoje nové procesory a využívať tak inštrukcie pre ne. Tým sa
umožnila optimalizácia kódu.
Táto jednoduchá asemblerovská inštrukcia umožňuje programátorovi získať mnoho
informácií: výrobcu CPU (napr. Intel, AMD, Cyrix,...), aké "extra" vlastnosti podporuje
(napr. MMX, FPU, 3DNow,...) a ďalšie.
Inštrukcia je k dispozícii od procesorov Pentium vyššie.
Inštrukcia nemá žiadne parametre, pred volaním sa nastaví požadovaná funkcia
zapísaním vhodného čísla do EAX.
Funkcia 0 vracia VendorString, reťazec 12 znakov identifikujúci výrobcu procesora. Prvé
štyri znaky sú v EBX, ďalšie v EDX a posledné štyri v ECX. V registri eax sa vráti
najvyyššia funkcia, ktorú ešte môžeme použiť. (12 chars = 3 x 4 bytes = 3 x 4 x 8 bits =
3 x 32 bits)
Ďalšie funkcie, ktoré dostaneme zapísaním prísušného čísla do EAX pred volaním funkcie
sú:
Funkcia 1 vracia Signature
Funkcia 2 vracia Config
Funkcia 3 SerialNumber
Vyššie čísla sú rezervované.
Podrobnosti nájdete na tejto stránke [niekedy nefunguje], prípadne pozrite
tento popis inštrukcie cpuid [sandpile.org] a samozrejme v manuáloch k
jednotlivým procesorom.
Error C2443: operandsizeconflict
Význnam: inštrukcia vyžaduje, aby oba operandy boli rovnakej veľkosti. Musíte jeden z
operandov zmeniť tak, aby oba mali rovnakú veľkosť.
Táto chyba môže znamenať aj to, že kompilátor NEVIE akú veľkosť máte na mysli, musíte
mu dať pomôcku ktorou presne špecifikujete veľkosť premennej.
Príklad:
charstr[4];

intmain()
{
__asmmovstr, EAX // chyba
__asmmov BYTE PTR str, AL // OK, presunie do str jeden byte
__asmmov WORD PTR str, AX // OK, presunie do str 2 byte
__asmmov DWORD PTR str, EAX // OK, presunie do str 4 byte
}

You might also like