Lekce 14 - Assembler - Registry procesoru
V minulé lekci, Assembler - Výpočty s reálnými čísly, jsme si probrali SSE instrukce pro práci s reálnými čísly.
V dnešním ASM tutoriálu si ukážeme skoro všechny registry procesorů x86 a x64.
Na registry jsme již v našem assembler kurzu narazili a víme, že jsou to "proměnné", napevno vytvořené přímo v procesoru. Jsou velmi rychlé, protože se ušetří cesta mezi procesorem a pamětí RAM. V miniaturním procesoru je samozřejmě také velmi omezené místo, proto je registrů vytvořených jen omezené množství a mají specifická určení. Registry se také výrazně liší podle architektury procesoru.
V dnešním ASM tutoriálu si skoro celé registry CPU konečně zmapujeme. Na začátku kurzu by to pro nás byly příliš vyčerpávající informace, nyní je již však dokážeme využít.
Obecné registry
Při programování nejčastěji pracujeme s obecnými registry (anglicky general-purpose). Do nich lze ukládat celá čísla a nebo adresy (ukazatele do paměti). Používají se při výpočtech, k ukládání dočasných hodnot nebo lokálních proměnných.
16-bitové
V éře MS-DOS a Windows 3.X byly registry pouze 16-bitové. To komplikovalo nejen výpočty s většími čísly, ale hlavně přístupy do paměti, protože 64 KB brzy přestalo aplikacím stačit. V procesoru je těchto 8 obecných registrů:
AX
- akumulátor (akumulační registr)BX
- bázový registrCX
- čítač (counter)DX
- datový registrSI
- index pro zdroj (Source Index)DI
- index pro cíl (Destination Index)BP
- ukazatel na záznam aktivní procedury na zásobníku (Base Pointer)SP
- ukazatel vrcholu zásobníku (Stack Pointer)
Registry BP
a SP
se používají při volání
funkcí. Ostatní registry jsou opravdu obecné a můžeme si do nich ukládat,
co chceme. Jejich pojmenování pak slouží k lepší orientaci v kódu, např.
do AX
uložíme výsledek nějaké aritmetické operace a do
CX
budeme ukládat počítadlo cyklu.
S registry AX
, BX
, CX
, DX
můžeme pracovat také tak, že si je rozdělíme na dva menší osmibitové
registry. Horní poloviny se označují AH
, BH
,
CH
, DH
(High). Dolní poloviny jsou pak
AL
, BL
, CL
, DL
(Low).
Následující příklad ukazuje, která část registru AX
se
změní, pokud zapíšeme hodnotu do AH
nebo AL
:
mov ax, 1122h ;ax=1122h mov al, 33h ;ax=1133h mov ah, 44h ;ax=4433h
32-bitové
Dostáváme se do časů Windows 95 - XP. Při rozšíření všech
16-bitových registrů na 32-bitové byl k názvu přidán prefix E
(Extended) - EAX
, EBX
, ECX
atd. Do
těchto registrů pak můžeme uložit větší čísla. V počítači můžeme
mít až 4 GB paměti.
Když v 32-bitové aplikaci zapíšeme hodnotu do 16-bitového registru, pak se změní dolních 16 bitů z 32-bitového registru, zatímco horních 16 bitů se nezmění. Ukažme si příklad:
mov eax,12345678h ;eax=12345678h mov ax,99aah ;eax=123499aah mov al,bbh ;eax=123499bbh mov ax,cch ;eax=123400cch
64-bitové
Dostáváme se k současným procesorům. V 64-bitových aplikacích máme dvakrát více obecných registrů.
Rozšíření původních registrů
Původních 8 registrů bylo rozšířeno a mají prefix R
-
RAX
, RBX
atd.
Nové registry
K tomu přibyly registry R8
až R15
. K těm lze
přidat sufixy D
, W
a B
pro přístup k
nižším 32
, 16
a 8
bitům. Např.
R8D
, R8W
, R8B
.
V 32-bitových aplikacích jsme měli často nedostatek registrů a lokální proměnné jsme museli ukládat do paměti, což bylo pomalé. V 64-bitových aplikacích máme takové množství registrů, že se do nich vejde většina lokálních proměnných a předávají se v nich i parametry funkcí.
Následující příklad ukazuje, jak se mění registr RAX
,
pokud zapisujeme hodnoty do AH
, AL
, AX
,
EAX
:
mov rax,1111222233334444h ;rax=1111222233334444h mov ah,55h ;rax=1111222233335544h mov al,66h ;rax=1111222233335566h mov ax,7777h ;rax=1111222233337777h mov eax,88888888h ;rax=0000000088888888h mov eax,-1 ;rax=00000000ffffffffh
Všimněte si, že při zápisu 8-bitové nebo 16-bitové hodnoty se vyšší bajty registru nemění. Ale při zapsání 32-bitové hodnoty se ostatní bajty vynulují. To můžeme využívat k optimalizaci. Pokud chceme do 64-bitového registru uložit malé nezáporné číslo, můžeme použít 32-bitový registr.
x87 registry
Při výpočtech s reálnými čísly se používají 80-bitové registry
ST(0)
až ST(7)
. K nim lze přistupovat jako k
zásobníku. To znamená, že při výpočtech se můžeme odkazovat na
konkrétní registry, ale často nám stačí jen přidávat čísla na vrchol
zásobníku a provádět s nimi matematické operace.
MMX registry
Registry MM0
až MM7
jsou synonyma pro
ST(0)
až ST(7)
, ale používají se k výpočtům s
celými čísly. V dnešní době už nemá smysl je používat, protože SSE je
lepší, viz dále.
SSE registry
Do šestnácti 128-bitových registrů XMM0
až
XMM15
můžeme ukládat reálná i celá čísla. Do každého
registru se vejde více čísel najednou (dvě 64-bitová čísla nebo čtyři
32-bitová). Jednou instrukcí tak lze vykonat operaci s několika hodnotami
naráz. To se nazývá SIMD (Single Instruction, Multiple Data). Této
technologie využijeme při práci s vektory nebo zpracování velkých dat.
Získáme tím samozřejmě vyšší výkon. Ve 32-bitových aplikacích je
přístupná jen polovina registrů (XMM0
až
XMM7
).
Kromě datových registrů existuje ještě stavový a řídící registr
MXCSR
.
AVX registry
Novější procesory mají SSE registry rozšířeny na 256 bitů a
nazývají se YMM0
až YMM15
. Ještě novější
procesory mají 512-bitové registry ZMM0
až ZMM15
a
navíc opmask registry K0
až K7
.
Příznaky - Registr EFLAGS
O příznacích jsme si již říkali v lekci Assembler -
Kombinace skoků a příznaky. Mohou mít hodnotu buď 0
nebo
1
a označují nějakou situaci, ke které při vykonávání
instrukcí došlo. Můžeme je následující instrukcí přečíst a na stav
programu reagovat.
Registr EFLAGS
tedy obsahuje několik jednobitových hodnot.
Některé z nich nemůžeme sami v aplikaci měnit a má k nim přístup jen
operační systém. Pro nás jsou nejdůležitější aritmetické příznaky
CF
, ZF
, SF
a OF
, které jsme
si již popsali.
Dnes si ukažme úplnější seznam:
- 0.
CF
(Carry) - přetečení při aritmetické operaci s čísly bez znaménka, nebo při bitových posuvech a rotacích - 2.
PF
(Parity) - sudá parita, dolní osmice bitů výsledku obsahuje sudý počet jedniček - 4.
AF
(Auxiliary Carry) - přetečení mezi 3. a 4. bitem při BCD aritmetice - 6.
ZF
(Zero) - nulový výsledek - 7.
SF
(Sign) - záporný výsledek - 8.
TF
(Trap) - krokování po instrukcích při ladění - 9.
IF
(Interruption) - povolená hardwarová přerušení - 10.
DF
(Direction) - řetězcové instrukce v opačném směru (např.MOVSB
snižujeSI
,DI
) - 11.
OF
(Overflow) - přetečení při aritmetické operaci s čísly se znaménkem - 12-13.
IOPL
(I/O Privilege Level) - úroveň oprávnění pro instrukceIN
/OUT
- 14.
NT
(Nested Task) - instrukceIRET
při návratu z přerušení přepne na další proces - 16.
RF
(Resume) - maskuje opakování ladícího přerušení - 17.
VM
(Virtual-8086 Mode) - virtualizace 16-bitových aplikací ve 32-bitovém operačním systému - 18.
AC
(Alignment Check) - kontrola zarovnání, vyvolá přerušení při přístupu na adresu, která není dělitelná délkou hodnoty - 19.
VIF
(Virtual Interrupt) - příznakIF
při virtualizaci - 20.
VIP
(Virtual Interrupt Pending) - bylo vyvoláno přerušení při virtualizaci - 21.
ID
(Identification) - pokud lze nastavit, je podporována instrukceCPUID
Segmentové registry
Segmenty se používaly v 16-bitových aplikacích. Paměť byla rozdělena na segmenty o velikostech 64 KB:
CS
- segment kóduDS
- datový segmentES
- extra segmentFS
- další extra segmentGS
- další extra segmentSS
- segment zásobníku (stack)
Pokud aplikace potřebovala více paměti, musela si segmenty přepínat. Ve
32-bitových aplikacích jsou segmenty zcela zbytečné, protože aplikace
může přistupovat až ke 4 GB paměti. V 64-bitových aplikacích pak byly
úplně zrušeny segmenty CS
, DS
, ES
,
SS
. Zůstaly pouze segmenty FS
a GS
,
protože operační systémy je používají k ukládání dat pro běžící
vlákno (thread local storage) nebo k informacím o výjimkách
(exceptions).
Registr IP
Instruction Pointer ukazuje na právě vykonávanou instrukci. Hodnotu tohoto
registru měníme například instrukcemi skoku (JMP
,
CALL
a RET
). V 64-bitových aplikacích můžeme
používat registr RIP
k relativnímu adresování.
Debug registry
Při ladění můžeme nastavovat až 4 datové breakpointy na proměnné.
Program se pak zastaví, když se proměnná změní. V registrech
DR0
, DR1
, DR2
, DR3
jsou
adresy breakpointů. Podle stavového registru DR6
se určí,
který z těch 4 breakpointů byl vyvolán. V kontrolním registru
DR7
se nastavuje, které breakpointy jsou aktivní, jestli jsou pro
zápis nebo pro čtení a kolik bajtů proměnná zabírá.
Kontrolní registry
Registry CR0
, CR2
, CR3
,
CR4
, CR8
používá operační systém při
virtualizaci, stránkování, nastavení zabezpečení nebo přepínání
různých režimů procesoru. Jejich význam je nad rámec této lekce.
V příští lekci, Assembler - Rejstřík instrukcí procesoru, si ukážeme stručný přehled nejdůležitějších instrukcí.