Padrão ELF para arquivos-objeto

Em 24/07/2010, em Protocolos e Padrões, por Sergio Prado

O ELF (Exe­cutable and Link­able For­mat) é um padrão para arquivos exe­cutáveis, arquivos-objeto e bib­liote­cas. Foi usado ini­cial­mente no Sys­tem V Unix, sis­tema opera­cional com­er­cial da AT&T na década de 80. Em 1993 o con­t­role do padrão pas­sou para um Comitê, o TIS (Tool Inter­face Stan­dards) for­mado por um grupo de empre­sas como IBM, Intel e Bor­land. Em 1995 foi lançada a ver­são 1.2 do padrão, ver­são atual usada até hoje em muitos sabores UNIX, incluindo o GNU/Linux.

Mas porque pre­cisamos de um padrão para arquivos-objeto? A resposta é porta­bil­i­dade. Para que pos­samos linkar códi­gos ger­a­dos por difer­entes com­pi­ladores, linkar nossa apli­cação com bib­liote­cas de difer­entes fornece­dores, exe­cu­tar o código em difer­entes sis­temas opera­cionais, etc.

Na definição do padrão, exis­tem 3 tipos prin­ci­pais de arquivos-objeto:

  • Arquivos relocáveis: são os arquivos-objeto pro­pri­a­mente ditos, por exem­plo os arquivos “.o” ger­a­dos pelo gcc quando com­pil­amos um módulo “.c”
  • Arquivos exe­cutáveis: são arquivos que podem ser exe­cu­ta­dos pelo sis­tema operacional.
  • Arquivos-objeto com­par­til­ha­dos: são as bib­liote­cas, “.so” ou “.a” em sis­temas GNU/Linux.

O padrão ELF tem a estru­tura exibida na figura abaixo:

elf spec Padrão ELF para arquivos objeto

O Header descreve infor­mações sobre o arquivo, como o tipo de arquivo (objeto, bib­lioteca ou exe­cutável), arquite­tura (MIPS, x86 68K, etc), ver­são, endereços de memória, etc. Já o Section/Segment são os tre­chos de código com­pi­la­dos. O Pro­gram Header Table é usado pelo SO para car­regar o pro­grama exe­cutável em memória, e o Ses­sion Header Table pos­sui infor­mações sobre o Ses­sion (tre­chos) do programa.

Nosso pro­grama de teste será o mais sim­ples pos­sível. Nosso obje­tivo aqui é apre­sen­tar fer­ra­men­tas de análise do for­mato ELF pre­sente em sis­temas GNU/Linux.

1
2
3
4
5
6
7
#include "stdio.h"
 
int main(void)
{
    printf("Blog do Sergio Prado\n");
    return 0;
}

Com­pile com o gcc, usando os coman­dos abaixo:

$ gcc -c main.c
$ gcc main.o -o app

Foram ger­a­dos dois arquivos, o arquivo objeto “main.o” e o exe­cutável “app”.

O comando “file” irá exibir infor­mações genéri­cas sobre os arquivos gerados:

$ file main.o
main.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
 
$ file app
app: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not stripped

Com o comando “read­elf”, podemos anal­isar todas as infor­mações do arquivo ger­ado. Vamos olhar o header o arquivo objeto main.o:

$ readelf -h main.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          220 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         11
  Section header string table index: 8

Veja que o header é flex­ivel o sufi­ciente para man­ter a porta­bil­i­dade do for­mato entre difer­entes arquiteturas.

Com o comando read­elf você pode ler qual­quer infor­mação de um arquivo no for­mato ELF, mas exis­tem alguns out­ros coman­dos que podem te aju­dar a anal­isar um arquivo objeto:

O comando “nm” lista os sim­bo­los do arquivo objeto:

$ nm app
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484bc R _IO_stdin_used
         w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
080482b8 T _init
08048330 T _start
0804a014 b completed.7021
0804a00c W data_start
0804a018 b dtor_idx.7023
080483c0 t frame_dummy
...........................
080483e4 T main
         U puts@@GLIBC_2.0

O comando “ldd” lista as depen­den­cias de bib­liote­cas linkadas dinamicamente:

$ ldd app
linux-gate.so.1 =>  (0x0088d000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00d1f000)
/lib/ld-linux.so.2 (0x0099f000)

Mas porque você pre­cisa saber de tudo isso? Talvez porque você vai se aven­tu­rar no desen­volvi­mento de um com­pi­lador. Ou porque você encon­trou algum prob­lema ao ten­tar linkar arquivos-objeto ou bib­liote­cas ger­adas por difer­entes tool­chains. Ou porque você esta tendo alguma difi­cul­dade ao ten­tar exe­cu­tar uma apli­cação com­pi­lada em um ambi­ente difer­ente do ambi­ente de execução.

Recen­te­mente tra­bal­hei em um pro­jeto onde uma apli­cação com­pi­lada em uma máquina x86 não rodava em outra máquina de mesma arquite­tura por incom­pat­i­bil­i­dade da ver­são da bib­lioteca GLIBC. Na minha máquina rodava a GLIBC_2.7 e na máquina des­tino rodava a GLIBC_2.6.

Tin­has duas opções, recom­pi­lar a apli­cação em um ambi­ente com a ver­são da GLIBC cor­reta, que pode­ria levar um certo tempo. Ou então deixar minha apli­cação mais portável para rodar em qual­quer ver­são de GLIBC, e encon­trar qual a função que estava req­ui­si­tando a GLIBC_2.7. 

Com o comando “nm” desco­bri que a função “sscanf” estava req­ui­si­tando a GLIBC_2.7.

$ nm app | grep 2.7
U sscanf@@GLIBC_2.7

Ree­screvi o tre­cho da apli­cação que usava a função sscanf, e o prob­lema foi resolvido de forma bem rápida.

Vale a pena dar uma lida no padrão ELF e enten­der como todo o processo de link­agem de obje­tos, ger­ação do exe­cutável, carga de bib­liote­cas dinâmi­cas e exe­cução fun­ciona. O padrão pode ser baix­ado aqui.

Para quem se inter­es­sar, exis­tem ainda out­ros for­matos além do ELF, como o a.out usado nos primór­dios de sis­temas UNIX, o COFF usado em alguns UNIX e adap­tado para o Microsoft Win­dows e o Mach-O usado no MacOS X.

Um abraço,

Ser­gio Prado

VN:F [1.9.13_1145]
Rat­ing: 10.0/10 (2 votes cast)
Padrão ELF para arquivos-objeto, 10.0 out of 10 based on 2 ratings

Posts rela­ciona­dos:

  1. Misra-C — Padrão para soft­ware em C
Tags: