C to ELF - Demostración de las fases de construcción de un programa - versión 64 bits

Intro

Construiremos un ejecutable usando dos archivos: main.c y foo.c.

main.c

#include <stdio.h>

#define MYCONST 15
// The function prototype of foo, which
// will be implemented elsewhere
int foo(char *input);

static int i = 100;

int aGlobal = 10;
long long int anotherGlobal = 20;

int main(void) {
    int a = MYCONST;
    int b = 6;
    int c = 7;
    int d = (a + b)*4 + c;
    int ret = d + foo("Hello, World!");
    return ret;
}

foo.c:

#include <stdio.h>

/* Declared as extern since defined in hello.c */
extern int aGlobal;

int foo(char *input) {
    printf("In foo: %s\n", input);
    return aGlobal;
}

Fase 1 - De código fuente a archivo pre-procesado

Usamos el comando gcc -E main.c para desplegar el código pre-procesado. Dos observaciones importantes:

  1. La constante definida MYCONST fue sustituida por su valor (15) el preprocesador.

  2. El directivo #include<stdio.h> fue sutitiuido por el contenido del archivo stdio.h (y por los contenidos de los archivos que incluye stdio.h).

Fase 2 - De pre-procesado a assembly

Usamos los comandos gcc -m32 -S main.c y gcc -m32 -S foo.c para obtener los files main.s y foo.s, que contienen el resultado del proceso de compilación, i.e. un programa en assembly. A continuación fragmentos de los archivos con algunas anotaciones (como comentairios)

main.s (fragmento)

    .size   i, 4
i:                          ; las variables globales
    .long   100                 ; son definidas de forma distinta 
    .globl  aGlobal           ; a las locales
    .align 4
    .type   aGlobal, @object
    .size   aGlobal, 4
aGlobal:                    ; otra variable global
    .long   10
    .globl  anotherGlobal
    .align 8
    .type   anotherGlobal, @object
    .size   anotherGlobal, 8
anotherGlobal:              ; otra variable global
    .quad   20
    .section    .rodata
.LC0:                       ; literal cuenta con su propio label
    .string "Hello, World!"
    .text
    .globl  main
    .type   main, @function
main:
    endbr64
    pushq   %rbp         }
    movq    %rsp, %rbp   } prologo de main
    subq    $32, %rsp    } 
    movl    $15, -20(%rbp)
...
    leaq    .LC0(%rip), %rax  ; literal siendo referenciado
    movq    %rax, %rdi
    call    foo@PLT
...

foo.s (fragmento)

.LC0:
    .string "In foo: %s\n"
    .text
    .globl  foo
    .type   foo, @function
foo:
    endbr64
    pushq   %rbp
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rsi
    leaq    .LC0(%rip), %rax
    movq    %rax, %rdi
    movl    $0, %eax
    call    printf@PLT
    movl    aGlobal(%rip), %eax
    leave

De assembly a objective file

Usamos el assembler as de la siguiente forma: as --32 -o main.o main.s y as --32 -o foo.o foo.s para obtener main.o y foo.o. Los archivos a partir de este paso son de tipo binario. En linux se utiliza el formato ELF para los object files y los ejecutables. Puedes usar programas como readelf o objdump para obtener información sobre los archivos de formato ELF.

Por ejemplo, el siguiente es el output de objdump -s main.o:

main.o:     file format elf64-x86-64

Contents of section .text:
 0000 f30f1efa 554889e5 4883ec20 c745ec0f  ....UH..H.. .E..
 0010 000000c7 45f00600 0000c745 f4070000  ....E......E....
 0020 008b55ec 8b45f001 d08d1485 00000000  ..U..E..........
 0030 8b45f401 d08945f8 488d0500 00000048  .E....E.H......H
 0040 89c7e800 0000008b 55f801d0 8945fc8b  ........U....E..
 0050 45fcc9c3                             E...            
Contents of section .data:
 0000 64000000 0a000000 14000000 00000000  d...............
Contents of section .rodata:
 0000 48656c6c 6f2c2057 6f726c64 2100      Hello, World!.  

Notarás que al igual que en el archivo en assembly, en el object file existe una distinción entre lo que son variables globales (section .data), constantes globales (section .rodata) e instrucciones (section .text). En la sección .data puedes ver los valores que asumirán las variables globales.

0000 64000000 0a000000 14000000 00000000  
---- -------- -------- -----------------
 |      |        |        |
 |      |        |        +-- el valor 20 que le es asignado a anotherGlobal
 |      |        +-- el valor de 10 que es asignado a aGlobal
 |      +-- el valor de 100 de la variable estática i
 +-- el offset desde el comienzo de la sección .data

Si hacemos readelf -all main.o encontramos información como la siguiente:

Relocation section '.rela.text' at offset 0x300 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000003b  000600000002 R_X86_64_PC32     0000000000000000 .rodata - 4
000000000043  000f00000004 R_X86_64_PLT32    0000000000000000 foo - 4

El relocation section contiene símbolos que son referenciados en el main.c pero cuyas implementaciones aun no se conocen. El prototipo de la función foo está en main.c pero no así su implementación.

La misma información para foo.o es:

Relocation section '.rela.text' at offset 0x2a8 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000001a  000600000002 R_X86_64_PC32     0000000000000000 .rodata - 4
000000000027  000d00000004 R_X86_64_PLT32    0000000000000000 printf - 4
00000000002d  000e00000002 R_X86_64_PC32     0000000000000000 aGlobal - 4

Noten que foo.c no conoce sobre la implementación de printf, ni sabe lo que es valor del símbolo aGlobal. En el próximo paso, el linker combinará los archivos main.o y foo.o.

Enlazando los objective files en un ejecutable

El comando gcc -o main main.o foo.o invoca al linker para generar el ejecutable main. Si miramos el relocation section del ejecutable notaremos que se han resuelto algunos problemas pero se han añadido otros relocations.

Relocation section '.rela.dyn' at offset 0x528 contains 8 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
...
000000003fd8  000100000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000003fe0  000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000003fe8  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000003ff0  000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000003ff8  000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0

Relocation section '.rela.plt' at offset 0x5e8 contains 1 entry:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000003fd0  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0

Nota que ya no aparece ni foo ni aGlobal. Sin embargo printf sigue apareciendo. Esto se debe a que la implementación de printf no estará incluida dentro del archivo ejecutable main. Cada vez que main invoque a printf el código de printf se accesará de una librería compartida que reside en otra parte de memoria. De hecho reside en otra parte de memoria para que otros ejecutables la puedan compartir. El loader se encarga de especificar la dirección de esa printf cuando carga a main a memoria.

ELF para dos cosas diferentes

Los archivos de formato Executable and Linkable Format se usan tanto para los object files como para los ejecutables en linux. ¿Como puedes diferenciar entre ellos? Basta con usar el comando file:

file main.o devuelve:

main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

La palabra relocatable delata que es un object file que todavía no está listo para ser ejecutado.

file main devuelve:

main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5a239bec3ac7bb8f555fd4701c1cb6a435c39559, for GNU/Linux 3.2.0, not stripped

La info dentro del ELF relocatable sirve para ayudar al LINKER a hacer su trabajo

A continuación una de las informaciones que desplega readelf -a main.o y que es útil para el linker: la tabla de las secciones. Los archivos ELF se organizan en secciones. Por ejemplo:

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         0000000000000318  00000318
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.gnu.propert NOTE             0000000000000338  00000338
       0000000000000020  0000000000000000   A       0     0     8
  [ 3] .note.gnu.build-i NOTE             0000000000000358  00000358
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .note.ABI-tag     NOTE             000000000000037c  0000037c
       0000000000000020  0000000000000000   A       0     0     4
  [ 5] .gnu.hash         GNU_HASH         00000000000003a0  000003a0
       0000000000000024  0000000000000000   A       6     0     8
  [ 6] .dynsym           DYNSYM           00000000000003c8  000003c8
       00000000000000a8  0000000000000018   A       7     1     8
  [ 7] .dynstr           STRTAB           0000000000000470  00000470
       0000000000000084  0000000000000000   A       0     0     1
  [ 8] .gnu.version      VERSYM           00000000000004f4  000004f4
       000000000000000e  0000000000000002   A       6     0     2
  [ 9] .gnu.version_r    VERNEED          0000000000000508  00000508
       0000000000000020  0000000000000000   A       7     1     8
  [10] .rela.dyn         RELA             0000000000000528  00000528
       00000000000000c0  0000000000000018   A       6     0     8
  [11] .rela.plt         RELA             00000000000005e8  000005e8
       0000000000000018  0000000000000018  AI       6    24     8
  [12] .init             PROGBITS         0000000000001000  00001000
       000000000000001b  0000000000000000  AX       0     0     4
  [13] .plt              PROGBITS         0000000000001020  00001020
       0000000000000020  0000000000000010  AX       0     0     16
  [14] .plt.got          PROGBITS         0000000000001040  00001040
       0000000000000010  0000000000000010  AX       0     0     16
  [15] .plt.sec          PROGBITS         0000000000001050  00001050
       0000000000000010  0000000000000010  AX       0     0     16
  [16] .text             PROGBITS         0000000000001060  00001060
       00000000000001e5  0000000000000000  AX       0     0     16
  [17] .fini             PROGBITS         0000000000001248  00001248
       000000000000000d  0000000000000000  AX       0     0     4
  [18] .rodata           PROGBITS         0000000000002000  00002000
       000000000000001e  0000000000000000   A       0     0     4
  [19] .eh_frame_hdr     PROGBITS         0000000000002020  00002020
       000000000000004c  0000000000000000   A       0     0     4
  [20] .eh_frame         PROGBITS         0000000000002070  00002070
       0000000000000128  0000000000000000   A       0     0     8
  [21] .init_array       INIT_ARRAY       0000000000003db8  00002db8
       0000000000000008  0000000000000008  WA       0     0     8
  [22] .fini_array       FINI_ARRAY       0000000000003dc0  00002dc0
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .dynamic          DYNAMIC          0000000000003dc8  00002dc8
       00000000000001f0  0000000000000010  WA       7     0     8
  [24] .got              PROGBITS         0000000000003fb8  00002fb8
       0000000000000048  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000004000  00003000
       0000000000000024  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000004024  00003024
       0000000000000004  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00003024
       000000000000002b  0000000000000001  MS       0     0     1
  [28] .symtab           SYMTAB           0000000000000000  00003050
       00000000000006a8  0000000000000018          29    49     8
  [29] .strtab           STRTAB           0000000000000000  000036f8
       0000000000000221  0000000000000000           0     0     1
  [30] .shstrtab         STRTAB           0000000000000000  00003919
       000000000000011a  0000000000000000           0     0     1

La info dentro del ELF ejecutable sirve para ayudar al LOADER a hacer su trabajo

El ELF de un ejecutable contiene una tabla que es utilizada por el loader para saber cómo organizar la memoria que se dedicará al ejecutar este programa. Por ejemplo,

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040
                 0x00000000000002d8 0x00000000000002d8  R      0x8
  INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000600 0x0000000000000600  R      0x1000
  LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                 0x0000000000000255 0x0000000000000255  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                 0x0000000000000198 0x0000000000000198  R      0x1000
  LOAD           0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                 0x000000000000026c 0x0000000000000270  RW     0x1000
  DYNAMIC        0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
                 0x00000000000001f0 0x00000000000001f0  RW     0x8
  NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  NOTE           0x0000000000000358 0x0000000000000358 0x0000000000000358
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338
                 0x0000000000000020 0x0000000000000020  R      0x8
  GNU_EH_FRAME   0x0000000000002020 0x0000000000002020 0x0000000000002020
                 0x000000000000004c 0x000000000000004c  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
                 0x0000000000000248 0x0000000000000248  R      0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .plt.got .plt.sec .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .data .bss 
   06     .dynamic 
   07     .note.gnu.property 
   08     .note.gnu.build-id .note.ABI-tag 
   09     .note.gnu.property 
   10     .eh_frame_hdr 
   11     
   12     .init_array .fini_array .dynamic .got