1. Introduction▲
Le format ELF (Executable and Linking Format) est le format de fichier standard pour les fichiers exécutables, les fichiers objets, les bibliothèques partagées et les core dumps sous Linux/Unix. Le format d'information de débogage le plus récent, compatible avec ELF, est DWARF 5.
Si vous n'écrivez pas un nouveau compilateur ou un débogueur, il n'est pas nécessaire de comprendre chaque bit de ces formats, mais il y a quelques concepts et quelques outils qui peuvent vous aider à écrire et à gérer votre code.
Prenons un exemple de code simple et compilons-le :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
#include<stdio.h>
int a1=10,a2=20;
void f2()
{
printf("X");
}
void f1()
{
int i;
for(i=0;i<100;i++)
{
if(i % 20 == 0)
a1++;
f2();
}
}
void main()
{
char *str = "Bonjour, bonne journée....";
f1();
puts(str);
printf("hello %d\n",a1);
}
Compilons le code avec les options par défaut :
# gcc -o app ./test.cNous pouvons maintenant obtenir les informations du fichier ELF avec l'outil readelf :
# readelf -a ./appOn voit alors :
- l'en-tête ELF : utilisé par la commande file pour afficher les informations générales et l'architecture matérielle du code ;
- les en-têtes Section : le code, les données, les chaînes de caractères et plus ;
- les en-têtes Program : les en-têtes pour les bibliothèques dynamiques, la pile, etc., avec leurs permissions (Lecture/Écriture/Exécution) ;
- les sections dynamiques ;
- et plus…
2. Les en-têtes Section▲
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 0000000 0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000090 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400348 00000348 0000000000000062 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000004003aa 000003aa 000000000000000c 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000004003b8 000003b8 0000000000000030 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 00000000004003e8 000003e8 0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400400 00000400 0000000000000060 0000000000000018 AI 5 24 8
[11] .init PROGBITS 0000000000400460 00000460 000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400480 00000480 0000000000000050 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 00000000004004d0 000004d0 0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 00000000004004e0 000004e0 00000000000002a2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 0000000000400784 00000784 0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 0000000000400790 00000790 000000000000001c 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004007ac 000007ac 0000000000000054 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400800 00000800 0000000000000174 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10 0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18 0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20 0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28 00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8 0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000 0000000000000038 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601038 00001038 0000000000000018 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601050 00001050 0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00001050 0000000000000034 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 00001084 00000000000000fc 0000000000000000 0 0 1Dans chaque section, le système de construction place différentes entités. Les sections importantes sont :
- .text : le code compilé ;
- .data : les données initialisées ;
- .bss : les données non initialisées.
2-1. .bss et .data▲
Pour voir la différence, regardons un code simple :
int arr[1000];
void main()
{
arr[0]=1;
arr[1]=2;
....
}Si nous déclarons un tableau global sans initialisation, le système de construction le place dans la section .bss :
Sortie de readelf :
Nous pouvons voir la taille des sections lors du chargement de l'application en mémoire, notamment la section .bss. Ci-dessous la taille du fichier ELF :
Developer@:~/testapp$ ls -l
total 16
-rw-rw-r-- 1 developer developer 287 fev 7 18:22 a.c
-rwxrwxr-x 1 developer developer 8848 fev 7 18:26 appMais si nous ajoutons quelques valeurs pour initialiser le tableau :
int arr[1024] = {1,2};
void main()
{
char *str = "Bonjour, passez une bonne journée.....";
...
}Nous pouvons constater que le tableau est maintenant dans la section .data :
Et le fichier ELF est maintenant plus gros :
Developer@:~/testapp$ ls -l
total 16
-rw-rw-r-- 1 developer developer 285 fev 7 18:32 a.c
-rwxrwxr-x 1 developer developer 12992 fev 7 18:32 appCela signifie que le fichier ELF contient tout le tableau, même si nous n'avons initialisé que deux éléments.
Donc, si le même programme déclare une taille de tableau de 1 000 000 :
int arr[1000000] = {1,2};
void main()
{
char *str = "Bonjour, passez une bonne journée.....";
...
}La taille est maintenant de :
Developer@:~/testapp$ ls -l
total 16
-rw-rw-r-- 1 developer developer 288 fev 7 18:32 a.c
-rwxrwxr-x 1 developer developer 4008896 fev 7 18:32 appLe fichier ELF est maintenant plein de zéros.
3. Les informations de débogage▲
Les informations de débogage sont placées dans le fichier ELF pour permettre au débogueur de savoir quelle ligne du source correspond au code machine. Le débogueur charge le programme et utilise les informations de débogage pour savoir où placer les points d'arrêt. Il remplace l'instruction machine par un trap (int 3 sur les CPU x86), ce qui provoque une exception CPU et le code d'origine est remis pour la reprise après l'exception.
Si nous compilons le code avec les informations de débogage, nous verrons que la taille devient plus grande :
developer@:~/testapp$ gcc -o app1 ./a.c
developer@:~/testapp$ gcc -g3 -o app2 ./a.c
developer@:~/testapp$ ls -l
total 48
-rw-rw-r-- 1 developer developer 290 fev 7 19:04 a.c
-rwxrwxr-x 1 developer developer 8824 fev 7 19:04 app1
-rwxrwxr-x 1 developer developer 29864 fev 7 19:04 app2Et les nouvelles sections :
4. Autres outils utiles▲
nm : liste les symboles avec les adresses :
developer@:~/testapp$ nm app2
0000000000601048 D a1
000000000060104c D a2
0000000000601050 B __bss_start
0000000000601050 b completed.7585
0000000000601038 D __data_start
0000000000601038 W data_start
.....objdump : affiche les informations sur un fichier objet : file :
- les en-têtes (-x) ;
- les informations de débogage (-g) ;
- désassemblage (-d) ;
- désassemblage avec le code source (-S) ;
- et plus…
# objdump -S ./app2
...
void main()
{
400626: 55 push %rbp
400627: 48 89 e5 mov %rsp,%rbp
40062a: 48 83 ec 10 sub $0x10,%rsp
char *str = "hello have a good day.....";
40062e: 48 c7 45 f8 f4 06 40 movq $0x4006f4,-0x8(%rbp)
400635: 00
f1();
400636: b8 00 00 00 00 mov $0x0,%eax
40063b: e8 87 ff ff ff callq 4005c7 <f1>
puts(str);addr2line : affiche le numéro de ligne du code source à partir de l'adresse connue.
Cet outil est très utile si le programme a planté et que nous avons écrit un gestionnaire d'erreur pour afficher l'adresse ayant généré celle-ci (compteur de programme). Nous pouvons utiliser cet outil avec un fichier ELF contenant des informations de débogage (le programme a pu être strippé - voir ci-dessous) :
developer@:~/testapp$ addr2line -e app2 0x400630
/home/developer/testapp/./a.c:25strip : retire du fichier ELF les symboles et les informations de débogage.
Avant de déployer notre application, nous pouvons supprimer tous les symboles et les informations de débogage sans avoir à la recompiler.
developer@:~/testapp$ strip -o appstripped ./app
developer@:~/testapp$ ls -l
total 44
-rw-rw-r-- 1 developer developer 290 dec 7 19:04 a.c
-rwxrwxr-x 1 developer developer 29864 dec 7 19:05 app
-rwxrwxr-x 1 developer developer 6336 dec 7 21:17 appstrippedobjcopy : copie et traduit des fichiers objets.
Vous pouvez utiliser objcopy pour copier du contenu d'un fichier ELF vers un autre, par exemple si vous voulez copier uniquement les informations de débogage dans un fichier séparé.
# gcc -g3 -o app ./a.c
# objcopy --only-keep-debug app app.debug
# strip -s ./appEt vous pouvez ajouter plus tard les informations de débogage :
# objcopy --add-gnu-debuglink app.debug app
# gdb ./appsize : affiche la taille des sections.
# size ./app
text data bss dec hex filename
1594 576 8 2178 882 ./app5. Notes de la rédaction▲
La rédaction remercie Liran B.H pour son autorisation de traduction de :
http://devarea.com/linux-inside-the-build-process/
La rédaction remercie également Chrtophe pour sa traduction ainsi que Jlliagre et f-leb pour leur relecture.





