🔝 Retour au Sommaire
Niveau : Débutant
Prérequis : Section 2.5 (Premier programme), Section 2.5.1 (Compilation étape par étape)
Objectifs : Maîtriser les outils d'inspection des fichiers ELF (objets et exécutables), savoir lire une table de symboles, interpréter un désassemblage, et identifier les dépendances d'un binaire.
La section précédente nous a appris à produire des fichiers objets et un exécutable en décomposant chaque étape de la compilation. Nous disposons maintenant de fichiers binaires — mais contrairement au code source, on ne peut pas les ouvrir dans un éditeur de texte pour comprendre ce qu'ils contiennent.
C'est là qu'interviennent les binutils : un ensemble d'outils fournis par le projet GNU (et installés avec build-essential sur Ubuntu) qui permettent de radiographier un binaire sous tous les angles. Savoir utiliser ces outils, c'est la différence entre constater qu'un linkage échoue et comprendre pourquoi il échoue.
Nous travaillons avec les fichiers produits en section 2.5.1 :
# S'assurer que tout est compilé
g++ -c main.cpp -o main.o
g++ -c mathutils.cpp -o mathutils.o
g++ main.o mathutils.o -o main Avant toute analyse approfondie, la commande file permet d'identifier rapidement la nature d'un fichier binaire. C'est le premier réflexe à avoir face à un fichier inconnu :
file main.o
file mathutils.o
file main main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
mathutils.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped
Chaque mot de cette sortie est porteur d'information :
- ELF : le format binaire standard de Linux (Executable and Linkable Format).
- 64-bit : binaire compilé pour une architecture 64 bits.
- LSB : Least Significant Byte first — l'architecture x86-64 est little-endian.
- relocatable vs pie executable : un fichier objet est relocatable (adresses non fixées) ; un exécutable est un PIE (Position-Independent Executable), c'est-à-dire chargeable à n'importe quelle adresse virtuelle pour des raisons de sécurité (ASLR).
- dynamically linked : l'exécutable dépend de librairies partagées chargées au lancement.
- interpreter /lib64/ld-linux-x86-64.so.2 : le chemin du dynamic linker responsable du chargement.
- not stripped : les symboles de débogage sont encore présents.
📎 La section 1.4 détaille la structure interne du format ELF (headers, sections, segments).
La table des symboles est l'annuaire d'un fichier binaire. Elle liste toutes les entités nommées — fonctions, variables globales, constantes — avec leur adresse, leur type, et leur visibilité. C'est la structure que l'éditeur de liens consulte pour résoudre les références entre fichiers objets et librairies.
nm mathutils.oLa sortie se lit en trois colonnes : adresse (ou vide si non résolue), type sur une lettre, et nom du symbole.
0000000000000000 T _Z11aire_cercled
0000000000000030 T _Z16perimetre_cercled
La lettre qui identifie le type d'un symbole est la clé de lecture de nm. Voici les types que vous rencontrerez le plus souvent :
| Majuscule | Minuscule | Signification |
|---|---|---|
T |
t |
Section text (code exécutable) |
D |
d |
Section data (variables globales initialisées) |
B |
b |
Section BSS (variables globales non initialisées / zéro) |
R |
r |
Section rodata (données en lecture seule) |
U |
— | Symbole indéfini (undefined) : référencé ici, défini ailleurs |
W |
w |
Symbole faible (weak) : peut être remplacé par un symbole fort |
C |
— | Symbole commun (common) : variable non initialisée (ancien style C) |
A |
a |
Valeur absolue : adresse fixe, non relocalisable |
La convention est simple : une majuscule signifie que le symbole est global (visible par le linker, exporté), et une minuscule qu'il est local (interne au fichier objet, non exporté). Cette distinction est fondamentale — un symbole local ne peut pas résoudre une undefined reference dans un autre fichier objet.
Les noms bruts produits par nm sont manglés : le compilateur C++ encode le nom de la fonction avec les types de ses paramètres pour permettre la surcharge. Comparez :
# Noms manglés (bruts)
nm mathutils.o0000000000000000 T _Z11aire_cercled
0000000000000030 T _Z16perimetre_cercled
# Noms démanglés (lisibles)
nm -C mathutils.o0000000000000000 T aire_cercle(double)
0000000000000030 T perimetre_cercle(double)
L'option -C (ou --demangle) traduit les noms manglés dans leur forme C++ originale. C'est presque toujours ce que vous voulez. On peut aussi démangler un nom isolé avec l'outil dédié :
c++filt _Z11aire_cercledaire_cercle(double)
La convention de mangling suit l'Itanium C++ ABI, partagée par GCC et Clang sur Linux. Le préfixe _Z marque un symbole C++ manglé. Le nombre qui suit (11) indique la longueur du nom de la fonction. Le suffixe d encode le type du paramètre (double). Pour une fonction à deux paramètres int, double, le suffixe serait id. Pour un const std::string&, l'encodage devient nettement plus complexe — d'où l'utilité de -C.
L'usage le plus courant de nm en diagnostic est de repérer les symboles indéfinis (U) — ceux que le fichier objet référence mais ne définit pas lui-même :
nm -Cu main.oCette commande affiche tous les symboles que main.o attend d'un autre fichier objet ou d'une librairie. Vous y trouverez aire_cercle(double), perimetre_cercle(double) (provenant de mathutils.o), mais aussi std::cout, std::basic_string<...>::basic_string(...), et de nombreux symboles de la librairie standard.
C'est exactement la liste que l'éditeur de liens doit résoudre. Si un symbole reste indéfini après le linkage, vous obtenez l'erreur undefined reference. Par conséquent, face à une telle erreur, la démarche est la suivante :
- Repérer le symbole manquant dans le message d'erreur.
- Chercher dans quel fichier objet ou quelle librairie il est défini (
T) avecnm -C. - Ajouter ce fichier ou cette librairie à la commande de linkage.
nm -C mathutils.o | grep aire_cercle
nm -C main | grep aire_cercle Dans le fichier objet, l'adresse est relative (souvent 0000000000000000 pour le premier symbole de la section .text). Dans l'exécutable, le linker a attribué une adresse virtuelle définitive. Cette adresse est celle où le code résidera en mémoire au moment de l'exécution (modulo la randomisation ASLR pour les exécutables PIE).
Voici les options que vous utiliserez le plus :
# Symboles indéfinis uniquement (ce que le fichier attend de l'extérieur)
nm -u main.o
# Symboles définis uniquement (ce que le fichier fournit)
nm --defined-only main.o
# Trier par adresse (utile pour voir l'organisation mémoire)
nm -n main
# Trier par taille de symbole (repérer les gros objets)
nm -S --size-sort main
# Afficher la taille de chaque symbole
nm -S -C main | head -20
# Format étendu (une colonne par champ)
nm -f posix -C mathutils.oL'option --size-sort combinée à -S est particulièrement utile pour identifier les fonctions ou variables qui consomment le plus d'espace dans le binaire. Sur un projet conséquent, c'est un premier pas vers l'optimisation de la taille.
objdump est un outil polyvalent qui permet de désassembler du code machine (le convertir en mnémoniques assembleur lisibles), d'afficher les en-têtes et sections d'un fichier ELF, et d'inspecter les données brutes. C'est l'outil de choix pour comprendre ce que le compilateur a réellement généré à partir de votre code.
La fonction la plus utilisée de objdump est le désassemblage du code machine :
# Désassembler toutes les sections de code
objdump -d -C mathutils.oL'option -d active le désassemblage, et -C démangle les noms C++. La sortie ressemble à :
mathutils.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <aire_cercle(double)>:
0: f2 0f 11 44 24 f8 movsd %xmm0,-0x8(%rsp)
6: f2 0f 10 0d 00 00 00 movsd 0x0(%rip),%xmm1
d: 00
e: f2 0f 59 c8 mulsd %xmm0,%xmm1
12: f2 0f 10 44 24 f8 movsd -0x8(%rsp),%xmm0
18: f2 0f 59 c1 mulsd %xmm1,%xmm0
1c: c3 ret
Chaque ligne contient l'offset dans la section (colonne de gauche), les octets du code machine en hexadécimal (colonne centrale), et l'instruction assembleur décodée (colonne de droite). Le code machine est ce que le processeur exécute réellement — l'assembleur n'en est qu'une représentation lisible.
Pour utiliser la syntaxe Intel, plus répandue dans la documentation :
objdump -d -C -M intel mathutils.o0000000000000000 <aire_cercle(double)>:
0: f2 0f 11 44 24 f8 movsd QWORD PTR [rsp-0x8],xmm0
6: f2 0f 10 0d 00 00 00 movsd xmm1,QWORD PTR [rip+0x0]
d: 00
e: f2 0f 59 c8 mulsd xmm1,xmm0
12: f2 0f 10 44 24 f8 movsd xmm0,QWORD PTR [rsp-0x8]
18: f2 0f 59 c1 mulsd xmm0,xmm1
1c: c3 ret
Sur un gros binaire, le désassemblage complet est volumineux. Pour cibler une fonction :
# Avec grep (simple mais approximatif)
objdump -d -C -M intel main | grep -A 30 '<main>:'
# Avec l'option --disassemble= (plus précis, GCC récent)
objdump -d -C -M intel --disassemble=main mainL'option -S (majuscule) entrelace le code source C++ avec l'assembleur correspondant, à condition que le binaire ait été compilé avec les informations de débogage (-g) :
g++ -c -g -O2 mathutils.cpp -o mathutils_debug.o
objdump -d -S -C -M intel mathutils_debug.o La sortie mêle les lignes de votre code C++ et les instructions assembleur qu'elles ont générées :
double aire_cercle(double rayon) {
return PI * rayon * rayon;
0: f2 0f 59 c0 mulsd xmm0,xmm0
4: f2 0f 59 05 00 00 00 mulsd xmm0,QWORD PTR [rip+0x0]
b: 00
c: c3 ret
C'est un outil extrêmement puissant pour vérifier qu'une optimisation a bien été appliquée, ou pour comprendre le coût réel d'une abstraction C++. Vous pouvez voir ici que PI * rayon * rayon a été réordonné en rayon * rayon * PI (deux mulsd), et que toute la fonction tient en trois instructions.
💡 L'outil en ligne Compiler Explorer (godbolt.org) offre cette même vue source/assembleur dans un navigateur, avec le support de dizaines de compilateurs et versions. C'est un complément indispensable à
objdumppour l'exploration rapide. Voir section 48.4.4.
Un fichier ELF est organisé en sections : des blocs nommés qui contiennent chacun un type de données. objdump -h affiche l'en-tête des sections :
objdump -h main.oSections:
Idx Name Size VMA LMA File off Algn
0 .text 000001a5 0000000000000000 0000000000000000 00000040 2**4
1 .rodata 00000098 0000000000000000 0000000000000000 000001e8 2**3
2 .data 00000000 0000000000000000 0000000000000000 00000280 2**0
3 .bss 00000000 0000000000000000 0000000000000000 00000280 2**0
...
Les sections fondamentales sont :
| Section | Contenu | Permissions mémoire |
|---|---|---|
.text |
Code machine (instructions exécutables) | Lecture + Exécution |
.rodata |
Données en lecture seule (chaînes littérales, constantes) | Lecture seule |
.data |
Variables globales et statiques initialisées | Lecture + Écriture |
.bss |
Variables globales et statiques non initialisées (zéro) | Lecture + Écriture |
.symtab |
Table des symboles | — (métadonnées) |
.strtab |
Table des chaînes (noms de symboles) | — (métadonnées) |
.rela.text |
Relocations pour la section .text |
— (métadonnées) |
.debug_* |
Informations de débogage DWARF | — (métadonnées) |
.comment |
Commentaires (version du compilateur, etc.) | — (métadonnées) |
La section .bss est notable : elle ne consomme aucun espace dans le fichier sur disque. Elle ne fait que déclarer une taille. Au chargement du programme, le système d'exploitation alloue cette quantité de mémoire et la remplit de zéros. C'est pourquoi les variables globales non initialisées (ou initialisées à zéro) n'augmentent pas la taille du binaire.
On peut examiner le contenu hexadécimal et ASCII d'une section spécifique :
# Contenu de .rodata (nos chaînes littérales)
objdump -s -j .rodata main.oContents of section .rodata:
0000 42696576 656e7565 20737572 20000056 Bienvenue sur ..V
0010 65727369 6f6e203a 20003120 2e302e30 ersion : .1.0.0
...
La colonne de droite affiche l'interprétation ASCII des octets. On y retrouve nos chaînes de caractères, ainsi que la valeur "1.0.0" issue de la macro APP_VERSION (substituée par le préprocesseur, puis stockée comme donnée en lecture seule par le compilateur).
# Contenu de .comment (version du compilateur)
objdump -s -j .comment main.oContents of section .comment:
0000 00474343 3a202855 62756e74 75203135 .GCC: (Ubuntu 15
0010 2e312e30 2d317562 756e7475 31293135 .1.0-1ubuntu1)15
...
La section .comment révèle la version exacte du compilateur qui a produit le fichier objet. C'est une information précieuse quand vous travaillez avec des binaires dont vous n'avez pas compilé le source vous-même.
readelf est un outil complémentaire à objdump, spécialisé dans l'analyse du format ELF lui-même. Là où objdump est orienté désassemblage et sections, readelf excelle dans l'affichage des structures de métadonnées ELF.
readelf -h mainELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
Type: DYN (Position-Independent Executable)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x10c0
...
Le magic number 7f 45 4c 46 correspond aux octets \x7fELF — c'est la signature qui identifie tout fichier ELF. Le champ Entry point address indique l'adresse de la première instruction exécutée au lancement. Ce n'est pas l'adresse de votre main() : c'est l'adresse de _start, le point d'entrée du runtime C qui initialise l'environnement avant d'appeler main().
readelf -s main.oreadelf -s affiche la table des symboles avec plus de détails que nm : l'index du symbole, sa taille, son type (FUNC, OBJECT, NOTYPE), son binding (LOCAL, GLOBAL, WEAK), la section à laquelle il appartient, et son nom.
# Avec démangling
readelf -sW main.o | c++filt | head -30L'option -W (wide) évite la troncature des noms longs, ce qui est indispensable pour les symboles C++ souvent verbeux.
readelf -S mainCette commande est similaire à objdump -h mais affiche des informations supplémentaires : les flags de chaque section (ALLOC, WRITE, EXEC), leur type, et leur alignement. La colonne Flags est particulièrement informative :
A(ALLOC) : la section occupe de la mémoire au chargement.X(EXEC) : la section contient du code exécutable.W(WRITE) : la section est inscriptible à l'exécution.
Ainsi, .text porte les flags AX (allouée, exécutable, non inscriptible), .data porte WA (allouée, inscriptible, non exécutable), et .rodata porte uniquement A (allouée, ni inscriptible ni exécutable). Cette séparation des permissions est un mécanisme de sécurité fondamental : si du code tente d'écrire dans .rodata ou d'exécuter du contenu de .data, le système d'exploitation provoque un segmentation fault.
Pour un exécutable dynamiquement lié, la section .dynamic liste les dépendances et les informations nécessaires au chargement :
readelf -d mainDynamic section at offset 0x2da8 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
...
Les entrées NEEDED listent les librairies partagées dont dépend l'exécutable. C'est l'information que le dynamic linker utilise au lancement pour savoir quelles librairies charger.
⚠️ Note : contrairement àldd(que nous verrons juste après),readelf -dne fait que lire des métadonnées dans le fichier. Il n'exécute rien, ne résout rien. C'est donc la méthode la plus sûre pour inspecter les dépendances d'un binaire potentiellement non fiable.
Là où readelf -d affiche les dépendances déclarées (les noms de librairies inscrits dans le binaire), ldd va plus loin : il résout chaque dépendance en trouvant le fichier .so correspondant sur le système et affiche son chemin complet.
ldd main linux-vdso.so.1 (0x00007ffcabffe000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4a8c200000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4a8c100000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4a8c600000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4a8be00000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4a8c800000)
Chaque ligne suit le format nom_déclaré => chemin_résolu (adresse_chargement). L'adresse entre parenthèses est l'adresse de base à laquelle la librairie sera mappée dans l'espace virtuel du processus.
Notre programme simple dépend de cinq librairies. Chacune a un rôle précis :
libstdc++.so.6 est l'implémentation de la librairie standard C++ de GCC. Elle contient le code de std::cout, std::string, les conteneurs, les algorithmes, le système d'exceptions, etc. Le chiffre 6 est le numéro de SONAME (version d'interface ABI), stable depuis GCC 3.4.
libm.so.6 est la librairie mathématique. Notre appel à std::sqrt en dépend. En C, il faudrait l'ajouter manuellement avec -lm ; en C++, g++ lie automatiquement libm.
libc.so.6 est la librairie C standard (glibc sur Ubuntu). Même un programme C++ pur en dépend, car libstdc++ s'appuie elle-même sur la glibc pour les allocations mémoire, les entrées/sorties système, et d'autres fonctionnalités bas niveau.
libgcc_s.so.1 est le runtime de support GCC. Il fournit des fonctions auxiliaires que le compilateur peut insérer implicitement, notamment pour le déroulement de pile (stack unwinding) lors de la propagation d'exceptions.
linux-vdso.so.1 est un cas particulier : ce n'est pas un fichier sur le disque. C'est un Virtual Dynamic Shared Object injecté par le noyau Linux directement dans l'espace mémoire de chaque processus. Il fournit des implémentations optimisées de certains appels système fréquents (clock_gettime, gettimeofday) en évitant le coût d'une transition vers le mode noyau.
/lib64/ld-linux-x86-64.so.2 est le dynamic linker lui-même (parfois appelé loader ou interpréteur ELF). C'est le tout premier code exécuté quand vous lancez votre programme : il charge les librairies partagées, résout les symboles, et passe le contrôle à _start.
ldd fonctionne en configurant des variables d'environnement spéciales puis en exécutant le binaire (ou plus précisément, en invoquant le dynamic linker sur ce binaire). Sur un binaire de confiance, c'est sans risque. Mais sur un binaire d'origine inconnue, l'exécution pourrait déclencher du code malveillant.
L'alternative sûre pour lister les dépendances déclarées sans aucune exécution est :
# Lecture statique des métadonnées — aucune exécution
readelf -d main | grep NEEDED
# Ou avec objdump
objdump -p main | grep NEEDEDCette commande n'affiche que les noms des librairies (sans résolution de chemin), mais c'est suffisant pour un premier diagnostic et c'est parfaitement sûr.
Quand ldd ne trouve pas une librairie, elle apparaît comme not found :
libexample.so.1 => not found
Au lancement du programme, cela provoquerait une erreur fatale immédiate. La section 2.5.3 détaille les mécanismes de résolution et les stratégies de diagnostic pour ce type de problème.
L'outil size donne une vue condensée de l'occupation mémoire d'un binaire, section par section :
size main.o mathutils.o main text data bss dec hex filename
1685 0 0 1685 695 main.o
56 0 0 56 38 mathutils.o
14523 616 8 15147 3b2b main
Les colonnes text, data et bss correspondent aux tailles (en octets) des trois sections principales. La colonne dec est la somme totale en décimal, et hex en hexadécimal.
L'intérêt principal de size est la comparaison entre différentes configurations de compilation :
g++ -O0 main.cpp mathutils.cpp -o main_O0
g++ -O2 main.cpp mathutils.cpp -o main_O2
g++ -Os main.cpp mathutils.cpp -o main_Os
g++ -O3 main.cpp mathutils.cpp -o main_O3
size main_O0 main_O2 main_Os main_O3Vous constaterez généralement que -Os (optimisation pour la taille) produit la plus petite section text, tandis que -O3 (optimisation agressive pour la vitesse) peut la faire grossir à cause de l'inlining et du déroulement de boucles. La section .data reste stable car elle ne dépend que des variables globales initialisées, pas du niveau d'optimisation.
L'outil strings parcourt un fichier binaire et extrait toutes les séquences de caractères imprimables d'une longueur minimale (4 par défaut) :
strings main | head -20C'est un outil simple mais étonnamment utile dans plusieurs situations.
Pour retrouver les messages de votre programme :
strings main | grep -i bienvenuePour identifier la version de la librairie C requise :
strings main | grep GLIBCGLIBC_2.34
GLIBC_2.17
Ces chaînes indiquent les version requirements de la glibc. Si vous essayez d'exécuter ce binaire sur un système avec une glibc plus ancienne que 2.34, il refusera de se lancer. C'est une cause fréquente de problèmes de portabilité entre distributions.
Pour identifier le compilateur :
strings main | grep GCCPar défaut, strings affiche les séquences d'au moins 4 caractères. On peut ajuster ce seuil :
# Chaînes d'au moins 10 caractères (réduit le bruit)
strings -n 10 mainJusqu'ici, tous nos binaires étaient not stripped — ils contenaient leur table de symboles complète. En production, on supprime souvent ces informations pour réduire la taille du binaire et rendre le reverse-engineering plus difficile :
# Copier l'exécutable avant de le strip
cp main main_stripped
# Supprimer les symboles
strip main_stripped
# Comparer
ls -l main main_stripped
file main_stripped
nm main_stripped La commande nm sur un binaire strippé retourne no symbols. La taille diminue significativement — les symboles et chaînes associées pouvant représenter une part importante du binaire.
On peut aussi séparer les symboles dans un fichier dédié, ce qui permet de les conserver pour le débogage tout en distribuant un binaire léger :
# Extraire les symboles de débogage dans un fichier séparé
objcopy --only-keep-debug main main.debug
# Stripper l'exécutable
strip main
# Ajouter un lien vers le fichier de symboles
objcopy --add-gnu-debuglink=main.debug mainCette technique est couramment utilisée en production : les serveurs exécutent des binaires strippés (taille réduite, surface d'attaque diminuée), tandis que les fichiers .debug sont archivés et utilisés pour analyser les core dumps en cas de crash.
📎 La section 29.3 traite du débogage post-mortem avec les core dumps et les fichiers de symboles séparés.
| Question | Outil | Commande |
|---|---|---|
| Quel type de fichier est-ce ? | file |
file main.o |
| Quels symboles sont définis/indéfinis ? | nm |
nm -Cu main.o |
| Quel est le code assembleur d'une fonction ? | objdump |
objdump -d -C -M intel main.o |
| Quel code C++ a produit cet assembleur ? | objdump |
objdump -d -S -C main_debug.o |
| Quelles sections contient le fichier ? | objdump/readelf |
objdump -h main.o |
| Quelles chaînes littérales sont présentes ? | objdump/strings |
objdump -s -j .rodata main.o |
| Quelles librairies dynamiques sont requises ? | ldd/readelf |
ldd main |
| Quelle taille font les sections ? | size |
size main |
| Quelle glibc est requise ? | strings |
strings main | grep GLIBC |
| Quels sont les en-têtes ELF ? | readelf |
readelf -h main |
| Un binaire est-il strippé ? | file |
file main |
Les outils d'inspection des binaires transforment un fichier opaque en une source d'information riche. Avec nm, vous interrogez la table des symboles pour comprendre ce qu'un binaire exporte et ce qu'il attend. Avec objdump et readelf, vous examinez la structure interne du fichier ELF, désassemblez le code machine, et vérifiez les propriétés des sections. Avec ldd, vous identifiez les librairies dynamiques nécessaires à l'exécution. Avec size et strings, vous obtenez des vues synthétiques rapides sur l'empreinte mémoire et le contenu textuel.
Ces outils ne sont pas réservés à l'analyse ponctuelle. Ils s'intègrent dans les scripts de build, les pipelines CI/CD (vérifier qu'un binaire de release est bien strippé, qu'il n'a pas de dépendances inattendues), et les audits de sécurité. Ils constituent le vocabulaire de base de tout développeur qui travaille avec des binaires compilés sur Linux.
Prochaine section : 2.5.3 — Dépendances dynamiques et résolution, où nous explorerons en profondeur le mécanisme par lequel Linux trouve et charge les librairies partagées au lancement d'un programme.