Ensamblador

Ensamblador
Imagen ilustrativa del artículo Ensamblador
Fecha de la primera versión 1949
Extensión de archivo asm ys

Un lenguaje ensamblador o lenguaje ensamblador es, en programación de computadoras , el lenguaje de nivel más bajo que representa el lenguaje de máquina en una forma legible por humanos. Las combinaciones de bits del lenguaje de máquina están representadas por los llamados símbolos " mnemónicos "   , es decir, fáciles de recordar. El programa ensamblador convierte estos mnemónicos en lenguaje de máquina, así como los valores (escritos en decimal) en binarios y las etiquetas de ubicaciones en direcciones, con el fin de crear, por ejemplo, un archivo objeto o un archivo ejecutable .

En la práctica actual, el mismo término ensamblador se usa tanto para designar el lenguaje ensamblador como el programa ensamblador que lo traduce. Hablamos así de “programación en ensamblador”.

La traducción de una vez por todas por muchos intérpretes de cada nombre de variable encontrado en una instrucción (avanzada) por la posición de memoria asociada y de cada constante (escrita por el usuario en decimal) a binario es típica de una operación d. 'Ensamblado, aunque el ensamblador de nombres no se usa comúnmente en este caso particular.

Historia

Los programas del EDSAC (1949), la primera computadora con programas grabados , fueron escritos usando mnemotécnicos alfabéticos de una letra para cada instrucción. Luego, los programadores hicieron la traducción a mano, una operación larga, tediosa y propensa a errores.

El primer programa de ensamblaje fue escrito por Nathaniel Rochester para IBM 701 (la primera computadora lanzada por IBM ) en 1954.

Los lenguajes ensambladores han eliminado muchos de los errores cometidos por los programadores de la primera generación de computadoras al prescindir de la necesidad de memorizar los códigos numéricos de las instrucciones y hacer cálculos de direcciones. La programación en ensamblador se utilizó para escribir todo tipo de programas.

En las décadas de 1970 y 1980, el uso de ensamblador para escribir aplicaciones fue reemplazado en gran medida por el uso de lenguajes de programación de alto nivel: Fortran , COBOL , PL / I , etc. : la potencia de las máquinas lo permitía y dedicar unos minutos de tiempo de computadora a una compilación para ahorrar unas horas de tiempo de programador era una operación rentable, incluso si los compiladores de la época proporcionaban un código menos eficiente (más grande y a menudo más lento). Además, estos lenguajes de alto nivel permitieron superar la dependencia de una única arquitectura de hardware.

Los sistemas operativos se escribieron en lenguaje ensamblador hasta la introducción de MCP a Burroughs en 1961, que estaba escrito en ESPOL, un dialecto de Algol .

El ensamblador ha vuelto algo a favor de los primeros microordenadores, donde las características técnicas (tamaño de memoria reducido, baja potencia de cálculo, arquitectura de memoria específica, etc.) imponían fuertes restricciones, a las que se suma un factor psicológico importante, la actitud "aficionada". de los primeros usuarios de microcomputadoras, que no se conformaron con la lentitud de los programas escritos con el BASIC interpretado generalmente suministrado con la computadora.

Grandes programas fueron escritos íntegramente en ensamblador para microcomputadoras, como el sistema operativo DOS del IBM PC (alrededor de 4000 líneas de código) y la hoja de cálculo Lotus 1-2-3 (su rival Multiplan, que ya existía bajo CP / M , era escrito en C ). En la década de 1990, este fue también el caso de la mayoría de los juegos para videoconsolas (por ejemplo, Mega Drive o Super Nintendo ).

Particularidades del ensamblador

Un idioma específico para cada procesador

El lenguaje de máquina es el único lenguaje que puede ejecutar un procesador . Sin embargo, cada familia de procesadores usa un conjunto diferente de instrucciones .

Por ejemplo, un procesador de la familia x86 reconoce una instrucción del tipo:

10110000 01100001

En lenguaje ensamblador, esta instrucción está representada por un equivalente más fácil de entender para el programador:

movb $0x61,%al

(10110000 = movb% al
01100001 = $ 0x61)

Lo que significa: "escribe el número 97 (el valor se da en hexadecimal  : 61 16 = 97 10 ) en el registro AL".

Por lo tanto, el lenguaje ensamblador, una representación exacta del lenguaje de máquina, es específico para cada arquitectura de procesador . Además, pueden existir varios grupos de mnemónicos o sintaxis de lenguaje ensamblador para un solo conjunto de instrucciones, creando así macroinstrucciones .

Desmontaje

La transformación del código ensamblador en lenguaje de máquina se logra mediante un programa llamado programa ensamblador . La operación inversa , es decir, encontrar el ensamblador equivalente a una pieza de código de máquina, tiene un nombre: es desmontaje .

Al contrario de lo que podría pensarse, no siempre existe una correspondencia uno a uno (una biyección ) entre el código ensamblador y el lenguaje de máquina. Por lo tanto, en algunos procesadores, el desmontaje puede dar como resultado un código que es muy difícil de entender para un ser humano, mientras que sigue siendo perfectamente compilable por una computadora. La imposibilidad de un desmontaje puede tener varios motivos: uso de código auto modificable, instrucciones de tamaño variable, imposibilidad de distinguir entre código y datos, etc. ( código impenetrable )

Además, muchos elementos presentes en el código ensamblador se pierden durante su traducción al lenguaje de máquina. Al crear código en ensamblador, el programador puede asignar nombres a posiciones en la memoria, comentar su código , usar macroinstrucciones o usar código generado bajo condiciones en el momento del ensamblaje. Todos estos elementos se reducen durante el montaje a lo estrictamente necesario para la máquina y por tanto no aparecen claramente durante el desmontaje: por ejemplo, una posición en la memoria solo se marca con su dirección numérica o con un offset .

Instrucciones de la máquina

Algunas operaciones fundamentales están disponibles en la mayoría de los conjuntos de instrucciones.

  • Desplazamiento en la memoria:
    • cargar un valor en un registro;
    • mover un valor de una ubicación de memoria a un registro y viceversa;
  • Cálculo:
    • suma o resta de los valores de dos registros y carga del resultado en un registro;
    • combinación de valores de dos registros después de una operación booleana (u operación bit a bit);
  • Modificación de la secuencia del programa:
    • saltar a otra ubicación en el programa (normalmente las instrucciones se ejecutan secuencialmente, una tras otra);
    • saltar a otra ubicación, pero después de haber guardado la ubicación de la siguiente instrucción para que pueda volver a ella (punto de retorno);
    • volver al último punto de retorno;
  • Comparación:
    • compare los valores de dos registros.

Y hay instrucciones específicas con una o algunas instrucciones para operaciones que deberían haber tomado mucho. Ejemplos:

Directivas de lenguaje ensamblador

Además de codificar las instrucciones de la máquina, los lenguajes ensambladores tienen directivas adicionales para ensamblar bloques de datos y asignar direcciones a instrucciones definiendo etiquetas o rótulos.

Son capaces de definir expresiones simbólicas que se evalúan en cada ensamblado, lo que hace que el código sea aún más fácil de leer y comprender.

Por lo general, tienen un lenguaje de macros incorporado para facilitar la generación de códigos complejos o bloques de datos.

Ejemplos sencillos

A continuación, se muestran algunos ejemplos sencillos:

$ gcc foo.S -c -o foo.o $ ld foo.o -o foo $ ./foo

Mostrar hola

(Los comentarios van después del punto y coma)

str: .ascii "Bonjour\n" .global _start _start: movl $4, %eax movl $1, %ebx movl $str, %ecx movl $8, %edx int $0x80 movl $1, %eax movl $0, %ebx int $0x80 ;Compilation: ;as code.s -o code.o ;ld code.o -o code ;Execution: ;./code

Leer el teclado (16 caracteres como máximo) y luego mostrarlo

# define N 16 .global _start .comm BUFF , N _start: mov $3 , %eax mov $0 , %ebx mov $BUFF , %ecx mov $N , %edx int $0x80 mov %eax , %edx mov $4 , %eax mov $1 , %ebx mov $BUFF , %ecx int $0x80 mov $1 , %eax mov $0 , %ebx int $0x80

Ejemplos simples, sintaxis Intel x86

A continuación, se muestran los mismos ejemplos, con algunas diferencias:

  • en sintaxis Intel x86 , escrito para el ensamblador NASM  ;
  • utilizando el conjunto de instrucciones i386  ;
  • para ser utilizado de la siguiente manera:
$ nasm -f elf foo.asm $ ld -o foo foo.o -melf_i386 $ ./foo

Mostrar buenas noches

(Los comentarios van después del punto y coma)

section .data ; Variables initialisées Buffer: db 'Bonsoir', 10 ; En ascii, 10 = '\n'. La virgule sert à concaténer les chaines BufferSize: equ $-Buffer ; Taille de la chaine section .text ; Le code source est écrit dans cette section global _start ; Définition de l'entrée du programme _start: ; Entrée du programme mov eax, 4 ; Appel de sys_write mov ebx, 1 ; Sortie standard STDOUT mov ecx, Buffer ; Chaine à afficher mov edx, BufferSize ; Taille de la chaine int 80h ; Interruption du kernel mov eax, 1 ; Appel de sys_exit mov ebx, 0 ; Code de retour int 80h ; Interruption du kernel


Leer el teclado (64 caracteres como máximo) y luego mostrarlo

section .bss ; Section des variables non-initialisees Buffer: resb 64 ; Reservation de 64 blocs (octets ?) memoire pour la variable où sera stockee l'entree de l'utilisateur BufferSize: equ $-Buffer ; taille de cette variable section .text ; Section du code source global _start _start: ; Entree du programme mov eax, 3 ; Appel de sys_read mov ebx, 0 ; Entree standard STDIN mov ecx, Buffer ; Stockage de l'entree de l'utilisateur mov edx, BufferSize ; Taille maximale int 80h ; Interruption du kernel mov eax, 4 ; Appel de sys_write mov ebx, 1 ; Sortie standard STDOUT mov ecx, Buffer ; Chaine à afficher mov edx, BufferSize ; Taille de la chaine int 80h ; Interruption du kernel mov eax, 1 ; Appel de sys_exit mov ebx, 0 ; Code de retour int 80h ; Interruption du kernel

Uso de lenguaje ensamblador

Hay debates sobre la utilidad del lenguaje ensamblador. En muchos casos, los compiladores - optimizadores pueden transformar el lenguaje de alto nivel en código que se ejecuta tan eficientemente como el código ensamblador escrito a mano por un muy buen programador, sin dejar de ser mucho más fácil, rápido (y por lo tanto menos eficiente). mantener.

La eficiencia ya era una preocupación en la década de 1950, encontramos un rastro de ella en el manual de lenguaje de Fortran (publicado en 1956) para la computadora IBM 704  : los programas de objetos producidos por Fortran serán casi tan eficientes como los escritos por buenos programadores .

Mientras tanto, los compiladores han hecho un enorme progreso, por lo tanto, es obvio que la gran mayoría de los programas ahora están escritos en lenguajes de alto nivel por razones económicas, el costo de programación adicional supera la ganancia resultante de la mejora esperada en el rendimiento.

Sin embargo, todavía hay algunos casos muy específicos en los que el uso de ensamblador todavía está justificado:

  1. Algunos cálculos complejos escritos directamente en ensamblador, en particular en máquinas masivamente paralelas , serán más rápidos, ya que los compiladores no serán lo suficientemente sofisticados para aprovechar las especificidades de estas arquitecturas;
  2. Algunas rutinas ( controladores ) a veces son más fáciles de escribir en un lenguaje de bajo nivel;
  3. Las tareas muy dependientes del sistema que se ejecutan en el espacio de memoria del sistema operativo a veces son difíciles o incluso imposibles de escribir en un lenguaje de alto nivel. Por ejemplo, las instrucciones de montaje que permiten a Windows gestionar el cambio de tarea (LGDT y LLDT) en el i386 y el siguiente microprocesador no pueden ser emuladas o generadas por un lenguaje de alto nivel. Deben codificarse necesariamente en una pequeña subrutina de ensamblaje que se llamará desde un programa escrito en lenguaje de alto nivel.

Algunos compiladores transforman, cuando su opción de optimización más alta no está habilitada , programas escritos en lenguaje de alto nivel en código ensamblador, cada instrucción de alto nivel resulta en una serie de instrucciones ensambladoras rigurosamente equivalentes y usando los mismos símbolos; esto le permite ver el código con fines de depuración y creación de perfiles , lo que a veces puede ahorrar mucho más tiempo al revisar un algoritmo . En ningún caso se podrán conservar estas técnicas para la optimización final.

La programación de sistemas embebidos , a menudo basados ​​en microcontroladores , es un “nicho” tradicional para la programación de ensamblajes. De hecho, estos sistemas a menudo tienen recursos muy limitados (por ejemplo, un microcontrolador PIC 16F84 está limitado a 1.024 instrucciones de 14 bits y su RAM contiene 136 bytes) y, por lo tanto, requieren una programación de bajo nivel muy optimizada para aprovechar sus posibilidades. Sin embargo, la evolución del hardware hace que los componentes de estos sistemas se vuelvan cada vez más potentes a un coste constante y con un consumo energético constante, la inversión en una programación "cualquier ensamblador" mucho más cara en horas de trabajo se convierte entonces en una inversión tonterías en términos de esfuerzo. Normalmente, la programación en ensamblador es mucho más larga, más delicada (porque el programador debe tener en cuenta todos los microdetalles del desarrollo de los que se abstiene en el lenguaje de alto nivel) y, por lo tanto, considerablemente más cara que la programación en lenguaje de alto nivel. Por lo tanto, solo debe reservarse para situaciones para las que no se puede hacer de otra manera.

Macro-ensamblador

Muchos ensambladores admiten un lenguaje de macros . Se trata de agrupar varias instrucciones para tener una secuencia más lógica y menos tediosa.
Por ejemplo (en ensamblador MASM de Microsoft ):

putchar Macro car ; Prototype de la macro ifdef car ; si car est défini mov dl,car ; le mettre dans dl endif mov ah,2 ; ah=2 : fonction "putchar" en DOS int 21h ; appel au DOS endm ; fin macro

es una macro que muestra un carácter en MS-DOS . Se utilizará, por ejemplo, de la siguiente manera:

putchar "X"

Y generará:

mov dl,"X" mov ah,2 int 21h

Pseudo-instrucciones

Una pseudoinstrucción es un tipo especial de macroinstrucción. Está predefinido por el editor del software de ensamblaje y su función es emular una instrucción faltante del procesador o facilitar el uso de una instrucción existente. Como la pseudoinstrucción tiene un nombre muy similar al de una instrucción de procesador real, a primera vista es posible confundirla con una de estas últimas. Por ejemplo, es posible que un procesador RISC no tenga una instrucción JMP, lo que le permite saltar a un punto particular del programa y continuar ejecutándolo en secuencia. En este caso, el editor de software habrá creado para el programador una pseudoinstrucción “JMP <parámetro>”, que será reemplazada durante el ensamblaje por una instrucción “mov pc , <parámetro>”, siendo pc la instrucción de puntero a punto de ser ejecutado. Otro ejemplo, una pseudoinstrucción “PUSH <parámetro>” será reemplazada por un almacenamiento de <parámetro> en la dirección apuntada por sp con pre-decremento de esta última, siendo sp el puntero de pila del procesador.

En microprocesadores o microcontroladores RISC como los de la familia ARM , no existe una instrucción de ensamblaje que permita cargar cualquier constante inmediata en un registro, independientemente de su valor. La mayoría de los ensambladores tienen una pseudoinstrucción que permite tal carga de la manera más eficiente posible en términos de tiempo de ejecución, ahorrándole esta tarea al programador.

Programación estructurada en ensamblador

Soporte para programación estructurada  : algunos elementos de programación estructurada han sido integrados para codificar el flujo de ejecución por Dr. HD Mills (Marzo de 1970), e implementado por Marvin Kessler, quien extendió el ensamblador de macros S / 360 con if / else / endif e incluso bloques de control de flujo. Esta fue una forma de reducir o eliminar el uso de operaciones de salto en código ensamblador.

Notas y referencias

  1. Laurent Bloch , Introducción a la programación con Scheme , Éditions TECHNIP,2011( leer en línea ).

Ver también