GNU Binary Utilities, obvykle nazývané binutils, je kolekce vývojových nástrojů, které zpracovávají soubory sestavení, soubory objektů a knihovny.
Nová generace programovacích jazyků, která přišla v posledních několika letech, skutečně maskuje funkčnost těchto utilit, protože k nim dochází na pozadí. Mnoho vývojářů tedy není těmto nástrojům vystaveno.
Ale pokud jste vývojář, který pracuje na platformě Linux / UNIX, je nezbytné porozumět různým příkazům, které jsou k dispozici jako součást vývojových nástrojů GNU.
Následuje 12 různých příkazů binutils, které jsou popsány v tomto tutoriálu.
- as – příkaz GNU Assembler
- ld – příkaz GNU Linker
- ar – příkaz GNU Archive
- nm – Seznam symbolů souborů objektů
- objcopy – Kopírování a překlad objektových souborů
- objdump – Zobrazení informací o souboru objektu
- velikost – Uveďte velikost sekce a celkovou velikost
- řetězce – zobrazení tisknutelných znaků ze souboru
- strip – Vyřazení symbolů ze souboru objektu
- c++filt – příkaz Demangle
- addr2line – Převést adresu na název souboru a čísla
- readelf – Zobrazení informací o souboru ELF
Tyto nástroje vám pomohou efektivně manipulovat s binárními, objektovými a knihovními soubory.
Z těchto 12 utilit, jak a ld jsou nejdůležitější, jsou výchozím backendem GNU Compiler Collection (gcc). GCC provádí pouze práci, při které se kompiluje z C/C++ do jazyka symbolických instrukcí, a jeho úkolem as a ld je výstup spustitelného binárního souboru.
Připravte si vzorový kód
Abychom pochopili, jak všechny tyto příkazy fungují, nejprve si připravíme ukázkový kód sestavení z kódu C pomocí gcc -S. Všechny zde uvedené experimenty se provádějí na x86 64bitovém linuxovém boxu.
Níže je kód C, který jako návratový kód používá pouze návratovou hodnotu externí funkce. Neexistuje žádný vstup/výstup, takže pokud chcete zkontrolovat, zda se program provedl podle očekávání, zkontrolujte návratový stav (echo $?). Máme tři funkce, hlavní, func1 a func2, a jeden soubor pro každou funkci.
// func1.c file: int func1() { return func2(); } // func2.c file: int func2() { return 1; } // main.c file: int main() { return func1(); }
GCC má podporu runtime knihovny C, takže hlavní funkce je považována za normální funkci. Pro zjednodušení dema nechceme při kompilaci a propojení těchto .s souborů zapojovat knihovnu C. Pro main.s jsou tedy provedeny dvě úpravy:
První změnou je přidání štítku _start pro fázi propojení.
_start label je vstupní bod aplikace, pokud není definován, bude při spuštění ld hlášeno varování jako níže.
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
Druhá modifikace spočívá v tom, že ret je nahrazeno voláním ukončení systému.
Měli bychom ručně zvýšit přerušení systému. %eax se používá k uchování návratové hodnoty funkce, ale volání ukončení systému ji podrží v %ebx. Takže jej zkopírujte z %eax do %ebx
Níže je uvedena znovu upravená verze kódu sestavení gcc.
soubor func1.s:
.file "func1.c" .text .globl func1 .type func1, @function func1: pushq %rbp movq %rsp, %rbp movl $0, %eax call func2 leave
soubor func2.s:
.file "func2.c" .text .globl func2 .type func2, @function func2: pushq %rbp movq %rsp, %rbp movl $1, %eax leave ret
soubor 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. jako – příkaz GNU Assembler
as bere soubor sestavení jako vstupní a výstupní soubor objektu. Objektový soubor je pouze interní formát, který bude použit jako vstup ld pro vytvoření konečného spustitelného souboru.
Spusťte příkaz as v souboru main.s, abyste získali soubor objektu main.o, jak je znázorněno níže.
as main.s -o main.o
soubor main.o (produkovaný „as main.s -o main.o“), můžeme získat níže uvedené informace.
main.o: ELF 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV), not stripped
Objektový soubor je ve formátu ELF, což je nejrozšířenější formát souboru pro linuxové distribuce.
Upozorňujeme, že příkaz „as“ má také podporu syntaxe pro předběžné zpracování, symbol, omezení, výraz, pseudo ops/direktivy a komentáře.
GNU Assembler může podporovat velkou sbírku počítačů, ale při kompilaci nebo křížové kompilaci je obvykle vybrán pouze jeden stroj/rodina architektury.
2. ld – Příkaz GNU Linker
Soubor objektu obvykle obsahuje odkaz na externí funkce v jiné knihovně/objektu a úkolem linkeru (ld) je zkombinovat všechny soubory objektů/knihoven potřebné pro finální binární soubor, přemístit sekce a vyřešit odkaz.
Skutečné chování ld je definováno ve skriptu linkeru, který popisuje rozložení paměti spustitelného souboru.
Pokud propojíme pouze main.o (ld main.o -o main), dojde k nedefinované chybě odkazu:
main.o: In function `_start': main.c:(.text+0xa): undefined reference to `func1'
Bez propojení všech tří souborů námitek (ld main.o func1.o func2.o -o main) nezískáme spustitelný soubor.
# file main main: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), statically linked, not stripped
Odlište se od objektového souboru, zde dostaneme staticky propojený spustitelný soubor.
as a ld pracuje na konkrétním cíli/architektuře. Ale existují některé nástroje, které pracují na BFD objektech definovaných v binutils.
Z několika posledních řádků výstupu objcopy -h můžeme získat cíle podpory.
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
Je třeba říci, že verilog, ihex nejsou podporovány skutečným OS, ale mohou být velmi užitečné při zpracování obsahu objektů v textovém formátu. Jsou široce používány v prostředí čipové simulace pro inicializaci paměti/ROM.
3. ar/ranlib – GNU Archive Command
ar lze použít ke generování a manipulaci se statickou knihovnou, což je archivní soubor složený z mnoha objektů.
Chování ar lze ovládat z argumentu příkazové řádky (unixový styl) nebo ze souboru skriptu. ranlib může přidat index symbolů do archivu, což může zrychlit rychlost linky a také usnadnit volání rutin. ar -s udělá to samé jako ranlib.
Pro můj test, s nebo bez -s, ar vždy vypíše index archivu.
Test1, ar bez -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
Úplné podrobnosti o příkazu ar naleznete v tomto:Příkaz ar pro Linux Příklady:Jak vytvářet, zobrazovat, extrahovat, upravovat archivní soubory C (*.a)
Test 2, ar s -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, spusťte znovu 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
Lze ukázat, že každý test dává stejný výsledek.
4. nm – Seznam symbolů souborů objektů
nm umí vypsat symboly z objektového souboru. Ve výše uvedené části jsme ukázali jeho použití.
Příkazy nm poskytují informace o symbolech používaných v objektovém souboru nebo spustitelném souboru.
Výchozí informace, které příkaz nm poskytuje, jsou následující:
- Virtuální adresa symbolu
- Znak, který znázorňuje typ symbolu. Pokud je znak napsán malým písmenem, pak je symbol místní, ale pokud je znak velkým, pak je symbol externí
- Název symbolu
$ 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
Přečtěte si více:10 praktických příkladů příkazů Linux nm
5. objcopy – Kopírování a překlad objektových souborů
objcopy může zkopírovat obsah jednoho objektového souboru do jiného objektového souboru a vstupní/výstupní objekt může mít jiný formát.
Jsou chvíle, kdy potřebujete přenést objektový soubor dostupný pro jeden druh platformy (jako ARM nebo x86) na jiný druh platformy.
Věci jsou relativně snadné, pokud je k dispozici zdrojový kód, protože jej lze znovu zkompilovat na cílové platformě.
Ale co když zdrojový kód není k dispozici a stále potřebujete přenést objektový soubor z typu platformy na jinou? Pokud používáte Linux, pak příkaz objcopy dělá přesně to, co potřebujete
Syntaxe tohoto příkazu je:
objcopy [options] infile [outfile]...
Přečtěte si více:Příklady příkazů Linux Objcopy pro kopírování a překlad objektových souborů
6. objdump – Zobrazení informací o souboru objektu
objdump umí zobrazit vybrané informace z objektových souborů. Můžeme použít objdump -d k použití disassemble na 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
Přečtěte si více:Příklady příkazů Linux Objdump (rozbalit binární soubor)
7. velikost – List Section Size and Toal Size
size může zobrazit informace o velikosti sekcí v objektových souborech.
# size main text data bss dec hex filename 51 0 0 51 33 main
8. strings – Zobrazení tisknutelných znaků ze souboru
string může zobrazit tisknutelnou sekvenci znaků ze souborů objektů. Ve výchozím nastavení vyhledává pouze v sekci .data. Pomocí přepínače -a lze prohledávat všechny sekce.
# strings -a main .symtab .strtab .shstrtab .text main.c func1.c func2.c func1 _start __bss_start main func2 _edata _end
Přečtěte si více:Příklady příkazů Linux Strings (Vyhledat text v binárních souborech UNIX)
9. strip – Discard Symbols from Object File
strip může odstranit symboly ze souboru objektu, což může snížit velikost souboru a urychlit provádění.
Tabulku symbolů můžeme zobrazit pomocí objdump. Tabulka symbolů ukazuje vstup/posun pro každou funkci/návěští.
# 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
Po stripu (#strip main) bude tabulka symbolů odstraněna.
#objdump -t main main: file format elf64-x86-64 SYMBOL TABLE: no symbols
Přečtěte si více:10 příkladů příkazů Linux Strip (zmenšit velikost spustitelného/binárního souboru)
10. c++filt – příkaz Demangle
C++ podporuje přetížení, které může nechat stejný název funkce přijmout různé druhy/počet argumentů.
To se provádí změnou názvu funkce na název nízkoúrovňového assembleru, který se nazývá mandlování. c++filt může provést demanglování pro C++ a Java.
Zde vytváříme nový ukázkový kód pro vysvětlení mandlování.
Předpokládejme, že máme dva typy func3, které používají různé druhy vstupních argumentů, void a int.
==> mangling.cpp <== int func3(int a) { return a; } int func3() { return 0; } int main() { return func3(1); }
Ve formátu sestavení mají různé názvy, _Z5func3v a _Z5func3i. A jeden z nich bude volán podle typu argumentu, který jsme předali funkci func3 v mangling.cpp. V tomto příkladu se nazývá _Z5func3i.
==> 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:
Tyto názvy funkcí assembleru můžeme předat c++filt a původní příkaz function define bude obnoven.
#grep func3.*: mangling.s | c++filt func3(int): func3():
objdump také umí demangle s různými styly:
-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 – Převést adresu na název souboru a čísla
addr2line může získat číslo souboru a řádku dané adresy nebo offset uvnitř přerozdělené sekce předáním informací o ladění.
Nejprve musíme zkompilovat soubor sestavení s parametrem -g, aby se do objektu přidaly ladicí informace. Níže lze ukázat, že nyní existují některé sekce ladění.
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
Z výsledku rozebrání uvedeného v sekci 2.d objdump můžeme vidět, že 0x400090 je záznam funkce func1, což je stejný jako výsledek daný addr2line.
addr2line -e mainD 0x400090 /media/shared/TGS/func1.s:6
12. readelf – Zobrazení informací o souboru ELF
readelf a elfedit mohou pracovat pouze se souborem elf.
readelf umí zobrazit informace ze souboru elf.
Můžeme zobrazit podrobné informace o hlavičce 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
Stejně jako readelf můžete také použít elfedit, který může aktualizovat stroj, typ souboru a OS ABI v hlavičce elf. Vezměte prosím na vědomí, že elfedit nemusí být ve výchozím nastavení součástí vaší distribuce.
Přečtěte si více:Linux ELF Object File Format (a ELF Header Structure) Základy