El programa anterior tiene 9 encabezados de programa, luego, el mapeo de segmentos indica en qué encabezado de programa (del 00 al 08) se encuentra cada sección.
PHDR - Encabezado de Programa
Contiene las tablas de encabezados de programa y la metadata en sí.
INTERP
Indica la ruta del cargador que se debe usar para cargar el binario en memoria.
LOAD
Estos encabezados se utilizan para indicar cómo cargar un binario en memoria.
Cada encabezado LOAD indica una región de memoria (tamaño, permisos y alineación) e indica los bytes del binario ELF que se copiarán allí.
Por ejemplo, el segundo tiene un tamaño de 0x1190, debe estar ubicado en 0x1fc48 con permisos de lectura y escritura y se llenará con 0x528 desde el desplazamiento 0xfc48 (no llena todo el espacio reservado). Esta memoria contendrá las secciones .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
Este encabezado ayuda a vincular programas a sus dependencias de biblioteca y aplicar reubicaciones. Consulta la sección .dynamic.
NOTE
Esto almacena información de metadata del proveedor sobre el binario.
GNU_EH_FRAME
Define la ubicación de las tablas de deshacer la pila, utilizadas por depuradores y funciones de manejo de excepciones de C++.
GNU_STACK
Contiene la configuración de la defensa de prevención de ejecución de la pila. Si está habilitado, el binario no podrá ejecutar código desde la pila.
GNU_RELRO
Indica la configuración RELRO (Relocation Read-Only) del binario. Esta protección marcará como de solo lectura ciertas secciones de la memoria (como el GOT o las tablas init y fini) después de que el programa se haya cargado y antes de que comience a ejecutarse.
En el ejemplo anterior, está copiando 0x3b8 bytes a 0x1fc48 como de solo lectura, afectando las secciones .init_array .fini_array .dynamic .got .data .bss.
Ten en cuenta que RELRO puede ser parcial o completo, la versión parcial no protege la sección .plt.got, que se utiliza para lazy binding y necesita este espacio de memoria para tener permisos de escritura para escribir la dirección de las bibliotecas la primera vez que se busca su ubicación.
TLS
Define una tabla de entradas TLS, que almacena información sobre variables locales de hilo.
Encabezados de Sección
Los encabezados de sección ofrecen una vista más detallada del binario ELF.
It also indicates the location, offset, permissions but also the tipo de datos it section has.
Secciones Meta
Tabla de cadenas: Contiene todas las cadenas necesarias para el archivo ELF (pero no las que realmente usa el programa). Por ejemplo, contiene nombres de secciones como .text o .data. Y si .text está en el offset 45 en la tabla de cadenas, usará el número 45 en el campo nombre.
Para encontrar dónde está la tabla de cadenas, el ELF contiene un puntero a la tabla de cadenas.
Tabla de símbolos: Contiene información sobre los símbolos como el nombre (offset en la tabla de cadenas), dirección, tamaño y más metadatos sobre el símbolo.
Secciones Principales
.text: La instrucción del programa a ejecutar.
.data: Variables globales con un valor definido en el programa.
.bss: Variables globales no inicializadas (o inicializadas a cero). Las variables aquí se inicializan automáticamente a cero, evitando así que se añadan ceros inútiles al binario.
.rodata: Variables globales constantes (sección de solo lectura).
.tdata y .tbss: Como .data y .bss cuando se utilizan variables locales de hilo (__thread_local en C++ o __thread en C).
.dynamic: Ver abajo.
Símbolos
Los símbolos son una ubicación nombrada en el programa que podría ser una función, un objeto de datos global, variables locales de hilo...
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Cada entrada de símbolo contiene:
Nombre
Atributos de enlace (débil, local o global): Un símbolo local solo puede ser accedido por el propio programa, mientras que el símbolo global se comparte fuera del programa. Un objeto débil es, por ejemplo, una función que puede ser sobrescrita por otra diferente.
Tipo: NOTYPE (sin tipo especificado), OBJECT (variable de datos global), FUNC (función), SECTION (sección), FILE (archivo de código fuente para depuradores), TLS (variable local de hilo), GNU_IFUNC (función indirecta para reubicación)
El directorio NEEDED indica que el programa necesita cargar la biblioteca mencionada para continuar. El directorio NEEDED se completa una vez que la biblioteca compartida está completamente operativa y lista para su uso.
Reubicaciones
El cargador también debe reubicar las dependencias después de haberlas cargado. Estas reubicaciones se indican en la tabla de reubicación en formatos REL o RELA y el número de reubicaciones se da en las secciones dinámicas RELSZ o RELASZ.
Si el programa se carga en un lugar diferente de la dirección preferida (usualmente 0x400000) porque la dirección ya está en uso o debido a ASLR o cualquier otra razón, una relocalización estática corrige punteros que tenían valores esperando que el binario se cargara en la dirección preferida.
Por ejemplo, cualquier sección de tipo R_AARCH64_RELATIV debería haber modificado la dirección en el sesgo de relocalización más el valor del aditivo.
Relocalizaciones Dinámicas y GOT
La relocalización también podría hacer referencia a un símbolo externo (como una función de una dependencia). Como la función malloc de libC. Entonces, el cargador al cargar libC en una dirección, al verificar dónde se carga la función malloc, escribirá esta dirección en la tabla GOT (Tabla de Desplazamiento Global) (indicada en la tabla de relocalización) donde debería especificarse la dirección de malloc.
Tabla de Enlace de Procedimientos
La sección PLT permite realizar enlace perezoso, lo que significa que la resolución de la ubicación de una función se realizará la primera vez que se acceda a ella.
Así que cuando un programa llama a malloc, en realidad llama a la ubicación correspondiente de malloc en el PLT (malloc@plt). La primera vez que se llama, resuelve la dirección de malloc y la almacena, de modo que la próxima vez que se llame a malloc, se utiliza esa dirección en lugar del código PLT.
Inicialización del Programa
Después de que el programa ha sido cargado, es hora de que se ejecute. Sin embargo, el primer código que se ejecuta no siempre es la función main. Esto se debe a que, por ejemplo, en C++ si una variable global es un objeto de una clase, este objeto debe ser inicializadoantes de que se ejecute main, como en:
Nota que estas variables globales se encuentran en .data o .bss, pero en las listas __CTOR_LIST__ y __DTOR_LIST__ se almacenan los objetos a inicializar y destruir para mantener un seguimiento de ellos.
Desde el código C es posible obtener el mismo resultado utilizando las extensiones de GNU:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
Desde la perspectiva de un compilador, para ejecutar estas acciones antes y después de que se ejecute la función main, es posible crear una función init y una función fini que serían referenciadas en la sección dinámica como INIT y FIN. y se colocan en las secciones init y fini del ELF.
La otra opción, como se mencionó, es referenciar las listas __CTOR_LIST__ y __DTOR_LIST__ en las entradas INIT_ARRAY y FINI_ARRAY en la sección dinámica y la longitud de estas se indica mediante INIT_ARRAYSZ y FINI_ARRAYSZ. Cada entrada es un puntero a función que se llamará sin argumentos.
Además, también es posible tener un PREINIT_ARRAY con punteros que se ejecutarán antes de los punteros INIT_ARRAY.
Orden de Inicialización
El programa se carga en memoria, las variables globales estáticas se inicializan en .data y las no inicializadas se ponen a cero en .bss.
Todas las dependencias para el programa o bibliotecas son inicializadas y se ejecuta el vinculador dinámico.
Se ejecutan las funciones de PREINIT_ARRAY.
Se ejecutan las funciones de INIT_ARRAY.
Si hay una entrada INIT, se llama.
Si es una biblioteca, dlopen termina aquí; si es un programa, es hora de llamar al punto de entrada real (función main).
Almacenamiento Local por Hilo (TLS)
Se definen utilizando la palabra clave __thread_local en C++ o la extensión de GNU __thread.
Cada hilo mantendrá una ubicación única para esta variable, por lo que solo el hilo puede acceder a su variable.
Cuando se utiliza esto, las secciones .tdata y .tbss se utilizan en el ELF. Que son como .data (inicializado) y .bss (no inicializado) pero para TLS.
Cada variable tendrá una entrada en el encabezado TLS especificando el tamaño y el desplazamiento TLS, que es el desplazamiento que utilizará en el área de datos local del hilo.
El __TLS_MODULE_BASE es un símbolo utilizado para referirse a la dirección base del almacenamiento local por hilo y apunta al área en memoria que contiene todos los datos locales por hilo de un módulo.