Assembler je jazyk, ve kterém se píší přímo instrukce pro procesor. Překladem vznikne spustitelný kód (např. EXE soubor), ve kterém je Assembler ve formě strojového kódu (binární kód), který vykonává procesor. Protože tvorba programů ve dvojkové (šestnáctkové) soustavě je prakticky nemožná, zavedl se symbolický zápis instrukcí. Možná se zamýšlíte nad tím, proč Assembler používat, když jsou tu vyšší programovací jazyky, jako např. C nebo Pascal. Důvodů je několik. V žádném vyšším programovacím jazyce nelze napsat program tak, aby byl tak rychlý, jako napsaný v Assembleru. Zároveň takový program v Assembleru zabírá nejméně místa. Nespornou výhodou je také to, že máte absolutní kontrolu nad tím, co program dělá. Samozřejmě i Assembler má nějaké nevýhody: kód je velmi dlouhý, těžko se hledají chyby. Protože tvorba složitějších programů jen v Assembleru by byla zdlouhavá, program se vytváří ve vyšším programovacím jazyce, a v Assembleru se píší jen ty jeho části, které se často opakují, jejich tvorba není náročná a potřebujeme je co nejrychlejší. Tyto bloky se nejlépe programují v tzv. vloženém Assembleru.
Obvod Intel 8086 je univerzální šestnáctibitový mikroprocesor. Je schopen provádět operace s šestnáctibitovými čísly. S okolím komunikuje po šestnáctibitové datové a dvacetibitové adresové sběrnici, z čehož vyplývá, že je schopen adresovat 1MB paměti. Od tohoto mikroprocesoru je odvozena spousta novějších, byly přidávány nové instrukce a tím pádem nové vlastnosti a možnosti.
Mikroprocesor 80286 je strukturou i vlastnostmi podobný 8086. Je schopen pracovat ve dvou režimech. V základním reálném téměř přesně simuluje obvod 8086, v chráněném (protected) módu je schopen adresovat paměť i nad 1MB.
Procesor 80386 už je 32-bitový, má zdvojnásobené registry a to mu umožňuje adresovat až 4GB paměti.
Procesor 80486 dokáže spracovávat až 4 instrukce zároveň (fázováním instrukcí - pipelining).
Pentium může zpracovat více instrukcí v jednom cyklu, Pentium MMX má zase zdvojené registry (64-bitové) a multimediální instrukce, další jsou Pentium Pro, Pentium II, Pentium III, Pentium IV.
My si vystačíme s 32-bitovým procesorem 80386, další nové instrukce nebudeme využívat.
Registr je speciální paměťové místo přímo v procesoru. Práce s nimi je tedy mnohem rychlejší než s pamětí. Procesor v nich uchovává hodnoty, s kterými právě pracuje.
Mikroprocesor musí být schopen pracovat i se vstupy-výstupy. Umístění jednotlivých portů určuje šestnáctibitová adresa umístěná nejčastěji v registru DX.
Vkládaný (inline) Assembler je blok v programu psaném v jazyce C (C++). Překladače (nebo vývojová prostředí) se mohou v zápisu bloku Assembleru lišit. V OS M$ Windows budeme programovat ve vývojovém prostředí M$ Visual C++. V tomto prostředí je tento blok uvozen klíčovým slovem _asm (nebo __asm) a je uzavřen do závorek ohraničujících blok { }. Řádky programu ve vkládaném Assembleru nemusí končit středníkem v případě, že na jednom řádku není více jak jedna instrukce (při více jak jedné instrukci musíme instrukce středníkem oddělit). Komentáře se píší stejně jako v C (C++) nebo za středník. V bloku a instrukcích lze použít jména proměnných, funkcí, konstant, typů, aj. z externího C (C++) kódu. Ve vkládaném Assembleru se standardně nerozlišuje velikost písmen.
Velmi užitečný nástroj je Disassembler. Ten nám ukáže všechny instrukce programu, tak, jak je vykonává procesor. Disassembler je tak vlastně opak Assembleru (jako překladače jazyka do binárního kódu stroje), převádí binární kód stroje zpět do symbolického jazyka. V M$ Visual C++ spusťte debugování pomocí F10 a pomocí Alt+8 se zobrazí okno Disassembleru, ve kterém vidíte instrukce programu.
Př. Napište v M$ Visual C++ nějaký jednoduchý program a podívejte se, jak je zapsaný pomocí instrukcí.
Instrukce má tvar: jmeno_instrukce operand1, operand2, ....
Počet operandů bývá 0 až 3.
Každý program musí být schopen přesunů dat a to mezi registry, registry a pamětí, registry a vstupy/výstupy. Při této operaci si musíme vždy uvědomit, kolikabitové číslo přesouváme. Počet bitů je většinou specifikován jménem použitého registru. V případě, že používáme jen paměť, specifikuje počet bitů pro operaci označení:
#include <stdio.h> unsigned char slabika; // v paměti rezervuj 8 bitů unsigned short slovo; // v paměti rezervuj 16 bitů unsigned long dvojslovo; // v paměti rezervuj 32 bitů int main() { _asm { mov al,100 // do registru AL dosaď 8 bitů, hodnotu 100 mov slabika,al // do paměti na místo ozn. slabika dosaď obsah AL mov bx,0x200 // do registru BX (16 bitový) dosaď 0x200 mov slovo,bx // do paměti na místo ozn. slovo dosaď 16 bitů BX mov ecx,0xABCDEF // do registru ECX (32 bitový) dosaď 0xABCDEF mov dvojslovo,ecx // do paměti na místo ozn. dvojslovo dosaď 32 bitů ECX } printf("%u %X %X\n", slabika, slovo, dvojslovo); return 0; }Pozor! Instrukce MOV nikdy nemění hodnoty žádných flagů.
Př. Zkuste si tento první příklad přeložit a spustit.
Místo v paměti označuje hodnota (adresa, přímo nebo v registru), která musí být zapsaná v hranatých závorkách.
Pozor! V jedné istrukci můžete pouze jedinkrát adresovat paměť. To znamená, že např. příkaz a=33; zapíšete normálně jako MOV a,33, ale naopak a=b; takto jednoduše napsat nejde. Byla by zde dvojí adresace v jedné instrukci.
Otázka: Jaké instrukce provedou a=b?
Assembler umožňuje dvě metody adresace.Přímé adresování je takové, kde jsou adresy známé přímo při překladu, tzn. při práci se statickými nebo globálními proměnnými (jazyka C).
MOV AH, [0x1A40] - do registru AH předej 8 bitů z adresy určené číslemstatic short thevar, thevar2; // proměnné thevar a thevar2 jsou v paměti za sebou _asm { mov ax,thevar ;ax=thevar - do registru ax vlož 16-bitovou hodnotu z paměti na místě ozn. thevar mov ax,thevar+2 ;ax=thevar+2=thevar2 - do registru ax vlož 16-bitovou hodnotu z paměti na místě thevar+2=thevar2 }Jméno proměnné se tedy vyhodnocuje jako přímá adresa (protože překladač zná adresu hodnoty proměnné v paměti), je to jen textové označení adresy hodnoty proměnné. Instrukce MOV ax,thevar je přepsána na MOV ax, [adresa_hodnoty_promenne_thevar]. S proměnnými jako adresami ještě můžeme provádět základní aritmetické operace.
_asm { mov ax,thevar ;ax=[adresa_hodnoty_thevar] mov ax,[thevar] ;ax=[[adresa_hodnoty_thevar]]=[adresa_hodnoty_thevar] }Pokud je thevar statická nebo globální proměnná, pak oba tyto příkazy dělají totéž, čili do AX se přiřadí hodnota proměnné thevar.
char *thepointer; // 32-bitová proměnná typu ukazatel na 8-bitovou hodnotu _asm { mov ebx,thepointer ;ebx=thepointer - přímá adresace mov esi,2 ;esi=2 mov al,[ebx+esi] ;al=*(thepointer+2)=thepointer[2] - nepřímá adresace mov ah,thepointer[esi] ;ah=[adresa_hodnoty_thepointer][esi] - ?? }
Otázka: Je poslední příkaz platný? Proč?
Př. Přepište příklad demostrující instrukci MOV (první, kompletní, příklad) tak, aby proměnné byly ukazatele na původní datové typy. Potom jej přepište tak, aby proměnné byly ukazatele na tyto ukazatele. Při obou úpravách samozřejmě musíte upravit i blok Assembleru. Cílem je vyzkoušet si, zda rozumíte přímé a nepřímé adresaci.
#include <stdio.h> unsigned short promenna; int main() { promenna=10; _asm { mov ax, promenna // obsah proměnné dosaď do registru AX mov ebx,0xBBBB // do regisru BX dosaď číslo push ax // ulož obsah AX push ebx // ulož obsah BX mov ax,0xAAA // přepiš obsah AX mov ebx,0xCCCC // přepiš obsah BX pop ebx // obnov obsah BX pop ax // obnov obsah AX mov promenna, ax // vrať obsah AX do proměnné } printf("%u\n", promenna); return 0; }
Na zásobníku jsou uloženy i lokální proměnné procedur a funkcí. Jsou zde i parametry, s kterými je podprogram volaný.
U registrů, které program používal před blokem Assembleru a které používáme v tomto bloku my, už překladač nemůže předpokládat jejich původní hodnoty, protože jsme je mohli přepsat. Proto by jsme správně měli hodnoty všech používaných registrů hned na začátku bloku uložit (na zásobník - PUSH), a na konci bloku pak obnovit (POP). Pozor!. Velikosti uložených dat na zásobník musí přesně odpovídat velikost dat vyjmutých ze zásobníku, jinak se program ve většině případů zhroutí. Prakticky to znamená, že data ze zásobníku vybíráme v přesně opačném pořadí, než v jakém jsme je tam uložili. V M$ Visual C++ můžete ve vkládaném Assembleru libovolně používat registry EAX, EBX, ECX, EDX, ESI, EDI. Překladač sám přidá potřebné instrukce PUSH a POP na začátek a konec bloku.
Otázka: Jakými instrukcemi lze nahradit instrukce PUSH a POP?
Př. V modifikovaném příkladu demostrujícím instrukci MOV uschovejte (a obnovte) všechny používané registry do (ze) zásobníku.