Lenguaje ensamblador (Parte 5)

 Lenguaje ensamblador 

Un lenguaje ensamblador es un nivel bajo lenguaje de programación diseñado para un tipo específico de procesador. Puede ser producido por compilar código fuente de un lenguaje de programación de alto nivel (como C / C ++) pero también se puede escribir desde cero. El código de ensamblaje se puede convertir a código de máquina usando un ensambladorYa que la mayoría compiladores convertir código fuente directamente al código de máquina, los desarrolladores de software a menudo crean de subrogación sin usar lenguaje ensamblador. Sin embargo, en algunos casos, el código de ensamblaje se puede usar para ajustar un programa. Por ejemplo, un programador puede escribir un específico está en lenguaje ensamblador para asegurarse de que funcione de la manera más eficiente posible.


Como se compila 

Un ensamblador permite ensamblar código en lenguaje ensamblador. Nasm es un ensamblador libre para la arquitectura x86. Podemos encontrarlo para diversos sistemas operativos como Linux o Windows. En este tutorial vamos a ver cómo crear un ejecutable en Linux y Windows a partir de un código fuente destinado a Nasm.

En Windows cambia la manera en la que se llama a los parámetros, la función llamada debe limpiar ella misma la pila. Además, main no existe y debe ser reemplazado por WinMain.

Si tu punto de entrada es _start o main, deberás cambiarlo a "_WinMain@16" y sustituir “ret” por "ret 16".

A continuación un ejemplo de un fichero fuente correcto utilizado en Windows:

section .text
global _WinMain@16
_WinMain@16:
mov eax, 0
ret 16

Etapa 1. Instalar los programas necesarios

Primero vamos a instalar Nasm. Podemos descargarlo desde el sitio oficial y seleccionamos la versión Win32 binario.

La etapa más delicada será la de instalar MingW, un entorno de desarrollo libre para Windows.

Hacemos clic y seleccionamos la última versión de MinGW. Ejecutamos este instalador. Si nos ofrece actualizar el instalador desde el inicio, decimos que no. Debemos dejar todas las opciones seleccionadas por defecto.

Ahora vamos a insertar Nasm en el entorno de desarrollo MingW. Descomprime el fichero Nasm y deberías encontrar una carpeta que contiene un fichero llamado nasm.exe.
Copia este fichero en la carpeta C:\MinGW\bin

Etapa 2. Crear un fichero fuente

Al igual que en Linux, no es necesario utilizar un editor específico para crear un fichero fuente destinado a Nasm. Podemos utilizar por ejemplo el bloc de notas, pero debemos tener cuidado con la extensión .txt.

Debemos evitar utilizar procesadores de texto como Word o WordPad que pueden guardar el fichero en un formato no deseado.

Si lo utilizas, también puedes utilizar un editor que utiliza la coloración sintáctica para la sintaxis de Nasm como NasmEdit IDE (gratuito).

En todos los casos recomiendo poner la extensión .asm a los ficheros.

Etapa 3. Ensamblar el fichero fuente

Abre el intérprete de comandos de Windows (escribe cmd.exe en “Ejecutar” del menú Inicio o directamente “cmd” en la barra de búsqueda) En el intérprete de comandos debes ir a la carpeta que contiene tu fichero fuente utilizando el comando “cd”.
Una vez que estés en esta carpeta, ensambla tu fichero fuente (llamémoslo test.asm) con este comando:

nasm -f win32 test.asm -o test.o

Ahora tienes un fichero objeto que aún no es ejecutable. Pasemos a la última etapa.

Etapa 4. Creación y ejecución del programa

En la ventana DOS escribe el siguiente comando para crear el ejecutable:

ld test.o -o test.exe

Para testar el ejecutable, simplemente escribe “test” en la ventana DOS. Si deseas un depurador para visualizar mejor lo que sucede, utiliza OllyDbg, un excelente depurador.


Tipos de datos 

El ensamblador reconoce un conjunto de tipos de datos internos básicos (tipos de datos intrínsecos), de acuerdo con el tamaño de los datos (bytes, palabras, palabras dobles, etc.), ya sea con signo, entero o número real para describir su tipo. Estos tipos tienen un grado considerable de superposición, por ejemplo, el tipo DWORD (entero sin signo de 32 bits) se puede intercambiar con el tipo SDWORD (entero con signo de 32 bits)

Algunas personas pueden decir que el programador usa SDWORD para decirle al lector que este valor está firmado, pero no es obligatorio para el ensamblador. El ensamblador solo evalúa el tamaño del operando. Entonces, por ejemplo, los programadores solo pueden especificar enteros de 32 bits como tipos DWORD, SDWORD o REAL4.

La siguiente tabla ofrece una lista de todos los tipos de datos internos Los símbolos IEEE en algunas entradas se refieren al formato de número real estándar publicado por IEEE Computer Society.



Operadores 

Cada operador tiene asignado un número de precedencia que determina el orden en el cual el operador y sus operandos son evaluados. Los números de prioridad van desde 1 (la mayor prioridad) a 7 (la menor prioridad). El nivel de prioridad 2 no existe y por lo tanto los niveles disponibles son 1 y del 3 al 7.

Las siguientes normas determinan que expresiones son evaluadas:

  • Los operadores de mayor prioridad son evaluados primero, después los operadores de segunda mayor prioridad y así se seguirá hasta que se evalúe el nivel de menor prioridad.
  • Los operadores de igual prioridad son evaluados de izquierda a derecha en la expresión.
  • Los paréntesis "( )" pueden ser utilizador para agrupar operadores y operandos, y para controlar el orden en el que las expresiones son evaluadas. Por ejemplo, la siguiente expresión da como resultado 1.

7/(1+(2*3))

Las tablas siguientes proporcionan un sumario de los operadores en orden de prioridad. Se muestra entre paréntesis un símbolo equivalente al operador, cuando éste existe.

       

Sumario de los operadores de ensamblador

           

Operadores unarios - 1

         

+

Suma unaria.

BITNOT (~)

NOT de bit.

BYTE2

Segundo byte.

BYTE3

Tercer byte.

DATE

Fecha / Hora

HIGH

Byte alto.

HWRD

Word alto.

LOW

Byte bajo.

LWRD

Word bajo.

NOT (!)

NOT lógico.

SFB

Comienzo de segmento.

SFE

Fin de segmento.

SIZEOF

Tamaño de segmento.

-

Resta unaria.

         

Operadores de desplazamiento y de aritmética de multiplicación - 3

          

*

Multiplicación.

/

División.

MOD (%)

Módulo.

SHL (<<)

Desplazamiento lógico hacia la izquierda.

SHR (>>)

Desplazamiento lógico hacia la derecha.

        

Operadores aritméticos de suma - 4

     

+

Suma

-

Resta

     

Operadores AND - 5

     

AND (&&)

AND lógico.

BITAND (&)

AND de bit.

           

Operadores OR - 6

    

BITOR ( | )

OR lógico de bit.

BITXOR ( ^ )

OR exclusiva de bit.

OR ( | | )

OR lógico.

XOR

OR lógico exclusiva.

        

Operadores de comparación – 7

        

EQ, =, = =

Igual.

GE, >=

Mayor o igual que.

GT, >

Mayor que.

LE, <=

Menor o igual que.

LT, <

Menor que.

NE, <>, !=

No igual.

UGT

Unsigned mayor que.

ULT

Unsigned menor que.

        

 Estructuras de control 

Las estructuras de control son un componente esencial al momento de desarrollar un programa, ya que estas nos permiten modificar el flujo dependiendo de ciertas condiciones todo esto para cumplir con una tarea específica.

Estas estructuras pueden ser divididas en 2 categorías:

Sentencias Selectivas: Seleccionan la ruta para solucionar un problema dependiendo una condición.

If, Else

If, Else If, Else


Procedimientos y Macros 

Un procedimiento es una secuencia de instrucciones que en conjunto llevan a cabo una tarea específica.

En programación un procedimiento es un segmento de código que cuenta con instrucciones a las cuales se puede acceder desde cualquier parte del programa y una vez se termina la ejecución de estas, el programa continua con su ejecución normal, tomando el control la siguiente línea después de la llamada al procedimiento. Los procedimientos tienden a ser grupos de instrucciones que se necesitara ejecutar más de una vez dentro de un programa, ya que un procedimiento puede ser llamado en cualquier momento durante la ejecución del programa principal, la cantidad de veces que sea necesario sin necesidad de reescribir el código.

En ensamblador los procedimientos están conformados por las siguientes partes:

Declaración del procedimiento:

Los procedimientos en ensamblador se declaran mediante la sintaxis nombreprocedimiento Proc [far/near] dependiendo de si es un procedimiento cercano o lejano.

Código del procedimiento:

Dentro del procedimiento se escribe el código de ensamblador que se quiere utilizar.

Directiva de regreso:

Antes del final de un procedimiento en ensamblador se escribe la directiva de regreso ret,, la cual regresa el control a la línea desde donde fue llamado el procedimiento.

Terminación del procedimiento:

Para terminar un procedimiento se escribe el nombre del procedimiento seguido de la palabra reservaba endp.

Existen dos tipos de procedimientos que pueden utilizarse dentro de ensamblador, estos son los internos y los externos.

Procedimientos Internos:

Estos procedimientos son aquellos que son declarados dentro del mismo archivo de programa que serán llamados, también se les llama procedimientos locales.

Para utilizarlos basta con escribir la palabra reservada call seguida del nombre del procedimiento a utilizar.

 

Procedimientos Externos:

Los procedimientos externos se crean de la misma forma que los internos pero tienen la diferencia de que están en archivos separados al programa de donde el procedimiento es llamado, por lo que se necesitan instrucciones extra para poder utilizarlos, las cuales son las siguientes:

PUBLIC:

Es necesario declarar como publico el procedimiento que se desea utilizar para que sea posible acceder a él desde otro programa.

EXTRN:

Permite abrir procedimientos desde otro programa aunque no se encuentre enlazado directamente.

INCLUDE:

Enlaza el programa que llama el procedimiento con el que lo contiene, permitiendo utilizarlo como si fuera un procedimiento propio.

Ejemplo:
public imprime               →       Procedimiento dentro del primero archivo declarado                                                                como publico.
imprime proc far           →              Declaracion del procedimiento.
mov ah,09h                     →             Código del procedimiento
int 21h
ret                                     →            Directiva de regreso.
imprime endp                 →            Fin del procedimiento.

extrn imprime:near      →             Se incluye el procedimiento externo imprime en el          segundo archivo.

call imprime                   →              Se llama al procedimiento como si fuera local.

Una macro es un conjunto de instrucciones que pueden ser llamadas utilizando su nombre para ejecutarse dentro de un programa, estas solo se escriben una vez dentro del código y pueden utilizarse las veces que sea necesario.

En ensamblador la diferencia entre los procedimientos y las macros es que las macros tienen la posibilidad de utilizar parámetros por lo que pueden llevar a cabo tareas que los procedimientos no podrían.

Las macros constan de tres partes que las definen:

Declaración:

El inicio de una macro se declara escribiendo el nombre que tendrá, seguido de la palabra reservada MACRO y opcionalmente, puede contener parámetros después.

Cuerpo:

Contiene todas las instrucciones que ejecutara la macro cuando sea llamada dentro del programa en ejecución.

Fin:

Toda macro debe terminar con la palabra reservada ENDM para indicar el fin de la misma.

Al igual que con los procedimientos, existen dos tipos de macros que son externas e internas, pero son muy fáciles de utilizar de cualquiera de las dos formas, si se desea utilizar una macro externa se escribe la palabra Include seguida del nombre del archivo de texto donde están guardadas las macros antes del código del programa.

Ejemplo:

Include Macro.txt                       →         Se enlaza con el archivo Macro.txt.

.model small                                →          Declaración del tamaño del programa.

.stack 64                                        →           Declaración de la pila.

.Data                                              →         Inicio del segmento de datos.

.Code                                             →        Inicio del segmento de código.

Macro1                                         →        Se llama a la macro Macro1.

.Exit                                               →          Inicio del segmento final.

End                                                →          Fin del programa.


Su vínculo con C/C++ 

Bueno supongo que todos saben que la mayoría de los compiladores de C++ permiten incluir instrucciones de ensamblador dentro del código fuente y estas son ensambladas directamente en el código objeto. A esto se le llama online assembler generalmente.

Pero eso no es siquiera interesante; lo que sí es muy interesante es la capacidad de escribir librerías (estáticas y dinámicas) tanto en ensamblador como en C++ (MASM y VC++) y linkearlas en ambos lenguajes.

O sea, que por ejemplo podemos crear una librería en ensamblador y linkearla estáticamente en un programa de C++. Vamos a ver el ejemplo.

Código de la librería de ensamblador: 

.386
.model stdcall,flat

include windows.inc
include kernel32.inc
include user32.inc

includelib kernel32.lib
includelib user32.lib

CTEXT MACRO text:VARARG
    LOCAL TxtName
    .data
     TxtName BYTE text,0
    .code
  EXITM <OFFSET TxtName>
ENDM

SayLong PROTO number:DWORD

.code

  SayLong PROC number:DWORD
    LOCAL pointer:DWORD
    invoke GetProcessHeap
    invoke HeapAlloc,eax,HEAP_ZERO_MEMORY or HEAP_GENERATE_EXCEPTIONS,1024
    mov pointer,eax
    invoke wsprintf,pointer,CTEXT("%d"),number
    invoke MessageBox,0,pointer,pointer,0
    invoke GetProcessHeap
    invoke HeapFree,eax,0,pointer
    ret
  SayLong ENDP

End



Tranquilidad que ahora paso a explicar el código anterior.

.386 es una directiva que le indica a MASM que nuestro código va optimizado para la arquitectura 386

.model stdcall,flat es una directiva que le indica a MASM que nuestro código usa stdcall (convención de funciones que utiliza la API de Windows) y un modelo de memoria plano (el unico posible en Windows).

Los includes justamente incluyen bibliotecas al estilo de los .H de C++, para conseguir estas bibliotecas hay que tener MASM32 MASM32 instalado y con el path configurado.

Los includelib nos ahorran pasarle en la línea de comandos parámetros de librerías que vamos a linkear, también hay que tener el MASM32 o sino crearlas (eso lo dejamos para otro tutorial).

El macro CTEXT nos permite utilizar texto al modo de C++ usando CTEXT("TEXTO") cosa que no es posible directamente en ensamblador.

La funcion SayLong simplemente muestra un MessageBox con el número que hayamos especificado como parámetro.

Ustedes dirán que carajo hacemos con este código, bueno acá esta la respuesta, ensamblamos y linkeamos con MASM:

ml /c /Cp /coff asm_called.asm
lib asm_called.obj

Con eso conseguimos el archivo asm_called.obj que es el código objeto y el archivo asm_called.lib que es el código ejecutable que vamos a linkear desde C++.

Ahora el código de C++ que llama a la función de ensamblador
#include <windows.h>
extern "C" void __stdcall SayLong(DWORD number);

void main()
{
  SayLong(50);
}


Muy simple este código, muy simple. Declara la función externa SayLong con el paso de parámetros de stdcall (la misma que usamos en ensamblador). Y la llama desde un main con un parámetro de 50.

Compilamos y linkeamos con VC++:

cl -c calling_asm.cpp
link calling_asm.obj asm_called.lib kernel32.lib user32.lib

Ahora obtenemos un archivo objeto calling_asm.obj y un archivo ejecutable calling_asm.exe.

Ejecuten el archivo calling_asm.exe y verán que aparece un MessageBox con el número 50, la rutina SayLong (programa en ensamblador) fue llamada desde C++.

 




Comentarios