En programación de computadoras , un ensamblador en línea es una característica de algunos compiladores que permite incrustar código de bajo nivel escrito en lenguaje ensamblador dentro de un programa, entre código que de otro modo habría sido compilado desde un lenguaje de nivel superior como C o Ada .
La incorporación de código en lenguaje ensamblador generalmente se realiza por una de estas razones: [1]
Por otro lado, el ensamblador en línea plantea un problema directo para el propio compilador, ya que complica el análisis de lo que se hace con cada variable, una parte clave de la asignación de registros. [2] Esto significa que el rendimiento podría disminuir. El ensamblador en línea también complica la portabilidad y el mantenimiento futuros de un programa. [1]
A menudo se proporcionan instalaciones alternativas como una forma de simplificar el trabajo tanto para el compilador como para el programador. La mayoría de los compiladores proporcionan funciones intrínsecas para instrucciones especiales y los contenedores de funciones C para llamadas arbitrarias al sistema están disponibles en todas las plataformas Unix .
El estándar ISO C++ y los estándares ISO C (anexo J) especifican una sintaxis soportada condicionalmente para ensamblador en línea:
Una declaración asm tiene la forma
declaración-asm :
asm ( literal de cadena );
La declaración de asm tiene respaldo condicional; su significado está definido por la implementación. [3]
Esta definición, sin embargo, rara vez se usa en C real, ya que es a la vez demasiado liberal (en la interpretación) y demasiado restringida (en el uso de un solo literal de cadena).
En el uso práctico, el ensamblaje en línea que opera con valores rara vez es independiente como código flotante. Dado que el programador no puede predecir a qué registro se asigna una variable, los compiladores suelen proporcionar una forma de sustituirlos como una extensión.
En general, existen dos tipos de ensamblados en línea admitidos por los compiladores C/C++:
Las dos familias de extensiones representan diferentes interpretaciones de la división del trabajo en el procesamiento del ensamblaje en línea. El formulario GCC preserva la sintaxis general del lenguaje y compartimenta lo que el compilador necesita saber: qué se necesita y qué se cambia. No requiere explícitamente que el compilador comprenda los nombres de las instrucciones, ya que el compilador solo necesita sustituir sus asignaciones de registros, además de algunas operaciones mov , para manejar los requisitos de entrada. Sin embargo, el usuario es propenso a especificar incorrectamente los registros afectados. La forma MSVC de un lenguaje integrado de dominio específico proporciona facilidad de escritura, pero requiere que el propio compilador conozca los nombres de los códigos de operación y sus propiedades de manipulación, lo que exige atención adicional en el mantenimiento y la portabilidad. [7] Todavía es posible verificar el ensamblaje estilo GCC en busca de errores con conocimiento del conjunto de instrucciones. [8]
GNAT (interfaz en lenguaje Ada de la suite GCC) y LLVM utiliza la sintaxis GCC. [9] [10] El lenguaje de programación D utiliza un DSL similar a la extensión MSVC oficialmente para x86_64, [11] pero el LDC basado en LLVM también proporciona la sintaxis de estilo GCC en cada arquitectura. [12] MSVC solo admite ensamblador en línea en x86 de 32 bits. [5]
Desde entonces, el lenguaje Rust ha migrado a una sintaxis que abstrae las opciones de ensamblaje en línea más allá de la versión LLVM (estilo GCC). Proporciona suficiente información para permitir transformar el bloque en una función ensamblada externamente si el backend no pudiera manejar el ensamblaje integrado. [7]
Por lo general, no es posible llamar directamente a un sistema operativo en un sistema que utiliza memoria protegida. El sistema operativo se ejecuta en un nivel más privilegiado (modo kernel) que el usuario (modo usuario); Se utiliza una interrupción (de software) para realizar solicitudes al sistema operativo. Esto rara vez es una característica en un lenguaje de nivel superior, por lo que las funciones contenedoras para las llamadas al sistema se escriben utilizando un ensamblador en línea.
El siguiente ejemplo de código C muestra un contenedor de llamadas al sistema x86 en la sintaxis del ensamblador de AT&T , utilizando el ensamblador GNU . Estas llamadas normalmente se escriben con la ayuda de macros; el código completo se incluye para mayor claridad. En este caso particular, el contenedor realiza una llamada al sistema de un número dado por la persona que llama con tres operandos, devolviendo el resultado. [13]
En resumen, GCC admite ensamblaje básico y extendido . El primero simplemente pasa el texto palabra por palabra al ensamblador, mientras que el segundo realiza algunas sustituciones de ubicaciones de registros. [4]
externo int errno ; int syscall3 ( int núm , int arg1 , int arg2 , int arg3 ) { int res ; __asm__ ( "int $0x80" /* realiza la solicitud al sistema operativo */ : "=a" ( res ), /* devuelve el resultado en eax ("a") */ "+b" ( arg1 ), /* pasa arg1 en ebx ("b") [como salida "+" porque la llamada al sistema puede cambiarla] */ "+c" ( arg2 ), /* pasa arg2 en ecx ("c") [ídem] */ "+ d" ( arg3 ) /* pasa arg3 en edx ("d") [ídem] */ : "a" ( num ) /* pasa el número de llamada del sistema en eax ("a") */ : "memoria" , "cc " , /* anuncia al compilador que los códigos de memoria y de condición han sido modificados */ "esi" , "edi" , "ebp" ); /* estos registros también están bloqueados [cambiados por la llamada al sistema] */ /* El sistema operativo devolverá un valor negativo en caso de error; * los contenedores devuelven -1 en caso de error y establecen la variable global errno */ if ( -125 < = res && res < 0 ) { errno = -res ; res = -1 ; } devolver resolución ; }
Este ejemplo de ensamblado en línea del lenguaje de programación D muestra el código que calcula la tangente de x usando las instrucciones FPU ( x87 ) del x86 .
// Calcular la tangente de x real tan ( real x ) { asm { fld x [ EBP ] ; // carga xfxam ; // prueba de valores extraños fstsw AX ; sahf ; jc trigerr ; // C0 = 1: x es NAN, infinito o vacío // Los 387 pueden manejar anormalidades SC18 : fptan ; fstpST ( 0 ) ; // volcamos X, que siempre es 1 fstsw AX ; sahf ; // if (!(fp_status & 0x20)) ir a Lret jnp Lret ; // C2 = 1: x está fuera de rango, reduce el argumento fldpi ; // carga pi fxch ; SC17 : fprem1 ; // recordatorio (parcial) fstsw AX ; sahf ; jp SC17 ; // C2 = 1: recordatorio parcial, es necesario realizar un bucle fstp ST ( 1 ) ; // elimina pi de la pila jmp SC18 ; } disparador : devuelve real . nan ; Lret : // No es necesario devolver nada manualmente ya que el valor ya está en la pila de FP ; }
Para los lectores que no estén familiarizados con la programación x87, el modismo fstsw-sahf seguido del salto condicional se utiliza para acceder a los bits C0 y C2 de la palabra de estado de la FPU x87. fstsw almacena el estado en un registro de uso general; sahf establece el registro FLAGS en los 8 bits superiores del registro; y el salto se utiliza para juzgar cualquier bit de bandera que corresponda al bit de estado de la FPU. [14]
Sin embargo, es posible implementar soporte para ensamblaje en línea sin soporte del backend del compilador utilizando un ensamblador externo.Solicitud de extracción para seguimiento de estado
La forma FNSTSW AX de la instrucción se utiliza principalmente en bifurcaciones condicionales...