Les utilitaires binaires GNU, généralement appelés binutils, sont une collection d'outils de développement qui gèrent les fichiers d'assemblage, les fichiers objet , et bibliothèques.
La nouvelle génération de langages de programmation apparue ces dernières années masque vraiment la fonctionnalité de ces utilitaires, car ils se produisent en arrière-plan. Ainsi, de nombreux développeurs ne sont pas exposés à ces outils.
Mais, si vous êtes un développeur travaillant sur la plate-forme Linux / UNIX, il est essentiel de comprendre les différentes commandes disponibles dans le cadre des outils de développement GNU.
Voici les 12 commandes binutils différentes abordées dans ce didacticiel.
- as – Commande de l'assembleur GNU
- ld – Commande GNU Linker
- ar - Commande d'archivage GNU
- nm - Liste des symboles de fichiers d'objets
- objcopy – Copier et traduire des fichiers d'objets
- objdump – Afficher les informations sur le fichier objet
- taille – Taille de la section de liste et taille totale
- strings – Afficher les caractères imprimables d'un fichier
- bande - Supprimer les symboles du fichier objet
- c++filt – Commande Demangle
- addr2line – Convertir l'adresse en nom de fichier et en numéros
- readelf - Afficher les informations sur le fichier ELF
Ces outils vous aideront à manipuler efficacement vos fichiers binaires, objets et bibliothèques.
Parmi ces 12 utilitaires, comme et ld sont les plus importants, ils constituent le backend par défaut de GNU Compiler Collection (gcc). GCC ne fait que le travail qui compile de C/C++ vers le langage assembleur, et son travail as et ld pour sortir le binaire exécutable.
Préparer un exemple de code
Pour comprendre le fonctionnement de toutes ces commandes, préparons d'abord un exemple de code assembleur à partir du code C en utilisant gcc -S. Toutes les expériences présentées ici sont effectuées sur une machine Linux x86 64 bits.
Ci-dessous se trouve le code C, qui utilise simplement la valeur de retour de la fonction externe comme code de retour. Il n'y a pas d'entrée/sortie, donc si vous voulez vérifier si le programme s'est exécuté comme prévu, veuillez vérifier l'état de retour (echo $?). Nous avons trois fonctions, main, func1 et func2, et un fichier pour chaque fonction.
// func1.c file: int func1() { return func2(); } // func2.c file: int func2() { return 1; } // main.c file: int main() { return func1(); }
GCC prend en charge la bibliothèque d'exécution C, de sorte que la fonction principale est traitée comme une fonction normale. Pour simplifier la démo, nous ne voulons pas impliquer la bibliothèque C lors de la compilation et de la liaison de ces fichiers .s. Donc, deux modifications sont faites pour main.s :
La première modification est que l'étiquette _start est ajoutée pour l'étape du lien.
_start label est le point d'entrée de l'application, s'il n'est pas défini, un avertissement comme ci-dessous sera signalé lors de l'exécution de ld.
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
La deuxième modification est que ret est remplacé par l'appel de sortie du système.
Nous devons déclencher manuellement l'interruption de sortie du système. %eax est utilisé pour conserver la valeur de retour de la fonction, mais l'appel de sortie système la conserve dans %ebx. Donc, copiez-le de %eax vers %ebx
Vous trouverez ci-dessous la version rééditée du code assembleur gcc.
fichier func1.s :
.file "func1.c" .text .globl func1 .type func1, @function func1: pushq %rbp movq %rsp, %rbp movl $0, %eax call func2 leave
fichier func2.s :
.file "func2.c" .text .globl func2 .type func2, @function func2: pushq %rbp movq %rsp, %rbp movl $1, %eax leave ret
fichier main.s :
.file "main.c" .text .globl main .globl _start .type main, @function _start: main: pushq %rbp movq %rsp, %rbp movl $0, %eax call func1 movl %eax, %ebx movl $1, %eax int $0x80 leave
1. as - Commande de l'assembleur GNU
as prend le fichier d'assemblage en entrée et en sortie un fichier objet. Le fichier objet n'est qu'un format interne, qui sera utilisé comme entrée de ld pour la production du fichier exécutable final.
Exécutez la commande as sur le fichier main.s pour obtenir le fichier objet main.o comme indiqué ci-dessous.
as main.s -o main.o
main.o (produit par "as main.s -o main.o"), nous pouvons obtenir les informations ci-dessous.
main.o: ELF 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV), not stripped
Le fichier objet est au format ELF, qui est le format de fichier le plus largement utilisé pour les distributions Linux.
Veuillez noter que la commande "as" prend également en charge la syntaxe pour le prétraitement, le symbole, la contrainte, l'expression, les pseudo-opérations/directives et les commentaires.
GNU Assembler peut prendre en charge une énorme collection de machines, mais généralement une seule famille de machines/architectures est sélectionnée lors de la compilation ou de la compilation croisée.
2. ld - Commande GNU Linker
Le fichier objet contient généralement une référence à des fonctions externes dans différentes bibliothèques/objets, et c'est le travail de l'éditeur de liens (ld) de combiner tous les fichiers objet/bibliothèque nécessaires pour le binaire final, de déplacer les sections et de résoudre la référence.
Le comportement réel de ld est défini dans le script de l'éditeur de liens, qui décrit la disposition de la mémoire de l'exécutable.
Si nous lions uniquement main.o (ld main.o -o main), il y aura une erreur de référence indéfinie :
main.o: In function `_start': main.c:(.text+0xa): undefined reference to `func1'
Nous n'obtiendrons pas de fichier exécutable sans lier les trois fichiers d'objection (ld main.o func1.o func2.o -o main).
# file main main: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), statically linked, not stripped
Soyez différent avec le fichier objet, ici nous obtenons un exécutable lié statiquement.
as et ld fonctionnent sur une cible/architecture spécifique. Mais il existe des outils qui fonctionnent sur des objets BFD définis dans binutils.
À partir des dernières lignes de la sortie de objcopy -h, nous pouvons obtenir les cibles de support.
objcopy: supported targets: elf64-x86-64 elf32-i386 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
Faut dire que verilog, ihex ne sont pas pris en charge par les vrais OS, mais cela peut être très utile pour traiter le contenu des objets au format texte. Ils sont largement utilisés dans les environnements de simulation de puces pour l'initialisation de la mémoire/ROM.
3. ar/ranlib - Commande d'archivage GNU
ar peut être utilisé pour générer et manipuler une bibliothèque statique, qui est un fichier d'archive composé de nombreux objets.
Le comportement de ar peut être contrôlé à partir d'un argument de ligne de commande (le style unix) ou d'un fichier de script. ranlib peut ajouter un index de symboles à une archive, ce qui peut accélérer la vitesse de liaison et également faciliter l'appel de routines. ar -s fera la même chose que ranlib.
Pour mon test, avec ou sans -s, ar affichera toujours l'index de l'archive.
Test1, ar sans -s.
# ar -r extern.a func1.o func2.o && nm -s extern.a ar: creating extern.a Archive index: func1 in func1.o func2 in func2.o func1.o: 0000000000000000 T func1 U func2 func2.o: 0000000000000000 T func2
Pour plus de détails sur la commande ar, lisez ceci :Linux ar command Exemples :comment créer, afficher, extraire et modifier des fichiers d'archive C (*.a)
Test 2, ar avec -s.
# ar -r -s externS.a func1.o func2.o && nm -s externS.a ar: creating externS.a Archive index: func1 in func1.o func2 in func2.o func1.o: 0000000000000000 T func1 U func2 func2.o: 0000000000000000 T func2
Test 3, relancez ranlib.
# cp extern.a externR.a && ranlib externR.a && nm -s externR.a Archive index: func1 in func1.o func2 in func2.o func1.o: 0000000000000000 T func1 U func2 func2.o: 0000000000000000 T func2
Il peut être démontré que chaque test produit le même résultat.
4. nm - Liste des symboles de fichiers d'objets
nm peut lister les symboles du fichier objet. Nous en avons montré l'utilisation dans la section ci-dessus.
Les commandes nm fournissent des informations sur les symboles utilisés dans un fichier objet ou un fichier exécutable.
Les informations par défaut fournies par la commande nm sont les suivantes :
- Adresse virtuelle du symbole
- Un caractère qui représente le type de symbole. Si le caractère est en minuscule alors le symbole est local mais si le caractère est en majuscule alors le symbole est externe
- Nom du symbole
$ nm -A ./*.o | grep func ./hello2.o:0000000000000000 T func_1 ./hello3.o:0000000000000000 T func_2 ./hello4.o:0000000000000000 T func_3 ./main.o: U func ./reloc.o: U func ./reloc.o:0000000000000000 T func1 ./test1.o:0000000000000000 T func ./test.o: U func
Lire la suite :10 exemples pratiques de commandes Linux nm
5. objcopy - Copier et traduire des fichiers d'objets
objcopy peut copier le contenu d'un fichier objet vers un autre fichier objet, et l'objet d'entrée/sortie peut le faire dans un format différent.
Il y a des moments où vous devez porter un fichier objet disponible pour un type de plate-forme (comme ARM ou x86) vers un autre type de plate-forme.
Les choses sont relativement faciles si le code source est disponible car il peut être recompilé sur la plate-forme cible.
Mais que se passe-t-il si le code source n'est pas disponible et que vous devez toujours porter un fichier objet d'un type de plate-forme à un autre ? Eh bien, si vous utilisez Linux, la commande objcopy fait exactement ce qui est requis
La syntaxe de cette commande est :
objcopy [options] infile [outfile]...
Lire la suite :Exemples de commandes Linux Objcopy pour copier et traduire des fichiers objets
6. objdump - Afficher les informations sur le fichier objet
objdump peut afficher des informations sélectionnées à partir de fichiers objets. Nous pouvons utiliser objdump -d pour appliquer le désassemblage à main.
# objdump -d main main: file format elf64-x86-64 Disassembly of section .text: 0000000000400078 <main>: 400078: 55 push %rbp 400079: 48 89 e5 mov %rsp,%rbp 40007c: b8 00 00 00 00 mov $0x0,%eax 400081: e8 0a 00 00 00 callq 400090 <func1> 400086: c9 leaveq 400087: 89 c3 mov %eax,%ebx 400089: b8 01 00 00 00 mov $0x1,%eax 40008e: cd 80 int $0x80 0000000000400090 <func1>: 400090: 55 push %rbp 400091: 48 89 e5 mov %rsp,%rbp 400094: b8 00 00 00 00 mov $0x0,%eax 400099: e8 02 00 00 00 callq 4000a0 <func2> 40009e: c9 leaveq 40009f: c3 retq 00000000004000a0 <func2>: 4000a0: 55 push %rbp 4000a1: 48 89 e5 mov %rsp,%rbp 4000a4: b8 01 00 00 00 mov $0x1,%eax 4000a9: c9 leaveq 4000aa: c3 retq
Lire la suite :Exemples de commandes Linux Objdump (Désassembler un fichier binaire)
7. taille - Taille de la section de liste et taille totale
size peut afficher les informations de taille des sections dans les fichiers objet.
# size main text data bss dec hex filename 51 0 0 51 33 main
8. strings - Afficher les caractères imprimables à partir d'un fichier
string peut afficher une séquence de caractères imprimable à partir de fichiers objets. Par défaut, il recherche uniquement dans la section .data. Avec -a switch, toutes les sections peuvent être recherchées.
# strings -a main .symtab .strtab .shstrtab .text main.c func1.c func2.c func1 _start __bss_start main func2 _edata _end
En savoir plus :Exemples de commandes de chaînes Linux (rechercher du texte dans des fichiers binaires UNIX)
9. strip - Supprimer les symboles du fichier objet
strip peut supprimer des symboles du fichier objet, ce qui peut réduire la taille du fichier et accélérer l'exécution.
Nous pouvons afficher la table des symboles par objdump. Le tableau des symboles montre l'entrée/décalage pour chaque fonction/étiquette.
# objdump -t main main: file format elf64-x86-64 SYMBOL TABLE: 0000000000400078 l d .text 0000000000000000 .text 0000000000000000 l df *ABS* 0000000000000000 main.c 0000000000000000 l df *ABS* 0000000000000000 func1.c 0000000000000000 l df *ABS* 0000000000000000 func2.c 0000000000400090 g F .text 0000000000000000 func1 0000000000400078 g .text 0000000000000000 _start 00000000006000ab g *ABS* 0000000000000000 __bss_start 0000000000400078 g F .text 0000000000000000 main 00000000004000a0 g F .text 0000000000000000 func2 00000000006000ab g *ABS* 0000000000000000 _edata 00000000006000b0 g *ABS* 0000000000000000 _end
Après strip (#strip main), la table des symboles sera supprimée.
#objdump -t main main: file format elf64-x86-64 SYMBOL TABLE: no symbols
En savoir plus :10 exemples de commande Linux Strip (réduire la taille du fichier exécutable/binaire)
10. c++filt - Commande Démangler
C++ prend en charge la surcharge qui peut laisser le même nom de fonction prendre différents types/nombres d'arguments.
Cela se fait en changeant le nom de la fonction en nom d'assembleur de bas niveau, appelé mangling. c++filt peut faire le démantèlement pour C++ et Java.
Ici, nous créons un nouvel exemple de code pour expliquer la manipulation.
Supposons que nous ayons deux types de func3 qui prennent différents types d'arguments d'entrée, le vide et l'int.
==> mangling.cpp <== int func3(int a) { return a; } int func3() { return 0; } int main() { return func3(1); }
Au format assembleur, ils ont des noms différents, _Z5func3v et _Z5func3i. Et, l'un d'entre eux sera appelé en fonction du type d'argument que nous avons passé à la func3 dans mangling.cpp. Dans cet exemple, _Z5func3i est appelé.
==> mangling.s <== .file "mangling.cpp" .text .globl _Z5func3i .type _Z5func3i, @function _Z5func3i: pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl -4(%rbp), %eax leave ret .globl _Z5func3v .type _Z5func3v, @function _Z5func3v: pushq %rbp movq %rsp, %rbp movl $0, %eax leave ret .globl main .type main, @function main: pushq %rbp movq %rsp, %rbp movl $1, %edi call _Z5func3i leave ret #grep func3.*: mangling.s _Z5func3i: _Z5func3v:
Nous pouvons passer ces noms de fonction d'assemblage à c++filt, et l'instruction de définition de fonction d'origine sera récupérée.
#grep func3.*: mangling.s | c++filt func3(int): func3():
objdump peut également effectuer le démontage avec différents styles :
-C, --demangle[=STYLE] Decode mangled/processed symbol names The STYLE, if specified, can be 'auto', 'gnu', 'lucid', 'arm', 'hp', 'edg', 'gnu-v3', 'java' or 'gnat'
11. addr2line - Convertir l'adresse en nom de fichier et en numéros
addr2line peut obtenir le fichier et le numéro de ligne de l'adresse donnée ou du décalage dans la section réallouée, en transmettant les informations de débogage.
Tout d'abord, nous devons compiler le fichier d'assemblage avec l'indicateur -g, afin que les informations de débogage soient ajoutées à l'objet. On peut voir ci-dessous qu'il existe maintenant des sections de débogage.
objdump -h mainD mainD: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000033 0000000000400078 0000000000400078 00000078 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .debug_aranges 00000090 0000000000000000 0000000000000000 000000b0 2**4 CONTENTS, READONLY, DEBUGGING 2 .debug_info 000000dd 0000000000000000 0000000000000000 00000140 2**0 CONTENTS, READONLY, DEBUGGING 3 .debug_abbrev 0000003c 0000000000000000 0000000000000000 0000021d 2**0 CONTENTS, READONLY, DEBUGGING 4 .debug_line 000000ba 0000000000000000 0000000000000000 00000259 2**0 CONTENTS, READONLY, DEBUGGING
D'après le résultat de désassemblage présenté dans la section 2.d objdump, nous pouvons voir que 0x400090 est l'entrée de func1, qui est identique au résultat donné par addr2line.
addr2line -e mainD 0x400090 /media/shared/TGS/func1.s:6
12. readelf - Afficher les informations sur le fichier ELF
readelf et elfedit ne peuvent fonctionner que sur le fichier elf.
readelf peut afficher des informations à partir du fichier elf.
Nous pouvons afficher des informations détaillées sur l'en-tête ELF.
#readelf -h main_full ELF 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 ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400078 Start of program headers: 64 (bytes into file) Start of section headers: 208 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 1 Size of section headers: 64 (bytes) Number of section headers: 5 Section header string table index: 2
Tout comme readelf, vous pouvez également utiliser elfedit qui peut mettre à jour la machine, le type de fichier et l'OS ABI dans l'en-tête elf. Veuillez noter que elfedit peut ne pas être inclus par défaut dans votre distribution.
En savoir plus :Principes de base du format de fichier objet ELF Linux (et de la structure d'en-tête ELF)