Llenguatge assemblador x86

De Viquipèdia
Dreceres ràpides: navegació, cerca

El llenguatge d'assemblador x86 és la família dels llenguatges assembladors retrocompatible per als processadors x86, entre els quals s'inclouen els Pentium de Intel i els Athlon de AMD. Com la resta de llenguatges assembladors, utilitza una sèrie de mnemotècnics per a representar les operacions fonamentals que el processador pot realitzar. Els compiladors sovint produeixen codi assemblador com un pas intermedi quan tradueixen un programa d'alt nivell a codi màquina. Considerat com un llenguatge de programació , el codi assemblador és de baix nivell i específic per a cada màquina. S'utilitza principalment en aplicacions crítiques com sistemes d'arrencada, Sistemes Operatius, nuclis i en controladores de dispositius, així com en sistemes en temps real o petits sistemes encastats.

Història[modifica | modifica el codi]

Els processadors Intel 8086 i 8088 van ser els primers de 16 bits a tenir un conjunt d'instruccions, conegut actualment com a x86. Van ser un pas evolutiu en comparació amb la generació anterior de CPU's de 8 bits, com el 8080 i van heretar moltes característiques i instruccions, les quals van ser esteses per a l'època dels 16 bits. Ambdues CPU's contenien un bus d'adreces de 20 bits i un registre intern de 16 bits. El 8086 tenia un bus de dades de 16 bits i un de 8 bits estava previst com una opció de baix cost per incorporar-la en el mercat. El llenguatge d'assemblador del x86 també fa referència a les diferents versions de processadors que Intel va seguir produint, com 80188, 80186, 80286, 80386, 80486, Pentium i els que no eren d'Intel com AMD i Cyrix. El terme x86 fa referència a tots els processadors que tenen el mateix llenguatge d'assemblador original.


El modern conjunt d'instruccions és en realitat una sèrie de sets d'instruccions que van començar amb el microprocessador Intel 8008. La quasi total retrocompatiblidad és present des dels xips Intel 8086 fins als moderns processadors Intel Pentium 4, Intel Core Duo, Intel Core i7, AMD Athlon 64, AMD Opteron, etc., encara que hi ha algunes excepcions. Aquesta compatibilitat s'aconsegueix gràcies a l'ús de 2 conjunts d'instruccions d'arquitectures, la qual cosa és comunament criticat. La compatibilitat dels programes en llenguatge d'assemblador amb processadors més antics només és possible quan el programa no inclou instruccions només disponibles en els processadors nous.

Mnemotècnics i codis d'operació[modifica | modifica el codi]

Cada instrucció de x86 està representada per un mnemotècnic, que tradueix directament a una sèrie de bytes la representació de la instrucció, anomenada codi d'operació. Per exemple, la instrucció NOP tradueix a 0x90 i la instrucció HLT tradueix a 0xF4. Alguns codis d'operació no tenen noms mnemotècnics i no estan documentades. Diferents processadors en la família del x86 poden interpretar codis d'operació indocumentats de manera diferent, fent que un mateix programa es comporti de manera diferent en diferents processadors.

Sintaxi[modifica | modifica el codi]

El llenguatge d'assemblador x86 té 2 vessants diferents quant a la seva sintaxi de programació: sintaxi Intel , usada en els seus inicis per la documentació de la plataforma x86, i sintaxi AT & T .[1] La sintaxi Intel és la dominant a la plataforma Windows, mentre que en Unix/Linux ambdues són utilitzades perquè GCC només suporta la sintaxi AT & T en les seves primeres versions.

La majoria dels assembladors x86 utilitzen la sintaxi d'Intel, com Masma, TASMAN, NASM, fasmatodeus and Yasmeen. GAS ha suportat les dues sintaxi des de la versió 2.10 a través de la directiva ". Intel_sintax".[1][2][3]

Registros[modifica | modifica el codi]

Els processadors x86 tenen una sèrie de registres disponibles per emmagatzemar informació. Aquest conjunt de registres són coneguts com a registres de propòsit general

A més dels GPR, hi ha addicionalment:

El registre IP apunta a la posició del programa en què el processador aquesta executant el codi. EL registre no pot ser accedit pel programador directament.

Els registres del x86 poden utilitzar mitjançant la instrucció MOV. Per exemple:

mov ax, 1234 h
mov bx, ax

còpia el valor 1234h en el registre ax i en la següent línia còpia el valor de ax en el registre bx.

Adreçament segmentat[modifica | modifica el codi]

L'arquitectura x86 utilitza el mètode de segmentació per adreçar memòria, en lloc del mètode lineal usat en altres arquitectures. La segmentació implica descompondre una adreça lineal en dues parts - un "segment" i un "desplaçament". El segment apunta a l'inici d'un bloc de 64K adreces i el desplaçament indica la diferència entre el lloc apuntat i l'inici del segment.

Aquesta manera d'adreçament s'utilitza per a aprofitar les característiques del processador. El problema estava en què els registres interns del processador eren de 16 bits, mentre que el bus d'adreces era de 20. Faltaven per tant 4 bits per poder aprofitar al màxim les capacitats d'adreçament del processador. Per resoldre això, cada adreça de memòria serà especificada com un segment i un desplaçament dins d'aquest segment. Aquesta solució divideix la memòria en segments de 64 K, la qual cosa va limitar bastant els dissenys dels processadors posteriors de la família (Intel 80286,Intel 80386, etc.), Tot i que posteriorment es van idear mètodes per a resoldre aquest problema, com la memòria estesa (no compatible amb el x86/x88). Amb això s'aconsegueix que el processador sigui capaç de direccionar 1.048.576 adreces d'1 byte, o el que és el mateix, 1Mbyte.

S'utilitzen dos registres per a l'adreçament: un per indicar el segment, i l'altre per indicar el desplaçament.

Per obtenir l'adreça de memòria (adreça efectiva): es pren el valor de registre de segment, es desplaça 4 bits a l'esquerra (multiplicació per 16), i se li suma el valor del desplaçament.

Exemple: Si DS conté 0x000A i DX conté 0x5F0A, apuntarien a l'adreça de memòria: 0x000A * 0x10+0x5F0A = 0x5FAA

Per referir-se a una adreça amb un segment i un desplaçament, s'utilitza la notació segment: desplaçament. En l'exemple anterior, la direcció lineal 0x5FAA es nomenaria com 0x000A: 0x5F0A, o si les dues parts es troben emmagatzemades en els registres esmentats, es podria utilitzar el parell DS: DX. Hi ha una sèrie de combinacions especials entre registres de segments i registres generals que apunten a adreces importants:

  • CS: IP apunta a la següent adreça de codi en què es posicionarà el processador.
  • SS: SP apunta a l'últim element apilat a la pila.
  • DS: SI se sol utilitzar per apuntar informació que serà copiada a ES: DI.

Modes d'execució[modifica | modifica el codi]

El processador suporta nombrosos modes d'operació per codi x86, en els quals no totes les instruccions estan disponibles. Un sub-repertori d'instruccions de 16 bits està disponible a "mode real" (disponible a tots els processadors x86), "mode protegit 16 bits" (disponible des del Intel 80286), o en el "mode v86 "(disponible des del Intel 80386). Per la seva banda, les instruccions de 32 bits estan disponibles per al "mode protegit 32 bits" i per al "mode heretat" (disponible amb les extensions de 64 bits). El repertori d'instruccions part d'idees similars a cada manera, però dóna lloc a diferents formes d'accés a memòria i d'aquesta manera empra estratègies de programació diferents.

Els modes en què el codi x86 pot ser executat són:

Tipus d'instruccions[modifica | modifica el codi]

En general, les característiques del repertori d'instruccions x86 són:

  • Una codificació compacta
    • Longitud variable i alineació independent (codificació en format little endian)
    • Instruccions d'una i dues direccions, en què el primer operand és també el destí.
    • Operands de memòria com a origen i destinació compatibles (normalment utilitzats per a llegir/escriure elements de la pila utilitzant petits desplaçaments immediats)
    • Ús dels registres generals i implícits, malgrat que els set registres generals (comptant 'EBP') poden ser utilitzats com acumuladors o per direccionar, la majoria d'ells són també usats implícitament per algunes instruccions especials, els registres afectats han de conservar temporalment la informació, si estan sent utilitzats durant l'ús d'aquestes instruccions (normalment mitjançant l'ús de la pila).
  • Produeix flags condicionals implícitament mitjançant l'ús de la majoria d'instruccions de la ALU
  • Suporta diversos modes d'adreçament
  • Inclou punt flotant en una pila de registres
  • Conté suport especial per a instruccions atòmiques (XCHG, CMPXCHG (8B), XADD i instruccions senceres combinades amb el prefix LOCK)
  • Instruccions SIMD (instruccions que apliquen una mateixa operació sobre un conjunt més o menys gran de dades)

Instruccions de pila[modifica | modifica el codi]

La pila és un segment que és de gran utilitat en aquests microprocessadors S'hi emmagatzemen valors temporals com les variables locals de les funcions, o les adreces de retorn d'aquestes. Una funció no és més que una subrutina, o un fragment de codi al qual se l'anomena generalment diverses vegades des del programa principal o des d'una funció jeràrquicament superior. Quan es crida a una funció es fa un simple salt al punt on comença aquest codi. No obstant això aquesta subrutina pot ser anomenada des de diferents punts del programa principal, de manera que cal emmagatzemar en algun lloc l'adreça des d'on es fa la trucada, cada vegada que aquesta trucada té lloc, perquè en finalitzar l'execució de la funció es reprengui el programa on es va deixar. Aquesta adreça pot emmagatzemar en un lloc fix (com fan alguns microcontroladors), però això té l'inconvenient que si aquesta funció al seu torn crida a una altra funció (o si mateixa !) podem sobreescriure la direcció de retorn anterior, i en tornar de la segona trucada, no podríem tornar des de la primera. A més, és desitjable que la funció guardi els valors de tots els registres que useu en algun lloc, perquè el que la truqui no hagi de preocupar d'això (ja que si sap que els registres seran modificats, però no sap quines, els guardarà tots per si de cas). Totes aquestes coses, i algunes més, es fan amb la pila.

El segment de pila està indicat per SS, i el desplaçament dins del segment, per SP.

Quan arrenca el programa, SP apunta al final del segment de pila. Per emmagatzemar informació a la pila es disminueix SP perquè apunti una mica més amunt i es copia a aquesta posició de memòria, SS: SP. Per treure'l, copiem el que hi hagi en SS: SP al nostre destí, i incrementem el punter.

Com amb tot el que es fa sovint, hi ha disposades instruccions pròpies per al maneig de la pila. Les dues bàsiques són PUSH origen (empènyer) i POP destinació (treure). La primera decrementa el punter de pila i còpia a la direcció apuntada per ell (SS: SP) l'operand origen (de mida múltiple de 16 bits), mentre que la segona emmagatzema el contingut de la pila (element apuntat per SS: SP) en destinació i altera el punter en conseqüència. Si l'operand és de 16 bits es modifica en 2 unitats, de 32 a 4, etc. El que s'incrementa/decrementa és sempre SP, és clar, perquè SS ens indica on està ubicat el segment de pila.

La instrucció ret size s'utilitza per recuperar de la pila dels valors d'IP o de CS i IP depenent del cas. En sortir d'un procediment cal deixar la pila com estava; per a això podem utilitzar la instrucció pop, o bé executar la instrucció ret size on size és el nombre de posicions que han de descartar de la pila.

Instruccions de la ALU amb enters[modifica | modifica el codi]

El assemblador x86 té les operacions matemàtiques estàndards, com add , sub , multi , with idiv , els operadors lògics and , or , XOR , neg ; desplaçaments, sal / sar , shl / SHR ; rotació amb/sense ròssec, RCL / RCR rol / ror , un complement d'instruccions aritmètiques BCD, aaa , AAD , daa i altres.

Instruccions en coma flotant[modifica | modifica el codi]

El assemblador x86 inclou instruccions per pila basada en unitats en coma flotant. Entre elles es troben la suma, resta, negació, multiplicació, divisió, resta, arrels quadrades, truncament sencer i truncament fraccionat. Les operacions també inclouen instruccions de conversió amb les quals es pot carregar o emmagatzemar un valor des de memòria a qualsevol dels següents formats: BCD, sencer de 32 bits, sencer de 64 bits, punt flotant de 32 bits, punt flotant de 64 bits o 80 bits. El x86 també inclou funcions com sinus, cosinus, tangent, arc tangent, exponent amb base 2 i logaritmes de base 2, 10 o i.

La conversió d'instruccions al format del registre de pila és normalment F (OP) st, st (*) o F (OP) st (*), st , on st és equivalent a st (0), i st (*) és un dels 8 registres de pila (st (0), st (1), ..., st (7)). Com amb els enters, el primer operand actua com a primera font i com operant destinació. La suma, resta, multiplicació, divisió, emmagatzematge i comparació d'instruccions inclou maneres d'instrucció que s'encarreguen de desapilador un cop completada l'operació.

En el cas que no existeixi cap operant, suposa destí = ST (1), font = ST i es fa més pop sobre la pila, de manera que el resultat se situa a la part alta de la pila. Per exemple, FADD calcula ST (1) = ST (1)+ST i fa pop sobre la pila (incrementant en un el punter de pila), de manera que el nou element a la part alta de la pila conté el resultat.

Instruccions SIMD[modifica | modifica el codi]

Els processadors x86 moderns tenen instruccions SIMD, que permeten realitzar la mateixa operació en paral·lel sobre diversos valors codificats en un registre SIMD. Diverses tecnologies d'instruccions suporten diferents operacions sobre diferents repertoris de registres, però tots (des MMX fins SSE4, 2) inclouen càlcul general sobre aritmètica sencera o en coma flotant (suma, resta, multiplicació, desplaçament, minimització, maximització, comparació, divisió o arrel quadrada). Per exemple, PADDW MM0, MM1 s'aplica 4 sumes paral·leles d'enters de 16 bits (a causa de la W que indica que són paraules) dels valors de mm0 fins MM1, i els emmagatzema en mm0. SSE també inclou la manera en coma flotant en què el primer valor dels registres està modificat (expandit al SSE2).

Instruccions de manipulació de dades[modifica | modifica el codi]

El processador x86 també inclou modes d'adreçament complex per adreçar memòria amb un desplaçament immediat, un registre, un registre amb desplaçament, un registre escalat amb desplaçament o sense i un registre amb desplaçament opcional i altre registre escalat. Llavors per exemple, un pot codificar mov eax, [Table+ebx+ESI * 4] com una instrucció simple que carrega 32 bits de dades des de la direcció localitzada en el desplaçament (Table+ebx+ESI * 4) des del segment DS, i emmagatzemar en el registre eax. En general, els processadors x86 poden carregar i fer ús de memòria ajustada a la mida del qualsevol registre sobre el qual aquesta operant.

Els repertoris d'instruccions x86 inclouen instruccions de càrrega, emmagatzematge i moviment de cadenes (Lods, quests and MOVS) que representen cada operació amb una mida especificat (B per bytes, W per paraules de 16-bits, D per dobles paraules de 32 bits ) i incrementen/decrementa el registre de direcció implícit (SI per Lods, DI per aquests i tots dos per MOVS). Per a la càrrega i emmagatzematge, el registre destí/font implícit és el AL, AX o EAX, depenent de la mida. El segment usat implícitament és DS per Lods, ÉS per aquests i tots dos per MOVS.

La pila està implementada amb un punter que disminueix (push) i augmenta (pop) implícitament. En el mode de 16 bits, aquest punter es correspon a la direcció SS: [SP], a 32 - bits seria SS: [CAT] i en 64 bits [RSP]. El punter de pila s'encarrega d'apuntar a l'últim valor emmagatzemat, assumint que la seva mida coincideix amb la manera del processador (12, 32 o 64 bits) perquè coincideixi amb l'amplada per defecte de les instruccions PUSH/POP/CALL/RET. Altres instruccions per manipular la pila són PUSHF i POPF, que s'utilitzen per emmagatzemar i recuperar el registre de FLAGS, emmagatzemat o retirant de la part alta de la pila.

Flux del programa[modifica | modifica el codi]

El ensamblador x86 té una operació de salt incondicional, JMP , que admet una adreça immediata, un registre o una adreça indirecta per mitjà de registre com a paràmetre.

També es permeten els salts condicionals, com je (saltar quan hi ha igualtat), jne (saltar en desigualtat), JG (saltar si 'major que ', amb signe), JL (saltar si' menor que ', amb signe), ja (saltar si' superior/més gran que ', sense signe), < code> JB (saltar si 'inferior/menor que', sense signe). Aquestes operacions condicionals es basen en l'estat específic dels bits del registre de FLAGS. La majoria de les operacions aritmètiques s'encarreguen d'actualitzar aquests flags segons el seu resultat. Les instruccions de comparació cmp i test modifiquen l'estat del registre de flags sense modificar els operands. Les comparacions en coma flotant es realitzen mitjançant les instruccions FCOM o FICOM.

Cada operació de salt té tres formes diferents, depenent de la mida de l'operand. Un salt sort fa servir un operant amb signe de 8 bits, que es correspon amb el desplaçament relatiu a la instrucció actual. El salt near és similar al curt però utilitza un operand de 16 o 32 bits amb signe. Un salt far utilitza el segment sencer base: desplaçament com una adreça total. També hi ha forma indirecta i indexada per a cada un d'ells.

A més de les operacions de salt, hi ha les instruccions call i ret per a trucar i tornar d'una subrutina. Abans de transferir el control a la subrutina, call apila el desplaçament de la instrucció següent a la crida a la pila; ret desapilador aquest valor de la pila, i transfereix el control a l'adreça que indicava el valor desapilador. En el cas que es tracti d'una crida a una funció llunyana, far call , la base també s'apila seguint el desplaçament.

Hi ha algunes instruccions similars, com la interrupció int . Aquesta activa el procediment d'interrupció especificat per l'operand i guarda el valor del registre de flags en la pila. L'activació de la interrupció es realitza mitjançant una trucada a un procediment llunyà ( far call ), però en lloc d'una adreça, utilitza aquest vector d'interrupció. La direcció d'aquest vector es calcula multiplicant per 4 l'operand, que és un valor entre 0 i 255. Normalment, el conductor d'interrupcions guarda tots els registres que el processador està utilitzant, a menys que estiguin sent usats per emmagatzemar el resultat d'una operació. D'altra banda, per tornar al programa des d'una interrupció s'utilitza iret , que s'encarrega de restablir el valor dels flags. Les interrupcions lleugeres descrites anteriorment són usades per alguns sistemes operatius per a les crides al sistema, i poden també ser usades per a depurar els controladors d'interrupcions forts. Aquestes són provocades per esdeveniments maquinari externs, i han de mantenir els valors de tots els registres, ja que l'estat del programa en execució es desconeix. En el Mode protegit, les interrupcions poden ser activades pel sistema operatiu per a realitzar un canvi de tasca, emmagatzemant automàticament tots els registres de la tasca en execució.

Vegeu també[modifica | modifica el codi]

Referències[modifica | modifica el codi]

  1. 1,0 1,1 Error en el títol o la url.Ram Narayama. «», 17-10-2007.
  2. Error en el títol o la url.Randall Hyde. «».
  3. «-CVSweb-markup & cvsroot = src GNU Assembler News, v2.1 supports Intel syntax», 04-04-2008.

Seguir llegint[modifica | modifica el codi]

Enllaços externs[modifica | modifica el codi]