Assembler comenzi carte de referință intel 8086. Programare în limbaj de asamblare pentru începători și multe altele

Primele microprocesoare au fost dispozitive pe 4 biți. Aceasta înseamnă că ar putea procesa doar patru biți de informații odată. Pentru a procesa mai mult de patru biți, au trebuit să efectueze mai multe operații secvențiale. Desigur, acest lucru a încetinit munca.

Primul microprocesor industrial de 8 biți (procesează 8 biți de informații simultan) a fost microprocesorul 8008, lansat de Intel în 1972. Este considerat cel mai bun microprocesor pe 8 biți din prima generație. În arhitectura sa, acest microprocesor este similar cu un calculator; are un acumulator, șase registre de uz general, un indicator de stivă (un registru special de adresă a celulei de lucru), opt registre de adrese și instrucțiuni speciale pentru introducerea și ieșirea datelor. În 1973, Intel a lansat o versiune de a doua generație a microprocesorului 8008, numită 8080.

În comparație cu microprocesorul 8008, microprocesorul 8080 putea să se adreseze mai multă memorie, avea capacități I/O extinse, avea instrucțiuni suplimentare și era mai rapid. Deși ideile arhitecturii microprocesorului 8008 au fost în mare măsură transferate de Intel către microprocesorul 8080, organizarea sa internă a fost îmbunătățită atât de mult încât microprocesorul 8080 a devenit standardul de facto pentru microprocesoarele din a doua generație; iar când vine vorba de microprocesoare, primul lucru care vine în minte pentru mulți oameni este microprocesorul 8080.

Progresele tehnologice au permis Intel să lanseze o versiune îmbunătățită a microprocesorului 8080, numită 8085, în 1976. Acesta diferă de microprocesorul 8080 în designul carcasei, avea o resetare la starea inițială (pentru a inițializa microprocesorul), vector întreruperi (pentru întreținerea dispozitivelor periferice), un port serial de intrare - ieșire (pentru conectarea imprimantelor și a altor dispozitive periferice). În plus, a necesitat doar o sursă de alimentare de +5V (microprocesorul 8080 necesita două surse de alimentare).

Până la lansarea microprocesorului 8085, Intel se confrunta cu o concurență serioasă pe piața microprocesoarelor pe 8 biți. Microprocesorul Z80 de la Zilog Corporation, o îmbunătățire a microprocesorului 8080, a început să se bucure de succes, la fel ca microprocesoarele 6800 de la Motorola și 6502 de la MOS Technology (acum Commodore), care diferă semnificativ de 8080 în arhitectura lor. Și, în loc să continue să se lupte pe piața aglomerată a microprocesoarelor de 8 biți, Intel a făcut un salt cuantic înainte și a lansat microprocesorul 8086 pe 16 biți în 1978, care ar putea procesa datele de 10 ori mai rapid decât microprocesorul 8080.

Microprocesorul 8086 este software compatibil cu microprocesorul 8080 la nivel de limbaj de asamblare. Aceasta înseamnă că, cu unele modificări minore, programele scrise pentru microprocesorul 8080 pot fi retraduse și executate pe microprocesorul 8086. Această compatibilitate este asigurată de faptul că registrele și setul de instrucțiuni ale microprocesorului 8080 sunt subseturi ale registrelor și instrucțiunilor microprocesorului 8086. Acest lucru a permis Intel să profite de experiența vastă a microprocesorului 8080 și să obțină un avantaj în aplicații mai complexe.

Deoarece mulți designeri încă preferau să folosească cipuri auxiliare și periferice de 8 biți mai ieftine în sistemele lor pe 16 biți, Intel a lansat o versiune a microprocesorului 8086 care avea același dispozitiv în interior, dar o magistrală de date pe 8 biți la exterior. Această versiune (microprocesor 8088) este identică cu 8086, dar transferul datelor pe 16 biți durează mai mult, ceea ce se realizează folosind două transferuri consecutive de 8 biți. Cu toate acestea, în aplicațiile care procesează în principal valori de 8 biți, 8088 funcționează cu 10% din 8086.

Astfel, puteți presupune că computerul dumneavoastră personal IBM are un microprocesor pe 16 biți. (Și, prin urmare, puteți folosi numeroasele literaturi despre microprocesorul 8086.) Să încheiem această introducere și să trecem la discutarea capacităților microprocesorului 8088.

Pentru a scrie programe în limbaj de asamblare, trebuie să știm ce registre de procesor există și cum pot fi utilizate. Toate procesoarele x86 (chiar și cele multi-core, mari și complexe) sunt descendenți îndepărtați ai vechiului Intel 8086 și sunt compatibile cu arhitectura acestuia. Aceasta înseamnă că programele scrise în limbajul de asamblare 8086 vor funcționa pe toate procesoarele x86 moderne.

Toate registrele interne ale procesorului Intel 8086 sunt pe 16 biți:

În total, procesorul conține 12 registre accesibile prin software, precum și un registru de steaguri (FLAGS) și un pointer de instrucțiuni (IP).

registre de uz general (RON) AX, BX, CX și DX sunt folosite pentru a stoca date și pentru a efectua diverse operații aritmetice și logice. În plus, fiecare dintre aceste registre este împărțit în 2 părți de 8 biți, care pot fi lucrate ca registre de 8 biți (AH, AL, BH, BL, CH, CL, DH, DL). Părțile inferioare ale registrelor au litera L în numele lor (din cuvântul Scăzut), iar cele mai vechi sunt H (din cuvânt Înalt). Unele instrucțiuni folosesc implicit un anumit registru, de exemplu, CX poate acționa ca un numărător de bucle.

Registrele indexate sunt destinate stocării de indici atunci când lucrați cu matrice. SI ( Index sursă) conține indexul sursei și DI ( Index de destinație) este indexul de destinație, deși pot fi utilizate și ca registre de uz general.

Registre pointer BP și SP sunt folosite pentru a lucra cu stiva. BP ( Indicator de bază) vă permite să lucrați cu variabile din stivă. Poate fi folosit și în alte scopuri. SP ( Stack Pointer) indică în partea de sus a stivei. Este folosit de comenzile care lucrează cu stiva. (Voi vorbi despre stiva în detaliu într-o parte separată a cursului de formare)

Registre de segmente CS ( Segment de cod), DS ( Segment de date), SS ( Segment de stivă) și ES ( Segment îmbunătățit) sunt concepute pentru a oferi adresarea segmentelor. Codul este în segmentul de cod, datele sunt în segmentul de date, stiva este în segmentul de stivă și există un segment de date suplimentar. Adresa fizică reală se obține prin deplasarea conținutului registrului de segment cu 4 biți la stânga și adăugarea unui offset (adresa relativă în cadrul segmentului). Adresarea segmentelor este discutată mai detaliat în Partea 31.

Un program COM rezidă întotdeauna într-un singur segment, care este simultan un cod, date și un segment de stivă. Când rulați un program COM, registrele de segmente vor conține aceleași valori.

Index de comandă IP ( Indicator de instrucțiuni) conține adresa comenzii (în segmentul de cod). Nu-i puteți modifica direct conținutul, dar procesorul o face singur. La executarea comenzilor obișnuite, valoarea IP este mărită cu dimensiunea comenzii executate. Există, de asemenea, instrucțiuni de transfer de control care modifică valoarea IP pentru a face tranziții în cadrul programului.

Registrul steagului FLAGS conține biți separați: steaguri de control și steaguri de rezultat. Steagurile de control modifică modul de operare al procesorului:

  • D ( Direcţie) - steag de direcție. Controlează direcția de procesare a liniilor de date: DF=0 - de la adresele joase la cele mai mari, DF=1 - de la adresele înalte la cele mai mici (pentru comenzi șiruri speciale).
  • eu ( Întrerupe) - steag de întrerupere. Dacă valoarea acestui bit este 1, atunci întreruperile sunt activate, în caz contrar sunt dezactivate.
  • T ( Capcană) - steag de urmărire. Folosit de depanator pentru a executa programul pas cu pas.

Atributele rezultatelor sunt setate după executarea comenzilor aritmetice și logice:

  • S ( Semn) - semnul rezultatului, egal cu bitul semn al rezultatului operației. Dacă este egal cu 1, atunci rezultatul este negativ.
  • Z ( Zero) - indicatorul rezultat zero. ZF=1 dacă rezultatul este zero.
  • P( Paritate) - un semn de uniformitate a rezultatului.
  • C ( Transporta) - poartă steagul. CF=1, dacă în timpul adunării/scăderii există o purtare/împrumut de la cea mai mare cifră. În timpul schimburilor, stochează valoarea bitului care este mutat.
  • A ( Auxiliar) - steag de transport suplimentar. Folosit în operațiuni cu numere zecimale binare împachetate.
  • O( Revărsare) - steag de preaplin. CF=1 dacă rezultatul este în afara intervalului acceptabil de valori.

Nu vă faceți griji dacă ceva pare neclar. Din explicația ulterioară, va deveni clar ce este ce și cum să folosiți toate acestea :)


Emu8086 este un program plătit. Cu toate acestea, îl puteți folosi gratuit pentru probă timp de 30 de zile.

Deci, ați descărcat și instalat programul Emu8086 pe computer. Îl lansăm și creăm un nou fișier prin meniul FILE – NEW – COM TEMPLATE (Fișier – New – COM File Template). În editorul de cod sursă, după aceasta, vom vedea următoarele:

Orez. 1.1. Crearea unui fișier nou în Emu8086.

Trebuie remarcat aici că programele create folosind asamblori pentru computere care rulează Windows sunt de două tipuri: COM și EXE. Ne vom uita la diferențele dintre aceste fișiere mai târziu, dar deocamdată este suficient să știți că pentru prima dată vom crea fișiere executabile cu extensia COM, deoarece sunt mai simple.

După crearea unui fișier în Emu8086 folosind metoda descrisă mai sus, în editorul de cod sursă veți vedea linia „adăugați codul dvs. auzi” - „adăugați codul aici” (Fig. 1.1). Ștergem această linie și inserăm următorul text în locul ei:

MOV AH, 02h MOV DL, 41h INT 21h INT 20h Astfel, textul integral al programului va arăta astfel: ORG 100h MOV AH, 02h MOV DL, 41h INT 21h INT 20h RET În plus, există și comentarii în partea de sus (în Fig. 1.1 – acesta este textul verde). Un comentariu în limbajul asamblare începe cu un simbol; (punct și virgulă) și continuă până la sfârșitul rândului. Dacă nu știi ce sunt comentariile și de ce sunt necesare, vezi cartea Cum să devii programator. După cum am spus deja, aici nu vom explica elementele de bază ale programării, deoarece cartea pe care o citiți acum este destinată persoanelor familiarizate cu elementele de bază ale programării.

De asemenea, rețineți că cazul caracterelor în limbajul de asamblare nu contează. Puteți scrie RET, ret sau Ret - va fi aceeași comandă.

Puteți salva acest fișier undeva pe disc. Dar nu trebuie să-l salvezi. Pentru a executa programul, apăsați butonul EMULATE (cu triunghiul verde) sau tasta F5. Se vor deschide două ferestre: fereastra emulator și fereastra cod sursă (Fig. 1.2).

Orez. 1.2. Fereastra emulatorului Emu8086.

Fereastra emulatorului afișează registre și conține butoane de control al programului. Fereastra Sursă afișează codul sursă al programului dvs., evidențiind linia care se execută în prezent. Toate acestea sunt foarte convenabile pentru studierea și depanarea programelor. Dar încă nu avem nevoie de asta.

În fereastra emulatorului, puteți rula programul pentru a fi executat în întregime (butonul RUN) sau în modul pas cu pas (butonul SINGLE STEP). Modul pas cu pas este convenabil pentru depanare. Ei bine, acum vom lansa programul pentru execuție folosind butonul RUN. După aceasta (dacă nu ați făcut nicio greșeală în textul programului), veți vedea un mesaj despre sfârșitul programului (Fig. 1.3). Aici sunteți informat că programul a transferat controlul către sistemul de operare, adică programul a fost finalizat cu succes. Faceți clic pe OK în această fereastră și veți vedea în sfârșit rezultatul primului program în limbaj de asamblare (Fig. 1.4).

Orez. 1.3. Mesaj de finalizare a programului.

Orez. 1.4. Primul tău program este finalizat.

După cum am spus deja, primul nostru program afișează litera engleză „A” pe ecran. Rezultatul a îndeplinit așteptările noastre - litera „A” a fost afișată pe ecran.

Este de remarcat aici că Emu8086 este un EMULATOR, adică emulează funcționarea unui computer cu procesor 8086. Prin urmare, în exemplul descris mai sus, programul este executat nu de sistemul de operare, ci de emulator. Emu8086 poate crea și programe reale care pot fi executate independent pe un computer. Dar o descriere a lucrului cu Emu8086 nu este inclusă în planurile noastre. Citiți ajutorul și experimentați - totul va funcționa pentru dvs.

În cazul nostru, nu contează modul în care este executat programul – de către un emulator sau un sistem de operare. Principalul lucru este să înțelegeți problema creării de programe în limbaj de asamblare. Prin urmare, să analizăm programul nostru simplu în detaliu.

#make_COM#– prima linie. Această comandă este specifică lui Emu8086. Este folosit pentru a determina tipul de fișier creat. În cazul nostru, acesta este un fișier cu extensia .COM.

ORG 100h– a doua linie. Această comandă setează contorul programului la 100h, deoarece atunci când un fișier COM este încărcat în memorie, DOS alocă primii 256 de octeți (zecimalul 256 este egal cu hex 100) blocului de date PSP. Codul programului se află numai după acest bloc. Toate programele care sunt compilate în fișiere de tip COM trebuie să înceapă cu această directivă.

MOV AH, 02h– a treia linie. Instrucțiunea (sau comanda) MOV plasează valoarea celui de-al doilea operand în primul operand. Adică valoarea 02h este plasată în registrul AN. De ce se face asta? 02h este o funcție DOS care afișează un caracter pe ecran. Scriem un program pentru DOS, așa că folosim comenzile acestui sistem de operare (OS). Și scriem această funcție (sau mai degrabă numărul ei) în registrul AH, deoarece întreruperea 21h folosește acest registru.

MOV DL, 41h– a 4-a linie. Codul caracterului „A” este introdus în registrul DL. Codul ASCII pentru caracterul „A” este 41h.

INT 21h– a 5-a linie. Aceasta este aceeași întrerupere 21h - o comandă care apelează funcția de sistem DOS specificată în registrul AH (în exemplul nostru aceasta este funcția 02h). Comanda INT 21h este principalul mijloc de interacțiune între programe și OS.

INT 20h– a 6-a linie. Aceasta este o întrerupere care îi spune sistemului de operare să iasă din program și să transfere controlul către aplicația consolă. Aceasta înseamnă că atunci când utilizați INT 20h în exemplul nostru, controlul va fi transferat în programul Emu8086. Și dacă programul a fost deja compilat și lansat din sistemul de operare, atunci comanda INT 20h ne va întoarce la sistemul de operare (de exemplu, la DOS). În principiu, în cazul lui Emu8086, această comandă ar putea fi omisă, deoarece aceeași funcție este realizată de comanda RET, care este inserată automat în textul sursă la crearea unui fișier nou folosind un șablon (cum am făcut mai devreme). Dar am decis să folosesc și aici INT 20h pentru compatibilitate cu alți asamblatori.

Pentru cei care nu înțeleg totul din aceste explicații, recomand să citească cartea Cum să devii programator, precum și următoarele capitole.

LIMBAJ DE ASAMBLAREMICROPROCESOR 8088

Limbajul de asamblare este limbajul mașinii scris sub formă simbolică. În timp ce adresele și instrucțiunile în limbajul mașinii sunt numere, limbajul de asamblare permite adreselor de memorie să li se atribuie nume de litere unice. Asa de Deoarece actuatoarele microprocesoarelor 8086 și 8088 sunt identice, limbajul de asamblare 8088 se dovedește, de asemenea, a fi identic cu limbajul de asamblare 8086 și face parte din limbajul de asamblare al microprocesoarelor 80186 și 80286, care sunt modificări îmbunătățite ale procesorului 8086.

Un program scris în limbajul de asamblare 8088 poate fi tradus (tradus) în limbajul mașinii folosind un program numit asamblator. Asamblatorul descris în această carte este un macro-asamblator Microsoft utilizat în computerul personal IBM cu sistemul de operare pe disc PC-DOS.

Există și alte versiuni de asamblare care sunt acceptabile pentru familia de microprocesoare 8086/8088, cum ar fi asamblatorul IASM-86 dezvoltat de Intel Corporation, care (include instrucțiuni mnemonice standard, inclusiv instrucțiuni aritmetice pentru numerele în virgulă mobilă în procesorul 8087. Deși sunt disponibile diferențele dintre acești asamblatori, structurile lor și formatele de comandă ale limbilor sunt în mare măsură compatibile.

De ce este necesar limbajul de asamblare?

Este foarte incomod, plictisitor și uneori dificil să scrii programe direct în limbajul mașinii. În acest caz, programatorul trebuie să efectueze o mulțime de operațiuni minuțioase și de rutină: să monitorizeze distribuția celulelor în memoria microcalculatorului pentru înregistrarea comenzilor sau a datelor de program, astfel încât aceste adrese să poată fi apoi specificate sau determinate în program; amintiți-vă instrucțiunile reale ale mașinii, reprezentate în cod binar sau hexazecimal și metodele de micro-adresare asociate


procesor; toate datele dintr-un program trebuie convertite în cod binar, astfel încât să poată fi incluse într-un program în limbaj mașină. Evident, ca urmare a unei astfel de lucrări, pot fi făcute multe greșeli.

Limbajul de asamblare este o modalitate de scriere simbolică a programelor în limbajul mașinii. De exemplu, codul de instrucțiune al mașinii ZS02 (hexazecimal) poate fi scris sub formă simbolică în limbaj de asamblare ca CMP AL,02 (această instrucțiune compară conținutul registrului AL cu numărul hexazecimal 02). Astfel, sunt posibile conversiile de rutină ale codurilor de mașină și ale datelor de program enumerate mai sus. transferați cât mai mult posibil în programul de asamblare. Prin urmare, scopul principal al asamblatorului este acela de a traduce programe scrise în limbaj de asamblare în limbaj mașină (coduri de mașină).

Asamblatorul ține evidența locațiilor de memorie utilizate, astfel încât instrucțiunile din program să facă referire la aceste adrese în mod simbolic, folosind nume și etichete care înlocuiesc valorile reale. De exemplu, codul mașinii în notație hexazecimală C606001201 poate fi scris în limbaj de asamblare ca MOV BYTE-COUNT,01, unde BYTE-COUNT este numele simbolic al adresei de memorie 1200H.

Asamblatorul permite reprezentarea simbolică a constantelor și datelor programului și realizează toate conversiile necesare ale acestora în codurile binare corespunzătoare, calculează numărul corect de octeți sau cuvinte care trebuie stocate și alcătuiește un tabel cu nume sau etichete și adresele corespunzătoare.

În limbajul de asamblare, un programator poate scrie o procedură (subrutină) ca un modul independent și complet separat. Asamblatorul generează informațiile necesare, astfel încât atunci când un modul obiect de procedură este încărcat în memoria microprocesorului, acesta poate fi combinat cu alte module conținute în biblioteci de programe separate.

Schimbarea programelor scrise în limbaj de asamblare este mult mai ușoară decât schimbarea programelor scrise direct în limbajul mașină. De exemplu, inserarea comenzilor se poate modifica




plasarea adreselor operanzilor sau datelor programului în memorie după punctul de inserare. Prin urmare, dacă programul este scris în codul mașinii, programatorul trebuie să schimbe adresele tuturor operanzilor și datelor programului aflate după comanda introdusă. Aceasta este o procedură foarte obositoare și consumatoare de timp, ca să nu mai vorbim de faptul că este predispusă la erori. Dacă inserarea este efectuată într-un program scris în limbaj de asamblare, atunci renumerotarea tuturor adreselor operanzilor din memorie se realizează automat în conformitate cu noua structură și plasarea datelor în programul modificat.

Asamblatorul poate produce o listă de program sursă (text) care conține instrucțiunile limbajului de asamblare original, limbajul mașinii asociat și codurile de date, rezultate de diagnosticare și/sau mesaje de eroare care sunt generate în timpul procesului de traducere. Prin urmare, multe erori de software pot fi detectate în avans în etapa de traducere. O listă de programe conține simboluri, nume sau etichete care sunt reprezentări simbolice ale constantelor sau datelor programului. Lista în sine este un instrument convenabil pentru analizarea și corectarea erorilor care pot apărea în timpul depanării programului.

Formatul programului

Fiecare așa-numită declarație de limbaj de asamblare individuală este de obicei tradusă într-o singură instrucțiune de mașină. În majoritatea limbajelor de asamblare, fiecare instrucțiune are patru câmpuri definite în mod unic care descriu diferite atribute ale instrucțiunii care este tradusă. Aceste câmpuri sunt câmpul de etichetă, câmpul de operație, câmpul de operanzi și câmpul de comentariu. Fiecare câmp poate fi de lungime variabilă și trebuie separat de vecinii săi prin unul sau mai multe spații.

Primul câmp este folosit pentru a specifica numele sau simbolul locației de memorie care conține comanda, constanta sau datele. Adresa acestei celule este conținută în

Asamblatorul încorporat (denumit în continuare pur și simplu asamblator) face posibilă programarea la nivelul instrucțiunilor individuale ale mașinii. Aceasta este principala diferență dintre asamblator și Pascal, iar toate avantajele și dezavantajele sale sunt concentrate în această diferență. Avantajul este că, la programarea în limbaj de asamblare, programatorul alege de obicei o secvență de instrucțiuni ale mașinii astfel încât să implementeze calculele dorite la viteza maximă cu un consum minim de memorie, în timp ce chiar și un compilator atât de avansat precum compilatorul Turbo Pascal introduce inevitabil codul are o oarecare redundanță, ceea ce reduce viteza de calcul și crește costurile de memorie. Pe de altă parte, programarea la nivelul instrucțiunilor mașinii este o sarcină extrem de supărătoare și nu poate fi comparată în viteza de dezvoltare a programului cu programarea în Pascal - acesta este principalul dezavantaj al asamblatorului.

Pentru a utiliza instrumentele de asamblare, trebuie să aveți o înțelegere clară a detaliilor arhitecturii microprocesorului Intel 80x86. Această familie include microprocesoare:

8086 - microprocesor pe 16 biți utilizat în IBM PC/XT;

8088 este un analog al lui 8086, diferă de acesta doar prin interacțiunea cu memoria: 8086 poate schimba atât octeți, cât și cuvinte de 16 biți cu memoria, în timp ce 8088 poate schimba doar octeți;

80286 - o versiune îmbunătățită a 8086 utilizată în IBM AT PC; poate funcționa în două moduri: în modul real, care emulează complet funcționarea MP 8086, și în modul protejat, în care este capabil să adreseze memorie de până la 16 MB (în modul real - până la 1 MB);

80386 - varianta pe 32 de biți a lui 80286; capabil să se adreseze până la 4 GB;

80486 - combinația 80386/80387, adică. are un subsistem intern pentru implementarea operațiunilor în virgulă mobilă;

80586 (Pentium) - are o serie de îmbunătățiri care îi asigură o creștere a performanței de 2...3 ori față de 80486, inclusiv capacitatea de a procesa numere pe 64 de biți.

Microprocesoarele din această familie își măresc capacitățile în ordinea enumerată, dar sunt strict compatibile de la modelele mai tinere la cele mai vechi: tot ceea ce poate face 8086/8088 este implementat de Pentium, dar nu invers. Arhitectura (structura internă, metodele de adresare și sistemul de comandă) MP 8086/8088 este discutată mai jos.



12.1.1. Registrele

MP 8086/8088 are 14 registre. Din punct de vedere funcțional, acestea sunt împărțite în grupuri:

· registre de uz general (AX, BX, CX, DX); sunt destinate stocării operanzilor și executării instrucțiunilor de bază; oricare dintre ele poate fi folosit ca un set de două registre independente de 8 biți: octetul înalt al registrului (AN, VN, CH, DH) și octetul inferior (AL, BL, CL, DL); de exemplu, AX constă din AN și AL;

· registre de segmente (CS, DS, SS, ES); folosit pentru a indica un segment la adresarea memoriei;

· registre pointer (SP, BP, IP); folosit pentru a indica un offset atunci când se adresează memoriei;

· registre index (SI, DI); folosit pentru adresarea indexului;

· registru steag; folosit pentru a stoca indicații ale stării procesorului.

În cadrul aceluiași grup funcțional, registrele sunt utilizate în moduri diferite. Specificul utilizării registrelor sunt descrise mai jos.

Înregistrează AX. Este sumatorul principal. Folosit în toate operațiile aritmetice (adunare, înmulțire etc.). Numai cu ajutorul AX și al semiregistrelor sale AHIAL este posibilă schimbul de date cu porturile I/O.

Registrul VX. Folosit ca sumator în operațiuni aritmetice și, de asemenea, ca registru de bază pentru adresarea indexului.

Înregistrați CX. Folosit în principal ca numărător atunci când se efectuează operații de repetare și schimbare. Poate participa și la operații aritmetice.

Registrul DX. Folosit ca registru de date în operațiunile I/O și, de asemenea, ca sumator la procesarea numerelor întregi lungi (32 de biți).

Registrul CS. Conține numărul segmentului de memorie (segment de cod) în care se află instrucțiunea curentă a mașinii. Pentru a obține adresa completă a următoarei comenzi, conținutul acesteia este deplasat la stânga cu 4 biți și adăugat la registrul pointerului IP. Conținutul CS este schimbat automat în comenzile de sărituri la distanță lungă (intersegment) și de apel de procedură.

Registrul IP. Determină decalajul relativ la începutul segmentului de cod CS al următoarei instrucțiuni de mașină executabilă. Conținutul IP-ului este schimbat automat în timpul execuției instrucțiunilor pentru a se asigura că instrucțiunile sunt preluate din memorie în ordinea corectă.

Registrul DS. Conține numărul segmentului de memorie (segment de date) în care se află datele (constantele și variabilele). Toate variabilele globale și constantele tastate ale unui program Turbo Pascal sunt întotdeauna situate într-un singur segment adresat de acest registru.

Registrul SS. Conține numărul segmentului stivei. Stiva este o secțiune de memorie auto-adresabilă concepută pentru a stoca temporar operanzi. Folosind stiva, TurboPascal organizează schimbul de date între program și proceduri, în plus, plasează în el toate variabilele locale (adică variabilele declarate în interiorul procedurii). Memoria stivei este utilizată conform regulii ultimului intrat, primul ieșit: cel mai recent operand împins din stivă va fi primul care va fi scos din acesta.

registrul SP. Indică în partea de sus a stivei, de ex. împreună cu registrul 55, se adresează celulei de memorie unde va fi plasat operandul sau de unde va fi preluat. Conținutul acestui registru este decrementat automat după ce următorul operand este împins pe stivă și este incrementat după ce operandul este scos din stivă.

Registrul VR. Așa-numitul indicator de bază. Facilitează crearea și utilizarea unei stive locale (adică o stivă pentru utilizare în cadrul unei proceduri).

registrul ES. Registrul de segment opțional ES este utilizat pentru schimbul de date intersegment și pentru unele operații cu șir.

registrul SI. Determină adresa sursei de informații la indexarea adresei datelor (de exemplu, la procesarea matricelor). Utilizat de obicei împreună cu registrul DS.

registrul DI.Împreună cu registrul de 5 GBP, determină receptorul de informații pentru schimbul de date intersegmentare.

Registrul steagului. Cifrele (biții) individuale ale acestui registru au următorul scop.

Carry flag CF. Conține 1 dacă o unitate a fost transportată în timpul adunării sau o unitate a fost împrumutată în timpul scăderii. Folosit și în operațiuni ciclice și de comparație.

steag de paritate PF. Conține 1 dacă operația are ca rezultat un număr cu un număr par de cifre semnificative, de ex. completează rezultatul cu un număr impar - utilizat în operațiunile de schimb pentru controlul datelor.

Steagul de transport extern AF. Controlează transferul de la al 3-lea bit de date. Util pentru operațiuni cu numere zecimale impachetate.

Steagul ZF zero. Egal cu 1 dacă rezultatul operației este zero și egal cu 0 în caz contrar.

Steagul semnului SF. Egal cu 1 dacă rezultatul operației este un număr negativ (cu unul în cifra cea mai semnificativă).

Steagul de urmărire TF. Egal cu 1 dacă programul este executat în trepte, cu controlul transferat după fiecare comandă executată prin întrerupere cu vectorul 1.

Indicatorul de întrerupere IF. Conține 1 dacă microprocesorul este activat să gestioneze întreruperi.

Steagul de direcție DF. Controlează direcția transferului de date: dacă conține 0, atunci după fiecare operație de index conținutul registrelor de index este mărit cu 1, în caz contrar, este micșorat cu 1.

Steagul de preaplin OF. Setați la unu dacă operația are ca rezultat un număr care se află în afara grilei de biți a microprocesorului.

12.1.2. Adresarea

În arhitectura MP 8086/8088, adresa oricărui octet este specificată de două cuvinte de 16 biți - un segment și un offset. Când se formează adresa completă de 20 de biți necesară pentru a adresa în termen de 1 MB, segmentul este deplasat la stânga cu 4 biți (înmulțit cu 16) și adăugat cu un offset. Deoarece capacitatea de offset de 16 biți este de 65536 de valori, până la 64 KB pot fi adresate într-un singur segment.

Arhitectura MP permite utilizarea a șapte metode diferite de adresare.

Inregistreaza-te

Preia un operand dintr-un registru sau îl plasează într-un registru. Exemple:

mov akh, bх (Eliminat din BX și plasat în AX)

adăugați cx,ax (Conținutul lui AX este adăugat la CX)

împinge ex (Impingeți conținutul CX pe stivă)

Direct

Operandul (constantă de 8 sau 16 biți) este conținut direct în corpul instrucțiunii. Exemple:

mov ax,100 (Încărcați valoarea 100 în AX)

adaugă toporul,5 (Adăugați 5 la conținutul lui AX)

mov cx,$FFFF (Plasați valoarea 65535 în CX)

Drept

Offset-ul operandului este specificat în corpul programului și adăugat la registrul DS; De exemplu:

X: Cuvânt; În: octet;

mută ah, X (Trimitem valoarea variabilei X registru AX)

adaugă ah, B (Adăugăm valoarea variabilei B la conținutul registrului AN)

mov X,ax (Transferăm conținutul registrului AX în zona de memorie a variabilei X)

Registrul indirect

Adresa executivă a operandului (mai precis, offset-ul său) este conținută în unul dintre registrele ВХ, BP, SI sau DI. Pentru a indica adresarea indirectă, acest registru trebuie inclus între paranteze drepte, de exemplu:

mov ax, (Conținutul cuvântului de 16 biți stocat în memorie la adresa DS:BX este trimis în registrul AX);

Fiecare dintre registrele BX...DI funcționează implicit cu propriul registru de segment:

DS:BX, SS:BP, DS:SI, ES:DI

Indicarea explicită a registrului de segment este permisă dacă diferă de cel implicit, de exemplu:

Adresare prin baza de date

Registrul de bază BX (sau BP) conține baza (adresa începutului unui fragment de memorie), în raport cu care asamblatorul calculează offset-ul, de exemplu:

mov ax,[bx]+10 (Încărcați în AX al 10-lea octet de la începutul bazei de memorie la adresa DS-.BX);

Adresarea la index

Unul dintre registrele index SI sau DI indică poziția elementului față de începutul unei zone de memorie. Să fie, de exemplu, AOB numele unei matrice de valori de tip Byte. Apoi puteți utiliza următoarele fragmente:

mov si,15 (Plasați constanta 15 în SI)

mută ah, AOB (Trimitem al 16-lea octet de la începutul matricei la AN)

mov AOB, ah (Trimitem ceea ce am primit la primul element al matricei)

Adresare prin baza de date cu indexare

O variantă de adresare a indexului pentru cazul în care zona de memorie indexată este specificată de baza sa. De exemplu:

Acest tip de adresare este util atunci când se prelucrează tablouri bidimensionale. Dacă, de exemplu, AOB este o matrice de 10x10 octeți din formular

AOB: matrice de octeți;

apoi pentru a accesa elementul AOB puteți folosi următorul fragment

mov bx,20 (Baza rândului 2)

movsi,2 (al treilea element numărul)

mov ax,AOB (Acces la elemente)

12.1.3. Sistem de comandă

Tabelele de mai jos oferă un mnemonic pentru toate instrucțiunile valide pentru 8086/8088 MP. Pentru ușurință în utilizare, toate comenzile sunt împărțite în 6 grupuri funcționale - transferuri de date, aritmetice, biți, șir, transferuri de control, întreruperi. În cadrul fiecărei grupe, echipele sunt combinate în subgrupe pe baza unor caracteristici suplimentare comune.

O analiză detaliată a tuturor comenzilor MP 8086/8088 ar ocupa prea mult spațiu, astfel încât doar cele mai populare comenzi sunt luate în considerare în explicațiile de după tabele. Veți găsi o descriere cuprinzătoare a tuturor comenzilor în,.

Comenzi de transfer de date

Mnemonice Format Explicaţie
Comenzi de uz general
MOV Chiuvetă MOV, sursă Valoare forward
APĂSAŢI sursa PUSH Așezați pe stivă
POP Receptor POP Ieși din stivă
XCHG Chiuvetă XCHG, sursă Valori de schimb
XLAT masa XLAT Încărcați un octet din tabel în AL
Comenzi I/O
ÎN IN baterie, port Citiți din port
OUT Port OUT, baterie Scrieți în port
Comenzi de redirecționare a adresei
LEA Registrul LEA 16, memoria 16 Încărcați adresa executivă
LDS Registrul LDS 16, memoria 32 Încărcați adresa completă în DS:register16
LES Registrul LES 16, memoria 32 Încărcați adresa completă în ES:register16
Semnalați comenzile de redirecționare
LAHF LAHF Încărcați steaguri în NA
SAHF SAHF Setați steaguri din NA
PUSHF PUSHF Așezați steaguri pe stivă
POPF POPF Scoate steaguri din stivă

Una dintre cele mai frecvent utilizate comenzi, MOV, vă permite să transferați în siguranță un octet sau un cuvânt din registru în registru, din memorie în registru sau din registru în memorie. Tipul de date transferate (octet sau cuvânt) este determinat de registrul implicat în transfer. Următoarele sunt exemple de utilizare a comenzii:

mov ah, Masa (Redirecționarea unui cuvânt din memorie către AX)

tabel mov, ah (Redirecționarea unui octet din AN către memorie)

mov ds,ax (Redirecționați către segmentul de date)

se mută:,ax (Redirecționarea unui cuvânt în memorie: Adresarea de bază cu înlocuirea segmentului)

mov ch,-17 (Redirecționați constanta pentru a vă înregistra)

tabel mov, $FF (Trimite constantă în memorie)

MOV nu poate fi folosit pentru a trimite:

din memorie în memorie, de exemplu, în loc de

ar trebui folosit

· o constantă sau o variabilă în DS, de exemplu, nu poate fi

· un segment se înregistrează la altul, de exemplu, este imposibil

· la registrul CS; valoarea acestui registru (segment de cod) se modifica automat la executarea comenzilor CALL si JMP la distanta; în plus, este încărcat din stivă atunci când este executată instrucțiunea RETF (procedura de ieșire departe).

Instrucțiunile stivei PUSH și POP sunt utilizate pe scară largă pentru a stoca temporar registre și date, precum și pentru a schimba valori între registre. Fiecare dintre ele lucrează cu un cuvânt, adică. Nu puteți împinge sau introduce un singur octet în stivă. Când se execută PUSH, conținutul pointerului SP este mai întâi decrementat cu 2, iar apoi operandul este plasat la adresa SS:SP. Când iese din stivă, memoria de la adresa SS:SP este mai întâi citită, iar apoi SP este incrementată cu 2. Astfel, când este plină, partea de sus a indicatorului de stivă SP este mutată la adrese joase, iar când este eliberată, la adrese înalte. . Când lucrați cu stiva, ar trebui să vă amintiți specificul utilizării memoriei stivei („ultimul intrat, primul ieșit”), precum și faptul că această memorie este utilizată intens la apelarea procedurilor, de exemplu. starea stivei la momentul ieșirii procedurii trebuie să fie strict în concordanță cu funcționarea ulterioară a programului. Prima condiție determină ordinea în care datele sunt scoase din stivă - trebuie să fie inversă ordinii în care datele au fost împinse în stivă. A doua condiție înseamnă de fapt că, după ieșirea din procedură, pointerul SP trebuie să conțină același offset ca atunci când intră în el. Cu alte cuvinte, procedura nu ar trebui să „uite” un cuvânt în plus de pe stivă sau să ia mai mult decât este necesar din el.

Instrucțiunea de încărcare a adresei LEA încarcă adresa (offset) locației de memorie dorită într-un registru. Acest lucru poate fi realizat și prin utilizarea cuvântului rezervat OFFSET înainte de numele variabilei. De exemplu:

mov ax, OFFSET X (Încărcați offset X în AX)

lea ax,X (Aceeași acțiune)

Diferența este că comanda LEA permite utilizarea adresei index, care este deosebit de convenabilă atunci când se trimit date în bloc.

Celelalte două instrucțiuni de încărcare a adresei, LDS și LES, încarcă primul cuvânt de 16 biți de la sursă în registrul de destinație, iar apoi următorul cuvânt în registrul DS sau ES, adică. sunt concepute pentru a încărca adresa completă a operandului (segment și offset).

Comenzi aritmetice

Mnemonice Format Un comentariu
Comenzi de adăugare
ADĂUGA ADD receptor, sursă Îndoiți
ADC Receptor ADC, sursă Îndoiți, adăugați transport
AAA AAA Ajustați adăugarea pentru tabelul ASCII
DAA DAA Adunarea corectă pentru numerele BCD
INC receptor INC Creste cu unu
Comenzi de scădere
SUB Receptor SUB, sursă Scădea
SBB Receptor SBB, sursă Scăderea cu împrumut
A.A.S. A.A.S. Ajustați scăderea pentru tabelul ASCII
DAS DAS Ajustați scăderea pentru numerele BCD
DEC receptor DEC Scade cu unu
N.E.G. Receptor NEG Semn invers
lucrări de construcție și instalare Receptor SMR, sursă Comparaţie
Instrucțiuni de înmulțire
MUL Sursa MUL Înmulțiți fără semn
IMUL Sursa IMUL Înmulțiți cu semn
AAM AAM Ajustați înmulțirea pentru tabelul ASCII
Comenzi de divizie
DIV sursa DIV Împărțiți fără semn
IDIV Sursa IDIV Împărțire cu semn
AAD AAD Ajustați diviziunea pentru tabelul ASCII
Semnează comenzile de extensie
CBW CBW Convertiți octet în cuvânt
CWD CWD Convertiți cuvântul în cuvânt dublu

Când utilizați comenzi aritmetice, rețineți că MP poate procesa numere semnate, numere nesemnate și numere BCD. Numerele fără semn folosesc toți biții pentru a reprezenta valoarea. acestea. sunt echivalente cu tipurile Byte și Word, în timp ce numerele cu semn în poziția cea mai semnificativă stochează semnul numărului și sunt echivalente cu tipurile ShortInt și Integer. Numerele BCD folosesc 4 biți pentru fiecare zecimală și pot fi împachetate sau dezambalate. În primul caz, un octet stochează 2 cifre zecimale (cea mai semnificativă este în cea mai semnificativă nibble), în al doilea - doar una (cea mai semnificativă nibble nu este folosită). Comenzile aritmetice de bază ale MP (ADD, SUB, MUL, DIV) nu țin cont de forma binară-zecimală a numerelor, astfel încât comenzile de corectare a rezultatelor sunt incluse în arhitectura MP.

Instrucțiuni pentru biți

Mnemonice Format Un comentariu
Comenzi logice
ȘI ȘI chiuvetă, sursă Executați AND
SAU SAU chiuvetă, sursă Executați SAU
XOR Receptor XOR, sursă Executați XOR
NU NU receptor Executați NU
TEST TEST receptor, sursă Verifica
Comenzi de schimbare
SAL/SHL Receptor SAL, contor Mută ​​la stânga
SAR/SHR Receptor SAR, contor Misca-te la dreapta
ROL Receptor ROL, contor Deplasați ciclic la stânga.
ROR Receptor ROR, contor Deplasați-vă la dreapta ciclic
RCL Receptor RCL, contor Deplasați-vă la stânga cu carry
RCR Receptor RCR, contor Deplasați-vă la dreapta cu wrap

Instrucțiunile de biți sunt utilizate la calcularea expresiilor logice, precum și în cazurile în care este necesară modificarea biților individuali ai operandului. Instrucțiunile logice AND, OR, XOR și NOT sunt echivalente cu operațiile Turbo Pascal corespunzătoare atunci când operanzii sunt expresii întregi. Comanda TEST efectuează o operație întreg de însumare pe biți și, dar nu modifică valorile operanzilor, ci setează doar steagurile în conformitate cu valoarea rezultatului comparației: resetează CF și OF, modifică PF, ZF, SF și nu modifică AF (steagul ZF va fi setat la 1 în cazul în care ambii operanzi conțin unul în cel puțin un bit corespunzător). Comenzile de deplasare SHL/SHR sunt echivalente cu operațiile Turbo Pascal cu același nume și diferă de comenzile de deplasare ciclică ROLIROR prin faptul că se pierd biții semnificativi deplasați în timpul executării lor, în timp ce în timpul unei deplasări ciclice acești biți apar „pe cealaltă parte. ”. De exemplu, dacă executați fragmentul

mov al,1 (Încărcați unitatea în AL)

shr al,1 (Deplasați la dreapta, cu 1 cifră)

registrul AL va conține 0 (cel deplasat la dreapta va fi plasat în CF), în timp ce după înlocuirea instrucțiunii SHR cu ROR va conține valoarea $80=128 (cel deplasat va fi plasat în bitul cel mai semnificativ al registrului). ).

Rețineți că contorul din instrucțiunile de schimbare poate fi 1 sau numărul de schimburi specificat în registrul CL.

Comenzile de transfer de control

Mnemonice Format Un comentariu
Salturi neconditionate
APEL Apelați numele Intră în procedură
RET RET [număr de parametri] Revenirea din procedura
A SARI JUMP nume Merge
Salturi condiționate
JA/JNBE JA close_mark Mergeți dacă este mai mare (după compararea operanzilor nesemnați)
JAE/JNB JAE close_mark Salt dacă este mai mare sau egal cu
JB/JBAE/JC JB close_mark Sariți dacă este mai jos
JBE/JNA JBE close_label Sari dacă este mai mic sau egal
JCXZ JCXZ close_mark Mergeți dacă CX=0
JE/JZ JE close_mark Du-te dacă egal
JG/JNLE JG close_mark Salt dacă este mai mare (după compararea operanzilor cu semn)
JGE/JNL LGE close_label Salt dacă este mai mare sau egal cu
JL/JNGE JL close_mark Sari dacă mai puțin
JLE/JNG JLE close_label Sari dacă este mai mic sau egal
J.N.C. JNC close_label Du-te dacă nu există transfer
JNE/JNZ JNE close_label Du-te dacă nu egal
JNO JNO close_label Sari dacă nu există preaplin
JNP/JPO JNP close_label Sari dacă este ciudat
JO JO close_mark Du-te dacă transfer
JP/JPE JP close_mark Du-te chiar dacă
JS JS close_label Du-te dacă este negativ
Comenzi de control ciclului
BUCLĂ LOOP close_label Repetați ciclul
LOOPE/LOOPZ LOOPE close_label Repetați până la egalitate
LOOPNE/LOOPNZ LOOPNE close_label Repetați până la egalitate

Comenzile de sărituri necondiționate CALL, RET, JMP pot folosi un model de memorie departe sau aproape, în timp ce comenzile de sărituri condiționate pot folosi doar unul mic (în intervalul -128...+127 de octeți). Cu modelul de memorie departe (setat de opțiunea Opțiuni/Compiler/Force far calls a mediului Turbo Pascal sau directiva compilatorului (F+)), se realizează transferul de control atât intrasegment cât și intersegment, cu cel apropiat - doar intrasegment.

Instrucțiunea CALL funcționează după cum urmează. Mai întâi, adresa instrucțiunii care urmează CALL (adresa de retur) este plasată pe stivă, apoi adresa punctului de intrare în procedură este plasată în registrul IP (sau în perechea CS:IP), deci imediat după instrucțiunea CALL se va executa prima comandă a procedurii. Ambele adrese (punctele de intrare și de retur) vor fi decalaje de 16 biți pentru un apel intra-segment sau adrese complete de 32 de biți pentru un apel inter-segment. Toate procedurile (funcțiile) Pascal traduse în modul (F+) sau care conțin cuvântul rezervat FAR în antet trebuie să fie numite ca departe. Pentru a face acest lucru, instrucțiunea CALL trebuie urmată de modelul de memorie:

Procedura MyProc; Departe;

apelați FAR MyProc (Apelarea unei proceduri îndepărtate)

Toate rutinele bibliotecii (adică cele declarate în părțile de interfață ale modulelor) trebuie apelate în același mod. Într-un apel departe, conținutul segmentului de cod CS este împins mai întâi pe stivă, urmat de compensarea de întoarcere.

La ieșirea din procedura departe, instrucțiunea RET scoate ambele cuvinte de 16 biți din stivă și îl plasează pe primul în IP și pe al doilea în CS, iar la ieșirea din cel apropiat, scoate doar offset-ul din stivă și îl plasează în IP. .

Instrucțiunile de ramificare condiționată sunt capabile să transfere controlul către o etichetă situată în cel mai apropiat plus sau minus 128 de octeți de la instrucțiunea în sine. Dacă trebuie să transferați controlul către o etichetă situată mai departe în același segment sau către o etichetă dintr-un segment diferit, o instrucțiune JMP sau CAL necondiționată este plasată imediat după comanda de transfer condiționat, de exemplu:

frica,0 (Verificând AH)

jne@NotZero (AX=0?)

jmp IsZero (Da, mergi la marcajul îndepărtat)

....... (Nu - continuăm să lucrăm)

În tabel, termenul „de mai sus/dedesubt” este folosit în relație cu compararea operanzilor nesemnați, iar „mai mult decât/mai puțin” este folosit pentru a compara operanzii semnati.

Deoarece salturile condiționate implementează ramificarea programului bazată pe verificarea steaguri, ele sunt de obicei imediat precedate de comenzi care modifică aceste steaguri, cel mai adesea comanda de comparare CMP. Mai jos sunt combinațiile CMP - conditional_jump pentru diversele relații de destinație și sursă (primul și al doilea operand) ale instrucțiunii CMP:

De exemplu:

pp ah,5 (AX>5?)

și @AboveS (Da, mai mult - hai să mergem mai departe)

pagina cutie, - 3 (VH<=-3 ?}

jle @LessM3 (Da, mai mic sau egal cu)

Comenzile LOOP/LOOPE/LOOPNE sunt folosite pentru a organiza bucle. Toate folosesc conținutul registrului CX ca numărător de repetiții. Instrucțiunea LOOP decrește CX cu unu și transferă controlul către marcajul de pornire a buclei dacă conținutul acestui registru este diferit de zero. Instrucțiunile LOOPE/LOOPNE decrementează de asemenea contorul CX, dar transferă controlul la începutul buclei în condiția combinată ca indicatorul ZF să fie setat (sau resetat) și contorul CX să nu fie egal cu zero.

Iată cum, de exemplu, puteți găsi octetul zero în matricea AOB:

AOB: matrice de octeți;

mov ex,It)00 (Initiem contorul CX)

lea bx,AOB (Plasați adresa AOB în BX)

dec bx (Pregătirea ciclului)

(Aici este începutul ciclului de revizuire)

@@Test: inc bx (Adresa octetului următor)

cmp BYTE PTR ,0 (Verificarea octetului)

loopne ©Test (Închide bucla)

jnz ©NotZero (Dacă nu este găsit octetul nul)

....... (Găsit un octet nul)

String comenzi

Mnemonice Format
Ți-a plăcut articolul? Impartasiti cu prietenii: