sábado, 12 de marzo de 2011

EL JUEGO DE INSTRUCCIONES DEL X86

¿Por qué aprender ensamblador?

A nosotros nos importa bien poco en qué lenguaje de programación se ha compilado una aplicación, sólo en alguna ocasión será interesante saber en qué lengua se ha escrito algún programa (y tampoco será imprescindible averiguarlo). Todos sabéis que se programe en el lenguaje que se programe la compilación traduce las líneas de código al único código que entiende la máquina, éste código es el ensamblador y es la única lengua que necesitamos conocer tanto para añadir como para quitar código a una aplicación. En esta página tenéis la posibilidad de aprender un poco cada una de las instrucciones más habituales y cómo hacer uso de ellas. Al final disponéis del juego de instrucciones completo, de momento está en Ingles pero lo iremos traduciendo al español, como siempre, cuando tengamos un rato.

Comenzaremos con los registros.

De los registros podíamos estar hablando días y días enteros, pero lo que nos interesa saber de ellos es que son lugares donde guardar y conocer datos. Con ellos podemos sumar, restar, multiplicar, guardar un dato, ejecutar una función determinada de una interrupción, en fin, son imprescindibles. Aquí tienes la lista de algunos registros.

AX Acumulador, de uso general. Se puede dividir en AH y AL. Obtendremos el mismo resultado si hacemos "MOV AX , FF00" que si hacemos primero "MOV AH , FF" y después "MOV AL , 00".

BX Registro base, de uso general. Se puede dividir en BH y BL.

CX Registro contador, de uso general. Se puede dividir en CH y CL.

DX Registro de datos, de uso general. Se puede dividir en DH y DL.

DS Registro del segmento de datos. Utilizado para apuntar a un segmento.

ES Registro del segmento extra. Lo mismo que DS.

SS Registro del segmento de pila. Nos indica el segmento donde se encuentra la pila (mas abajo hablaremos de ella).

CS Registro del segmento de código. Nos indica el segmento donde se encuentra el código que se está ejecutando.

BP Registro de apuntadores base. Digamos que este registro se utiliza para apuntar donde guardamos datos.

SI Registro índice fuente.

DI Registro índice destino.

SP Registro del apuntador de la pila. Este registro nos indica dónde está la pila en la que se guardan los datos que introducimos en ella.

IP Registro que apunta a la siguiente instrucción que se tiene que ejecutar.

F Registro de flags. Este registro es necesario leerlo en binario. Cada bit representa el resultado de alguna operación, por ejemplo si comparamos dos números este en este registro encontramos información sobre si uno es menor o mayor que el otro o si son iguales, etc. Los flags (o banderas) son los siguientes CF, PF, AF, ZF, SF, TF, IF y OF.

Antes de continuar hablaremos de la instrucción NOP. Esta instrucción es nula, o sea que no hace absolutamente nada. Es utilizada para provocar un retraso en la CPU y nosotros la utilizamos PARA ELIMINAR EL CODIGO QUE NO QUEREMOS QUE SE EJECUTE (una forma de parchear) cambiando el código que existe por tantos nop's como bytes queremos eliminar.

¿Qué es la pila?

Ahora que sabemos como guardar un dato determinado usando los registros, nos damos cuenta de que en ocasiones necesitaremos guardar mas datos que registros tenemos. ¿Como solucionamos esto? pues con el uso de la pila. Supongamos que debemos de guardar 10 números y solo disponemos de 4 registros de uso general, la solución es hacer los siguiente:

MOV AX , PRIMER_NUMERO..... <= cargamos en ax el número

PUSH AX ....................................... <= y lo dejamos caer en la pila

MOV AX , SEGUNDO_NUMERO. <= cargamos en ax el siguiente nº

PUSH AX........................................ <= y también lo descargamos en la pila.

.... así hasta terminar de descargar nuestros datos. ¿Donde se han guardado? pues en SS:SP.

Para recuperar lo introducido en la pila, hay que saber que siempre sale primero el último dato que entra, este es el motivo de que se le llame pila, se apilan los datos. Imagina que amontonas platos uno encima de otro... para sacar el primer plato que has puesto debes de sacarlos TODOS. Este es el concepto de la pila.

POP AX <= Recupera de la pila EL ULTIMO DATO INTRODUCIDO y lo deposita en el registro AX.

Bien... Ya hemos visto que son los registro, ahora vamos a ver como los guardamos en la pila y como los recuperamos de ella...

PUSH REGISTRO..... <= Guarda en la pila un registro.

PUSH NUMERO........ <= Guarda en la pila un número.

PUSHF....................... <= Guarda en la pila los flag.

PUSHA...................... <= Guarda en la pila los registros AX, CX, DX, BX, SP, BP, SI y DI en este mismo orden.

POPA........................ <= Recupera de la pila los registros DI, SI, PB, SP, BX, DX, CX y AX en este mismo orden.

POPF......................... <= Recupera de la pila los flags.

POP REGISTRO....... <= recupera un dato de la pila y lo deposita en "REGISTRO".

Instrucciones de salto

Si has programado alguna vez en cualquier Básic, sabrás que las instrucciones de salto tales como goto y gosub son la madre de cualquier programa. En ensamblador es lo mismo, solo que tenemos muchisimas más posibilidades.

JMP DIRECCION.... <= Hace que el programa salte a dirección. Sería como el goto del Básic.

CALL DIRECCION. <= Hace que el programa salte a dirección para ejecutar un fragmento de código y cuando termina retorna a la siguiente instrucción despues de call con RET. Sería como el gosub del Básic.

Después de una instrucción de comparación de 2 números podemos hacer saltos condicionados:

CMP AX , BX............ <= Compara el registro ax con bx.

JE DIRECCION......... <= Salta a dirección si los registros son iguales.

CMP AX , NUMERO. <= Compara ax con número.

JA DIRECCION......... <= Salta si ax es mayor que número.

JAE DIRECCION....... <= Salta si ax es mayor o igual que número.

JB DIRECCION.......... <= Salta si ax es menor que número.

JBE DIRECCION....... <= Salta si ax es menor o igual que número.

JA DIRECCION.......... <= Salta si ax es mayor o igual que número.

JNE DIRECCION....... <= Salta si no son iguales.

JCXZ DIRECCION..... <= Salta si CX es cero.


Instrucción nula

¿para qué sirve una instrucción que no hace nada? NOP es una instrucción cuya misión es la de provocar un retraso en la CPU, es frecuente verla en fragmentos de código donde se hacen operaciones de lectura/escritura en puertos para darle tiempo a aceptar otra instrucción al periferico al que se le hace la solicitud. Esta instrucción es muy útil a la hora de parchear un programa, cuando hay un fragmento de código que no queremos que se ejecute simplemente NOPEAMOS los bytes que nos interesa y eliminamos lo que no nos interesa.

Instrucciones aritméticas

Hay muchas instrucciones que nos permiten sumar, restar, multiplicar, dividir etc. pero solo mencionaré las de uso mas frecuente.

ADD AX , BX................. <= Esta instrucción la leeremos como "súmale a ax el contenido de bx y depositalo en ax". Sería como a=a+b en Básic.

SUB AX , NUMERO........ <= Es lo mismo que la instrucción anterior pero restando.

INC BX............................ <= Incrementa en uno el contenido de bx.

DEC CX........................... <= Decrementa en uno el contenido de cx.

MUL BX.......................... <= Atención, para multiplicar un número pondremos en el registro ax uno de los números a multiplicar, a continuación hacemos mul destino donde destino ha de ser bx, cx ó dx y además contendrá el otro número por el que hay que multiplicar ax. En Básic A=A*B.

DIV BX........................... <= Funciona igual mul pero para dividir.

Instrucciones de bucle

Si necesitamos repetir un fragmento de código cualquier cantidad de veces usaremos los bucles que son como los for-next del Básic.

MOV CX , REPETICION...<= Carga en cx la cantidad de veces que se repetirá el bucle.

bucle:...........................<= Esto es una etiqueta.

... ................................<= Instrucciones a ejecutar.

... ................................<= Instrucciones a ejecutar.

LOOP bucle.....................<= Reduce el valor de cx en uno y salta a bucle mientras cx sea mayor que cero.

Instrucciones de lectura/escritura de puertos

Cada dispositivo que está conectado a nuestro ordenador se comunica con él a través de un puerto. Habras oido hablar del puerto serie o el paralelo. Piensa en los puertos como en lugares a donde ha de llegar un dato o donde podremos encontrar un dato. Para enviar un dato a un puerto se utilizan las siguientes instrucciones:

MOV DX , NUMERO_DE_PUERTO


MOV AX , NUMERO_A_ENVIAR


OUT DX , AX

Y para leer un dato de un puerto:

MOV DX , NUMERO_DE_PUERTO


IN AX , DX

Juego completo (con tiempo iremos traduciendo y comentando todas las instrucciones)

AAA - Ascii Adjust for Addition

AAD - Ascii Adjust for Division

AAM - Ascii Adjust for Multiplication

AAS - Ascii Adjust for Subtraction

ADC - Add With Carry

ADD - Arithmetic Addition

AND - Logical And

ARPL - Adjusted Requested Privilege Level of Selector (286+ PM)

BOUND - Array Index Bound Check (80188+)

BSF - Bit Scan Forward (386+)

BSR - Bit Scan Reverse (386+)

BSWAP - Byte Swap (486+)

BT - Bit Test (386+)

BTC - Bit Test with Compliment (386+)

BTR - Bit Test with Reset (386+)

BTS - Bit Test and Set (386+)

CALL - Procedure Call

CBW - Convert Byte to Word

CDQ - Convert Double to Quad (386+)

CLC - Clear Carry

CLD - Clear Direction Flag

CLI - Clear Interrupt Flag (disable)

CLTS - Clear Task Switched Flag (286+ privileged)

CMC - Complement Carry Flag

CMP - Compare

CMPS - Compare String (Byte, Word or Doubleword)

CMPXCHG - Compare and Exchange

CWD - Convert Word to Doubleword

CWDE - Convert Word to Extended Doubleword (386+)

DAA - Decimal Adjust for Addition

DAS - Decimal Adjust for Subtraction

DEC - Decrement

DIV - Divide

ENTER - Make Stack Frame (80188+)

ESC - Escape

HLT - Halt CPU

IDIV - Signed Integer Division

IMUL - Signed Multiply

IN - Input Byte or Word From Port

INC - Increment

INS - Input String from Port (80188+)

INT - Interrupt

INTO - Interrupt on Overflow

INVD - Invalidate Cache (486+)

INVLPG - Invalidate Translation Look-Aside Buffer Entry (486+)

IRET/IRETD - Interrupt Return

Jxx - Jump Instructions Table

JCXZ/JECXZ - Jump if Register (E)CX is Zero

JMP - Unconditional Jump

LAHF - Load Register AH From Flags

LAR - Load Access Rights (286+ protected)

LDS - Load Pointer Using DS

LEA - Load Effective Address

LEAVE - Restore Stack for Procedure Exit (80188+)

LES - Load Pointer Using ES

LFS - Load Pointer Using FS (386+)

LGDT - Load Global Descriptor Table (286+ privileged)

LIDT - Load Interrupt Descriptor Table (286+ privileged)

LGS - Load Pointer Using GS (386+)

LLDT - Load Local Descriptor Table (286+ privileged)

LMSW - Load Machine Status Word (286+ privileged)

LOCK - Lock Bus

LODS - Load String (Byte, Word or Double)

LOOP - Decrement CX and Loop if CX Not Zero

LOOPE/LOOPZ - Loop While Equal / Loop While Zero

LOOPNZ/LOOPNE - Loop While Not Zero / Loop While Not Equal

LSL - Load Segment Limit (286+ protected)

LSS - Load Pointer Using SS (386+)

LTR - Load Task Register (286+ privileged)

MOV - Move Byte or Word

MOVS - Move String (Byte or Word)

MOVSX - Move with Sign Extend (386+)

MOVZX - Move with Zero Extend (386+)

MUL - Unsigned Multiply

NEG - Two's Complement Negation

NOP - No Operation (90h)

NOT - One's Compliment Negation (Logical NOT)

OR - Inclusive Logical OR

OUT - Output Data to Port

OUTS - Output String to Port (80188+)

POP - Pop Word off Stack

POPA/POPAD - Pop All Registers onto Stack (80188+)

POPF/POPFD - Pop Flags off Stack

PUSH - Push Word onto Stack

PUSHA/PUSHAD - Push All Registers onto Stack (80188+)

PUSHF/PUSHFD - Push Flags onto Stack

RCL - Rotate Through Carry Left

RCR - Rotate Through Carry Right

REP - Repeat String Operation

REPE/REPZ - Repeat Equal / Repeat Zero

REPNE/REPNZ - Repeat Not Equal / Repeat Not Zero

RET/RETF - Return From Procedure

ROL - Rotate Left

ROR - Rotate Right

SAHF - Store AH Register into FLAGS

SAL/SHL - Shift Arithmetic Left / Shift Logical Left

SAR - Shift Arithmetic Right

SBB - Subtract with Borrow/Carry

SCAS - Scan String (Byte, Word or Doubleword)

SETAE/SETNB - Set if Above or Equal / Set if Not Below (386+)

SETB/SETNAE - Set if Below / Set if Not Above or Equal (386+)

SETBE/SETNA - Set if Below or Equal / Set if Not Above (386+)

SETE/SETZ - Set if Equal / Set if Zero (386+)

SETNE/SETNZ - Set if Not Equal / Set if Not Zero (386+)

SETL/SETNGE - Set if Less / Set if Not Greater or Equal (386+)

SETGE/SETNL - Set if Greater or Equal / Set if Not Less (386+)

SETLE/SETNG - Set if Less or Equal / Set if Not greater or Equal

SETG/SETNLE - Set if Greater / Set if Not Less or Equal (386+)

SETS - Set if Signed (386+)

SETNS - Set if Not Signed (386+)

SETC - Set if Carry (386+)

SETNC - Set if Not Carry (386+)

SETO - Set if Overflow (386+)

SETNO - Set if Not Overflow (386+)

SETP/SETPE - Set if Parity / Set if Parity Even (386+)

SETNP/SETPO - Set if No Parity / Set if Parity Odd (386+)

SGDT - Store Global Descriptor Table (286+ privileged)

SIDT - Store Interrupt Descriptor Table (286+ privileged)

SHL - Shift Logical Left

SHR - Shift Logical Right

SHLD/SHRD - Double Precision Shift (386+)

SLDT - Store Local Descriptor Table (286+ privileged)

SMSW - Store Machine Status Word (286+ privileged)

STC - Set Carry

STD - Set Direction Flag

STI - Set Interrupt Flag (Enable Interrupts)

STOS - Store String (Byte, Word or Doubleword)

STR - Store Task Register (286+ privileged)

SUB - Subtract

TEST - Test For Bit Pattern

VERR - Verify Read (286+ protected)

VERW - Verify Write (286+ protected)

WAIT/FWAIT - Event Wait

WBINVD - Write-Back and Invalidate Cache (486+)

XCHG - Exchange

XLAT/XLATB - Translate

XOR - Exclusive OR

0 comentarios:

Publicar un comentario