Articulo de referencia

lenguaje ensamblador

.asm , .s , .S , .inc , .wla , .SRC as well as several others depending on the assembler"},"file format":{"wt":""},"website":{"wt":""},"implementations":{"wt":""},"dialects":{"w...

En informática, el lenguaje ensamblador (alternativamente lenguaje ensamblador [ 1 ] o código máquina simbólico ), [ 2 ] [ 3 ] [ 4 ] a menudo denominado simplemente ensamblador y comúnmente abreviado como ASM o asm , es cualquier lenguaje de programación de bajo nivel con una correspondencia muy fuerte entre las instrucciones del lenguaje y las instrucciones del código máquina de la arquitectura . [ 5 ] El lenguaje ensamblador suele tener una instrucción por cada instrucción del código máquina (1:1), pero también se admiten generalmente constantes, comentarios , directivas del ensamblador , [ 6 ] etiquetas simbólicas de, por ejemplo, ubicaciones de memoria , registros y macros [ 7 ] [ 1 ] .

El primer código ensamblador en el que se utiliza un lenguaje para representar instrucciones de código máquina se encuentra en la obra de Kathleen y Andrew Donald Booth de 1947, Coding for ARC . [ 8 ] El código ensamblador se convierte en código máquina ejecutable mediante un programa de utilidad denominado ensamblador . El término "ensamblador" se atribuye generalmente a Wilkes , Wheeler y Gill en su libro de 1951 The Preparation of Programs for an Electronic Digital Computer , [ 9 ] quienes, sin embargo, utilizaron el término para referirse a "un programa que ensambla otro programa que consta de varias secciones en un solo programa". [ 10 ] El proceso de conversión se denomina ensamblaje , como en el ensamblaje del código fuente . El paso computacional en el que un ensamblador procesa un programa se denomina tiempo de ensamblaje .

Debido a que el lenguaje ensamblador depende de las instrucciones del código máquina, cada lenguaje ensamblador [ nb 1 ] es específico de una arquitectura de computadora particular , como x86 o ARM . [ 11 ] [ 12 ] [ 13 ]

A veces existe más de un ensamblador para la misma arquitectura, y a veces un ensamblador es específico de un sistema operativo o de sistemas operativos particulares. La mayoría de los lenguajes ensamblador no proporcionan una sintaxis específica para las llamadas al sistema operativo, y la mayoría de los lenguajes ensamblador se pueden usar universalmente con cualquier sistema operativo, [ nb 2 ] ya que el lenguaje proporciona acceso a todas las capacidades reales del procesador , sobre las cuales se basan en última instancia todos los mecanismos de llamadas al sistema . A diferencia de los lenguajes ensamblador, la mayoría de los lenguajes de programación de alto nivel son generalmente portables a través de múltiples arquitecturas, pero requieren interpretación o compilación , tareas mucho más complicadas que el ensamblaje.

En las primeras décadas de la informática, era común que tanto la programación de sistemas como la de aplicaciones se realizaran íntegramente en lenguaje ensamblador. Si bien sigue siendo insustituible para algunos fines, la mayor parte de la programación se lleva a cabo actualmente en lenguajes interpretados y compilados de alto nivel. En « No Silver Bullet », Fred Brooks resumió los efectos del abandono de la programación en lenguaje ensamblador: «Sin duda, el golpe más poderoso para la productividad, la fiabilidad y la simplicidad del software ha sido el uso progresivo de lenguajes de alto nivel para la programación. La mayoría de los observadores atribuyen a este desarrollo al menos un aumento de cinco veces en la productividad, con las consiguientes mejoras en la fiabilidad, la simplicidad y la comprensibilidad». [ 14 ]

Actualmente, es común utilizar pequeñas cantidades de código ensamblador dentro de sistemas más grandes implementados en un lenguaje de alto nivel, por razones de rendimiento o para interactuar directamente con el hardware de maneras no compatibles con el lenguaje de alto nivel. Por ejemplo, poco menos del 2 % del código fuente de la versión 4.9 del kernel de Linux está escrito en ensamblador; más del 97 % está escrito en C. [ 15 ]

sintaxis del lenguaje ensamblador

El lenguaje ensamblador utiliza símbolos mnemotécnicos para representar instrucciones de máquina de bajo nivel ( códigos de operación ), directivas y, generalmente, registros y banderas de arquitectura . [ 16 ] Algunos de los mnemotécnicos pueden estar integrados y otros definidos por el usuario. Muchas operaciones requieren uno o más operandos para formar una instrucción completa. La mayoría de los ensambladores permiten constantes con nombre, registros y etiquetas para ubicaciones de programa y memoria, y pueden calcular expresiones para operandos. De esta manera, los programadores se liberan de cálculos repetitivos tediosos y los programas en ensamblador son mucho más legibles que el código máquina. Dependiendo de la arquitectura, estos elementos también pueden combinarse para instrucciones específicas o modos de direccionamiento utilizando desplazamientos u otros datos, así como direcciones fijas. Muchos ensambladores ofrecen mecanismos adicionales para facilitar el desarrollo de programas, controlar el proceso de ensamblaje y ayudar en la depuración .

Algunos ensambladores tienen sintaxis orientada a columnas, con campos específicos en columnas específicas; esto era muy común en las máquinas que usaban tarjetas perforadas en las décadas de 1950 y principios de 1960. Algunos ensambladores tienen sintaxis de formato libre, con campos separados por delimitadores, por ejemplo, signos de puntuación o espacios en blanco . Algunos ensambladores son híbridos, con, por ejemplo, etiquetas en una columna específica y otros campos separados por delimitadores; esto se volvió más común que la sintaxis orientada a columnas en la década de 1960.

Terminología

  • Un ensamblador de macros es un ensamblador que incluye una función de macroinstrucciones para que el texto en lenguaje ensamblador (parametrizado) pueda representarse mediante un nombre, y ese nombre pueda utilizarse para insertar el texto expandido en otro código.
    • El código abierto se refiere a cualquier entrada de ensamblador que no esté definida dentro de una macro.
  • Un ensamblador cruzado (véase también compilador cruzado ) es un ensamblador que se ejecuta en un ordenador o sistema operativo (el sistema anfitrión ) de un tipo diferente al del sistema en el que se ejecutará el código resultante (el sistema de destino ). El ensamblaje cruzado facilita el desarrollo de programas para sistemas que no disponen de los recursos necesarios para el desarrollo de software, como un sistema embebido o un microcontrolador . En tal caso, el código objeto resultante debe transferirse al sistema de destino mediante memoria de solo lectura (ROM, EPROM , etc.), un programador (cuando la memoria de solo lectura está integrada en el dispositivo, como en los microcontroladores) o un enlace de datos, utilizando una copia exacta bit a bit del código objeto o una representación textual de dicho código (como Intel hex o Motorola S-record ).
  • Un ensamblador de alto nivel es un programa que proporciona abstracciones de lenguaje más comúnmente asociadas con lenguajes de alto nivel, como estructuras de control avanzadas ( IF/THEN/ELSE , DO CASE, etc.) y tipos de datos abstractos de alto nivel, incluyendo estructuras/registros, uniones, clases y conjuntos.
  • Un microensamblador es un programa que ayuda a preparar un microprograma para controlar el funcionamiento de bajo nivel de un ordenador.
  • Un metaensamblador es "un programa que acepta la descripción sintáctica y semántica de un lenguaje ensamblador y genera un ensamblador para ese lenguaje" [ 17 ] o que acepta un archivo fuente de ensamblador junto con dicha descripción y ensambla el archivo fuente de acuerdo con esa descripción. Los ensambladores "Meta-Symbol" para las series de computadoras SDS 9 y SDS Sigma son metaensambladores. [ 18 ] Sperry Univac también proporcionó un metaensamblador para la serie UNIVAC 1100/2200 . [ 19 ]
  • El ensamblador en línea (o ensamblador embebido ) es código ensamblador contenido dentro de un programa en lenguaje de alto nivel. [ 20 ] Esto se usa con mayor frecuencia en programas de sistema que necesitan acceso directo al hardware.

Conceptos clave

Ensamblador

Un programa ensamblador crea código objeto traduciendo combinaciones de mnemónicos y sintaxis para operaciones y modos de direccionamiento a sus equivalentes numéricos. Esta representación normalmente incluye un código de operación (" opcode ") así como otros bits de control y datos. El ensamblador también calcula expresiones constantes y resuelve nombres simbólicos para ubicaciones de memoria y otras entidades. [ 21 ] El uso de referencias simbólicas es una característica clave de los ensambladores, que ahorra cálculos tediosos y actualizaciones manuales de direcciones después de modificaciones del programa. La mayoría de los ensambladores también incluyen funciones de macro para realizar sustitución textual, por ejemplo, para generar secuencias cortas comunes de instrucciones como inline , en lugar de subrutinas llamadas .

Algunos ensambladores también pueden realizar optimizaciones sencillas específicas del conjunto de instrucciones . Un ejemplo concreto son los omnipresentes ensambladores x86 de diversos proveedores. Mediante el dimensionamiento de saltos , [ 21 ] la mayoría de ellos pueden realizar reemplazos de instrucciones de salto (saltos largos reemplazados por saltos cortos o relativos) en cualquier número de pasadas, según se solicite. Otros incluso pueden realizar una simple reorganización o inserción de instrucciones, como algunos ensambladores para arquitecturas RISC que ayudan a optimizar una planificación de instrucciones adecuada para aprovechar la tubería de la CPU de la forma más eficiente posible. [ 22 ]

Los ensambladores existen desde la década de 1950, como primer paso previo al lenguaje máquina y a los lenguajes de programación de alto nivel como Fortran , Algol , COBOL y Lisp . También han existido diversas clases de traductores y generadores de código semiautomáticos con propiedades similares tanto al lenguaje ensamblador como a los lenguajes de alto nivel, siendo Speedcode quizás uno de los ejemplos más conocidos.

Puede haber varios ensambladores con sintaxis diferente para una CPU o arquitectura de conjunto de instrucciones en particular . Por ejemplo, una instrucción para agregar datos de memoria a un registro en un procesador de la familia x86 podría ser , en la sintaxisadd eax,[ebx] original de Intel , mientras que esta se escribiría en la sintaxis AT&T utilizada por el ensamblador GNU . A pesar de las diferentes apariencias, las diferentes formas sintácticas generalmente generan el mismo código máquina numérico . Un solo ensamblador también puede tener diferentes modos para admitir variaciones en las formas sintácticas, así como sus interpretaciones semánticas exactas (como la sintaxis FASM , la sintaxis TASM , el modo ideal, etc., en el caso especial de la programación en ensamblador x86 ).addl (%ebx),%eax

Número de pases

Existen dos tipos de ensambladores, según la cantidad de pasadas que se necesiten a través del código fuente (cuántas veces el ensamblador lee el código fuente) para producir el archivo objeto.

  • Los ensambladores de una sola pasada procesan el código fuente una sola vez. Para los símbolos utilizados antes de ser definidos, el ensamblador emitirá "erratas" después de la definición final, indicando al enlazador o al cargador que corrijan las ubicaciones donde se habían utilizado los símbolos aún no definidos.
  • Los ensambladores de múltiples pasadas crean una tabla con todos los símbolos y sus valores en las primeras pasadas, y luego utilizan esa tabla en pasadas posteriores para generar código.

En ambos casos, el ensamblador debe poder determinar el tamaño de cada instrucción en las pasadas iniciales para calcular las direcciones de los símbolos subsiguientes. Esto significa que si el tamaño de una operación que hace referencia a un operando definido posteriormente depende del tipo o la distancia del operando, el ensamblador realizará una estimación pesimista al encontrar la operación por primera vez y, si es necesario, la rellenará con una o más instrucciones de " no operación " en una pasada posterior o en la errata. En un ensamblador con optimización de "peephole" , las direcciones pueden recalcularse entre pasadas para permitir reemplazar el código pesimista con código adaptado a la distancia exacta del objetivo.

La razón original para el uso de ensambladores de una sola pasada era el tamaño de la memoria y la velocidad de ensamblaje; a menudo, una segunda pasada requería almacenar la tabla de símbolos en la memoria (para manejar referencias hacia adelante ), rebobinar y releer el código fuente del programa en cinta , o releer una baraja de cartas o una cinta perforada . Las computadoras posteriores con memorias mucho más grandes (especialmente almacenamiento en disco) tenían el espacio para realizar todo el procesamiento necesario sin dicha relectura. La ventaja del ensamblador de múltiples pasadas es que la ausencia de erratas hace que el proceso de enlace (o la carga del programa si el ensamblador produce directamente código ejecutable) sea más rápido. [ 23 ]

Ejemplo: en el siguiente fragmento de código, un ensamblador de una sola pasada podría determinar la dirección de la referencia hacia atrás BKWD al ensamblar la instrucción S2 , pero no podría determinar la dirección de la referencia hacia adelante FWD al ensamblar la instrucción de bifurcación S1 ; de hecho, FWD podría no estar definida. Un ensamblador de dos pasadas determinaría ambas direcciones en la primera pasada, por lo que se conocerían al generar el código en la segunda pasada.

S1 B DELANTERO ... EQUIPO DE ADELANTE * ... BKWD EQU * ... S2 B BKWD

Ensambladores de alto nivel

Los ensambladores de alto nivel más sofisticados proporcionan abstracciones de lenguaje como:

Consulte la sección "Diseño del idioma" a continuación para obtener más detalles.

lenguaje ensamblador

Un programa escrito en lenguaje ensamblador consta de una serie de instrucciones de procesador mnemotécnicas y meta-instrucciones (conocidas como operaciones declarativas, directivas, pseudo-instrucciones, pseudo-operaciones y pseudo-ops), comentarios y datos. Las instrucciones en lenguaje ensamblador suelen constar de un código de operación mnemotécnico seguido de un operando , que puede ser una lista de datos, argumentos o parámetros. [ 25 ] Algunas instrucciones pueden ser "implícitas", lo que significa que los datos sobre los que opera la instrucción están definidos implícitamente por la propia instrucción; dicha instrucción no requiere un operando. La instrucción resultante es traducida por un ensamblador a instrucciones en lenguaje máquina que pueden cargarse en memoria y ejecutarse.

Por ejemplo, la siguiente instrucción le indica a un procesador x86 / IA-32 que mueva un valor inmediato de 8 bits a un registro . El código binario de esta instrucción es 10110 seguido de un identificador de 3 bits que indica qué registro usar. El identificador del registro AL es 000, por lo que el siguiente código máquina carga el registro AL con los datos 01100001. [ 25 ]

10110000 01100001

Este código informático binario se puede hacer más legible para los humanos expresándolo en hexadecimal de la siguiente manera.

B0 61

Aquí, B0significa "Mover una copia del siguiente valor a AL ", y 61es una representación hexadecimal del valor 01100001, que es 97 en decimal . El lenguaje ensamblador para la familia 8086 proporciona el mnemónico MOV (abreviatura de move ) para instrucciones como esta, por lo que el código máquina anterior se puede escribir de la siguiente manera en lenguaje ensamblador, con un comentario explicativo si es necesario, después del punto y coma. Esto es mucho más fácil de leer y recordar.

MOV AL , 61h ; Cargar AL con 97 decimal (61 hexadecimal)

En algunos lenguajes ensamblador (incluido este), el mismo mnemónico, como MOV, puede usarse para una familia de instrucciones relacionadas para cargar, copiar y mover datos, ya sean valores inmediatos, valores en registros o ubicaciones de memoria a las que apuntan valores en registros o direcciones directas incrustadas en la instrucción. Otros ensambladores pueden usar mnemónicos de código de operación separados, como L para "mover memoria a registro", ST para "mover registro a memoria", LR para "mover registro a registro", MVI para "mover operando inmediato a memoria", etc.

Si se utiliza el mismo mnemónico para diferentes instrucciones, significa que el mnemónico corresponde a varios códigos de instrucción binarios diferentes, excluyendo los datos (por ejemplo, 61hen este ejemplo), dependiendo de los operandos que siguen al mnemónico. Por ejemplo, para las CPU x86/IA-32, la sintaxis del lenguaje ensamblador de Intel MOV AL, AHrepresenta una instrucción que mueve el contenido del registro AH al registro AL . La forma hexadecimal [ nb 3 ] de esta instrucción es:

88 E0

El primer byte, 88h, identifica un movimiento entre un registro del tamaño de un byte y otro registro o memoria, y el segundo byte, E0h, está codificado (con tres campos de bits) para especificar que ambos operandos son registros, la fuente es AH y el destino es AL .

En un caso como este, donde el mismo mnemónico puede representar más de una instrucción binaria, el ensamblador determina qué instrucción generar examinando los operandos. En el primer ejemplo, el operando 61hes una constante numérica hexadecimal válida y no un nombre de registro válido, por lo que solo la B0instrucción es aplicable. En el segundo ejemplo, el operando AHes un nombre de registro válido y no una constante numérica válida (hexadecimal, decimal, octal o binaria), por lo que solo la 88instrucción es aplicable.

Los lenguajes ensamblador siempre se diseñan de manera que esta falta de ambigüedad se imponga universalmente mediante su sintaxis. Por ejemplo, en el lenguaje ensamblador Intel x86, una constante hexadecimal debe comenzar con un dígito numérico, de modo que el número hexadecimal 'A' (equivalente a diez en decimal) se escribiría como 0Aho 0AH, no AH, específicamente para que no pueda parecer el nombre del registro AH . (La misma regla también evita la ambigüedad con los nombres de los registros BH , CH y DH , así como con cualquier símbolo definido por el usuario que termine con la letra H y que contenga únicamente caracteres que sean dígitos hexadecimales, como la palabra "BEACH").

Volviendo al ejemplo original, mientras que el código de operación x86 10110000 ( B0) copia un valor de 8 bits en el registro AL , 10110001 ( B1) lo mueve a CL y 10110010 ( B2) lo hace a DL . A continuación se muestran ejemplos en lenguaje ensamblador. [ 25 ]

MOV AL , 1h ; Cargar AL con valor inmediato 1 MOV CL , 2h ; Cargar CL con valor inmediato 2 MOV DL , 3h ; Cargar DL con valor inmediato 3

La sintaxis de MOV también puede ser más compleja, como muestran los siguientes ejemplos. [ 26 ]

MOV EAX , [ EBX ] ; Mueve los 4 bytes de memoria en la dirección contenida en EBX a EAX. MOV [ ESI + EAX ], CL ; Mueve el contenido de CL al byte en la dirección ESI+EAX. MOV DS , DX ; Mueve el contenido de DX al registro de segmento DS.

En cada caso, el mnemónico MOV se traduce directamente a uno de los códigos de operación 88-8C, 8E, A0-A3, B0-BF, C6 o C7 mediante un ensamblador, y el programador normalmente no tiene que saber ni recordar cuál. [ 25 ]

Un ensamblador transforma el lenguaje ensamblador en código máquina, y un desensamblador puede realizar la transformación inversa, al menos parcialmente . A diferencia de los lenguajes de alto nivel , existe una correspondencia uno a uno entre muchas instrucciones simples de ensamblador y las instrucciones del lenguaje máquina. Sin embargo, en algunos casos, un ensamblador puede proporcionar pseudoinstrucciones (esencialmente macros) que se expanden en varias instrucciones del lenguaje máquina para proporcionar funcionalidades de uso común. Por ejemplo, para una máquina que carece de la instrucción "saltar si es mayor o igual", un ensamblador puede proporcionar una pseudoinstrucción que se expanda a las instrucciones de la máquina "establecer si es menor que" y "saltar si es cero (en el resultado de la instrucción establecer)". La mayoría de los ensambladores completos también proporcionan un lenguaje de macros avanzado (que se describe más adelante) que utilizan los proveedores y programadores para generar secuencias de código y datos más complejas. Dado que la información sobre pseudoinstrucciones y macros definidas en el entorno del ensamblador no está presente en el programa objeto, un desensamblador no puede reconstruir las invocaciones de macros y pseudoinstrucciones, sino que solo puede desensamblar las instrucciones de máquina reales que el ensamblador generó a partir de esas entidades abstractas del lenguaje ensamblador. Del mismo modo, dado que el ensamblador ignora los comentarios en el archivo fuente del lenguaje ensamblador y estos no tienen ningún efecto en el código objeto que genera, un desensamblador siempre es completamente incapaz de recuperar los comentarios del código fuente.

Cada arquitectura informática tiene su propio lenguaje máquina. Las computadoras difieren en la cantidad y el tipo de operaciones que admiten, en el tamaño y la cantidad de registros, y en la representación de los datos almacenados. Si bien la mayoría de las computadoras de propósito general pueden realizar esencialmente la misma funcionalidad, la forma en que lo hacen difiere; los lenguajes ensamblador correspondientes reflejan estas diferencias.

Para un mismo conjunto de instrucciones, pueden existir varios conjuntos de mnemónicos o sintaxis de lenguaje ensamblador, que normalmente se implementan en diferentes programas ensambladores. En estos casos, el más común suele ser el proporcionado por el fabricante de la CPU y utilizado en su documentación.

Dos ejemplos de CPU con conjuntos de mnemónicos diferentes son la familia Intel 8080 y la Intel 8086/8088. Dado que Intel reclamó los derechos de autor de sus mnemónicos en lenguaje ensamblador (al menos en cada página de su documentación publicada en las décadas de 1970 y principios de 1980), algunas empresas que producían de forma independiente CPU compatibles con los conjuntos de instrucciones de Intel inventaron sus propios mnemónicos. La CPU Zilog Z80 , una mejora de la Intel 8080A , admite todas las instrucciones de la 8080A y muchas más; Zilog inventó un lenguaje ensamblador completamente nuevo, no solo para las nuevas instrucciones , sino también para todas las instrucciones de la 8080A. Por ejemplo, mientras que Intel utiliza los mnemónicos MOV , MVI , LDA , STA , LXI , LDAX , STAX , LHLD y SHLD para diversas instrucciones de transferencia de datos, el lenguaje ensamblador Z80 utiliza el mnemónico LD para todas ellas. Un caso similar son las CPU NEC V20 y V30 , copias mejoradas de las Intel 8086 y 8088, respectivamente. Al igual que Zilog con la Z80, NEC inventó nuevos mnemónicos para todas las instrucciones de las 8086 y 8088, para evitar acusaciones de infracción de los derechos de autor de Intel. (Es cuestionable si tales derechos de autor pueden ser válidos, y compañías de CPU posteriores como AMD [ nb 4 ] y Cyrix republicaron los mnemónicos de instrucciones x86/IA-32 de Intel exactamente sin permiso ni sanción legal). Es dudoso que en la práctica muchas personas que programaron las V20 y V30 realmente escribieron en el lenguaje ensamblador de NEC en lugar del de Intel; dado que dos lenguajes ensamblador para la misma arquitectura de conjunto de instrucciones son isomorfos (algo así como el inglés y el Pig Latin ), no hay obligación de usar el lenguaje ensamblador publicado por un fabricante con los productos de ese fabricante.

Ejemplos

"¡Hola, mundo!" en hardware x86 de 32 bits sin protección

"¡Hola, mundo!" se puede imprimir usando lenguaje ensamblador de 32 bits para un procesador x86 con poca ayuda del sistema operativo. "Call outchr" llama a un mecanismo que imprime un carácter en AL en la consola. Una cadena de caracteres distinta de cero debe terminar con un byte cero.

Hola: mov esi , msg ; dirección de la cadena en ESI cld ; Establecer la dirección para incrementar ESI lodsb ; Cargar el primer carácter en AL, inc ESI chrlp: call outchr ; Imprimir el carácter en AL lodsb ; Cargar el siguiente carácter en AL, inc ESI or al , al ; ¿Es un terminador cero? bne chrlp ; Si no, continuar ret ; Regresar a la función que la llamómsg: db '¡Hola, mundo!' , 0xa , 0x0 ; cadena que se imprimirá

"¡Hola, mundo!" en Linux x86 de 32 bits

En lenguaje ensamblador de 32 bits para Linux en un procesador x86 , "¡Hola, mundo!" se imprimiría mediante una sola llamada al sistema operativo:

sección .texto ; inicio del segmento de código global _start ; declarar _start para que sea visible en el archivo objeto generado _start: mov edx , len ; longitud de la cadena, tercer argumento de write() mov ecx , msg ; dirección de la cadena, segundo argumento de write() mov ebx , 1 ; descriptor de archivo (salida estándar), primer argumento de write() mov eax , 4 ; número de llamada al sistema para write() int 0x80 ; trampa de llamada al sistema mov ebx , 0 ; código de salida, primer argumento de exit() mov eax , 1 ; número de llamada al sistema para exit() int 0x80 ; trampa de llamada al sistemasección .data ; inicio del segmento de datos msg db '¡Hola, mundo!' , 0xa ; cadena a imprimir len equ $ - msg ; longitud de esa cadena como una constante calculada en tiempo de ensamblaje

Diseño de lenguaje

Elementos básicos

Existe una gran diversidad en la forma en que los autores de lenguajes ensambladores categorizan las instrucciones y en la nomenclatura que utilizan. En particular, algunos describen cualquier cosa que no sea un mnemónico de máquina o un mnemónico extendido como una pseudooperación (pseudo-op). Un lenguaje ensamblador típico consta de 3 tipos de instrucciones que se utilizan para definir las operaciones del programa:

Mnemotecnia de códigos de operación y mnemotecnia extendida

Las instrucciones (sentencias) en lenguaje ensamblador son generalmente muy simples, a diferencia de las de los lenguajes de alto nivel . Generalmente, un mnemónico es un nombre simbólico para una sola instrucción ejecutable en lenguaje máquina (un opcode ), y hay al menos un mnemónico de opcode definido para cada instrucción en lenguaje máquina. Cada instrucción normalmente consta de una operación o opcode más cero o más operandos . La mayoría de las instrucciones se refieren a un solo valor o a un par de valores. Los operandos pueden ser inmediatos (valor codificado en la propia instrucción), registros especificados en la instrucción o implícitos, o las direcciones de datos ubicados en otro lugar de la memoria. Esto viene determinado por la arquitectura del procesador subyacente: el ensamblador simplemente refleja cómo funciona esta arquitectura. Los mnemónicos extendidos se utilizan a menudo para especificar una combinación de un opcode con un operando específico, por ejemplo, los ensambladores de System/360 usan Bcomo mnemónico extendido para BCcon una máscara de 15 y NOP("NO OPeration" – no hacer nada durante un paso) para BCcon una máscara de 0.

Los mnemónicos extendidos se utilizan a menudo para admitir usos especializados de las instrucciones, a menudo para propósitos que no son obvios a partir del nombre de la instrucción. Por ejemplo, muchas CPU no tienen una instrucción NOP explícita, pero sí tienen instrucciones que pueden usarse para ese propósito. En las CPU 8086, la instrucción se usa para , siendo un pseudocódigo de operación para codificar la instrucción . Algunos desensambladores reconocen esto y decodificarán la instrucción como . De manera similar, los ensambladores de IBM para System/360 y System/370 usan los mnemónicos extendidos y para y con máscaras de cero. Para la arquitectura SPARC, estas se conocen como instrucciones sintéticas . [ 27 ]xchgax,axnopnopxchgax,axxchgax,axnopNOPNOPRBCBCR

Algunos ensambladores también admiten macroinstrucciones integradas simples que generan dos o más instrucciones de máquina. Por ejemplo, con algunos ensambladores Z80 ld hl,bcse reconoce la instrucción para generar ld l,cseguida de ld h,b. [ 28 ] A veces se les conoce como pseudocódigos de operación .

Los mnemónicos son símbolos arbitrarios; en 1985, el IEEE publicó el estándar 694 para un conjunto uniforme de mnemónicos que debían usar todos los ensambladores. [ 29 ] El estándar fue retirado posteriormente.

Directivas de datos

Existen instrucciones que se utilizan para definir elementos de datos que almacenan información y variables. Estas instrucciones definen el tipo de datos, su longitud y su alineación . También pueden definir si los datos están disponibles para programas externos (programas compilados por separado) o solo para el programa en el que se define la sección de datos. Algunos ensambladores las clasifican como pseudooperaciones.

Directivas de la asamblea

Las directivas de ensamblaje, también llamadas pseudocódigos de operación, pseudooperaciones o pseudo-ops, son comandos que se le dan a un ensamblador para que realice operaciones distintas a las instrucciones de ensamblaje. [ 21 ] Las directivas afectan el funcionamiento del ensamblador y pueden afectar el código objeto, la tabla de símbolos, el archivo de listado y los valores de los parámetros internos del ensamblador. A veces, el término pseudocódigo de operación se reserva para directivas que generan código objeto, como las que generan datos. [ 30 ]

Los nombres de las pseudooperaciones suelen comenzar con un punto para distinguirlas de las instrucciones de máquina. Las pseudooperaciones pueden hacer que el ensamblaje del programa dependa de los parámetros introducidos por el programador, de modo que un mismo programa pueda ensamblarse de diferentes maneras, quizás para distintas aplicaciones. También pueden utilizarse para manipular la presentación de un programa y facilitar su lectura y mantenimiento. Otro uso común de las pseudooperaciones es reservar áreas de almacenamiento para datos en tiempo de ejecución e inicializar opcionalmente su contenido con valores conocidos.

Los ensambladores simbólicos permiten a los programadores asociar nombres arbitrarios ( etiquetas o símbolos ) con ubicaciones de memoria y diversas constantes. Generalmente, a cada constante y variable se le asigna un nombre para que las instrucciones puedan referenciar esas ubicaciones por su nombre, lo que fomenta la autodocumentación del código . En el código ejecutable, el nombre de cada subrutina se asocia con su punto de entrada, de modo que cualquier llamada a una subrutina puede usar su nombre. Dentro de las subrutinas, a los destinos GOTO se les asignan etiquetas. Algunos ensambladores admiten símbolos locales que a menudo son léxicamente distintos de los símbolos normales (por ejemplo, el uso de "10$" como destino GOTO).

Algunos ensambladores, como NASM , ofrecen una gestión flexible de símbolos, lo que permite a los programadores administrar diferentes espacios de nombres , calcular automáticamente desplazamientos dentro de las estructuras de datos y asignar etiquetas que hacen referencia a valores literales o al resultado de cálculos sencillos realizados por el ensamblador. Las etiquetas también se pueden usar para inicializar constantes y variables con direcciones reubicables.

Los lenguajes ensamblador, al igual que la mayoría de los lenguajes de programación, permiten añadir comentarios al código fuente del programa , los cuales se ignoran durante el ensamblaje. Un buen uso de los comentarios es fundamental en los programas en lenguaje ensamblador, ya que el significado y la finalidad de una secuencia de instrucciones binarias de máquina pueden ser difíciles de determinar. El código ensamblador "en bruto" (sin comentarios) generado por compiladores o desensambladores resulta bastante difícil de leer cuando es necesario realizar modificaciones.

Macros

Muchos ensambladores admiten macros predefinidas , y otros admiten macros definidas por el programador (y redefinibles repetidamente) que implican secuencias de líneas de texto en las que se insertan variables y constantes. La definición de la macro suele ser [ nb 5 ] una combinación de instrucciones de ensamblador, por ejemplo, directivas, instrucciones de máquina simbólicas y plantillas para instrucciones de ensamblador. Esta secuencia de líneas de texto puede incluir códigos de operación o directivas. Una vez definida una macro, su nombre puede utilizarse en lugar de un mnemónico. Cuando el ensamblador procesa dicha instrucción, la reemplaza con las líneas de texto asociadas a esa macro y, a continuación, las procesa como si existieran en el archivo de código fuente (incluida, en algunos ensambladores, la expansión de cualquier macro existente en el texto de reemplazo). Las macros en este sentido se remontan a los autocodificadores de IBM de la década de 1950. [ 31 ]

Los ensambladores de macros suelen tener directivas para, por ejemplo, definir macros, definir variables, asignar a las variables el resultado de una expresión aritmética, lógica o de cadena, iterar y generar código condicionalmente. Algunas de estas directivas pueden estar restringidas al uso dentro de una definición de macro, por ejemplo, MEXIT en HLASM , mientras que otras pueden estar permitidas en código abierto (fuera de las definiciones de macros), por ejemplo, AIF y COPY en HLASM.

<