Dernière modificiation le 18/01/2007
Le site des Hiboux

Créer un fichier ELF sans même GNU/ld

Créer des fichiers ELF sous Linux aussi facilement que des fichiers COM sous DOS

rechercher sur le site accueil
 

Linux consomme beaucoup de ressources, et il en consomme tellement qu'il ne laisse paradoxalement même pas une chance de créer des applications alternatives dans un environnement restreint. Un environnement traditionnel, même sans passer par GCC, nécessite LD. Mais LD nécessite GLIBC... qui est une librairie lourde. Cette page vous présente un moyen de créer des fichiers ELF sans même employer un lieur comme LD. Ce sera aussi je l'espère une occasion de vous donner l'envie de découvrir le format de fichier ELF. Accessoirement, la solution présentée vous permettra même de créer des binaires Linux depuis Windows sans environnement de cross-compilation.

Qu'est-ce que le format ELF ?

ELF est un sigle signifiant Executable and Linkable Format. Sans artifices et 100% utile, ce format est employé par plusieurs systèmes, notamment BSB, Unix-SystemV et Linux. Ce format a remplacé maintenant depuis quelque temps l'ancien format a.out, qui posait trop de problèmes techniques (la liaison dynamique avec le format a.out nécessitait littéralement des hacks, et cela n'était plus tolérable pour une architecture système). Le format ELF version 1.2 existe depuis 1997, et c'est la version la plus récente à cette année 2007. (la version ELF 1.1 date de 1995). Vous pourrez consulter cette référence dans ce fichier PDF : TIS ELF 1.2 specifications (as of 1997) Lien web. La référence est maintenue par l'organisation TIS (Tool Interface Standard).

Windows ne connais pas le format de fichier ELF, et même les Windows à noyau NT utilisent encore le format COFF pourtant obsolète. Les autres version de Windows (non-NT) utilisent le format PE/ME. Cette page ne concerne donc que la création de binaires ELF pour BSD/Unix/Linux.

ELF et la GLibC

Le format ELF a rendu les systèmes plus propres et plus fiables en permettant de se passer de certains hackings officiels. Malheureusement, sous Linux, l'intégration du format ELF a été accompagnée d'une mise à jour, non seulement du noyau Linux, mais également de la GLibC. La GLibC-2, encore appelé LibC-6 est connue pour être celle qui a introduit le support ELF (voir Liaison dynamique). Pour être exact, la libc5 était le première à supporter ELF, mais glibc1 qui était alors un fork, n'a rejoins libc qu'avec libc6 (glibc1 passait alors glibc2). L'ennui est que la libc5 était déjà trop volumineuse pour que Linux puisse honnêtement se targuer d'être un système utilisable par les configurations légères... et avec la libc6/glibc2, les chose se sont encore nettement aggravé : on a jamais vu une libc être aussi lourde.

En conséquence, les système Linux supportant ELF sont des systèmes lourds. Cependant, il n'est pas obligatoire d'avoir une libc installée pour exécuter des applications au format ELF. En effet, c'est le noyau qui a en charge le chargement de l'application, et celui ci ne fait qu'invoquer le lieur dynamique (ld.so.2 pour libc6/glibc2) habituellement associé au format ELF. Pour aller plus loin encore, notez que rien n'impose d'employer la libc6/glibc2 pour exécuter des applications au format ELF. En effet, le format prévoit un champs spécifiant un chemin vers un interpréteur, qui n'est en fait sous Linux, rien d'autre que le chemin vers le lieur dynamique. Ainsi, il est en théorie tout à fait possible de créer des applications au format ELF, qui serait dynamiquement liées avec ld.so.1 et libc5 (ou même encore libc4 ou moins).

Mais en pratique, là ou il y a ELF, il y a libc6/glibc2.

Le probleme

Le problème que cela pose est assez ennuyeux. Vous avez un Linux trop lourd pour être utilisable sur les anciens PC (contrairement à ce qu'il prétend), et cela vous empêche même de créer vos applications légère dans un environnement restreint. Car la création de fichier ELF passe habituellement par GNU/ld, et GNU/ld requière libc2/glibc6, et la taille de cette librairie interdit de l'installer sur un système modeste de base. Mais remarquons au passage que la librairie n'est pas la seule en cause, et que le lieur est lui même un peu trop volumineux.

Une solution

Ceux/celles d'entre vous qui ont connu l'âge d'or du DOS, une époque bénis ou on programmais joyeusement avec un système accessible à tous/toutes, et bien documenté, se souviennent des joies qu'ils/elles ont eu en découvrant à quelle point il était facile de créer un fichier *.COM . Il n'y avait d'ailleurs même pas besoin d'environnement de développement, puisque le programme DEBUG (très utile et très léger, il a fait des heureux-ses), fourni en standard avec DOS, permettait d'enregistrer des fichiers binaires.

La solution qui vous est proposée ressemble tout à fait dans le principe à celle employée sous DOS à l'époque. Et elle est même encore bien plus confortable est efficace.

Nous allons créer des fichiers assembleur (fichiers sources persistant, donc plus pratique qu'avec DEBUG), qui seront compilés avec NASM (qui reconnais un jeux d'instruction bien plus vaste que DEBUG), et au début desquels nous placerons une structure de donnée caractéristique des fichiers ELF. Cette structure comprendra l'entête du fichier, ainsi que deux entrées de descripteur de segment : une pour le code, et une pour les données en accès lecture/écriture (plus évolué que les fichiers COM du DOS).

Avantages de cette solution

Les versions statiques de nasm sont légères (entre 150 et 200KB environ). Il existe un éditeur en console que je vous invite à découvrir, qui se nomme « E3 ». Il est beaucoup plus commode à utiliser que VI/VIM, et même s'il n'a pas encore l'ergonomie d'EDIT du DOS, il est encore plus léger : 15KB. Quelques documentations au format texte pèseront au plus 20KB. Pour un total de de 185 à 235KB environ, soit même pas le quart d'une disquette 1.44M, vous avez un environnement de développement parfaitement fonctionnel. Ceci vous permettra de créer des applications légères en environnement restreint, vous autorisant à court-circuiter la lourdeur des installations Linux habituelles. Je vous recommande, de vous documentez sur les syscalls, qui sont un moyen d'accéder aux fonctions systèmes, sans même employer la moindre librairie (ne pensez même pas aux libc... les dernières ne tiendraient même pas sur un petit disque dur).

Le bonus cross-compilation

Comme il existe une version Windows de nasm, cette méthode vous permettra même de faire de la cross compilation, et de compiler sous Windows, des binaires ELF qui pourront fonctionner sous Linux. Ce bonus sera très appréciable si vous travailler à la création d'une installation légère de Linux : vous pourrez concevoir et compiler vos programme sous Windows, sans même avoir besoin que votre installation Linux soit achevée pour se faire.

À-propos du code

Concrètement, vous emploierez un pattern, que vous éditerez pour simplement y ajouter votre code et vos données. Vous avez le choix entre deux patterns : un pour les programmes ayant un segment de code et un segment de données en lecture/écriture, et un autre pour les programmes n'ayant besoin de rien de plus qu'un segment de code. Notez que pour les programme ne comprenant que des données en lecture seule, il est plus astucieux d'utiliser la version avec un segment de code seul (on peut placer des données en lecture seule dans un segment de code, sans que cela ne pose la moindre soucis).

Une petite remarque sur le code des structures ELF : j'ai utilisé un alignement sur 4 bytes pour les segments. Bien que la taille des pages mémoire sous Linux soit de 4096 bytes, il ne semble pas obligatoire que les segment soit alignés sur les pages, et une page peut donc apparemment chevaucher deux segments (la gestion des pages et la gestion des segments sont transparentes l'une à l'autre, aussi bien pour le noyau Linux que pour les processeurs i80x86). L'alignement sur 4 bytes est nécessaire, car si mes souvenirs sont bons, les processeurs intel exigent que les adresses de base des segments soient alignées sur 4 bytes (à vérifier, mais trop de prudence ici ne nuira pas). Il existe un flag dans les descripteurs de page, sur les processeurs intel, qui commande le déclenchement d'une exception en cas d'accès à des données non alignées sur 4 bytes, mais ceci est indépendant de la question qui nous intéresse, car c'est de l'alignement des adresses de base des segments qu'il s'agit ici.

La version avec segments de code et de données

Cette version sera à utiliser pour créer un fichier ELF avec un segment de code et un segment de données en lecture/écriture. Pour l'utiliser, éditez simplement les sections de code et de donnée entre les marqueurs « BEGIN » et « END », en commentaires dans chacun des deux segments. Vous ne devez pas modifier le reste du code, sauf si vous savez vraiment avec quoi vous jouez ;). Finalement vous compilerez le code avec la commande « nasm -f bin [-O2] votrecode.asm ». L'option « -f bin » cré un format binaire plat, c'est à dire sans format particulier (le format est contenu dans notre code). L'option « -O2 » est optionnelle, mais elle est souvent nécessaire pour les codes contenant des sauts conditionnels par exemple (référez vous à la documentation de nasm pour plus d'informations).

Pensez à consulter la deuxième version simplifiée également (plus loin), qui sera a préféré pour les codes ne contenant pas de données en écriture.

Vous pourrez également utiliser ce code comme une introduction au format ELF, si vous désirez l'étudier un jours (vous en tirerez sûrement du bonheur, et pour étudier ce format en profondeur, vous pourrez consulter la référence citée en début de ce document).

Vous pouvez consulter ce code ici en ligne, ou le télécharger : elf-with-data.asm (texte au format DOS CR-LF).


; (hint:tabulation size is 3)
; (date-dd-mm-yyyy:03/01/2007)

; Creating a fully functional ELF file without even GNU/ld
; ---------------------------------------------------------
;
; This a pattern file which allow you to create ELF executable without
; even a linker in hands. This is useful for custom installation with
; low resource, such as old PC. Indeed, the usual way is to use GNU/ld,
; but unfortunatly, ld rely on glibc, which is very-very hugh en
; outrageously resource consuming.
;
; You can use it with nasm (the syntax used here is the one of nasm).
; A static version of nasm if very light (between 150 and 200KB). So
; with a static version of e3 (which is no more than 13KB), and
; a few text files documentations about syscalls and some other good
; stuff, you can have a real developpement environement under less
; than the quarter of an 1.44M floppy disk.
;
; Please visit http://www.les-ziboux.rasama.org/elf-without-ld.html
; for updates. In the futur, I will mirror a very small static version
; of nasm (102KB), a static version of e3, and some useful texte
; documentation.
;
; You can use this pattern in any project... it is not mandatory, but this
; would be very nice of you to put this url on top of your product sources :
; http://www.les-ziboux.rasama.org/elf-without-ld.html
; Thank you :)
;
; To use this pattern, just edit the place between the BEGIN and END mark,
; both in code and data segment.
;
; If you code only adress read-only data, then you can use the other
; "elf-simple.asm", beceause is there are only read-only datas, it is
; best to put them in code segment.
;
; Finaly you have to compile your code with "nasm -f bin [-O2] yourcode.asm"
; (the -O parameter is optional, alghtough often requited because of
; short-jumps and some stuff like that).

; This file is full of comments... you may remove them after reading ;)
;
; Comments are welcome : les-ziboux@rasama.org (fr/en)
; You wanna learn Arabic ? -> http://www.les-ziboux.rasama.org (fr)

cpu 386
bits 32

org 0x08048000 ; Linux applications are loaded at this virtual address.

; === Header ================================================================

elf_header:
.start
   db 0x7F, "ELF"          ; ELF signature                 -- constant
   db 1                    ; Architecture(1)               -- 1 = 32 bits
   db 1                    ; Data enconding                -- 1 = LSB-First
   db 1                    ; File version                  -- 1 = v1
   db 0,0,0,0,0,0,0,0,0    ; 9 bytes padding               -- should be zero
   dw 2                    ; Type                          -- 2 = executable
   dw 3                    ; Architecture(2)               -- 3 = i386
   dd 1                    ; ELF Version                   -- 1 = ELF-v1
   dd _start               ; Entry point adress in memory  -- virtual adress
   dd segments_table - $$  ; Segments table offset in file
   dd 0                    ; Sections table offset in file -- 0 = none)
   dd 0                    ; File's flags
   dw elf_header.size      ; ELF Header's size
   dw 32                   ; Segments table entries's size
   dw 2                    ; Number of segment descriptors
   dw 0                    ; Sections table entries's size -- 0 = none
   dw 0                    ; Number of sections descriptor -- 0 = none
   dw 0                    ; String table index            -- 0 = none
.size equ $ - .start

segments_table:

code_segment_descriptor:
.start:
   dd 1                    ; Type            -- 1 = loadable into memory
   dd 0                    ; Offset in file  -- include ELF header and table
   dd $$                   ; Virtual address in memory
   dd 0                    ; Physical adress -- 0 = no physical address
   dd code_size            ; Size in file
   dd code_size            ; Size in memory
   dd 5                    ; Permission flags -- 0x4 + 0x1 = read and execute
   dd 0x4                  ; Alignment in memory (and in file)
.size equ $ - .start

data_segment_descriptor:
.start:
   dd 1                    ; Type -- 1 = loadable into memory
   dd _data - $$           ; Offset in file
   dd _data                ; Virtual adress in memory
   dd 0                    ; Physical adress -- 0 = no physical address
   dd data_size            ; Size in file
   dd data_size            ; Size in memory
   dd 6                    ; Permission flags -- 0x4 + 0x2 = read and write
   dd 0x4                  ; Alignment in memory (and in file)
.size equ $ - .start

; === Code ==================================================================

_code :

_start: ; You can move the start label where-ever you want in code segment.

   ; ----- BEGIN of your code -----

   ; Here is a sample code which simply say good bye and terminate.
   ; It give an example of usage of both code segment and data
   ; segment. Although this example does write to data segment,
   ; the data segment is still fully writable. Note that read-only
   ; datas can also reside in code segment (you should use
   ; the pattern "elf-simple.asm" for this purpose).
   ; You can put your own code between the BEGIN and END mark.
   ; Do not modify something else, unless you're sure about what
   ; you are playing with.

   mov  eax, 4                ; function id for sys_write
   mov  ebx, 1                ; descriptor of standard output
   mov  ecx, message          ; offset of bytes to write
   mov  edx, message.length   ; number of bytes to write
   int  0x80                  ; syscall

   mov  eax, 1                ; function id for sys_exit
   mov  ebx, 0                ; return code from the program (0 mean Ok)
   int  0x80                  ; syscall

   ; ----- END of your code -----

align 4
code_size equ $ - $$

; === Data ==================================================================

align 4
_data:

   ; ----- BEGIN of your datas -----

   ; This is a simple data example. This data segment is writable.
   ; Note that you can put read-only data in the code segment. If you
   ; only have read-only data, and no writable data in you application,
   ; then putting them in code segmet, avoid the creation of a data
   ; segment, reducing a bit the size of the executable. For this
   ; purpose, use the other patten name "elf-simple.asm".
   ; You can put your own datas between the BEGIN and END mark.
   ; Do not modify something else, unless you're sure about what
   ; you are playing with.

message:
   db "Bye, Au-revoir, Ma'a as-salama, Bisbald ... :)", 10
.length equ $ - message

   ; ----- END of your datas -----

align 4
data_size equ $ - _data

		

La version avec segment de code seul

Cette version du code est a utiliser pour les applications n'ayant qu'un segment de code, et pas de donnée en écriture (les données constantes, en lecture seule, peuvent être placées dans le segment de code).

Vous pouvez consulter ce code ici en ligne, ou le télécharger : elf-simple.asm (texte au format DOS CR-LF).


; (hint:tabulation size is 3)
; (date-dd-mm-yyyy:03/01/2007)

; Creating a fully functional ELF file without even GNU/ld
; ---------------------------------------------------------
;
; There are no explanations here : read "elf-with-data.asm" for
; informations on usage and copyright.
;
; Compile: nasm -f bin [-O2] yourcode.asm
; Updates : http://www.les-ziboux.rasama.org/elf-without-ld.html
; Comments are welcome : les-ziboux@rasama.org (fr/en)
; You wanna learn Arabic ? -> http://www.les-ziboux.rasama.org (fr)

cpu 386
bits 32

org 0x08048000 ; Linux applications are loaded at this virtual address.

; === Header ================================================================

elf_header:
.start
   db 0x7F, "ELF"          ; ELF signature                 -- constant
   db 1                    ; Architecture(1)               -- 1 = 32 bits
   db 1                    ; Data enconding                -- 1 = LSB-First
   db 1                    ; File version                  -- 1 = v1
   db 0,0,0,0,0,0,0,0,0    ; 9 bytes padding               -- should be zero
   dw 2                    ; Type                          -- 2 = executable
   dw 3                    ; Architecture(2)               -- 3 = i386
   dd 1                    ; ELF Version                   -- 1 = ELF-v1
   dd _start               ; Entry point adress in memory  -- virtual adress
   dd segments_table - $$  ; Segments table offset in file
   dd 0                    ; Sections table offset in file -- 0 = none)
   dd 0                    ; File's flags
   dw elf_header.size      ; ELF Header's size
   dw 32                   ; Segments table entries's size
   dw 1                    ; Number of segment descriptors -- just one (code)
   dw 0                    ; Sections table entries's size -- 0 = none
   dw 0                    ; Number of sections descriptor -- 0 = none
   dw 0                    ; String table index            -- 0 = none
.size equ $ - .start

segments_table:

code_segment_descriptor:
.start:
   dd 1                    ; Type            -- 1 = loadable into memory
   dd 0                    ; Offset in file  -- include ELF header and table
   dd $$                   ; Virtual address in memory
   dd 0                    ; Physical adress -- 0 = no physical address
   dd code_size            ; Size in file
   dd code_size            ; Size in memory
   dd 5                    ; Permission flags -- 0x4 + 0x1 = read and execute
   dd 0x4                  ; Alignment in memory (and in file)
.size equ $ - .start

; === Code ==================================================================

_code :

_start: ; You can move the start label where-ever you want in code segment.

   ; ----- BEGIN of your code -----

   ; Here is a sample code which simply cleanly terminate :P

   mov  eax, 1                ; function id for sys_exit
   mov  ebx, 0                ; return code from the program (0 mean Ok)
   int  0x80                  ; syscall

   ; ----- END of your code -----

   ; You may have read-only data here as well ;)

align 4
code_size equ $ - $$


		

Tous mes voeux d'inspiration :)

Google
 
Index

La création logicielle

Suite de cette page

Choix des langages de programmation

Page dont celle-ci est la suite

Printf or not printf ?

Accueil du site

Accueil

Contact

Contact

Lien inactif

Info