<docere>http://www.docere.ro/ |

Investigarea setului de instrucţiuni CPU
Instrucţiunile pot fi împărţite în mai multe tipuri; într-un acelaşi grup, variază tipul operanzilor, sau modul de accesare a acestora. Codul de instrucţiune trebuie să reflecte categoria instrucţiunii si să precizeze, într-un mod sau altul, operanzii necesari. Tipul de instrucţiune este codificat în cadrul primului octet, sau uneori şi în cadrul celui de-al doilea octet; biţii rămaşi disponibili din primul şi al doilea octet, precum şi restul de octeţi (din secvenţa recunoscută în ansamblul ei, drept instrucţiune) furnizează de regulă informaţiile necesare privind tipul operanzilor şi locul unde se află aceştia, sau eventual chiar valorile lor.
Să considerăm de exemplu, operaţia de a înregistra în stivă conţinutul unui registru. I80x86 dispune în acest scop de instrucţiuni codificate pe câte un singur octet, în cadrul căruia se reprezintă atât tipul operaţiei realizate - push: se descreşte SP cu 2 şi la adresa SS:SP rezultată se copiază operandul - cât şi registrul care conţine valoarea de depus în vârful stivei:
0 1 0 1 0 b2 b1 b0 tipul operaţiei: PUSH = 010102 operand: registrul codificat pe ultimii 3 biţi din octet
Valorile posibile ale acestui octet sunt 0x50 (când b2b1b0 = "000"), 0x51, ..., 0x57 (când b2b1b0 = "111") - deci această categorie cuprinde 8 instrucţiuni. Putem angaja programul debug din Windows, pentru a constata că formatul instrucţiunilor PUSH cu operand registru se reduce la octetul de cod prezentat mai sus şi pentru a determina codurile de 3 biţi ale regiştrilor: trebuie să introducem în memorie secvenţa de octeţi 0x50, 0x51, ..., 0x57 (folosind comanda E, "Enter") şi să dezasamblăm apoi zona respectivă (comanda U, "Unassemble"):

Vedem astfel corespondenţa următoare:
| adresa | conţinut | instrucţiune | b2b1b0 | registrul |
|---|---|---|---|---|
| 135E:0100 | 50 | push AX | 000 | AX |
| 135E:0101 | 51 | push CX | 001 | CX |
| 135E:0102 | 52 | push DX | 010 | DX |
| 135E:0103 | 53 | push BX | 011 | BX |
| 135E:0104 | 54 | push SP | 100 | SP |
| 135E:0105 | 55 | push BP | 101 | BP |
| 135E:0106 | 56 | push SI | 110 | SI |
| 135E:0107 | 57 | push DI | 111 | DI |
Se confirmă astfel că formatul instrucţiunilor PUSH registru constă dintr-un singur octet, în care primii 5 biţi "01010" indică operaţia respectivă (depunere pe stivă a conţinutului unui registru de 16 biţi care nu este un registru de segment), iar ultimii 3 biţi corespund operandului; deducem si codificarea regiştrilor AX, CX, DX, BX, SP, BP, SI, DI (8 regiştri, codificaţi sau adresaţi prin "000", "001", ..., "111").
Dacă este cazul să studiem şi efectul instrucţiunii, putem asambla (comanda A, "Assemble") o instrucţiune de încărcare în AX şi una PUSH AX, folosind apoi comanda T "Trace" şi eventual D "Dump":

După prima comandă t, vedem că AX a fost încărcat cu 0x1234 şi SP cu 0xFFEE; după următorul t (executând PUSH AX de la adresa CS:0x0103) vedem că SP a coborât cu 2 (SP = 0xFFEC); după execuţia celui de-al doilea PUSH rezultă SP = 0xFFEA. Prin d SS:ffea, vedem conţinutul stivei de la offsetul 0xFFEA: două secvenţe de doi octeţi 0x34, 0x12 (reprezentând "little-endian" valoarea 0x1234 din AX).
Pentru a găsi codul-maşină asociat instrucţiunilor prin care se depune pe stivă conţinutul unui registru de segment, putem proceda invers faţă de maniera în care ne-am ocupat mai înainte de PUSH AX: asamblăm (folosind A) instrucţiunile corespunzătoare (push DS, push CS, etc.) şi apoi dezasamblăm (comanda U) zona instrucţiunilor introduse. Găsim astfel, următoarea corespondenţă:
instrucţiune cod în binar
push ES 0x06 00000110
push CS 0x0E 00001110
push SS 0x16 00010110
push DS 0x1E 00011110
Comparând reprezentările binare ale codurilor, deducem că formatul instrucţiunilor PUSH pentru cazul când operandul este într-un registru de segment constă dintr-un octet cu şablonul binar 000b4b3110; rezultă totodată codificarea asociată regiştrilor de segment ES "Extra Segment", CS "Code Segment", SS "Stack Segment" şi DS "Data Segment".
Unele instrucţiuni au efect numai asupra operandului destinaţie; de exemplu, instrucţiunea mov AX, BX determină înlocuirea conţinutului existent în AX cu acela din BX - fără a modifica "sursa" BX şi fără nici un alt efect secundar (în particular, nu este afectat nici într-un fel registrul flagurilor).
Să considerăm însă o instrucţiune aritmetică, de exemplu v := v + 100 în Pascal, sau v += 100 în C şi analog, ADD AL, 100 în limbajul de asamblare; în principiu, se adună valoarea 100 cu valoarea existentă în variabila v şi respectiv în registrul AL şi rezultatul adunării devine noua valoare din v şi respectiv din AL.
De data aceasta, efectuarea operaţiei trebuie să ţină seama de tipul destinaţiei: dacă v este de tip byte (ca şi AL) şi valoarea iniţială din v este 200,atunci rezultatul ar fi 200 + 100 = 300 - valoare care nu aparţine tipului byte (nu "încape" pe un octet); se va reţine atunci în v (în AL) octetul low al valorii 300 = 1*28 + 44 (deci x <— 44, respectiv AL <— 44).
La nivelul "high" al limbajului (în Pascal, de exemplu) nu apar alte efecte (decât trunchierea menţionată, sau eventual întreruperea execuţiei prin Error: Range check error); în schimb, ADD AL, 100 (la nivelul de limbaj "low") va fi totdeauna executată, posibil cu trunchierea la octetul low al valorii-rezultat; în plus, bitul Cf "Carry flag" (CY în notaţia din debug) din registrul indicatorilor de stare (registrul flagurilor) va fi setat (comutat pe valoarea 1) dacă rezultatul operaţiei depăşeşte capacitatea registrului destinaţie:

S-a asamblat instrucţiunea add AL, 64 (avem 0x64 = 6*16 + 4 = 100); apoi s-a vizualizat conţinutul registrului AX (r AX) şi s-a fixat în AX valoarea 0x64; apoi s-a executat de două ori succesiv, instrucţiunea de la adresa CS:0x100.
După primul t (prima execuţie) vedem că AX = 0x00C8 (adică AL = 0xC8), ca efect al operaţiei 100 + 100 = 200 = 0xC8; iar flagul Carry este resetat (= 0, ceea ce debug indică prin "NC"—Not Carry) - ceea ce înseamnă că nu s-a produs, prin operaţia executată, depăşirea capacităţii registrului destinaţie AL. Mai vedem de exemplu, că indicatorul de zero ("NZ"—Not Zero în debug) este şi el resetat, semnificând că rezultatul operaţiei nu a devenit zero.
La repetarea executării instrucţiunii (prin t = 100, nu prin t care ar fi pus în execuţie următoarea instrucţiune, de la offsetul IP = 0x0102) - deci după ce AL conţine valoarea 200 - vedem că a rezultat AL = 0x2C = 44 (= 300 mod 28) şi flagul Carry a fost setat, semnalând depăşirea capacităţii destinaţiei AL (registrul AH rămâne nemodificat, bitul de depăşire fiind "preluat" de Carry şi nu de "prelungirea" AH a lui AL).
Ţinând seama de principiul firesc, ca toate instrucţiunile dintr-o aceeaşi categorie (aceeaşi operaţie şi acelaşi tip de operand) să fie executate într-un acelaşi mod — putem conchide numai pe baza singurului exemplu analizat mai sus, că efectul instrucţiunilor ADD reg8, _octet unde reg8 este oricare registru de 8 biţi, iar _octet este orice valoare de 8 biţi, constă în:
reg8 <— (reg8 + _octet) MOD 256, Cf <— (reg8 + _octet) DIV 256 ∊ {0, 1} Zf <— 1 ⇔ reg8 + _octet = 28 = 10256 (pot fi afectate şi alte flaguri)
debug poate fi utilizat astfel - cum se vede pe exemplele redate mai sus - pentru a investiga toate categoriile de instrucţiuni ale microprocesorului Intel 8086. Este adevărat că debug nu recunoaşte mnemonicele de instrucţiuni (şi de regiştri extinşi) ulterioare lui I8086 - de exemplu introducând sub comanda a mov EBX, 12345678 se obţine "Error" (registrul extins "EBX" nu este recunoscut).
Dar să încercăm o distincţie: debug nu recunoaşte instrucţiunea în limbaj de asamblare (şi deci n-o poate "converti" în cod-maşină), dar pune în execuţie orice cod-maşină recunoscut de către microprocesor! Ar fi suficient să cunoaştem modalitatea prin care se face distincţia între regiştrii obişnuiţi şi cei extinşi (pe care nu-i recunoaşte), pentru a putea introduce instrucţiunile nu în limbaj de asamblare (că nu ar fi recunoscute), ci direct prin octeţii de cod-maşină corespunzători - putând astfel studia cu debug şi instrucţiuni specifice microprocesoarelor ulterioare lui I8086.
Am evidenţiat anterior (în lecţiile precedente) că INTEL a adoptat consecvent principiul compatibilităţii în jos; trecerea la un nou microprocesor (inclusiv extinderea regiştrilor) a însemnat o completare (şi nu o structură complet nouă) a setului de instrucţiuni. Aşa se face că diferenţa dintre instrucţiunile I8086 şi cele care ar angaja regiştrii extinşi se face pur şi simplu prin prefixarea codului instrucţiunii cu un octet cu valoarea constantă 0x66, indicând că operandul respectiv este de tip dword (pe 32 de biţi); de exemplu, am văzut mai sus că push BX are codul 0x53 - atunci, push EBX are codul de doi octeţi: 0x66, 0x53.
Următoarea imagine demonstrează valabilitatea distincţiei sugerate mai sus, arătând că debug se poate folosi şi pentru a investiga instrucţiuni ulterioare lui I8086.

debug nu a recunoscut EBX, când s-a încercat asamblarea instrucţiunii mov EBX, 0x12345678. Introducând totuşi codul instrucţiunii: 66 BB 78 56 34 12, vedem că t o pune în execuţie (în BX apare valoarea 0x5678; în EBX ar trebui să avem 0x12345678). Am introdus apoi şi codul 66 53 pentru push EBX, încât valoarea 0x12345678 ar trebui să fie depusă pe stivă; am adăugat instrucţiunile pop DX şi pop AX, pentru a descărca de pe stivă în DX şi AX. Vedem după execuţia ultimelor trei instrucţiuni (prin t 3) că DX a preluat de pe stivă valoarea 0x5678, iar AX a preluat 0x1234 - ceea ce arată că valoarea salvată pe stivă a fost într-adevăr, aceea care s-a dorit iniţial să fie încărcată în registrul extins EBX. Deci instrucţiunea "nerecunoscută" mov EBX, 0x12345678 a fost executată, trebuind doar să fie introdusă prin codul ei (nu prin mnemonice).
De fapt, acest comportament (nu înţelege instrucţiunea, dar o execută) chiar n-ar trebui să surprindă… (nu întâlnim mereu această situaţie?). Comenzile a şi t sunt independente; debug asamblează instrucţiunea dată în limbaj de asamblare dacă o recunoaşte (pe baza tabelelor proprii de translatare); în schimb, t pune în execuţie codul indicat de CS:IP şi nu debug execută codul, ci microprocesorul (nu debug trebuie să recunoască acel cod, ci microprocesorul).
ORAR orarul şcolii
SitSco situaţie şcolară
ŞAH prin corespondenţă
doChess a Javascript chess engine
doPGN a Javascript PGN-browser
Cal++ ambiţiile Calului
aşaAzis momente lingvistice
Comentarii
—cum ar trebui calculată Media şcolară?
completely rethink the browser:
Google chrome