El principio de todo proceso, define al mismo como un archivo en un dispositivo de almacenamiento, que una vez compilado y al ser ejecutado, es cargado en memoria para que el scheduler (mediante el sistema operativo), le asigne tiempo de CPU y recursos a fin de poder ejecutar las rutinas definidas en el.
Este archivo, ya convertido en binario, se encuentra escrito en algunos de los cientos lenguajes de programación que existen actualmente, por ejemplo ANSI C o Fortran. Cuando fue compilado, el compilador en un momento determinado ejecuto el linker, y definió en un header todas aquellas librerías que el programa necesita para poder ejecutarse y que estás les provean aquellas funciones y procedimientos que utilice.
Un binario, puede ser compilado de dos formas, una es estática y la otra dinámica. Compilar un binario de manera estática (parámetro -Bstatic en gcc(2)), significa que se incluirán en el mismo binario todas aquellas librerías que el programa requiera. Por su parte, la principal idea de las librerías compartidas, es tener una sola copia de las mismas instaladas en el sistema operativo y que todo aquel programa que las necesite, haga uso de ellas, y las cargue en memoria para ejecución, en segmentos que pueden ser mapeados de manera privada para el proceso, o compartido entre varios procesos/threads.
Los procesos en Unix, nacen de alguna de las variantes de la syscall fork(2). fork(2), bifurca el proceso padre en una nueva imagen del proceso (una nueva entrada en la estructura proc_t), y mediante exec(2) desplaza esta imagen para crear el mapeo y la estructuras en memoria para el nuevo proceso ejecutado.
Una vez realizado el exec del proceso, y si este está compilado de manera dinámica, en invocado el runtime linker, en este caso ld.so.1, para así poder efectuar el linkeo con todas las otras librerias que el objeto requiera, como por ejemplo libc.so. Veamos un sencillo ejemplo de esto:
#include <stdio.h>
int main()
{
printf("Hello world\n");
return 0;
}
Mediante ldd(1), podemos conocer contra que librerías se encuentra linkeado un binario:
# ldd example01
linux-vdso.so.1 => (0x00007fff371ff000)
libc.so.6 => /lib64/libc.so.6 (0x000000384ae00000)
/lib64/ld-linux-x86-64.so.2 (0x000000384aa00000)
Podemos diferenciar básicamente dos linkers el primero de ellos ld(1) el cual es ejecutado durante la compilación, y ld.so, que es invocado por exec(2) a fin de realizar el linkeo dinámico al momento de la ejecución de programa.
La variable de entorno LD_DEBUG, puede darnos un buen vistazo de esto:
#: LD_DEBUG=libs,files ./example01
5708:
5708: file=libc.so.6 [0]; needed by ./example01 [0]
5708: find library=libc.so.6 [0]; searching
5708: search cache=/etc/ld.so.cache
5708: trying file=/lib64/libc.so.6
5708:
5708: file=libc.so.6 [0]; generating link map
5708: dynamic: 0x000000384b1b0b40 base: 0x0000000000000000 size: 0x00000000003b7538
5708: entry: 0x000000384ae217b0 phdr: 0x000000384ae00040 phnum: 10
5708:
5708: calling init: /lib64/ld-linux-x86-64.so.2
5708: calling init: /lib64/libc.so.6
5708: initialize program: ./example
5708: transferring control: ./example
5708:
Hello world
5708:
5708: calling fini: ./example [0]
ELF es parte de la "System V application binary interface (ABI)", que define una interfaz del sistema operativo para operar con programas compilados ejecutables, y sus funciones, por ejemplo: el manejo del stack, manejo del heap, señales, inicialización y finalización de procesos, llamadas al sistema, como así también información especifica a las distintas arquitecturas que tenga soporte el sistema operativo.
Existen tres tipos de archivos ELF: ejecutables, relocatable, y shared objects, el tipo de cada ELF, es generado dependiendo la opción utilizada durante la compilación de los mismos. Un ELF, en su formato executable, contiene varios segmentos, incluyendo uno muy importante: el header ELF, que contiene información específica sobre el objeto, y una serie de campos que describe los diferentes componentes del archivo. Dentro del header ELF se encuentran dos partes importantes, ellas son el Section Header y el Program Header. El Section header es definido por la Section Header Table o SHT, y en el se encuentran todas las secciones "linkeables" del ejecutable. Por su parte el Program Header Table, o PHT, define los distintos segmentos del programa, por ejemplo aquellas secciones ejecutables.
readelf(1), en Linux (a este lo provee binutils), o elfdump(1) en FreeBSD, OpenBSD o Solaris, permite inspeccionar el header ELF, y las distintas secciones que lo componen, por ejemplo si necesitamos ver el header de example01:
#: readelf -h ./example01
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4003e0
Start of program headers: 64 (bytes into file)
Start of section headers: 2560 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 27
El primer campo e_ident, (Magic en la salida de readelf(1)), es un array que especifica el Magic number del archivo ELF que analizamos, este Magic number, identifica de manera única el tipo de archivo y se encuentra compuesto de una serie de valores que representan en forma de códigos hexadecimales distintos datos, por ejemplo: 0x7f, dice que es un ELF ejecutable, 0x45 es la letra "E" en ASCII, 0x4c es la letra "L", y 0x46 es la letra "F". Luego el número 0x02 significa que es un object file compilado para CPU's de 64 bits (0x01 para CPU's de 32 bits), y 0x01 que es little-endian (0x02 en caso de ser big-endian).
Este magic es leído por la syscall read(2) al momento de ejecutarse el binario, lo cual le dará la pauta de que tipo de archivo es:
# strace ./simple
execve("./simple", ["./simple"], [/* 49 vars */]) = 0
brk(0) = 0x1653000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe6f50bf000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=109077, ...}) = 0
mmap(NULL, 109077, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe6f50a4000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\27\342J8\0\0\0"..., 832) = 832
El tipo de archivo ELF también es obtenido de e_type, que se define de la siguiente manera:
El header ELF tiene básicamente dos vistas una vista desde el linker, y otra en ejecución, como se muestra en la figura anterior. Esto es, como es visto por el linker, y como es visto al momento de ejecución, ya que el mapeo de memoria difiere de un momento a otro.
Ahora bien, veamos el layout de un archivo ELF, como comenté anteriormente, el header ELF mantiene referencias hacia dos tablas la SHT (Section Header Table) y la PHT (Program Header Table).
Para imprimir la PHT utilizamos nuevamente readelf(1) de la siguiente manera:
# readelf -l ./example01
Elf file type is EXEC (Executable file)
Entry point 0x4003e0
There are 8 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001c0 0x00000000000001c0 R E 8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000006b4 0x00000000000006b4 R E 200000
LOAD 0x00000000000006b8 0x00000000006006b8 0x00000000006006b8
0x00000000000001ec 0x0000000000000200 RW 200000
DYNAMIC 0x00000000000006e0 0x00000000006006e0 0x00000000006006e0
0x0000000000000190 0x0000000000000190 RW 8
NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000005e4 0x00000000004005e4 0x00000000004005e4
0x000000000000002c 0x000000000000002c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 8
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version
.gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
Podemos ver también otro tipo de información como que tipo de archivo es (EXEC en este caso, osea un archivo ejecutable), la cantidad de program headers (cantidad entradas en la PHT), el Entry point, que es la dirección de memoría en la región .text (segmento de memoria al que se mapea el código ejecutable del ELF), aquí se encuentra la sección _start, lugar donde se ejecuta el Procedure Log (PROLOG), a fin de iniciar la ejecución del binario, podemos ver esto utilizando objdump(1):
#: objdump -d -j .text example01
example: file format elf64-x86-64
Disassembly of section .text:
00000000004003e0 <_start>:
4003e0: 31 ed xor %ebp,%ebp
4003e2: 49 89 d1 mov %rdx,%r9
4003e5: 5e pop %rsi
4003e6: 48 89 e2 mov %rsp,%rdx
4003e9: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
4003ed: 50 push %rax
4003ee: 54 push %rsp
4003ef: 49 c7 c0 70 05 40 00 mov $0x400570,%r8
4003f6: 48 c7 c1 e0 04 40 00 mov $0x4004e0,%rcx
4003fd: 48 c7 c7 c4 04 40 00 mov $0x4004c4,%rdi
400404: e8 c7 ff ff ff callq 4003d0 <__libc_start_main@plt>
400409: f4 hlt
40040a: 90 nop
40040b: 90 nop
# readelf -S example01
There are 30 section headers, starting at offset 0xa00:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400200 00000200
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 000000000040021c 0000021c
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 000000000040023c 0000023c
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400260 00000260
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400280 00000280
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004002e0 000002e0
000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000040031e 0000031e
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400328 00000328
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400348 00000348
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400360 00000360
0000000000000030 0000000000000018 A 5 12 8
[11] .init PROGBITS 0000000000400390 00000390
0000000000000018 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003b0 000003b0
0000000000000030 0000000000000010 AX 0 0 16
[13] .text PROGBITS 00000000004003e0 000003e0
00000000000001d8 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004005b8 000005b8
000000000000000e 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 00000000004005c8 000005c8
000000000000001c 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 00000000004005e4 000005e4
000000000000002c 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400610 00000610
00000000000000a4 0000000000000000 A 0 0 8
[18] .ctors PROGBITS 00000000006006b8 000006b8
0000000000000010 0000000000000000 WA 0 0 8
[19] .dtors PROGBITS 00000000006006c8 000006c8
0000000000000010 0000000000000000 WA 0 0 8
[20] .jcr PROGBITS 00000000006006d8 000006d8
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 00000000006006e0 000006e0
0000000000000190 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600870 00000870
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000600878 00000878
0000000000000028 0000000000000008 WA 0 0 8
[24] .data PROGBITS 00000000006008a0 000008a0
0000000000000004 0000000000000000 WA 0 0 4
[25] .bss NOBITS 00000000006008a8 000008a4
0000000000000010 0000000000000000 WA 0 0 8
[26] .comment PROGBITS 0000000000000000 000008a4
0000000000000058 0000000000000001 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 000008fc
00000000000000fe 0000000000000000 0 0 1
[28] .symtab SYMTAB 0000000000000000 00001180
0000000000000600 0000000000000018 29 46 8
[29] .strtab STRTAB 0000000000000000 00001780
00000000000001f4 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
ELF Section | Purpose |
---|---|
.bss | Uninitialized global data ("Block Started by Symbol"). |
.comment | A series of NULL-terminated strings containing compiler information. |
.ctors | Pointers to functions which are marked as __attribute__ ((constructor)) as well as static C++ objects' constructors. They will be used by __libc_global_ctors function.See paragraphs below. |
.data | Initialized data. |
.data.rel.ro | Similar to .data section, but this section should be made Read-Only after relocation is done. |
.debug_XXX | Debugging information (for the programs which are compiled with -g option) which is in the DWARF 2.0 format. |
.dtors | Pointers to functions which are marked as __attribute__ ((destructor)) as well as static C++ objects' destructors.See paragraphs below. |
.dynamic | For dynamic binaries, this section holds dynamic linking information used by ld.so. See paragraphs below. |
.dynstr | NULL-terminated strings of names of symbols in .dynsym section.One can use commands such as readelf -p .dynstr a.out to see these strings. |
.dynsym | Runtime/Dynamic symbol table. For dynamic binaries, this section is the symbol table of globally visible symbols. For example, if a dynamic link library wants to export its symbols, these symbols will be stored here. On the other hand, if a dynamic executable binary uses symbols from a dynamic link library, then these symbols are stored here too.The symbol names (as NULL-terminated strings) are stored in .dynstr section. |
.eh_frame .eh_frame_hdr | Frame unwind information (EH = Exception Handling). To see the content of .eh_frame section, use readelf --debug-dump=frames-interp a.out |
.fini | Code which will be executed when program exits normally. See paragraphs below. |
.fini_array | Pointers to functions which will be executed when program exits normally. See paragraphs below. |
.GCC.command.line | A series of NULL-terminated strings containing GCC command-line (that is used to compile the code) options.This feature is supported since GCC 4.5 and the program must be compiled with -frecord-gcc-switches option. |
.gnu.hash | GNU's extension to hash table for symbols. |
.gnu.linkonceXXX | GNU's extension. It means only a single copy of the section will be used in linking. This is used to by g++. g++ will emit each template expansion in its own section. The symbols will be defined as weak, so that multiple definitions are permitted. |
.gnu.version | Versions of symbols. |
.gnu.version_d | Version definitions of symbols. |
.gnu.version_r | Version references (version needs) of symbols. |
.got | For dynamic binaries, this Global Offset Table holds the addresses of variables which are relocated upon loading. See paragraphs below. |
.got.plt | For dynamic binaries, this Global Offset Table holds the addresses of functions in dynamic libraries. They are used by trampoline code in .plt section. If .got.plt section is present, it contains at least three entries, which have special meanings. See paragraphs below. |
.hash | Hash table for symbols. |
.init | Code which will be executed when program initializes. See paragraphs below. |
.init_array | Pointers to functions which will be executed when program starts. See paragraphs below. |
.interp | For dynamic binaries, this holds the full pathname of runtime linker ld.so |
.jcr | Java class registration information. |
.note.ABI-tag | This Linux-specific section is structured as a note section in ELF specification. |
.note.gnu.build-id | A unique build ID. |
.nvFatBinSegment | This segment contains information of nVidia's CUDA fat binary container. Its format is described by struct __cudaFatCudaBinaryRec in __cudaFatFormat.h |
.plt | For dynamic binaries, this Procedure Linkage Table holds the trampoline/linkage code. See paragraphs below. |
.preinit_array | Similar to .init_array section. See paragraphs below. |
.rela.dyn | Runtime/Dynamic relocation table.For dynamic binaries, this relocation table holds information of variables which must be relocated upon loading. Each entry in this table is a struct Elf64_Rela which has only three members:
|
.rela.plt | Runtime/Dynamic relocation table.This relocation table is similar to the one in .rela.dyn section; the difference is this one is for functions, not variables. The relocation type of entries in this table is R_386_JMP_SLOT or R_X86_64_JUMP_SLOT and the "offset" refers to memory addresses which are inside .got.plt section. Simply put, this table holds information to relocate entries in .got.plt section. |
.rel.text .rela.text | Compile-time/Static relocation table.For programs compiled with -c option, this section provides information to the link editor ld where and how to "patch" executable code in .text section. The difference between .rel.text and .rela.text is entries in the former does not have addend member. (Compare struct Elf64_Rel with struct Elf64_Rela in /usr/include/elf.h) Instead, the addend is taken from the memory location described by offset member. Whether to use .rel or .rela is platform-dependent. For x86_32, it is .rel and for x86_64, .rela |
.rel.XXX .rela.XXX | Compile-time/Static relocation table for other sections. For example, .rela.init_array is the relocation table for .init_array section. |
.rodata | Read-only data. |
.shstrtab | NULL-terminated strings of section names.One can use commands such as readelf -p .shstrtab a.out to see these strings. |
.strtab | NULL-terminated strings of names of symbols in .symtab section.One can use commands such as readelf -p .strtab a.out to see these strings. |
.symtab | Compile-time/Static symbol table.This is the main symbol table used in compile-time linking or runtime debugging. The symbol names (as NULL-terminated strings) are stored in .strtab section. Both .symtab and .symtab can be stripped away by the strip command. |
.tbss | Similar to .bss section, but for Thread-Local data. See paragraphs below. |
.tdata | Similar to .data section, but for Thread-Local data. See paragraphs below. |
.text | User's executable code |
Buen post, interesante, sin embargo tengo una duda, en C como abres un archivo ELF para poder obtener los campos de esas estructuras?
ResponderEliminarSaludos.
Buenas, lo hace leyendo el magic del archivo e identificando la ABI mediante este.
ResponderEliminarIt was very nice article and it is very useful to Linux learners.We also provide Linux online training
ResponderEliminar