Hugo Jacotot – David Andrawos Saad – Matthieu Le Franc
Supposons que nous ayons un programme écrit en langage C. Ce programme ne peut être utilisé sur différentes machines que si celles-ci possèdent la même architecture matérielle ainsi que le même environnement d’exécution et de compilation. Cela s’explique par le fait que le langage C est compilé en un langage machine spécifique à l’architecture matérielle, et que le programme C dépend des services fournis par les bibliothèques et le système d’exploitation. Par exemple, l’ABI fourni par le système d’exploitation définit la manière dont le programme C interagit avec son environnement, ce qui signifie qu’un programme fonctionnant sous Linux ne sera pas nécessairement compatible avec Windows.
On peut dire que cette non-portabilité constitue un obstacle. Cependant, pour résoudre ce problème, il est possible d’utiliser un format de fichier spécifique définissant une interface entre le système et le programme tel que l’ELF, le Mach-O ou bien encore COFF… Notre choix s’est tourné sur l’ELF étant donné qu’il est directement pris en charge par le GRUB.
L’ELF (Executable and Linkable Format) est un type de format de fichier largement utilisé pour divers éléments tels que les exécutables, les bibliothèques partagées (.so), les fichiers objets (.o), les modules de noyau chargeables…
Structure d’un fichier ELF :
Le Header :
Tout fichier ELF possède un header permettant de l’identifier. Voici la représentation des différents attributs en C :
struct Elf32_Ehdr { unsigned char e_ident[EI_NIDENT]; // ELF Identification bytes Elf32_Half e_type; // Type of file (see ET_* below) Elf32_Half e_machine; // Required architecture for this file (see EM_*) Elf32_Word e_version; // Must be equal to 1 Elf32_Addr e_entry; // Address to jump to in order to start program Elf32_Off e_phoff; // Program header table’s file offset, in bytes Elf32_Off e_shoff; // Section header table’s file offset, in bytes Elf32_Word e_flags; // Processor-specific flags Elf32_Half e_ehsize; // Size of ELF header, in bytes Elf32_Half e_phentsize; // Size of an entry in the program header table Elf32_Half e_phnum; // Number of entries in the program header table Elf32_Half e_shentsize; // Size of an entry in the section header table Elf32_Half e_shnum; // Number of entries in the section header table Elf32_Half e_shstrndx; // Sect hdr table index of sect name string table }; |
Voici les caractéristiques de notre fichier exécutable kernel.elf :
$ readelf -h kernel.elf ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2’s complement, little endian Version: 1 (current) OS/ABI: UNIX – System V ABI Version: 0 Type: EXEC (Executable file) Machine: Intel 80386 Version: 0x1 Entry point address: 0x10023c Start of program headers: 52 (bytes into file) Start of section headers: 28040 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 4 Size of section headers: 40 (bytes) Number of section headers: 16 Section header string table index: 15 |
Le Corps :
Un fichier ELF est composé de segments et de sections, dont le nombre peut être nul ou multiple. De plus, un segment peut contenir 0 ou plusieurs sections. Par ailleurs, chaque segment et section possède un header (que nous aborderons plus tard).
Les segments et sections se différencient par le fait qu’en mémoire, un segment contient les informations nécessaires à l’exécution, tandis qu’une section contient les informations nécessaires à l’édition des liens.
Prenons l’exemple de notre exécutable kernel.elf :
$ readelf -l kernel.elf Elf file type is EXEC (Executable file) Entry point 0x10023c There are 4 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x001000 0x00100000 0x00100000 0x00ee4 0x00ee4 R E 0x1000 LOAD 0x002000 0x00101000 0x00101000 0x00524 0x00524 R 0x1000 LOAD 0x003000 0x00102000 0x00102000 0x0005a 0x058a0 RW 0x1000 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10 Section to Segment mapping: Segment Sections… 00 .text 01 .rodata .eh_frame 02 .data .bss 03 |
Quelques explications s’imposent concernant les différentes sections :
.text : contient le code exécutable du programme ainsi que les instructions du cpu.
.data : peut être un ensemble de variables initialisé ou bien le fragment d’un fichier objet.
.rodata : de même que .data mais uniquement accessible en lecture (voir program header).
.eh_frame : instructions de gcc indiquant comment dépiler la pile en cas de gestion d’erreurs.
.bss : enregistre les variables non initialisées, qu’elles soient global, static ou bien encore externe.
Voici leur répartitions en mémoire ainsi que leur nécessité en fonction du point de vue :
On peut noter que la vue de liaison traite des sections tandis que la vue d’exécution traite des segments.
Concernant notre exécutable on remarque que le premier LOAD (Code) ne contient qu’un seul segment associé à une section nommée .text.
Le second segment LOAD contient les segments .rodata et .eh_frame.
Le troisième LOAD (Data) quant à lui contient deux segments ; .data et .bss.
On remarque que GNU_STACK (Uninitialized de 02) n’est associé à aucun segment ni de section de manière implicite car il est géré dynamiquement par le système d’exploitation. L’utilité de GNU_STACK ici, est d’indiquer que la pile doit être configurée de tel sorte à avoir les autorisations en RWE.
Ainsi, on peut représenter celà sous cette forme :
Il est important de noter que ici, l’ordre des segments n’est pas important.
Les entêtes :
Program Header :
Un programme header joue un rôle crucial dans la description des segments d’un fichier binaire. Ces segments sont essentiels lors du processus de chargement du programme par le kernel car ils fournissent des informations sur la structure de l’exécutable.
Le program header fournit des informations sur le chargement en mémoire pour permettre la création d’une image de processus. Ainsi celà conduit à rendre le program header indispensable pour les fichiers exécutables, mais facultatif pour les fichiers objets. Concrètement, dans les fichiers objets (.o), le program header est omis, car ces fichiers doivent être liés à un exécutable plutôt que chargés directement en mémoire.
Voici la représentation en langage C de la table de program header :
// Program header for ELF32. struct Elf32_Phdr { Elf32_Word p_type; // Type of segment Elf32_Off p_offset; // File offset where segment is located, in bytes Elf32_Addr p_vaddr; // Virtual address of beginning of segment Elf32_Addr p_paddr; // Physical address of beginning of segment (OS-specific) Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero) Elf32_Word p_memsz; // Num. of bytes in mem image of segment (may be zero) Elf32_Word p_flags; // Segment flags Elf32_Word p_align; // Segment alignment constraint }; |
Section Header :
Une table de section header, représentée par la structure Elf32_Shdr, regroupe divers attributs tels que la localisation, la taille, etc… de chaque section dans un fichier binaire au format ELF.
Dans le cadre du développement de logiciel la section header peut être utilisée pour faire du débogage ou bien encore de la rétro ingénierie. Elle offre des informations cruciales sur la disposition des sections.
Par ailleurs, contrairement au segment header, la présence d’une section header n’est pas strictement obligatoire pour le bon déroulement du programme. Cela vient du fait que la section header ne lie pas la disposition de la mémoire au binaire. Cependant, une observation attentive révèle que les sections peuvent tout de même mémoriser le code et les données, offrant ainsi une flexibilité dans la structuration du programme.
Voici la structure Elf32_Shdr, une représentation en langage C de la table de section header :
// Section header. struct Elf32_Shdr { Elf32_Word sh_name; // Section name (index into string table) Elf32_Word sh_type; // Section type (SHT_*) Elf32_Word sh_flags; // Section flags (SHF_*) Elf32_Addr sh_addr; // Address where section is to be loaded Elf32_Off sh_offset; // File offset of section data, in bytes Elf32_Word sh_size; // Size of section, in bytes Elf32_Word sh_link; // Section type-specific header table index link Elf32_Word sh_info; // Section type-specific extra information Elf32_Word sh_addralign; // Section address alignment Elf32_Word sh_entsize; // Size of records contained within the section }; |
Sources
What’s the difference of section and segment in ELF file format
What is the role of .eh_frame segment
https://llvm.org/doxygen/BinaryFormat_2ELF_8h_source.html
https://gist.github.com/CMCDragonkai/10ab53654b2aa6ce55c11cfc5b2432a4
COFF on Linux or ELF on Windows
What’s the difference of section and segment in ELF file format
Très complet :
http://www.skyfree.org/linux/references/ELF_Format.pdf
https://stackoverflow.com/a/2456882/16440965
Ping : Noyau UNIX: Conception d’un OS kernel – Sysblog