Die GNU Binary Utilities, normalerweise als Binutils bezeichnet, sind eine Sammlung von Entwicklungswerkzeugen, die Assembly-Dateien und Objektdateien verarbeiten , und Bibliotheken.
Die neue Generation von Programmiersprachen, die in den letzten Jahren auf den Markt kam, maskiert die Funktionalität dieser Dienstprogramme wirklich, da sie im Hintergrund ablaufen. Daher sind viele Entwickler diesen Tools nicht ausgesetzt.
Wenn Sie jedoch ein Entwickler sind, der auf einer Linux-/UNIX-Plattform arbeitet, ist es wichtig, die verschiedenen Befehle zu verstehen, die als Teil der GNU-Entwicklungstools verfügbar sind.
Im Folgenden sind die 12 verschiedenen binutils-Befehle aufgeführt, die in diesem Tutorial behandelt werden.
- als – GNU-Assembler-Befehl
- ld – GNU-Linker-Befehl
- ar – GNU-Archivierungsbefehl
- nm – Objektdateisymbole auflisten
- objcopy – Objektdateien kopieren und übersetzen
- objdump – Informationen zu Objektdateien anzeigen
- Größe – Abschnittsgröße und Gesamtgröße auflisten
- strings – Zeigt druckbare Zeichen aus einer Datei an
- strip – Symbole aus Objektdatei verwerfen
- c++filt – Demangle-Befehl
- addr2line – Adresse in Dateinamen und Zahlen umwandeln
- readelf – ELF-Dateiinformationen anzeigen
Diese Tools helfen Ihnen, Ihre Binär-, Objekt- und Bibliotheksdateien effektiv zu manipulieren.
Von diesen 12 Dienstprogrammen sind as und ld die wichtigsten, sie sind das Standard-Backend der GNU Compiler Collection (gcc). GCC führt nur die Arbeit aus, die von C/C++ in die Assemblersprache kompiliert, und die Aufgabe von as und ld, ausführbare Binärdateien auszugeben.
Vorbereiten eines Beispielcodes
Um zu verstehen, wie all diese Befehle funktionieren, bereiten wir zunächst einen Beispiel-Assemblercode aus C-Code vor, indem wir gcc -S verwenden. Alle hier gezeigten Experimente wurden auf einer x86 64-Bit-Linux-Box durchgeführt.
Unten ist der C-Code, der nur den Rückgabewert der externen Funktion als Rückgabecode verwendet. Es gibt keine Ein-/Ausgabe. Wenn Sie also überprüfen möchten, ob das Programm wie erwartet ausgeführt wurde, überprüfen Sie bitte den Rückgabestatus (echo $?). Wir haben drei Funktionen, main, func1 und func2, und eine Datei für jede Funktion.
// func1.c file: int func1() { return func2(); } // func2.c file: int func2() { return 1; } // main.c file: int main() { return func1(); }
GCC unterstützt C-Laufzeitbibliotheken, sodass die Hauptfunktion als normale Funktion behandelt wird. Zur Vereinfachung der Demo wollen wir beim Kompilieren und Linken dieser .s-Dateien keine C-Bibliothek einbeziehen. Für main.s werden also zwei Änderungen vorgenommen:
Die erste Änderung besteht darin, dass das Label _start für die Linkphase hinzugefügt wird.
_start label ist der Einstiegspunkt der App, wenn nicht definiert, wird eine Warnung wie unten gemeldet, wenn ld ausgeführt wird.
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
Die zweite Änderung besteht darin, dass ret durch den Systemexit-Aufruf ersetzt wird.
Wir sollten den System-Exit-Interrupt manuell auslösen. %eax wird verwendet, um den Rückgabewert der Funktion zu halten, aber der Aufruf des Systemausgangs hält ihn in %ebx. Kopieren Sie es also von %eax nach %ebx
Unten ist die überarbeitete Version des gcc-Assembler-Codes.
func1.s-Datei:
.file "func1.c" .text .globl func1 .type func1, @function func1: pushq %rbp movq %rsp, %rbp movl $0, %eax call func2 leave
func2.s-Datei:
.file "func2.c" .text .globl func2 .type func2, @function func2: pushq %rbp movq %rsp, %rbp movl $1, %eax leave ret
main.s-Datei:
.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 – GNU Assembler-Befehl
as nimmt eine Assembly-Datei als Eingabe und gibt eine Objektdatei aus. Die Objektdatei ist nur ein internes Format, das als Eingabe von ld für die Erstellung der endgültigen ausführbaren Datei verwendet wird.
Führen Sie den Befehl as für die Datei main.s aus, um die Objektdatei main.o wie unten gezeigt zu erhalten.
as main.s -o main.o
Datei main.o (erzeugt von „as main.s -o main.o“), können wir die folgenden Informationen abrufen.
main.o: ELF 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV), not stripped
Die Objektdatei ist im ELF-Format, dem am weitesten verbreiteten Dateiformat für Linux-Distributionen.
Bitte beachten Sie, dass der Befehl „as“ auch Syntaxunterstützung für Vorverarbeitung, Symbol, Einschränkung, Ausdruck, Pseudo-Ops/Direktiven und Kommentare bietet.
GNU Assembler kann eine riesige Sammlung von Maschinen unterstützen, aber normalerweise wird beim Kompilieren oder Kreuzkompilieren nur eine Maschinen-/Architekturfamilie ausgewählt.
2. ld – GNU-Linker-Befehl
Die Objektdatei enthält normalerweise Verweise auf externe Funktionen in verschiedenen Bibliotheken/Objekten, und die Aufgabe des Linkers (ld) besteht darin, alle Objekt-/Bibliotheksdateien zu kombinieren, die für die endgültige Binärdatei benötigt werden, Abschnitte zu verschieben und die Referenz aufzulösen.
Das tatsächliche Verhalten von ld wird im Linker-Skript definiert, das das Speicherlayout der ausführbaren Datei beschreibt.
Wenn wir nur main.o verlinken (ld main.o -o main), wird es einen undefinierten Referenzfehler geben:
main.o: In function `_start': main.c:(.text+0xa): undefined reference to `func1'
Wir erhalten keine ausführbare Datei, ohne alle drei Widerspruchsdateien zu verknüpfen (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
Anders bei der Objektdatei, hier bekommen wir eine statisch gelinkte ausführbare Datei.
as und ld arbeiten an bestimmten Zielen/Architekturen. Aber es gibt einige Tools, die mit BFD-Objekten arbeiten, die in binutils definiert sind.
Aus den letzten paar Zeilen der Ausgabe von objcopy -h können wir die Unterstützungsziele erhalten.
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
Ich muss sagen, dass verilog, ihex nicht von echten Betriebssystemen unterstützt werden, aber es kann sehr nützlich sein, den Inhalt von Objekten im Textformat zu verarbeiten. Sie werden häufig in Chip-Simulationsumgebungen zur Speicher-/ROM-Initialisierung verwendet.
3. ar/ranlib – GNU-Archivierungsbefehl
ar kann verwendet werden, um eine statische Bibliothek zu generieren und zu manipulieren, die eine Archivdatei ist, die aus vielen Objekten besteht.
Das Verhalten von ar kann über ein Befehlszeilenargument (im Unix-Stil) oder eine Skriptdatei gesteuert werden. ranlib kann einem Archiv einen Index von Symbolen hinzufügen, was die Verbindungsgeschwindigkeit beschleunigen und auch den Aufruf von Routinen erleichtern kann. ar -s macht dasselbe wie ranlib.
Für meinen Test gibt ar mit oder ohne -s immer den Archivindex aus.
Test1, ar ohne -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
Ausführliche Informationen zum Befehl ar finden Sie hier:Beispiele für den Befehl ar unter Linux:Erstellen, Anzeigen, Extrahieren und Ändern von C-Archivdateien (*.a)
Test 2, ar mit -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, ranlib erneut ausführen.
# 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
Es kann gezeigt werden, dass jeder Test das gleiche Ergebnis ausgibt.
4. nm – Objektdateisymbole auflisten
nm kann Symbole aus Objektdateien auflisten. Wir haben die Verwendung davon im obigen Abschnitt gezeigt.
Die nm-Befehle liefern Informationen über die Symbole, die in einer Objektdatei oder einer ausführbaren Datei verwendet werden.
Die Standardinformationen, die der nm-Befehl bereitstellt, sind die folgenden:
- Virtuelle Adresse des Symbols
- Ein Zeichen, das den Symboltyp darstellt. Wenn das Zeichen klein geschrieben ist, ist das Symbol lokal, aber wenn das Zeichen groß geschrieben ist, ist das Symbol extern
- Name des Symbols
$ 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
Weiterlesen:10 praktische Linux-nm-Befehlsbeispiele
5. objcopy – Objektdateien kopieren und übersetzen
objcopy kann den Inhalt einer Objektdatei in eine andere Objektdatei kopieren, und Eingabe-/Ausgabeobjekte können ein anderes Format haben.
Es gibt Zeiten, in denen Sie eine Objektdatei, die für eine Art von Plattform (wie ARM oder x86) verfügbar ist, auf eine andere Art von Plattform portieren müssen.
Wenn der Quellcode verfügbar ist, ist es relativ einfach, da er auf der Zielplattform neu kompiliert werden kann.
Was aber, wenn der Quellcode nicht verfügbar ist und Sie dennoch eine Objektdatei von einem Plattformtyp auf einen anderen portieren müssen? Nun, wenn Sie Linux verwenden, dann macht der Befehl objcopy genau das Erforderliche
Die Syntax dieses Befehls lautet:
objcopy [options] infile [outfile]...
Lesen Sie mehr:Beispiele für Objcopy-Befehle unter Linux zum Kopieren und Übersetzen von Objektdateien
6. objdump – Informationen zur Objektdatei anzeigen
objdump kann ausgewählte Informationen aus Objektdateien anzeigen. Wir können objdump -d verwenden, um die Disassemblierung auf main anzuwenden.
# 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
Lesen Sie mehr:Beispiele für Objdump-Befehle unter Linux (Disassemblieren einer Binärdatei)
7. Größe – Abschnittsgröße und Gesamtgröße auflisten
size kann die Größeninformationen von Abschnitten in Objektdateien anzeigen.
# size main text data bss dec hex filename 51 0 0 51 33 main
8. strings – Zeigt druckbare Zeichen aus einer Datei an
string kann druckbare Zeichenfolgen aus Objektdateien anzeigen. Standardmäßig wird nur im Abschnitt .data gesucht. Mit -a Schalter können alle Abschnitte durchsucht werden.
# strings -a main .symtab .strtab .shstrtab .text main.c func1.c func2.c func1 _start __bss_start main func2 _edata _end
Lesen Sie mehr:Linux-Strings-Befehlsbeispiele (Text in UNIX-Binärdateien suchen)
9. strip – Symbole aus Objektdatei verwerfen
Strip kann Symbole aus Objektdateien entfernen, was die Dateigröße reduzieren und die Ausführung beschleunigen kann.
Wir können die Symboltabelle mit objdump anzeigen. Die Symboltabelle zeigt den Eintrag/Offset für jede Funktion/Label.
# 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
Nach strip (#strip main) wird die Symboltabelle entfernt.
#objdump -t main main: file format elf64-x86-64 SYMBOL TABLE: no symbols
Lesen Sie mehr:10 Beispiele für Linux-Strip-Befehle (Größe der ausführbaren Datei/Binärdatei reduzieren)
10. c++filt – Demangle-Befehl
C++ unterstützt das Überladen, bei dem derselbe Funktionsname unterschiedliche Arten/Anzahl von Argumenten annehmen kann.
Dies geschieht durch Ändern des Funktionsnamens in einen Assemblernamen auf niedriger Ebene, was als Mangeln bezeichnet wird. c++filt kann das Entwirren für C++ und Java übernehmen.
Hier erstellen wir einen neuen Beispielcode zur Erklärung des Mangels.
Angenommen, wir haben zwei Arten von func3, die unterschiedliche Arten von Eingabeargumenten annehmen, void und int.
==> mangling.cpp <== int func3(int a) { return a; } int func3() { return 0; } int main() { return func3(1); }
Im Assembly-Format haben sie unterschiedliche Namen, _Z5func3v und _Z5func3i. Und einer davon wird gemäß dem Argumenttyp aufgerufen, den wir in mangling.cpp an func3 übergeben haben. In diesem Beispiel wird _Z5func3i aufgerufen.
==> 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:
Wir können diese Assembly-Funktionsnamen an c++filt übergeben, und die ursprüngliche Funktionsdefinitionsanweisung wird wiederhergestellt.
#grep func3.*: mangling.s | c++filt func3(int): func3():
objdump kann das Demangle auch mit verschiedenen Stilen durchführen:
-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 – Adresse in Dateinamen und Zahlen umwandeln
addr2line kann die Datei- und Zeilennummer der angegebenen Adresse oder des Offsets innerhalb des neu zugewiesenen Abschnitts erhalten, indem die Debug-Informationen weitergegeben werden.
Zuerst müssen wir die Assembly-Datei mit dem Flag -g kompilieren, damit Debug-Informationen zum Objekt hinzugefügt werden. Es kann von unten gezeigt werden, dass es jetzt einige Debug-Abschnitte gibt.
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
Aus dem Disassemblierungsergebnis in Abschnitt 2.d objdump können wir ersehen, dass 0x400090 der Eintrag von func1 ist, was dasselbe ist wie das Ergebnis von addr2line.
addr2line -e mainD 0x400090 /media/shared/TGS/func1.s:6
12. readelf – ELF-Dateiinformationen anzeigen
readelf und elfedit können nur mit elf Dateien arbeiten.
readelf kann Informationen aus der Elf-Datei anzeigen.
Wir können detaillierte Informationen des ELF-Headers anzeigen.
#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
Genau wie readelf können Sie auch elfedit verwenden, das Maschine, Dateityp und Betriebssystem-ABI im Elf-Header aktualisieren kann. Bitte beachten Sie, dass elfedit möglicherweise nicht standardmäßig in Ihrer Distribution enthalten ist.
Lesen Sie mehr:Grundlagen des Linux-ELF-Objektdateiformats (und der ELF-Header-Struktur)