Tabla de Contenidos
Guía de supervivencia del enlazador
Descargo
Esta guía no pretende ser un texto que trate de forma exhaustiva los pormenores de los métodos de enlace, su asociación con la interfaz binaria de la aplicación específica de una arquitectura ABI x86, así como la sintaxis del GNU LD, sino que empleará un enfoque meramente práctico para la resolución de los ejercicios requeridos por la materia, haciendo hincapié en algunos conceptos útiles que deben ser comprendidos para la formación del futuro ingeniero.
Introducción
La guía solo cubre la sintaxis GNU LD y la ABI x86 desarrollada por el Tool Interface Standards (TIS) del UNIX System Laboratories. Para una mejor comprensión de los conceptos que se brindarán, el lector debe estar familiarizado con los siguiente temas:
- Etapas de la generación de un binario ejecutable (ensamblar, compilar, preprocesar, enlazar)
- Executable and Linking Format (ELF)
- Manejo básico del lenguaje ensamblador.
Misión de un enlazador
El enlazador tiene por objetivo combinar varios fragmentos de código y datos en un único fichero binario, resolviendo las direcciones de las diversas bifurcaciones de código. El binario generado puede presentar la misma organización de la memoria en la cual se carga, por ejemplo una ROM, o diferir como suele ser el caso de la RAM, en donde por lo general se reubica. Vale la pena aclarar que también es posible que la organización de la memoria de carga (ROM) coincida con la virtual (RAM).
Lo expresado anteriormente puede comprenderse claramente mediante el siguiente ejemplo.
En un sistema basado en un procesador x86 con una ROM de 1kB, una RAM de 8MB y sin sistema operativo alguno, se desea realizar un programa que establezca a '0' un área de memoria de 4MB previamente inicializada en un algun valor. Antes de continuar leyendo reflexione sobre lo siguiente: * Elabore mentalmente un programa que cumpla con el objetivo propuesto, sabiendo que el sistema no dispone de ningún sistema operativo. * ¿Donde se encuentra el programa durante la ejecución? * ¿Como llegó a la ubicación de la respuesta anterior? * ¿Que tamaño, aunque más no sea basandose en el tamaño de los datos, estima que tendría el programa?
#define MEM_4MB (4*1024*1024) char mem_ptr_a[MEM_4MB]={'C','h','r','i','s','t','i','a','N'}; /*Algunos elementos inicializados a algun valor*/ int main(void) { unsigned int contador; for(contador=0; contador<MEM_4MB; contador++) { mem_ptr_a[contador]=0; } }
Resulta evidente que el binario generado ocupará al menos 4MBytes, por lo cual no cabrá en la ROM del sistema. Para solventar el problema mencionado se propone:
char mem_ptr_c[]="ChristiaN"; int main(void) { unsigned int contador; for(contador=0; contador<MEM_4MB; contador++) { mem_ptr_c[contador]=0; } }
El código propuesto genera un binario con el siguiente mapa de memoria simplificado.
ROM | |
---|---|
0x0000 | |
CODIGO | 0x0034 0x005D |
DATOS | 0x0060 0x006A |
0x0400 |
A partir del mismo se aprecia que no es posible utilizar este binario como código de arranque de nuestro sistema ya que la familia de procesadores Intel IA32 requieren que el mismo se encuentre a partir de la dirección 0xFFFF0, es decir, que el mapa de memoria necesario sería
ROM | |
---|---|
0xFFBFF | |
DATOS | DAT_INI DAT_FIN |
CODIGO | COD_INI COD_FIN |
0xFFFF0 |
Como puede observarse no solo es necesario alterar el orden de la sección de datos y código sino que también, se deben modificar las direcciones de los punteros de datos y las llamadas a función.
Para resolver esta clase de problemas se utiliza el enlazador. Sin más preámbulos, a continuación se explicará como realizar estas operaciones.
Utilización de LD
Conceptos básicos
Al igual que muchas otras herramientas GNU, conocidas en la jerga como binutils, el enlazador ld se ejecuta desde la línea de comandos utilizando el siguiente formato:
ld <opciones> fichero_de_entrada_1 fichero_de_entrada_2 fichero_de_entrada_n -o fichero_de_salida
Dentro de las múltiples opciones que presenta la herramienta, las cuales pueden listarse mediante man -a ld, vale la pena mencionar -T, la cual permite indicarle al ld que utilice un script proporcionado por el usuario para enlazar los fiheros ELF de entrada. Este script conocido como linker script o lds por su extensión permite indicarle al ld como debe organizar las diferentes secciones que compondrán el fichero de salida, así como también en que dirección deberán ser referenciadas y/o ubicadas físicamente las mismas. Estas direcciones, como se explicará a continuación, se conocen como VMA y LMA respectivamente. El enlazador asocia cada sección de un programa a dos direcciones de memoria, que pueden coincidir o no:
- Virtual [VMA] : es aquella donde el binario copia los datos modificables y en algunos casos una parte de si mismo para su posterior ejecución, por lo general se la asocia a la memoria RAM.
- De Carga [LMA] : es aquella en donde se almacena el binario para su ejecución inicial, generalmente se la asocia a la memoria ROM.
La herramienta utiliza un registro interno, llamado contador de posición (location counter) y representado por el operador ., para llevar la cuenta en forma de desplazamiento relativo, de la ubicación las secciones. Es altamente recomendable que las direcciones VMA se especifiquen en orden creciente, ya que como se vera en las secciones subsiguientes el contador de posición opera utilizando desplazamientos relativos a la sección anterior, por lo cual si se estableciera una dirección anterior ocurrirá un desborde.
Formato de un LDS
Un fichero linker script puede llegar a ser tan críptico que se requieran varios minutos en entender su objetivo, sin embargo conceptualmente se debe comprender que no es más que un agrupamiento de secciones y sus respectivas direcciones VMA y LMA, las cuales se gestionan mediante el contador de posición cuyo símbolo es el punto[.]. La función de este último es simplemente disponer de un índice que permita gestionar los desplazamientos relativos y/o absolutos de una sección respecto a otra. Si bien existe una gran cantidad de opciones y comandos disponibles para un linker script, la presente guía se focalizará en el siguiente modelo, el cual se explicará con ejemplos en los apartados siguientes.
SECTIONS { section [direccion] [(tipo)] : [AT(lma)] [ALIGN(alinear_la_seccion)] [SUBALIGN(alinear_subseccion)] { comandos_para_la_seccion } [>region] [AT>lma_region] [:direccion_fisica :direccion_fisica ...] [=expresion_para_el_relleno_de_espacios] }
Casos de uso
Reubicación completa del binario
El siguiente ejemplo tiene por objetivo mostrar como es posible modificar las direcciones absolutas de las variables, es decir el equivalente a la directiva ORG de NASM. Como se puede apreciar las direcciones se desplazan a partir de la dirección 0x08000. El fichero de enlace es mi_linker_script.lds
SECTIONS { . = 0x08000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x08000*/ /* por lo cual todas las operaciones sucesivas se realizarán a partir de*/ /* esta posición.*/ }
Los parámetros del enlazador
ld -z max-page-size=0x01000 --oformat=binary -m elf_i386 -T mi_linker_script.lds -e Inicio fichero.elf -o fichero.bin
El código y sus salidas
+--------------------------------+--------------------------------------------------------------+--------------------+ |.asm | .lst |.bin | +--------------------------------+--------------------------------------------------------------+--------------------+ | 1 bits 32 | 1 bits 32 | | | 2 | 2 | | | 3 global Inicio | 3 global Inicio | | | 4 | 4 | | | 5 dato: dw 0x1234 | 5 00000000 3412 dato: dw 0x1234 | 34 12 | | 6 | 6 | | | 7 Inicio: | 7 Inicio: | | | 8 xor edi, edi | 8 00000002 31FF xor edi, edi | 31 ff | | 9 mov dword edi, 0x01 | 9 00000004 BF01000000 mov dword edi, 0x01 | bf 01 00 00 00 | |10 xor esi, esi | 10 00000009 31F6 xor esi, esi | 31 f6 | |11 mov dword esi,0x03 | 11 0000000B BE03000000 mov dword esi,0x03 | be 03 00 00 00 | |12 mov dword [variable], 0x04 | 12 00000010 C705[2E000000]0400- mov dword [variable], 0x04 | c7 05 2e 80 00 | |13 | 13 00000018 0000 | 00 04 00 00 00 | |14 xor eax, eax | 14 0000001A 31C0 xor eax, eax | 31 c0 | |15 add eax, edi | 15 0000001C 01F8 add eax, edi | 01 f8 | |16 add eax, esi | 16 0000001E 01F0 add eax, esi | 01 f0 | |17 add dword eax, [variable] | 17 00000020 0305[2E000000] add dword eax, [variable] | 03 05 2e 80 00 00 | <-En el binario final se modifica la |18 mov dword [resultado], eax | 18 00000026 A3[2C000000] mov dword [resultado], eax | a3 2c 80 00 00 | direccion absoluta |19 hlt | 19 0000002B F4 hlt | f4 | |20 | 20 | | |21 resultado: dw 0x9876 | 21 0000002C 7698 resultado: dw 0x9876 | 76 98 | |22 variable: resd 1 | 22 0000002E <res 00000004> variable: resd 1 | 00 00 00 00 | +--------------------------------+--------------------------------------------------------------+--------------------+
variable en el codigo ensamblado se encuentra en la posición 0x00002E (base 0x00000, desplazamiento 0x00002E), sin embargo gracias al enlazador es posible indicarle que en el mapa de memoria de nuestro hardware, la misma se ubicará a partir de una nueva base, en este caso 0x08000. De no realizar esta traslación, si se ejecutara el binario obtenido por el ensamblador (visualizado a través del .lst), la instrucción add tomaría el contenido de la dirección 0x00002E, en vez de 0x0802E donde realmente se encuentra la variable.
Reubicación de secciones
A continuación se muestra como es posible reubicar diferentes secciones de un programa. Como se puede apreciar las direcciones que referencian a los datos y de las bifuraciones de flujo de ejecución se modifican según lo especificado en el fichero “.lds”. El fichero de enlace es mi_linker_script.lds
SECTIONS { . = 0x08000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x08000 */ __codigo_inicio = .; /* Se crea una constante cuyo valor es el del contador de posición al momento de su creación, en este caso 0x08000 */ .text : { *(.codigo_principal); }/* En la sección predefinida por la ABI "text" se le agregan todas las secciones denominadas "codigo_principal" */ . = 0x09000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x09000*/ __datos_iniciali_inicio = .; .data : { *(.dat_inic*); } /* Físicamente los datos se establecen a partir de la dirección 0x1000.*/ . = 0x0A000; __datos_no_iniciali_inicio = .; .bss : { *(.dat_no_inic*); } . = 0x0B000; .codigo_adicional : AT (ADDR(.text) + SIZEOF(.text)) { *(.codigo_funciones); } /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x0B000 */ /* El "codigo_funciones" se coloca fisicamente a continuación del "codigo_principal" principal, pero reemplaza*/ /* la base de las direcciones VMA por 0x0B000*/ }
Los parámetros del enlazador
ld -z max-page-size=0x01000 --oformat=binary -m elf_i386 -T mi_linker_script.lds -e Inicio fichero.elf -o fichero.bin
El código y sus salidas
+-----------------------------------+--------------------------------------------------------------------------+--------------------+ |.asm | .lst |.bin | +-----------------------------------+--------------------------------------------------------------------------+--------------------+ | 1 bits 32 | 1 bits 32 | | | 2 | 2 | | | 3 global Inicio | 3 global Inicio | | | 4 | 4 | | | 5 SECTION .codigo_principal | 5 SECTION .codigo_principal | | | 6 | 6 | | | 7 Inicio: | 7 Inicio: | | | 8 mov dword edi, 0x1 | 8 00000000 BF01000000 mov dword edi, 0x1 | bf 01 00 00 00 | | 9 mov dword [parametro_a], edi | 9 00000005 893D[04000000] mov dword [parametro_a], edi | 89 3d 04 a0 00 00 | |10 mov dword esi, 0x3 | 10 0000000B BE03000000 mov dword esi, 0x3 | be 03 00 00 00 | |11 mov dword [parametro_b], esi | 11 00000010 8935[06000000] mov dword [parametro_b], esi | 89 35 06 a0 00 00 | |12 call far [_suma] | 12 00000016 FF1D[00000000] call far [_suma] | ff 1d 00 b0 00 00 | |13 mov dword eax, [resultado] | 13 0000001C A1[00000000] mov dword eax, [resultado] | a1 00 90 00 00 | |14 add dword eax, [variable_a] | 14 00000021 0305[00000000] add dword eax, [variable_a] | 03 05 00 a0 00 00 | |15 mov dword [array_a], eax | 15 00000027 A3[08000000] mov dword [array_a], eax | a3 08 a0 00 00 | |16 hlt | 16 0000002C F4 hlt | f4 | |17 | 17 | | |18 SECTION .dat_inic_a progbits | 18 SECTION .dat_inic_a progbits | | |19 resultado: dw 0x9876 | 19 00000000 7698 resultado: dw 0x9876 | 76 98 | |20 texto_a: db "Hola" | 20 00000002 486F6C61 texto_a: db "Hola" | 48 6f 6c 61 | |21 | 21 | | |22 SECTION .dat_no_inic_a nobits1 | 22 SECTION .dat_no_inic_a nobits1 | | |23 variable_a: resd 1 | 23 00000000 <res 00000004> variable_a: resd 1 | 00 00 00 00 |<-Cada sección comienza | | | 00 00 00 00 | a partir de 0x0000 |24 parametro_a: resw 1 | 24 00000004 <res 00000002> parametro_a: resw 1 | 00 00 00 00 | |25 parametro_b: resw 1 | 25 00000006 <res 00000002> parametro_b: resw 1 | 00 00 00 00 | |26 array_a: resb 4 | 26 00000008 <res 00000004> array_a: resb 4 | 00 | |27 | 27 | | |28 SECTION .codigo_funciones | 28 SECTION .codigo_funciones | | |29 _suma: | 29 _suma: | | |30 push edi | 30 00000000 57 push edi | 57 | |31 push esi | 31 00000001 56 push esi | 56 | |32 push eax | 32 00000002 50 push eax | 50 | |33 | 33 | | |34 xor edi, edi | 34 00000003 31FF xor edi, edi | 31 ff | |35 mov dword edi,[parametro_a]| 35 00000005 8B3D[04000000] mov dword edi, [parametro_a] | 8b 3d 04 a0 00 00 |<-Dirección modificada |36 xor esi, esi | 36 0000000B 31F6 xor esi, esi | 31 f6 | de 0x0004 a 0xa004 |37 mov dword esi,[parametro_b]| 37 0000000D 8B35[06000000] mov dword esi, [parametro_b] | 8b 35 06 a0 00 00 |<-Dirección modificada |38 xor eax, eax | 38 00000013 31C0 xor eax, eax | 31 c0 | de 0x0006 a 0xa006 |39 add eax, edi | 39 00000015 01F8 add eax, edi | 01 f8 | |40 add eax, esi | 40 00000017 01F0 add eax, esi | 01 f0 | |41 mov dword [resultado], eax | 41 00000019 A3[00000000] mov dword [resultado], eax | a3 00 90 00 00 |<-Dirección modificada |42 | 42 | | de 0x0000 a 0x9000 |43 pop eax | 43 0000001E 58 pop eax | 58 | |44 pop esi | 44 0000001F 5E pop esi | 5e | |45 pop edi | 45 00000020 5F pop edi | 5f | |46 | 46 | | |47 ret | 47 00000021 C3 ret | c3 | +-----------------------------------+--------------------------------------------------------------------------+--------------------+
Fichero binario (el desplazamiento solo se presenta a titulo informativo, no se encuentra en el fichero). Se puede apreciar claramente como el binario final dispone de un orden diferente al que se genera utilizando solo el ensamblador
+----------------+--------------------------------------------------+ | DESPLAZAMIENTO | CODIGOS DE OPERACION | +----------------+--------------------------------------------------+ | 00000000 | bf 01 00 00 00 89 3d 04 a0 00 00 be 03 00 00 00 | | 00000010 | 89 35 06 a0 00 00 ff 1d 00 b0 00 00 a1 00 90 00 | | 00000020 | 00 03 05 00 a0 00 00 a3 08 a0 00 00 f4 57 56 50 | <-En "0xf4" finaliza el "codigo_principal" y comienza "codigo_adicional" | 00000030 | 31 ff 8b 3d 04 a0 00 00 31 f6 8b 35 06 a0 00 00 | | 00000040 | 31 c0 01 f8 01 f0 a3 00 90 00 00 58 5e 5f c3 00 | | 00000050 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | | * | | | 00001000 | 76 98 48 6f 6c 61 | | 00001006 | | +----------------+--------------------------------------------------+
dat_inic se establecen a partir de la dirección 0x1000 ya que el contador de posición se desplaza desde 0x08000 a 0x09000 en el fichero lds y como no se indica ninguna reubicación física, como en el caso de codigo_adicional, la LMA coincide con el desplazamiento solicitado a la VMA no con la VMA.
Definición de zonas de memoria
Por defecto el enlazador utiliza todo el mapa de memoria de la arquitectura para realizar los cálculos de los desplazamientos y la ubicación de las secciones. En ciertos casos frecuentemente utilizados, suele ser necesario disponer de un mapa de memoria acorde a la cantidad de memoria que se utilizará, más que el provisto por la arquitectura. A modo de ejemplo se puede indicar que el GNU ld 2.26.1 utiliza por defecto el rango
Name Origin Length *default* 0x0000000000000000 0xffffffffffffffff
sin embargo en sistemas dedicados la memoria dificilmente supere el rango
Name Origin Length memory 0x0000000000000000 0x00000000ffffffff
e incluso hasta hace relativamente pocos años el mapa de memoria de una PC podría haberse simplificado en forma extrema como
Name Origin Length ram 0x0000000000000000 0x00000000ffff0000 rom 0x00000000ffff0000 0x000000000000ffff
Para definir las zonas de memoria de un diseño determinado, especificar el rango de direcciones de una sección e incluso para obtener información sobre la tasa de ocupación de una zona en especial, el enlazador provee el comando MEMORY, cuya sintáxis y utilización básica se indica a continuación
SECTIONS { MEMORY { nombre [(atributos)] : ORIGIN = origen, LENGTH = largo } }
Como objetivo ejemplificador se utilizará un procesador de 20bits para el cual se requiere una ROM de inicialización de 64kB, que debe situarse en el tramo final del rango de direcciones y una RAM de 512kB que se ubica en el inicio del mismo. El fichero de enlace para tal fin tendrá el siguiente formato
MEMORY { memory (wx) : ORIGIN = 0x00000000, LENGTH = 0xFFFFFFFF rom16 (rx) : ORIGIN = 0x0000, LENGTH = 64k } SECTIONS { .kernel 0x00010000: /* El codigo debera copiarse en tiempo de ejecucion a la dirección VMA(RAM) 0x10000 */ AT( 0xFFFF0000 ) /* El codigo debe ubicarse inicialmente a partir de dirección LAM(ROM) 0xFFFF0000 */ {*(.kernel_init);} >memory .reset 0xFFF0: AT( 0xFFFFFFF0 ) {*(.rst_vect);} >rom16 }
Definición de ficheros de entrada y salida
En algunos casos puede resultar útil, sobre todo cuando los ficheros de entrada son numerosos, especificar los ficheros de entrada y salida dentro del mismo fichero de enlace. A tal fin se utilizan las siguientes directivas
INPUT(fichero_objeto_enlazable_entrada_1) INPUT(fichero_objeto_enlazable_entrada_2) ... INPUT(fichero_objeto_enlazable_entrada_n) OUTPUT(fichero_objeto_ejecutable_salida)
Cada fichero de entrada debe disponer de una directiva INPUT
El fichero de enlace para el caso Reubicación de secciones resultará
INPUT(fichero.elf) OUTPUT(fichero.bin) SECTIONS { . = 0x08000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x08000 */ __codigo_inicio = .; /* Se crea una constante cuyo valor es el del contador de posición al momento de su creación, en este caso 0x08000 */ .text : { *(.codigo_principal); }/* En la sección predefinida por la ABI "text" se le agregan todas las secciones denominadas "codigo_principal" */ . = 0x09000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x09000*/ __datos_iniciali_inicio = .; .data : { *(.dat_inic*); } /* Físicamente los datos se establecen a partir de la dirección 0x1000.*/ . = 0x0A000; __datos_no_iniciali_inicio = .; .bss : { *(.dat_no_inic*); } . = 0x0B000; .codigo_adicional : AT (ADDR(.text) + SIZEOF(.text)) { *(.codigo_funciones); } /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x0B000 */ /* El "codigo_funciones" se coloca fisicamente a continuación del "codigo_principal" principal, pero reemplaza*/ /* la base de las direcciones VMA por 0x0B000*/ }
Mientras que el comando de enlace se simplificará a ld -z max-page-size=0x01000 –oformat=binary -m elf_i386 -T mi_linker_script.lds -e Inicio
Definición de punto de entrada
Todo código fuente dispone de una instrucción a partir de la cual comienza la ejecución. Dicho punto de entrada se le debe especificar al enlazador de forma tal que este lo establezca al inicio durante la construcción del binario ejecutable. En los ejemplos anteriores el punto de entrada se le pasa como argumento al enlazador mediante el parámetro -e etiqueta, siendo esta Inicio. Sin embargo es posible indicar dicho punto de entrada en el fichero de enlace mediante
ENTRY(symbol)
Tomando el fichero de enlace para el caso Reubicación de secciones resultará
INPUT(fichero.elf) OUTPUT(fichero.bin) ENTRY(Inicio) SECTIONS { . = 0x08000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x08000 */ __codigo_inicio = .; /* Se crea una constante cuyo valor es el del contador de posición al momento de su creación, en este caso 0x08000 */ .text : { *(.codigo_principal); }/* En la sección predefinida por la ABI "text" se le agregan todas las secciones denominadas "codigo_principal" */ . = 0x09000; /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x09000*/ __datos_iniciali_inicio = .; .data : { *(.dat_inic*); } /* Físicamente los datos se establecen a partir de la dirección 0x1000.*/ . = 0x0A000; __datos_no_iniciali_inicio = .; .bss : { *(.dat_no_inic*); } . = 0x0B000; .codigo_adicional : AT (ADDR(.text) + SIZEOF(.text)) { *(.codigo_funciones); } /* Se iguala al contador de posición (operador ".") a la dirección VMA 0x0B000 */ /* El "codigo_funciones" se coloca fisicamente a continuación del "codigo_principal" principal, pero reemplaza*/ /* la base de las direcciones VMA por 0x0B000*/ }
Resultando el comando de enlace simplificado a ld -z max-page-size=0x01000 –oformat=binary -m elf_i386 -T mi_linker_script.lds
Reubicación de secciones utilizando C y ensamblador
Un recurso frecuentemente utilizado es la mezcla de C y ensamblador en un mismo proyecto. Para tal fin el enlazador es una herramienta crucial. A continuación se ejemplifica la metodología a emplear, para utilizar funciones de escritas en C dentro de un fuente de ensamblador y relocalizarlas mediante el enlazador. Es importante observar como el código queda agrupado en forma consecutiva, pero las direcciones de llamada y acceso, corresponden al mapa de memoria virtual según lo especificado en el fichero “.lds”. El fichero de enlace es mi_linker_script.lds
SECTIONS { .text 0x08000: AT (0x000000) { *(.codigo_principal); } . = 0x09000; __datos_iniciali_inicio = .; .data : { *(.dat_inic*); } . = 0x0A000; __datos_no_iniciali_inicio = .; .bss : { *(.dat_no_inic*); } __codigo_adicional 0x0B000: AT ( LOADADDR(.text) + SIZEOF(.text)) { *(.codigo_funciones); } .eh_frame 0x0D000: { *(.eh_frame); } }
Los parámetros del enlazador
ld -z max-page-size=0x01000 --oformat=binary -m elf_i386 -T mi_linker_script.lds -e Inicio fichero_s.elf fichero_c.elf -o fichero.bin
El código y sus salidas
+-----------------------------------+--------------------------------------------------------------------------+--------------------+ |.asm | .lst |.bin | +-----------------------------------+--------------------------------------------------------------------------+--------------------+ | 1 bits 32 | 1 bits 32 | | | 2 | 2 | | | 3 global Inicio | 3 global Inicio | | | 4 | 4 | | | 5 SECTION .codigo_principal | 5 SECTION .codigo_principal | | | 6 | 6 | | | 7 Inicio: | 7 Inicio: | | | 8 call [clrscr] | 8 00000000 FF15[00000000] call [clrscr] | ff 15 00 00 00 00 | | 9 mov dword edi, 0x1 | 9 00000000 BF01000000 mov dword edi, 0x1 | bf 01 00 00 00 | |10 mov dword [parametro_a], edi | 10 00000005 893D[04000000] mov dword [parametro_a], edi | 89 3d 04 a0 00 00 | |11 mov dword esi, 0x3 | 11 0000000B BE03000000 mov dword esi, 0x3 | be 03 00 00 00 | |12 mov dword [parametro_b], esi | 12 00000010 8935[06000000] mov dword [parametro_b], esi | 89 35 06 a0 00 00 | |13 call far [_suma] | 13 00000016 FF1D[00000000] call far [_suma] | ff 1d 00 b0 00 00 | |14 mov dword eax, [resultado] | 14 0000001C A1[00000000] mov dword eax, [resultado] | a1 00 90 00 00 | |15 add dword eax, [variable_a] | 15 00000021 0305[00000000] add dword eax, [variable_a] | 03 05 00 a0 00 00 | |16 mov dword [array_a], eax | 16 00000027 A3[08000000] mov dword [array_a], eax | a3 08 a0 00 00 | |17 hlt | 17 0000002C F4 hlt | f4 | |18 | 18 | | |19 SECTION .dat_inic_a progbits | 19 SECTION .dat_inic_a progbits | | |20 resultado: dw 0x9876 | 20 00000000 7698 resultado: dw 0x9876 | 76 98 | |21 texto_a: db "Hola" | 21 00000002 486F6C61 texto_a: db "Hola" | 48 6f 6c 61 | |22 | 22 | | |23 SECTION .dat_no_inic_a nobits1 | 23 SECTION .dat_no_inic_a nobits1 | | |24 variable_a: resd 1 | 24 00000000 <res 00000004> variable_a: resd 1 | 00 00 00 00 | | | | 00 00 00 00 | |25 parametro_a: resw 1 | 25 00000004 <res 00000002> parametro_a: resw 1 | 00 00 00 00 | |26 parametro_b: resw 1 | 26 00000006 <res 00000002> parametro_b: resw 1 | 00 00 00 00 | |27 array_a: resb 4 | 27 00000008 <res 00000004> array_a: resb 4 | 00 | |28 | 28 | | |29 SECTION .codigo_funciones | 29 SECTION .codigo_funciones | | |30 _suma: | 30 _suma: | | |31 push edi | 31 00000000 57 push edi | 57 | |32 push esi | 32 00000001 56 push esi | 56 | |33 push eax | 33 00000002 50 push eax | 50 | |34 | 34 | | |35 xor edi, edi | 35 00000003 31FF xor edi, edi | 31 ff | |36 mov dword edi,[parametro_a]| 36 00000005 8B3D[04000000] mov dword edi, [parametro_a] | 8b 3d 04 a0 00 00 | |37 xor esi, esi | 37 0000000B 31F6 xor esi, esi | 31 f6 | |38 mov dword esi,[parametro_b]| 38 0000000D 8B35[06000000] mov dword esi, [parametro_b] | 8b 35 06 a0 00 00 | |39 xor eax, eax | 39 00000013 31C0 xor eax, eax | 31 c0 | |40 add eax, edi | 40 00000015 01F8 add eax, edi | 01 f8 | |41 add eax, esi | 41 00000017 01F0 add eax, esi | 01 f0 | |42 mov dword [resultado], eax | 42 00000019 A3[00000000] mov dword [resultado], eax | a3 00 90 00 00 | |43 | 43 | | |44 pop eax | 44 0000001E 58 pop eax | 58 | |45 pop esi | 45 0000001F 5E pop esi | 5e | |46 pop edi | 46 00000020 5F pop edi | 5f | |47 | 47 | | |48 ret | 48 00000021 C3 ret | c3 | +-----------------------------------+--------------------------------------------------------------------------+--------------------+
El código fuente C que implementa la función clrcr, y el binario asociado sin considerar la sección exception handler frame (eh_frame)
#include "libfc.h" void clrscr(void) { unsigned short cont_aux; unsigned char *ptr_video_text = (unsigned char *)VIDEO_BUFFER_INI; for (cont_aux=0; cont_aux<VIDEO_BUFFER_LENGTH; cont_aux++) { *ptr_video_text++ = SPACE_CHAR; *ptr_video_text++ = VIDEO_BCKGRND_BLACK; } }
+----------------+--------------------------------------------------+ | DESPLAZAMIENTO | CODIGOS DE OPERACION | +----------------+--------------------------------------------------+ |00000000 | 55 89 e5 83 ec 10 c7 45 f8 00 80 0b 00 66 c7 45 | |00000010 | fe 00 00 eb 19 8b 45 f8 c6 00 20 83 45 f8 01 8b | |00000020 | 45 f8 c6 00 07 83 45 f8 01 66 83 45 fe 01 66 81 | |00000030 | 7d fe cf 07 76 df c9 c3 | +----------------+--------------------------------------------------+
A modo meramente informativo se presenta el ensamblador generado por gcc a partir de fuente .C
00000000 <clrscr> push %ebp 00000001 <clrscr+0x1> mov %esp,%ebp 00000003 <clrscr+0x3> sub $0x10,%esp 00000006 <clrscr+0x6> movl $0xb8000,-0x8(%ebp) 0000000d <clrscr+0xd> movw $0x0,-0x2(%ebp) 00000013 <clrscr+0x13> jmp 0000002e <clrscr+0x2e> 00000015 <clrscr+0x15> mov -0x8(%ebp),%eax 00000018 <clrscr+0x18> movb $0x20,(%eax) 0000001b <clrscr+0x1b> addl $0x1,-0x8(%ebp) 0000001f <clrscr+0x1f> mov -0x8(%ebp),%eax 00000022 <clrscr+0x22> movb $0x7,(%eax) 00000025 <clrscr+0x25> addl $0x1,-0x8(%ebp) 00000029 <clrscr+0x29> addw $0x1,-0x2(%ebp) 0000002e <clrscr+0x2e> cmpw $0x7cf,-0x2(%ebp) 00000034 <clrscr+0x34> jbe 00000015 <clrscr+0x15> 00000036 <clrscr+0x36> leave 00000037 <clrscr+0x37> ret
La cabecera en la cual se explicita la sección donde se debe ubicar el código de máquina generado a partir del fuente detallado precedentemente, debe disponer de los siguientes atributos
#ifndef __LIBFC_H #define __LIBFC_H #define VIDEO_BUFFER_INI 0xB8000 #define VIDEO_TEXT_SCREEN_WITDH 80 #define VIDEO_TEXT_SCREEN_HIGH 25 #define VIDEO_BUFFER_LENGTH (VIDEO_TEXT_SCREEN_HIGH*VIDEO_TEXT_SCREEN_WITDH) #define VIDEO_BCKGRND_BLACK 0x07 #define SPACE_CHAR 0x20 void clrscr(void) __attribute__(( section(".codigo_funciones"))); #endif
Por último y para concluir este apartado se muestra el binario obtenido, en el cual es importante apreciar (ver lo indicado entre ><) como las direcciones virtuales han sido reemplazadas en las instrucciones correspondiente y son completamente diferentes a las direcciones de carga.
+----------------+--------------------------------------------------+ | DESPLAZAMIENTO | CODIGOS DE OPERACION | +----------------+--------------------------------------------------+ |00000000 |>ff 15 22 b0 00 00<bf 01 00 00 00>89 3d 04 a0 00 | |00000010 | 00<be 03 00 00 00>89 35 06 a0 00 00>ff 15 00 b0 | |00000020 | 00 00 a1 00 90 00 00 03 05 00 a0 00 00 a3 08 a0 | |00000030 | 00 00<f4 00 57 56 50 31 ff>8b 3d 04 a0 00 00<31 | En 34 comienza la función suma |00000040 | f6>8b 35 06 a0 00 00<31 c0 01 f8 01 f0 a3 00 90 | |00000050 | 00 00 58 5e 5f c3 55 89 e5 83 ec 10 c7 45 f8 00 | En 56 comienza la función clrscr |00000060 | 80 0b 00 66 c7 45 fe 00 00 eb 19 8b 45 f8 c6 00 | |00000070 | 20 83 45 f8 01 8b 45 f8 c6 00 07 83 45 f8 01 66 | |00000080 | 83 45 fe 01 66 81 7d fe cf 07 76 df c9 c3 00 00 | |00000090 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |* | | |00001000 | 76 98 48 6f 6c 61 00 00 00 00 00 00 00 00 00 00 | |00001010 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | |* | | |00002030 | 00 00 00 00 14 00 00 00 00 00 00 00 01 7a 52 00 | |00002040 | 01 7c 08 01 1b 0c 04 04 88 01 00 00 1c 00 00 00 | |00002050 | 1c 00 00 00 02 e0 ff ff 38 00 00 00 00 41 0e 08 | |00002060 | 85 02 42 0d 05 74 c5 0c 04 04 00 00 | +----------------+--------------------------------------------------+
— ChristiaN 2023/04/27 12:03
FAQ
- ¡No tengo conocimiento de ensamblador, pero si de C ¿puedo leer esta guía?
Si dispone de un buen conocimiento de ELF y de como C gestiona los diferentes tipos de datos, los conceptos brindados serán comprensibles.
- ¡No tengo conocimiento de ensamblador ni de C, pero sé muchísimo de Java ¿puedo leer esta guía?
Si puede leerla, pero no la entenderá.
- ¡Soy especialista en Phyton, Visual Basic y C# ¿puedo leer esta guía?