Lenguaje ensamblador - Assembly language

Lenguaje ensamblador
Motorola 6800 Assembly Language.png
Salida secundaria típica de un ensamblador, que muestra el lenguaje ensamblador original (derecha) para el Motorola MC6800 y la forma ensamblada
Paradigma Imperativo , desestructurado
Apareció por primera vez 1949 ; Hace 72 años ( 1949 )

En la programación de computadoras , el lenguaje ensamblador (o lenguaje ensamblador ), a veces abreviado como asm , es cualquier lenguaje de programación de bajo nivel en el que existe una correspondencia muy fuerte entre las instrucciones en el lenguaje y las instrucciones del código máquina de la arquitectura . Debido a que el ensamblaje depende de las instrucciones del código de la máquina, cada lenguaje ensamblador está diseñado para exactamente una arquitectura de computadora específica. El lenguaje ensamblador también puede denominarse código máquina simbólico .

El código de ensamblaje se convierte en código de máquina ejecutable mediante un programa de utilidad denominado ensamblador . El proceso de conversión se denomina ensamblaje , como ensamblar el código fuente . El lenguaje ensamblador generalmente tiene una instrucción por instrucción de máquina (1: 1), pero generalmente también se admiten constantes, comentarios , directivas de ensamblador , etiquetas simbólicas de ubicaciones de programa y memoria y macros .

El término "ensamblador" se atribuye generalmente a Wilkes , Wheeler y Gill en su libro de 1951 La preparación de programas para una computadora digital electrónica , quienes, sin embargo, usaron el término para significar "un programa que ensambla otro programa que consta de varias secciones en una programa único ".

Cada lenguaje ensamblador es específico de una arquitectura de computadora en particular y, a veces, de un sistema operativo . Sin embargo, algunos lenguajes ensambladores no proporcionan una sintaxis específica para las llamadas al sistema operativo, y la mayoría de los lenguajes ensambladores se pueden usar universalmente con cualquier sistema operativo, ya que el lenguaje proporciona acceso a todas las capacidades reales del procesador , sobre las cuales todos los mecanismos de llamada al sistema descansan en última instancia. . A diferencia de los lenguajes ensambladores, 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 , una tarea mucho más complicada que ensamblar.

El paso computacional cuando un ensamblador está procesando un programa se llama tiempo de ensamblaje .

Sintaxis del lenguaje ensamblador

El lenguaje ensamblador usa un mnemónico para representar cada instrucción de máquina de bajo nivel o código de operación , típicamente también cada registro arquitectónico , bandera , etc. Muchas operaciones requieren uno o más operandos para formar una instrucción completa. La mayoría de los ensambladores permiten constantes, registros y etiquetas con nombre para ubicaciones de programa y memoria, y pueden calcular expresiones para operandos. Por lo tanto, los programadores se liberan de los tediosos cálculos repetitivos y los programas en ensamblador son mucho más legibles que el código de máquina. Dependiendo de la arquitectura, estos elementos también pueden combinarse para instrucciones específicas o modos de direccionamiento utilizando compensaciones 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 a la depuración .

Terminología

  • Un ensamblador de macros es un ensamblador que incluye una función de macroinstrucción para que el texto en lenguaje ensamblador (parametrizado) se pueda representar con un nombre, y ese nombre se puede usar para insertar el texto expandido en otro código.
  • Un ensamblador cruzado (ver también compilador cruzado ) es un ensamblador que se ejecuta en una computadora o sistema operativo (el sistema host ) de un tipo diferente 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 tienen los recursos para soportar el desarrollo de software, como un sistema integrado o un microcontrolador . En tal caso, el código de objeto resultante debe transferirse al sistema de destino, a través de una 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 usando una copia exacta bit a bit del código objeto o una representación basada en texto de ese código (como Intel hexadecimal o Motorola S-record ).
  • Un ensamblador de alto nivel es un programa que proporciona abstracciones de lenguaje asociadas más a menudo con lenguajes de alto nivel, como estructuras de control avanzadas ( IF / THEN / ELSE , DO CASE, etc.) y tipos de datos abstractos de alto nivel, incluidas estructuras / registros, uniones, clases y conjuntos.
  • Un microensamblador es un programa que ayuda a preparar un microprograma , llamado firmware , para controlar el funcionamiento de bajo nivel de una computadora.
  • Un meta-ensamblador es "un programa que acepta la descripción sintáctica y semántica de un lenguaje ensamblador y genera un ensamblador para ese lenguaje". Los ensambladores "Meta-Symbol" para las series de computadoras SDS 9 y SDS Sigma son meta-ensambladores. Sperry Univac también proporcionó un metaensamblador para la serie UNIVAC 1100/2200 .
  • El ensamblador en línea (o ensamblador incrustado ) es un código ensamblador contenido dentro de un programa de lenguaje de alto nivel. Esto se usa con mayor frecuencia en programas de sistemas que necesitan acceso directo al hardware.

Conceptos clave

Ensamblador

Un ensamblador programa crea código objeto por traducir combinaciones de mnemotécnicos y la sintaxis para las operaciones y modos de direccionamiento en sus equivalentes numéricos. Esta representación incluye típicamente un código de operación (" código de operación ") 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. El uso de referencias simbólicas es una característica clave de los ensambladores, ya que ahorra tediosos cálculos y actualizaciones manuales de direcciones después de las modificaciones del programa. La mayoría de los ensambladores también incluyen funciones de macro para realizar la sustitución textual, por ejemplo, para generar secuencias cortas comunes de instrucciones en línea , en lugar de llamadas subrutinas .

Algunos ensambladores también pueden realizar algunos tipos simples de optimizaciones específicas del conjunto de instrucciones . Un ejemplo concreto de esto pueden ser los omnipresentes ensambladores x86 de varios proveedores. Llamado tamaño de salto , 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 pases, a pedido. Otros pueden incluso hacer una simple reorganización o inserción de instrucciones, como algunos ensambladores para arquitecturas RISC que pueden ayudar a optimizar una programación de instrucciones sensata para explotar la canalización de la CPU de la manera más eficiente posible.

Los ensambladores han estado disponibles desde la década de 1950, como primer paso por encima del lenguaje máquina y antes de los lenguajes de programación de alto nivel como Fortran , Algol , COBOL y Lisp . También ha habido varias clases de traductores y generadores de código semiautomáticos con propiedades similares a los lenguajes ensambladores y de alto nivel, con Speedcode como quizás uno de los ejemplos más conocidos.

Puede haber varios ensambladores con diferente sintaxis para una CPU particular o una arquitectura de conjunto de instrucciones . Por ejemplo, una instrucción para agregar datos de memoria a un registro en un procesador de la familia x86 podría estar add eax,[ebx], en la sintaxis original de Intel , mientras que esto estaría escrito addl (%ebx),%eaxen la sintaxis de AT&T utilizada por GNU Assembler . A pesar de las diferentes apariencias, las diferentes formas sintácticas generalmente generan el mismo código de 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 FASM -syntax, TASM -syntax, modo ideal, etc., en el caso especial de la programación en ensamblador x86 ).

Número de pases

Hay dos tipos de ensambladores basados ​​en cuántos pases a través de la fuente se necesitan (cuántas veces el ensamblador lee la fuente) para producir el archivo de objeto.

  • Los ensambladores de una pasada revisan el código fuente una vez. Cualquier símbolo usado antes de ser definido requerirá una "errata" al final del código de objeto (o, al menos, no antes del punto donde se define el símbolo) que le indique al vinculador o al cargador que "regrese" y sobrescriba un marcador de posición que se había dejado donde se usó el símbolo aún no definido.
  • Los ensambladores de múltiples pasadas crean una tabla con todos los símbolos y sus valores en las primeras pasadas, luego usan la 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 se refiere a un operando definido más adelante depende del tipo o la distancia del operando, el ensamblador hará una estimación pesimista cuando encuentre la operación por primera vez y, si es necesario, la rellenará con uno o más " no -operación "instrucciones en una pasada posterior o la errata. En un ensamblador con optimización de mirilla , 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 un solo paso fue el tamaño de la memoria y la velocidad de ensamblaje; a menudo, un segundo paso requeriría almacenar la tabla de símbolos en la memoria (para manejar referencias hacia adelante ), rebobinar y releer la fuente del programa en cinta o releer un baraja de cartas o cinta de papel 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 tal relectura. La ventaja del ensamblador de múltiples pasadas es que la ausencia de erratas hace que el proceso de vinculación (o la carga del programa si el ensamblador produce directamente código ejecutable) sea más rápido.

Ejemplo: en el siguiente fragmento de código, un ensamblador de un solo paso 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 directa FWD al ensamblar la instrucción de rama S1 ; de hecho, FWD puede no estar definido. Un ensamblador de dos pasos determinaría ambas direcciones en el paso 1, por lo que serían conocidas al generar el código en el paso 2.

S1   B    FWD
  ...
FWD   EQU *
  ...
BKWD  EQU *
  ...
S2    B   BKWD

Ensambladores de alto nivel

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

Consulte Diseño de idiomas 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-declaraciones (conocidas como directivas, pseudo-instrucciones y pseudo-operaciones), comentarios y datos. Las instrucciones en lenguaje ensamblador generalmente consisten en un mnemónico de código de operación seguido de un operando , que puede ser una lista de datos, argumentos o parámetros. Algunas instrucciones pueden estar "implícitas", lo que significa que los datos sobre los que opera la instrucción están implícitamente definidos por la instrucción en sí; dicha instrucción no toma un operando. La declaración resultante es traducida por un ensamblador a instrucciones en lenguaje de máquina que pueden cargarse en la memoria y ejecutarse.

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

10110000 01100001

Este código informático binario se puede hacer más legible por 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 (una abreviatura de mover ) para instrucciones como esta, por lo que el código de máquina anterior se puede escribir de la siguiente manera en lenguaje ensamblador, completo 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       ; Load AL with 97 decimal (61 hex)

En algunos lenguajes ensambladores (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 señaladas por valores en registros o por direcciones inmediatas (también conocidas como directas). Otros ensambladores pueden usar nemotécnicos 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 usa el mismo mnemónico para diferentes instrucciones, eso significa que el mnemónico corresponde a varios códigos de instrucción binaria diferentes, excluyendo 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 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, se codifica (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 es un nombre de registro válido, por lo que solo la B0instrucción puede ser 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 binario), por lo que solo la 88instrucción puede ser aplicable.

Los lenguajes ensambladores siempre están diseñados para que este tipo de falta de ambigüedad sea universalmente reforzado por 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' (igual al diez decimal) se escriba como 0Aho 0AHno AH, específicamente para que no parezca ser 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, de lo contrario, contenga solo caracteres que sean dígitos hexadecimales, como la palabra "PLAYA ".)

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 para estos.

MOV AL, 1h        ; Load AL with immediate value 1
MOV CL, 2h        ; Load CL with immediate value 2
MOV DL, 3h        ; Load DL with immediate value 3

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

MOV EAX, [EBX]	  ; Move the 4 bytes in memory at the address contained in EBX into EAX
MOV [ESI+EAX], CL ; Move the contents of CL into the byte at address ESI+EAX
MOV DS, DX        ; Move the contents of DX into segment register 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 por un ensamblador, y el programador normalmente no tiene que saber ni recordar cuál.

Transformar el lenguaje ensamblador en código de máquina es el trabajo de un ensamblador, y lo contrario puede lograrse, al menos parcialmente, mediante un desensamblador . A diferencia de los lenguajes de alto nivel , existe una correspondencia uno a uno entre muchas instrucciones de ensamblaje simples e instrucciones en lenguaje de máquina. Sin embargo, en algunos casos, un ensamblador puede proporcionar pseudoinstrucciones (esencialmente macros) que se expanden en varias instrucciones en lenguaje de máquina para proporcionar la funcionalidad comúnmente necesaria. Por ejemplo, para una máquina que carece de una instrucción "bifurcar si es mayor o igual", un ensamblador puede proporcionar una pseudoinstrucción que se expanda a la máquina "establecer si es menor que" y "bifurcar si es cero (en el resultado de la instrucción establecida)". . La mayoría de los ensambladores con todas las funciones también proporcionan un rico lenguaje de macros (que se analiza a continuación) que los proveedores y programadores utilizan para generar secuencias de datos y códigos más complejos. 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 en lenguaje ensamblador. Del mismo modo, dado que el ensamblador ignora los comentarios en el archivo fuente del lenguaje ensamblador y no tienen ningún efecto en el código objeto que genera, un desensamblador siempre es completamente incapaz de recuperar los comentarios fuente.

Cada arquitectura de computadora tiene su propio lenguaje de máquina. Las computadoras difieren en el número y tipo de operaciones que admiten, en los diferentes tamaños y números de registros y en las representaciones de los datos almacenados. Si bien la mayoría de las computadoras de uso general pueden realizar esencialmente la misma funcionalidad, las formas en que lo hacen difieren; los lenguajes ensambladores correspondientes reflejan estas diferencias.

Pueden existir múltiples conjuntos de mnemónicos o sintaxis en lenguaje ensamblador para un solo conjunto de instrucciones, típicamente instanciadas en diferentes programas ensambladores. En estos casos, el más popular suele ser el suministrado por el fabricante de la CPU y utilizado en su documentación.

Dos ejemplos de CPU que tienen dos conjuntos diferentes de nemotécnicos son la familia Intel 8080 y la Intel 8086/8088. Debido a que Intel reclamó los derechos de autor sobre sus nemotécnicos en lenguaje ensamblador (en cada página de su documentación publicada en la década de 1970 y principios de los 80, al menos), algunas empresas que producían de forma independiente CPU compatibles con los conjuntos de instrucciones de Intel inventaron sus propios nemotécnicos. La CPU Zilog Z80 , una mejora del Intel 8080A , admite todas las instrucciones del 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 del 8080A. Por ejemplo, cuando Intel usa los mnemónicos MOV , MVI , LDA , STA , LXI , LDAX , STAX , LHLD y SHLD para varias instrucciones de transferencia de datos, el lenguaje ensamblador Z80 usa el mnemónico LD para todos ellos. Un caso similar es el de las CPU NEC V20 y V30 , copias mejoradas de Intel 8086 y 8088, respectivamente. Al igual que Zilog con el Z80, NEC inventó nuevos mnemónicos para todas las instrucciones 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 y Cyrix volvieron a publicar los mnemónicos de instrucciones x86 / IA-32 de Intel exactamente sin permiso ni penalización legal). Es dudoso que en la práctica muchas personas que programaron el V20 y V30 en realidad escribió en lenguaje ensamblador de NEC en lugar de en Intel; Dado que dos lenguajes ensambladores cualesquiera para la misma arquitectura de conjunto de instrucciones son isomórficos (algo así como el inglés y el latín Pig ), no es necesario utilizar el lenguaje ensamblador publicado por el propio fabricante con los productos de ese fabricante.

Diseño de lenguaje

Elementos basicos

Existe un gran grado de diversidad en la forma en que los autores de ensambladores categorizan las declaraciones 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:

Mnemónicos de código de operación y mnemónicos extendidos

Las instrucciones (declaraciones) 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 de máquina (un código de operación ), y hay al menos un mnemónico de código de operación definido para cada instrucción en lenguaje de máquina. Cada instrucción normalmente consta de una operación o código de operación más cero o más operandos . La mayoría de las instrucciones se refieren a un solo valor o 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 los datos ubicados en otro lugar del almacenamiento. Esto está 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 código de operación con un operando específico, por ejemplo, los ensambladores System / 360 usan Bcomo un mnemónico extendido para BCcon una máscara de 15 y NOP("NO OPeration" - no hacer nada para un paso) para BCcon una máscara de 0.

Los mnemónicos extendidos se utilizan a menudo para respaldar usos especializados de instrucciones, a menudo con fines 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 tienen instrucciones que se pueden usar para ese propósito. En las CPU 8086 se utiliza la instrucción , siendo un pseudo-có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 cero. Para la arquitectura SPARC, se conocen como instrucciones sintéticas . xchg ax,axnopnopxchg ax,axxchg ax,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 que la instrucción genera ld l,cseguido de ld h,b. Estos a veces se conocen como pseudo-opcodes .

Los mnemónicos son símbolos arbitrarios; en 1985 el IEEE publicó el Estándar 694 para un conjunto uniforme de mnemónicos para ser utilizado por todos los ensambladores. Desde entonces, la norma ha sido retirada.

Directivas de datos

Hay instrucciones que se utilizan para definir elementos de datos para contener datos y variables. Definen el tipo de datos, la longitud y la alineación de los datos. Estas instrucciones también pueden definir si los datos están disponibles para programas externos (programas ensamblados por separado) o solo para el programa en el que se define la sección de datos. Algunos ensambladores los clasifican como pseudo-operaciones.

Directivas de montaje

Las directivas de ensamblaje, también llamadas pseudo-opcodes, pseudo-operaciones o pseudo-operaciones, son comandos que se le dan a un ensamblador "dirigiéndolo a realizar operaciones distintas a las instrucciones de ensamblaje". Las directivas afectan la forma en que opera el 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 pseudo-opcode se reserva para las directivas que generan código objeto, como las que generan datos.

Los nombres de las pseudo-operaciones a menudo comienzan con un punto para distinguirlos de las instrucciones de la máquina. Las pseudo-operaciones pueden hacer que el ensamblaje del programa dependa de los parámetros ingresados ​​por un programador, de modo que un programa se puede ensamblar de diferentes maneras, quizás para diferentes aplicaciones. O se puede utilizar una pseudo-operación para manipular la presentación de un programa para que sea más fácil de leer y mantener. Otro uso común de las pseudo-operaciones es reservar áreas de almacenamiento para datos en tiempo de ejecución y, opcionalmente, inicializar su contenido a valores conocidos.

Los ensambladores simbólicos permiten a los programadores asociar nombres arbitrarios ( etiquetas o símbolos ) con ubicaciones de memoria y varias constantes. Por lo general, a cada constante y variable se le asigna un nombre para que las instrucciones puedan hacer referencia a esas ubicaciones por su nombre, promoviendo así el código autodocumentado . En el código ejecutable, el nombre de cada subrutina está asociado con su punto de entrada, por lo que cualquier llamada a una subrutina puede usar su nombre. Dentro de las subrutinas, los destinos GOTO reciben 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 , brindan administración de símbolos flexible, lo que permite a los programadores administrar diferentes espacios de nombres , calcular automáticamente las compensaciones dentro de las estructuras de datos y asignar etiquetas que se refieren a valores literales o al resultado de cálculos simples realizados por el ensamblador. Las etiquetas también se pueden utilizar para inicializar constantes y variables con direcciones reubicables.

Los lenguajes ensambladores, como la mayoría de los otros lenguajes informáticos, permiten que se agreguen comentarios al código fuente del programa que se ignorarán durante el ensamblaje. Los comentarios juiciosos son esenciales en los programas de lenguaje ensamblador, ya que el significado y el propósito de una secuencia de instrucciones de máquina binaria pueden ser difíciles de determinar. El lenguaje ensamblador "crudo" (sin comentarios) generado por compiladores o desensambladores es bastante difícil de leer cuando se deben realizar cambios.

Macros

Muchos ensambladores admiten macros predefinidas y otros admiten macros definidas por el programador (y redefinidas repetidamente) que involucran secuencias de líneas de texto en las que se incrustan variables y constantes. La definición de macro es más comúnmente una mezcla de declaraciones de ensamblador, por ejemplo, directivas, instrucciones de máquina simbólicas y plantillas para declaraciones de ensamblador. Esta secuencia de líneas de texto puede incluir códigos de operación o directivas. Una vez que se ha definido una macro, su nombre puede usarse en lugar de un mnemónico. Cuando el ensamblador procesa tal declaración, reemplaza la declaración con las líneas de texto asociadas con esa macro, luego las procesa como si existieran en el archivo de código fuente (incluida, en algunos ensambladores, la expansión de cualquier macros existente en el texto de reemplazo) . En este sentido, las macros datan de los autocodificadores de IBM de la década de 1950.

En lenguaje ensamblador, el término "macro" representa un concepto más completo que en otros contextos, como el preprocesador en el lenguaje de programación C , donde su directiva #define se usa típicamente para crear macros cortas de una sola línea. Las macroinstrucciones del ensamblador, como las macros en PL / I y algunos otros lenguajes, pueden ser "programas" largos por sí mismos, ejecutados por interpretación del ensamblador durante el ensamblaje.

Dado que las macros pueden tener nombres 'cortos' pero expandirse a varias o muchas líneas de código, pueden usarse para hacer que los programas en lenguaje ensamblador parezcan ser mucho más cortos, requiriendo menos líneas de código fuente, como ocurre con los lenguajes de nivel superior. También se pueden usar para agregar niveles más altos de estructura a los programas de ensamblaje, opcionalmente introducir código de depuración incrustado a través de parámetros y otras características similares.

Los ensambladores de macros a menudo permiten que las macros tomen parámetros . Algunos ensambladores incluyen lenguajes de macros bastante sofisticados, incorporando elementos de lenguaje de alto nivel como parámetros opcionales, variables simbólicas, condicionales, manipulación de cadenas y operaciones aritméticas, todos utilizables durante la ejecución de una macro determinada y que permiten que las macros guarden contexto o intercambien información. . Por tanto, una macro puede generar numerosas instrucciones en lenguaje ensamblador o definiciones de datos, basadas en los argumentos de la macro. Esto podría usarse para generar estructuras de datos de estilo de registro o bucles " desenrollados ", por ejemplo, o podría generar algoritmos completos basados ​​en parámetros complejos. Por ejemplo, una macro de "clasificación" podría aceptar la especificación de una clave de clasificación compleja y generar código diseñado para esa clave específica, sin necesitar las pruebas en tiempo de ejecución que serían necesarias para un procedimiento general que interpreta la especificación. Se puede considerar que una organización que usa lenguaje ensamblador que se ha extendido mucho usando un conjunto de macros de este tipo está trabajando en un lenguaje de nivel superior, ya que dichos programadores no están trabajando con los elementos conceptuales de nivel más bajo de una computadora. Subrayando este punto, se utilizaron macros para implementar una máquina virtual temprana en SNOBOL4 (1967), que fue escrito en SNOBOL Implementation Language (SIL), un lenguaje ensamblador para una máquina virtual. La máquina de destino traduciría esto a su código nativo usando un ensamblador de macros . Esto permitió un alto grado de portabilidad para el momento.

Las macros se utilizaron para personalizar sistemas de software a gran escala para clientes específicos en la era del mainframe y también fueron utilizadas por el personal del cliente para satisfacer las necesidades de sus empleadores mediante la creación de versiones específicas de los sistemas operativos de los fabricantes. Esto lo hicieron, por ejemplo, programadores de sistemas que trabajaban con el sistema de monitorización conversacional / máquina virtual ( VM / CMS ) de IBM y con los complementos de "procesamiento de transacciones en tiempo real" de IBM, el sistema de control de información del cliente CICS y ACP / TPF . la aerolínea / sistema financiero que comenzó en la década de 1970 y que todavía opera muchos grandes sistemas de reserva por computadora (CRS) y sistemas de tarjetas de crédito en la actualidad.

También es posible utilizar únicamente las capacidades de procesamiento de macros de un ensamblador para generar código escrito en lenguajes completamente diferentes, por ejemplo, para generar una versión de un programa en COBOL utilizando un programa ensamblador de macros puro que contiene líneas de código COBOL dentro de los operadores de tiempo de ensamblaje. instruir al ensamblador para que genere código arbitrario. IBM OS / 360 utiliza macros para realizar la generación del sistema . El usuario especifica opciones codificando una serie de macros de ensamblador. El ensamblaje de estas macros genera una secuencia de trabajos para construir el sistema, incluido el lenguaje de control de trabajos y las declaraciones de control de utilidades .

Esto se debe a que, como se advirtió en la década de 1960, el concepto de "macroprocesamiento" es independiente del concepto de "ensamblaje", siendo el primero en términos modernos más procesamiento de texto, procesamiento de texto, que generación de código objeto. El concepto de procesamiento de macros apareció y aparece en el lenguaje de programación C, que admite "instrucciones de preprocesador" para establecer variables y realizar pruebas condicionales sobre sus valores. A diferencia de ciertos procesadores de macros anteriores dentro de ensambladores, el preprocesador de C no es Turing completo porque carece de la capacidad de realizar un ciclo o "ir a", lo que permite que los programas lo hagan.

A pesar del poder del procesamiento de macros, cayó en desuso en muchos lenguajes de alto nivel (las principales excepciones son C , C ++ y PL / I) sin dejar de ser una constante para los ensambladores.

La sustitución de parámetros de macros se realiza estrictamente por nombre: en el momento del procesamiento de macros, el valor de un parámetro se sustituye textualmente por su nombre. La clase de errores más famosa resultante fue el uso de un parámetro que en sí mismo era una expresión y no un nombre simple cuando el escritor de macros esperaba un nombre. En la macro:

foo: macro a
load a*b

la intención era que la persona que llama proporcionara el nombre de una variable, y la variable "global" o la constante b se utilizaría para multiplicar "a". Si se llama a foo con el parámetro a-c, se load a-c*bproduce la expansión macro de . Para evitar cualquier posible ambigüedad, los usuarios de procesadores de macros pueden poner entre paréntesis los parámetros formales dentro de las definiciones de macros, o los llamadores pueden poner entre paréntesis los parámetros de entrada.

Soporte para programación estructurada

Se han escrito paquetes de macros que proporcionan elementos de programación estructurados para codificar el flujo de ejecución. El primer ejemplo de este enfoque estaba en el conjunto de macros Concept-14 , propuesto originalmente por Harlan Mills (marzo de 1970), e implementado por Marvin Kessler en la División de Sistemas Federales de IBM, que proporcionó IF / ELSE / ENDIF y bloques de flujo de control similares para OS / Programas ensambladores 360. Esta fue una forma de reducir o eliminar el uso de operaciones GOTO en código ensamblador, uno de los principales factores que causan el código espagueti en lenguaje ensamblador. Este enfoque fue ampliamente aceptado a principios de la década de 1980 (los últimos días del uso del lenguaje ensamblador a gran escala). El kit de herramientas de ensamblador de alto nivel de IBM incluye un paquete de macros de este tipo.

Un diseño curioso fue A-natural , un ensamblador "orientado a flujo" para procesadores 8080 / Z80 de Whitesmiths Ltd. (desarrolladores del sistema operativo Idris similar a Unix , y lo que se informó que era el primer compilador comercial de C ). El lenguaje se clasificó como ensamblador porque trabajaba con elementos de máquina en bruto, como códigos de operación , registros y referencias de memoria; pero incorporó una sintaxis de expresión para indicar el orden de ejecución. Los paréntesis y otros símbolos especiales, junto con construcciones de programación estructuradas orientadas a bloques, controlaban la secuencia de las instrucciones generadas. A-natural se construyó como el lenguaje de objetos de un compilador de C, en lugar de para la codificación manual, pero su sintaxis lógica ganó algunos fanáticos.

Ha habido poca demanda aparente de ensambladores más sofisticados desde el declive del desarrollo del lenguaje ensamblador a gran escala. A pesar de eso, todavía se están desarrollando y aplicando en los casos en que las limitaciones de recursos o las peculiaridades en la arquitectura del sistema de destino impiden el uso efectivo de lenguajes de nivel superior.

Los ensambladores con un potente motor de macros permiten la programación estructurada a través de macros, como la macro de conmutador proporcionada con el paquete Masm32 (este código es un programa completo):

include \masm32\include\masm32rt.inc	; use the Masm32 library

.code
demomain:
  REPEAT 20
	switch rv(nrandom, 9)	; generate a number between 0 and 8
	mov ecx, 7
	case 0
		print "case 0"
	case ecx				; in contrast to most other programming languages,
		print "case 7"		; the Masm32 switch allows "variable cases"
	case 1 .. 3
		.if eax==1
			print "case 1"
		.elseif eax==2
			print "case 2"
		.else
			print "cases 1 to 3: other"
		.endif
	case 4, 6, 8
		print "cases 4, 6 or 8"
	default
		mov ebx, 19		     ; print 20 stars
		.Repeat
			print "*"
			dec ebx
		.Until Sign?		 ; loop until the sign flag is set
	endsw
	print chr$(13, 10)
  ENDM
  exit
end demomain

Uso de lenguaje ensamblador

Perspectiva historica

Los lenguajes ensambladores no estaban disponibles en el momento en que se introdujo la computadora con programa almacenado . Kathleen Booth "tiene el mérito de haber inventado el lenguaje ensamblador" basado en el trabajo teórico que comenzó en 1947, mientras trabajaba en el ARC2 en Birkbeck, Universidad de Londres, tras la consulta de Andrew Booth (más tarde su esposo) con el matemático John von Neumann y el físico Herman Goldstine en el Instituto de Estudios Avanzados .

A finales de 1948, la Calculadora Automática de Almacenamiento con Retraso Electrónico (EDSAC) tenía un ensamblador (llamado "pedidos iniciales") integrado en su programa de arranque . Utilizaba mnemotécnicos de una letra desarrollados por David Wheeler , a quien la IEEE Computer Society acredita como el creador del primer "ensamblador". Los informes sobre el EDSAC introdujeron el término "ensamblaje" para el proceso de combinar campos en una palabra de instrucción. SOAP ( Programa de ensamblaje óptimo simbólico ) era un lenguaje ensamblador para la computadora IBM 650 escrito por Stan Poley en 1955.

Los lenguajes ensambladores eliminan gran parte de la programación de primera generación , propensa a errores, tediosa y que consume mucho tiempo, necesaria en las primeras computadoras, liberando a los programadores del tedio, como recordar códigos numéricos y calcular direcciones.

Los lenguajes ensambladores alguna vez fueron ampliamente utilizados para todo tipo de programación. Sin embargo, en la década de 1980 (década de 1990 en microcomputadoras ), su uso había sido reemplazado en gran medida por lenguajes de nivel superior, en la búsqueda de una mejora en la productividad de la programación . Hoy en día, el lenguaje ensamblador todavía se usa para la manipulación directa de hardware, el acceso a instrucciones de procesador especializadas o para abordar problemas críticos de rendimiento. Los usos típicos son controladores de dispositivos , sistemas integrados de bajo nivel y sistemas en tiempo real.

Históricamente, numerosos programas se han escrito íntegramente en lenguaje ensamblador. El Burroughs MCP (1961) fue el primer ordenador para el que no se desarrolló un sistema operativo enteramente en lenguaje ensamblador; fue escrito en Lenguaje Orientado a Problemas de Sistemas Ejecutivos (ESPOL), un dialecto de Algol. Muchas aplicaciones comerciales también se escribieron en lenguaje ensamblador, incluida una gran cantidad de software de mainframe de IBM escrito por grandes corporaciones. COBOL , FORTRAN y algunos PL / I eventualmente desplazaron gran parte de este trabajo, aunque varias organizaciones grandes conservaron las infraestructuras de aplicaciones en lenguaje ensamblador hasta bien entrada la década de 1990.

La mayoría de las primeras microcomputadoras se basaron en el lenguaje ensamblador codificado a mano, incluida la mayoría de los sistemas operativos y aplicaciones grandes. Esto se debía a que estos sistemas tenían graves limitaciones de recursos, imponían arquitecturas de visualización y memoria idiosincrásicas y proporcionaban servicios de sistema limitados y con errores. Quizás más importante fue la falta de compiladores de lenguaje de alto nivel de primera clase adecuados para el uso de microcomputadoras. Un factor psicológico también puede haber jugado un papel: la primera generación de programadores de microcomputadoras mantuvo una actitud de aficionado, "alambres y alicates".

En un contexto más comercial, las razones más importantes para usar el lenguaje ensamblador fueron una hinchazón mínima (tamaño), una sobrecarga mínima, una mayor velocidad y confiabilidad.

Ejemplos típicos de grandes programas en lenguaje ensamblador de esta época son los sistemas operativos IBM PC DOS , el compilador Turbo Pascal y aplicaciones tempranas como el programa de hoja de cálculo Lotus 1-2-3 . El lenguaje ensamblador se usó para obtener el mejor rendimiento de Sega Saturn , una consola que fue notoriamente desafiante para desarrollar y programar juegos. El juego de arcade de 1993 NBA Jam es otro ejemplo.

El lenguaje ensamblador ha sido durante mucho tiempo el lenguaje de desarrollo principal para muchas computadoras domésticas populares de las décadas de 1980 y 1990 (como MSX , Sinclair ZX Spectrum , Commodore 64 , Commodore Amiga y Atari ST ). Esto se debió en gran parte a que los dialectos BÁSICOS interpretados en estos sistemas ofrecían una velocidad de ejecución insuficiente, así como instalaciones insuficientes para aprovechar al máximo el hardware disponible en estos sistemas. Algunos sistemas incluso tienen un entorno de desarrollo integrado (IDE) con funciones de macro y depuración muy avanzadas. Algunos compiladores disponibles para Radio Shack TRS-80 y sus sucesores tenían la capacidad de combinar la fuente de ensamblaje en línea con declaraciones de programa de alto nivel. Tras la compilación, un ensamblador incorporado produjo un código de máquina en línea.

Uso actual

Siempre ha habido debates sobre la utilidad y el rendimiento del lenguaje ensamblador en relación con los lenguajes de alto nivel.

Aunque el lenguaje ensamblador tiene usos de nicho específicos donde es importante (ver más abajo), existen otras herramientas para la optimización.

En julio de 2017, el índice TIOBE de popularidad del lenguaje de programación clasifica al lenguaje ensamblador en el puesto 11, por delante de Visual Basic , por ejemplo. El ensamblador se puede utilizar para optimizar la velocidad u optimizar el tamaño. En el caso de la optimización de la velocidad, se afirma que los compiladores de optimización modernos convierten los lenguajes de alto nivel en un código que puede ejecutarse tan rápido como un ensamblaje escrito a mano, a pesar de los contraejemplos que se pueden encontrar. La complejidad de los procesadores y subsistemas de memoria modernos hace que la optimización efectiva sea cada vez más difícil para los compiladores, así como para los programadores de ensamblajes. Además, el aumento del rendimiento del procesador ha significado que la mayoría de las CPU permanezcan inactivas la mayor parte del tiempo, con retrasos causados ​​por cuellos de botella predecibles como fallas de caché, operaciones de E / S y paginación . Esto ha hecho que la velocidad de ejecución del código sin formato no sea un problema para muchos programadores.

Hay algunas situaciones en las que los desarrolladores pueden optar por utilizar el lenguaje ensamblador:

  • Escribir código para sistemas con procesadores más antiguos que tienen opciones limitadas de lenguaje de alto nivel, como Atari 2600 , Commodore 64 y calculadoras gráficas . Los programas para estas computadoras de las décadas de 1970 y 1980 a menudo se escriben en el contexto de subculturas de demoscene o retrogaming .
  • Código que debe interactuar directamente con el hardware, por ejemplo, en controladores de dispositivos y manejadores de interrupciones .
  • En un procesador integrado o DSP, las interrupciones de alta repetición requieren la menor cantidad de ciclos por interrupción, como una interrupción que ocurre 1000 o 10000 veces por segundo.
  • Programas que necesitan usar instrucciones específicas del procesador no implementadas en un compilador. Un ejemplo común es la instrucción de rotación bit a bit en el núcleo de muchos algoritmos de cifrado, así como la consulta de la paridad de un byte o el acarreo de 4 bits de una adición.
  • Se requiere un ejecutable independiente de tamaño compacto que debe ejecutarse sin recurrir a los componentes de tiempo de ejecución o bibliotecas asociadas con un lenguaje de alto nivel. Los ejemplos han incluido firmware para teléfonos, sistemas de encendido y combustible de automóviles, sistemas de control de aire acondicionado, sistemas de seguridad y sensores.
  • Programas con bucles internos sensibles al rendimiento, donde el lenguaje ensamblador brinda oportunidades de optimización que son difíciles de lograr en un lenguaje de alto nivel. Por ejemplo, álgebra lineal con BLAS o transformación de coseno discreta (por ejemplo , versión de ensamblaje SIMD de x264 ).
  • Programas que crean funciones vectorizadas para programas en lenguajes de nivel superior, como C. En el lenguaje de nivel superior, esto a veces es ayudado por funciones intrínsecas del compilador que se asignan directamente a los mnemónicos SIMD, pero sin embargo dan como resultado una conversión de ensamblaje uno a uno específico. para el procesador vectorial dado.
  • Programas en tiempo real como simulaciones, sistemas de navegación de vuelo y equipos médicos. Por ejemplo, en un sistema fly-by-wire , la telemetría se debe interpretar y actuar dentro de estrictas limitaciones de tiempo. Dichos sistemas deben eliminar las fuentes de demoras impredecibles, que pueden ser creadas por (algunos) lenguajes interpretados, recolección automática de basura , operaciones de paginación o multitarea preventiva . Sin embargo, algunos lenguajes de nivel superior incorporan componentes de tiempo de ejecución e interfaces del sistema operativo que pueden introducir tales retrasos. La elección de lenguajes de ensamblaje o de nivel inferior para tales sistemas brinda a los programadores una mayor visibilidad y control sobre los detalles del procesamiento.
  • Algoritmos criptográficos que siempre deben tomar estrictamente el mismo tiempo para ejecutarse, evitando ataques de tiempo .
  • Modifique y amplíe el código heredado escrito para mainframe IBM.
  • Situaciones en las que se requiere un control total sobre el medio ambiente, en situaciones de seguridad extremadamente alta en las que nada se puede dar por sentado .
  • Virus informáticos , cargadores de arranque , ciertos controladores de dispositivos u otros elementos muy cercanos al hardware o al sistema operativo de bajo nivel.
  • Simuladores de conjuntos de instrucciones para monitoreo, rastreo y depuración donde la sobrecarga adicional se mantiene al mínimo.
  • Situaciones en las que no existe un lenguaje de alto nivel, en un procesador nuevo o especializado para el que no hay un compilador cruzado disponible.
  • Ingeniería inversa y modificación de archivos de programa como:
    • binarios existentes que pueden o no haber sido escritos originalmente en un lenguaje de alto nivel, por ejemplo, cuando se intenta recrear programas para los cuales el código fuente no está disponible o se ha perdido, o cuando se rompe la protección contra copia de software propietario.
    • Videojuegos (también denominados pirateo de ROM ), que es posible mediante varios métodos. El método más utilizado es alterar el código del programa a nivel del lenguaje ensamblador.

El lenguaje ensamblador todavía se enseña en la mayoría de los programas de ciencias de la computación e ingeniería electrónica . Aunque hoy en día pocos programadores trabajan regularmente con el lenguaje ensamblador como herramienta, los conceptos subyacentes siguen siendo importantes. Temas fundamentales como aritmética binaria , asignación de memoria , procesamiento de pila , codificación de juegos de caracteres , procesamiento de interrupciones y diseño de compiladores serían difíciles de estudiar en detalle sin una comprensión de cómo funciona una computadora a nivel de hardware. Dado que el comportamiento de una computadora se define fundamentalmente por su conjunto de instrucciones, la forma lógica de aprender tales conceptos es estudiar un lenguaje ensamblador. La mayoría de las computadoras modernas tienen conjuntos de instrucciones similares. Por tanto, estudiar un solo lenguaje ensamblador es suficiente para aprender: I) los conceptos básicos; II) reconocer situaciones en las que el uso del lenguaje ensamblador podría ser apropiado; y III) para ver cuán eficiente se puede crear código ejecutable a partir de lenguajes de alto nivel.

Aplicaciones Típicas

  • El lenguaje ensamblador se usa generalmente en el código de inicio de un sistema, el código de bajo nivel que inicializa y prueba el hardware del sistema antes de iniciar el sistema operativo y, a menudo, se almacena en la ROM . ( BIOS en sistemas de PC compatibles con IBM y CP / M es un ejemplo).
  • El lenguaje ensamblador se usa a menudo para código de bajo nivel, por ejemplo, para núcleos de sistemas operativos , que no pueden depender de la disponibilidad de llamadas al sistema preexistentes y, de hecho, deben implementarlas para la arquitectura de procesador particular en la que se ejecutará el sistema.
  • Algunos compiladores traducen primero los lenguajes de alto nivel en ensamblador antes de compilar completamente, lo que permite ver el código ensamblador con fines de depuración y optimización.
  • Algunos compiladores para lenguajes de nivel relativamente bajo, como Pascal o C , permiten al programador incrustar el lenguaje ensamblador directamente en el código fuente (llamado ensamblado en línea ). Los programas que utilizan estas funciones pueden construir abstracciones utilizando un lenguaje ensamblador diferente en cada plataforma de hardware. El código portátil del sistema puede utilizar estos componentes específicos del procesador a través de una interfaz uniforme.
  • El lenguaje ensamblador es útil en ingeniería inversa . Muchos programas se distribuyen solo en forma de código de máquina, que es sencillo de traducir al lenguaje ensamblador por un desensamblador , pero más difícil de traducir a un lenguaje de nivel superior a través de un descompilador . Herramientas como el desensamblador interactivo hacen un uso extensivo del desmontaje para tal fin. Los piratas informáticos utilizan esta técnica para descifrar software comercial y los competidores para producir software con resultados similares de empresas competidoras.
  • El lenguaje ensamblador se usa para mejorar la velocidad de ejecución, especialmente en las primeras computadoras personales con capacidad de procesamiento y RAM limitadas.
  • Los ensambladores se pueden utilizar para generar bloques de datos, sin sobrecarga de lenguaje de alto nivel, a partir de código fuente formateado y comentado, para ser utilizados por otro código.

Ver también

Notas

Referencias

Otras lecturas

enlaces externos