Představte si, že nemáte přístup ke zdrojovému kódu softwaru, ale přesto můžete porozumět tomu, jak je software implementován, najít v něm zranitelná místa a – ještě lépe – opravit chyby. To vše v binární podobě. Zní to jako superschopnosti, že?
I vy můžete mít takové superschopnosti a GNU binární nástroje (binutils) jsou dobrým výchozím bodem. GNU binutils jsou sbírkou binárních nástrojů, které jsou standardně instalovány ve všech distribucích Linuxu.
Binární analýza je nejvíce podceňovanou dovedností v počítačovém průmyslu. Většinou jej využívají analytici malwaru, reverzní inženýři a lidé
pracující na softwaru nízké úrovně.
Tento článek zkoumá některé nástroje dostupné prostřednictvím binutils. Používám RHEL, ale tyto příklady by měly běžet na jakékoli distribuci Linuxu.
[~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.6 (Maipo)
[~]#
[~]# uname -r
3.10.0-957.el7.x86_64
[~]#
Všimněte si, že některé příkazy balení (jako rpm ) nemusí být k dispozici v distribucích založených na Debianu, takže použijte ekvivalentní dpkg příkaz tam, kde je to možné.
Vývoj softwaru 101
Ve světě open source se mnoho z nás zaměřuje na software ve zdrojové podobě; když je zdrojový kód softwaru snadno dostupný, je snadné jednoduše získat kopii zdrojového kódu, otevřít svůj oblíbený editor, dát si šálek kávy a začít prozkoumávat.
Ale zdrojový kód není to, co se provádí na CPU; jsou to instrukce binárního nebo strojového jazyka, které jsou prováděny na CPU. Binární nebo spustitelný soubor je to, co získáte při kompilaci zdrojového kódu. Lidé zkušení v ladění často získají výhodu pochopením tohoto rozdílu.
Kompilace 101
Než se pustíte do samotného balíčku binutils, je dobré porozumět základům kompilace.
Kompilace je proces převodu programu z jeho zdrojového nebo textového formátu v určitém programovacím jazyce (C/C++) do strojového kódu.
Strojový kód je posloupnost 1 a 0, kterým CPU (nebo hardware obecně) rozumí, a proto je může CPU spustit nebo spustit. Tento strojový kód je uložen do souboru ve specifickém formátu, který je často označován jako spustitelný soubor nebo binární soubor. V Linuxu (a BSD, při použití Linux Binary Compatibility) se to nazývá ELF (Executable and Linkable Format).
Proces kompilace prochází řadou komplikovaných kroků, než představí spustitelný nebo binární soubor pro daný zdrojový soubor. Zvažte tento zdrojový program (kód C) jako příklad. Otevřete svůj oblíbený editor a napište tento program:
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
Krok 1:Předzpracování pomocí cpp
Preprocesor C (cpp ) se používá k rozšíření všech maker a zahrnutí hlavičkových souborů. V tomto příkladu je soubor záhlaví stdio.h bude součástí zdrojového kódu. stdio.h je hlavičkový soubor, který obsahuje informace o printf funkce, která se v programu používá. cpp běží na zdrojovém kódu a výsledné instrukce jsou uloženy v souboru s názvem hello.i . Otevřete soubor pomocí textového editoru, abyste viděli jeho obsah. Zdrojový kód pro tisk ahoj světe je na konci souboru.
[testdir]# cat hello.c
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
[testdir]#
[testdir]# cpp hello.c > hello.i
[testdir]#
[testdir]# ls -lrt
total 24
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
[testdir]#
Krok 2:Kompilace s gcc
Toto je fáze, ve které je předzpracovaný zdrojový kód z kroku 1 převeden do instrukcí v jazyce symbolických instrukcí bez vytvoření objektového souboru. Používá GNU Compiler Collection (gcc ). Po spuštění gcc příkaz s -S možnost na ahoj.i soubor, vytvoří nový soubor s názvem hello.s . Tento soubor obsahuje instrukce v assembleru pro program C.
Obsah můžete zobrazit pomocí libovolného editoru nebo kočky příkaz.
[testdir]#
[testdir]# gcc -Wall -S hello.i
[testdir]#
[testdir]# ls -l
total 28
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
[testdir]# cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "Hello World"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)"
.section .note.GNU-stack,"",@progbits
[testdir]#
Krok 3:Sestavení s as
Účelem assembleru je převést instrukce jazyka symbolických instrukcí na kód strojového jazyka a vygenerovat objektový soubor, který má .o rozšíření. Použijte GNU assembler jako který je standardně dostupný na všech platformách Linux.
[testdir]# as hello.s -o hello.o
[testdir]#
[testdir]# ls -l
total 32
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
[testdir]#
Nyní máte svůj první soubor ve formátu ELF; zatím jej však nemůžete provést. Později uvidíte rozdíl mezi objektovým souborem a spustitelný soubor .
[testdir]# file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Krok 4:Propojení s ld
Toto je poslední fáze kompilace, kdy jsou soubory objektů propojeny a vytvářejí spustitelný soubor. Spustitelný soubor obvykle vyžaduje externí funkce, které často pocházejí ze systémových knihoven (libc ).
Linker můžete vyvolat přímo pomocí ld příkaz; tento příkaz je však poněkud komplikovaný. Místo toho můžete použít gcc kompilátor s -v (úplný) příznak, abyste pochopili, jak k propojení dochází. (Pomocí ld příkaz pro propojení je cvičení, které můžete prozkoumat.)
[testdir]# gcc -v hello.o
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man [...] --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:[...]:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2 --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu [...]/../../../../lib64/crtn.o
[testdir]#
Po spuštění tohoto příkazu byste měli vidět spustitelný soubor s názvem a.out :
[testdir]# ls -l
total 44
-rwxr-xr-x. 1 root root 8440 Sep 13 03:45 a.out
-rw-r--r--. 1 root root 76 Sep 13 03:20 hello.c
-rw-r--r--. 1 root root 16877 Sep 13 03:22 hello.i
-rw-r--r--. 1 root root 1496 Sep 13 03:39 hello.o
-rw-r--r--. 1 root root 448 Sep 13 03:25 hello.s
Spuštění souboru příkaz na a.out ukazuje, že se skutečně jedná o spustitelný soubor ELF:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=48e4c11901d54d4bf1b6e3826baf18215e4255e5, not stripped
Spusťte spustitelný soubor a zjistěte, zda funguje podle pokynů zdrojového kódu:
[testdir]# ./a.out
Hello World
To ano! V zákulisí se toho tolik děje, jen aby bylo možné vytisknout Ahoj světe na obrazovce. Představte si, co se děje ve složitějších programech.
Prozkoumejte nástroje binutils
Toto cvičení poskytlo dobrý základ pro použití nástrojů, které jsou v balíčku binutils. Můj systém má binutils verze 2.27-34; můžete mít jinou verzi v závislosti na vaší distribuci Linuxu.
[~]# rpm -qa | grep binutils
binutils-2.27-34.base.el7.x86_64
Následující nástroje jsou dostupné v balíčcích binutils:
[~]# rpm -ql binutils-2.27-34.base.el7.x86_64 | grep bin/
/usr/bin/addr2line
/usr/bin/ar
/usr/bin/as
/usr/bin/c++filt
/usr/bin/dwp
/usr/bin/elfedit
/usr/bin/gprof
/usr/bin/ld
/usr/bin/ld.bfd
/usr/bin/ld.gold
/usr/bin/nm
/usr/bin/objcopy
/usr/bin/objdump
/usr/bin/ranlib
/usr/bin/readelf
/usr/bin/size
/usr/bin/strings
/usr/bin/strip
Výše uvedené kompilační cvičení již prozkoumalo dva z těchto nástrojů:as příkaz byl použit jako assembler a ld příkaz byl použit jako linker. Čtěte dále a dozvíte se o dalších sedmi nástrojích balíčků GNU binutils zvýrazněných výše tučně.
readelf:Zobrazí informace o souborech ELF
Ve výše uvedeném cvičení jsou uvedeny pojmy objektový soubor a spustitelný soubor . Pomocí souborů z daného cvičení zadejte readelf pomocí -h (header) možnost vypsat záhlaví ELF souborů na vaši obrazovku. Všimněte si, že soubor objektu končící na .o přípona je zobrazena jako Typ:REL (přemístitelný soubor) :
[testdir]# readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 [...]
[...]
Type: REL (Relocatable file)
[...]
Pokud se pokusíte spustit tento soubor, zobrazí se chyba, že jej nelze spustit. To jednoduše znamená, že ještě nemá informace, které jsou nutné pro jeho spuštění na CPU.
Nezapomeňte, že musíte přidat x nebo spustitelný bit nejprve na soubor objektu pomocí chmod jinak dostanete Oprávnění odepřeno chyba.
[testdir]# ./hello.o
bash: ./hello.o: Permission denied
[testdir]# chmod +x ./hello.o
[testdir]#
[testdir]# ./hello.o
bash: ./hello.o: cannot execute binary file
Pokud zkusíte stejný příkaz na a.out soubor, vidíte, že jeho typ je EXEC (spustitelný soubor) .
[testdir]# readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
[...]
Type: EXEC (Executable file)
Jak bylo vidět dříve, tento soubor může být přímo spuštěn CPU:
[testdir]# ./a.out
Hello World
readelf příkaz poskytuje velké množství informací o binárním systému. Zde vám říká, že je v ELF64bitovém formátu, což znamená, že jej lze spustit pouze na 64bitovém CPU a nebude fungovat na 32bitovém CPU. Také vám říká, že je určen k provádění na architektuře X86-64 (Intel/AMD). Vstupní bod do binárního souboru je na adrese 0x400430, což je pouze adresa hlavního funkce ve zdrojovém programu C.
Vyzkoušejte readelf příkaz na jiných systémových binárních souborech, které znáte, jako je ls . Pamatujte, že váš výstup (zejména Typ: ) se mohou lišit na systémech RHEL 8 nebo Fedora 30 a vyšších kvůli změnám ve spustitelných souborech nezávislých na pozici (PIE) provedeným z bezpečnostních důvodů.
[testdir]# readelf -h /bin/ls
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)
Přečtěte si, jaké systémové knihovny ls příkaz je závislý na použití ldd příkaz takto:
[testdir]# ldd /bin/ls
linux-vdso.so.1 => (0x00007ffd7d746000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f060daca000)
libcap.so.2 => /lib64/libcap.so.2 (0x00007f060d8c5000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007f060d6bc000)
libc.so.6 => /lib64/libc.so.6 (0x00007f060d2ef000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f060d08d000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007f060ce89000)
/lib64/ld-linux-x86-64.so.2 (0x00007f060dcf1000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007f060cc84000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f060ca68000)
Spusťte readelf na libc knihovna, abyste viděli, o jaký druh souboru se jedná. Jak zdůrazňuje, jedná se o DYN (soubor sdílených objektů) , což znamená, že jej nelze přímo spustit samostatně; musí být používán spustitelným souborem, který interně používá jakékoli funkce zpřístupněné knihovnou.
[testdir]# readelf -h /lib64/libc.so.6
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: DYN (Shared object file)
velikost:Uvádí velikosti sekcí a celkovou velikost
velikost příkaz funguje pouze na objektových a spustitelných souborech, takže pokud jej zkusíte spustit na jednoduchém souboru ASCII, vyvolá chybu, že Formát souboru nebyl rozpoznán .
[testdir]# echo "test" > file1
[testdir]# cat file1
test
[testdir]# file file1
file1: ASCII text
[testdir]# size file1
size: file1: File format not recognized
Nyní spusťte velikost v souboru objektu a spustitelný soubor z výše uvedeného cvičení. Všimněte si, že spustitelný soubor (a.out ) obsahuje podstatně více informací než soubor objektu (ahoj.o ), na základě výstupu příkazu size:
[testdir]# size hello.o
text data bss dec hex filename
89 0 0 89 59 hello.o
[testdir]# size a.out
text data bss dec hex filename
1194 540 4 1738 6ca a.out
Ale co ten text , data a bss sekce znamenají?
text sekce odkazují na kódovou sekci binárního souboru, která obsahuje všechny spustitelné instrukce. data sekce jsou místa, kde jsou všechna inicializovaná data, a bss je místo, kde jsou uložena všechna neinicializovaná data.
Porovnejte velikost s některými dalšími dostupnými systémovými binárními soubory.
Pro ls příkaz:
[testdir]# size /bin/ls
text data bss dec hex filename
103119 4768 3360 111247 1b28f /bin/ls
Můžete vidět, že gcc a gdb jsou mnohem větší programy než ls pouhým pohledem na výstup velikosti příkaz:
[testdir]# size /bin/gcc
text data bss dec hex filename
755549 8464 81856 845869 ce82d /bin/gcc
[testdir]# size /bin/gdb
text data bss dec hex filename
6650433 90842 152280 6893555 692ff3 /bin/gdb
řetězce:Vytiskne řetězce tisknutelných znaků v souborech
Často je užitečné přidat -d příznak do řetězců příkaz k zobrazení pouze tisknutelných znaků z datové části.
ahoj.o je objektový soubor, který obsahuje pokyny k vytištění textu Hello World . Tedy jediný výstup z řetězců příkaz je Ahoj světe .
[testdir]# strings -d hello.o
Hello World
Spuštěné řetězce na a.out (spustitelný soubor) na druhé straně zobrazuje další informace, které byly zahrnuty do binárního souboru během fáze propojení:
[testdir]# strings -d a.out
/lib64/ld-linux-x86-64.so.2
!^BU
libc.so.6
puts
__libc_start_main
__gmon_start__
GLIBC_2.2.5
UH-0
UH-0
=(
[]A\A]A^A_
Hello World
;*3$"
Připomeňme, že kompilace je proces převodu instrukcí zdrojového kódu do strojového kódu. Strojový kód se skládá pouze z 1 a 0 a je pro člověka obtížně čitelný. Proto pomáhá prezentovat strojový kód jako instrukce v assembleru. Jak vypadají jazyky symbolických instrukcí? Pamatujte, že jazyk symbolických instrukcí je specifický pro architekturu; protože používám architekturu Intel nebo x86-64, pokyny se budou lišit, pokud ke kompilaci stejných programů používáte architekturu ARM.
objdump:Zobrazí informace ze souborů objektů
Další nástroj binutils, který dokáže vypsat instrukce strojového jazyka z binárního kódu, se nazývá objdump .
Použijte -d volba, která rozebere všechny montážní pokyny z binárního souboru.
[testdir]# objdump -d hello.o
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov $0x0,%edi
9: e8 00 00 00 00 callq e
e: b8 00 00 00 00 mov $0x0,%eax
13: 5d pop %rbp
14: c3 retq
Tento výstup se na první pohled zdá zastrašující, ale než se pohnete dál, věnujte mu chvíli pochopení. Připomeňme, že .text sekce obsahuje všechny instrukce strojového kódu. Montážní návod je uveden ve čtvrtém sloupci (tj. zatlačte , mov , callq , pop , retq ). Tyto instrukce působí na registry, což jsou paměťová místa zabudovaná do CPU. Registry v tomto příkladu jsou rbp , rsp , edi , eax , atd. a každý registr má zvláštní význam.
Nyní spusťte objdump ve spustitelném souboru (a.out ) a uvidíte, co získáte. Výstup objdump na spustitelný soubor může být velký, takže jsem ho zúžil na hlavní pomocí funkce grep příkaz:
[testdir]# objdump -d a.out | grep -A 9 main\>
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
400521: bf d0 05 40 00 mov $0x4005d0,%edi
400526: e8 d5 fe ff ff callq 400400
40052b: b8 00 00 00 00 mov $0x0,%eax
400530: 5d pop %rbp
400531: c3 retq
Všimněte si, že pokyny jsou podobné jako u objektového souboru hello.o , ale obsahují některé další informace:
- Objektový soubor hello.o má následující instrukci:
callq e
- Spustitelný soubor a.out sestává z následující instrukce s adresou a funkcí:
callq 400400 <puts@plt>
Výše uvedená instrukce sestavení volá puts funkce. Nezapomeňte, že jste použili printf funkce ve zdrojovém kódu. Kompilátor vložil volání puts funkce knihovny pro výstup Ahoj světe na obrazovku.
Podívejte se na pokyny pro řádek nad umístěním :
- Objektový soubor hello.o má pokyn mov :
mov $0x0,%edi
- Pokyn mov pro spustitelný soubor a.out má skutečnou adresu ($0x4005d0 ) namísto $0x0 :
mov $0x4005d0,%edi
Tato instrukce přesune vše, co je na adrese $0x4005d0 v binárním souboru do registru s názvem edi .
Co jiného by mohlo být v obsahu toho paměťového místa? Ano, uhodli jste správně:není to nic jiného než text Ahoj, světe . Jak si můžete být jisti?
readelf umožňuje vypsat libovolnou část binárního souboru (a.out ) na obrazovku. Následující text jej požádá o výpis .rodata , což jsou data pouze pro čtení, na obrazovku:
[testdir]# readelf -x .rodata a.out
Hex dump of section '.rodata':
0x004005c0 01000200 00000000 00000000 00000000 ....
0x004005d0 48656c6c 6f20576f 726c6400 Hello World.
Můžete vidět text Ahoj světe na pravé straně a jeho adresa v binární podobě na levé straně. Odpovídá adrese, kterou jste viděli v mov? návod výše? Ano, platí.
strip:Zahodí symboly ze souborů objektů
Tento příkaz se často používá ke zmenšení velikosti binárního souboru před jeho odesláním zákazníkům.
Pamatujte, že to brání procesu ladění, protože důležité informace jsou odstraněny z binárního souboru; nicméně binární soubor funguje bezchybně.
Spusťte jej na a.out spustitelný a všimněte si, co se stane. Nejprve se ujistěte, že binární soubor není odstraněn spuštěním následujícího příkazu:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] not stripped
Před spuštěním stripu také sledujte počet bajtů původně v binárním souboru příkaz:
[testdir]# du -b a.out
8440 a.out
Nyní spusťte proužek příkaz ve spustitelném souboru a pomocí souboru zajistěte, aby fungoval příkaz:
[testdir]# strip a.out
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] stripped
Po odstranění binárního souboru se jeho velikost snížila na 6296 z předchozího 8440 bajtů pro tento malý program. Není divu, že s takovými úsporami za malý program jsou velké programy často odstraněny.
[testdir]# du -b a.out
6296 a.out
addr2line:Převede adresy na názvy souborů a čísla řádků
řádek addr2 nástroj jednoduše vyhledá adresy v binárním souboru a porovná je s řádky v programu zdrojového kódu C. Docela v pohodě, ne?
Napište pro to jiný testovací program; pouze tentokrát se ujistěte, že jste jej zkompilovali pomocí -g příznak pro gcc , který přidává další ladicí informace pro binární soubor a také pomáhá zahrnutím čísel řádků (uvedených ve zdrojovém kódu zde):
[testdir]# cat -n atest.c
1 #include <stdio.h>
2
3 int globalvar = 100;
4
5 int function1(void)
6 {
7 printf("Within function1\n");
8 return 0;
9 }
10
11 int function2(void)
12 {
13 printf("Within function2\n");
14 return 0;
15 }
16
17 int main(void)
18 {
19 function1();
20 function2();
21 printf("Within main\n");
22 return 0;
23 }
Kompilace pomocí -g označit a provést jej. Žádné překvapení:
[testdir]# gcc -g atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
Nyní použijte objdump k identifikaci adres paměti, kde začínají vaše funkce. Můžete použít grep příkaz k odfiltrování konkrétních řádků, které chcete. Níže jsou zvýrazněny adresy vašich funkcí:
[testdir]# objdump -d a.out | grep -A 2 -E 'main>:|function1>:|function2>:'
000000000040051d :
40051d: 55 push %rbp
40051e: 48 89 e5 mov %rsp,%rbp
--
0000000000400532 :
400532: 55 push %rbp
400533: 48 89 e5 mov %rsp,%rbp
--
0000000000400547 :
400547: 55 push %rbp
400548: 48 89 e5 mov %rsp,%rbp
Nyní použijte addr2line nástroj k mapování těchto adres z binárního kódu tak, aby odpovídaly adresám ve zdrojovém kódu C:
[testdir]# addr2line -e a.out 40051d
/tmp/testdir/atest.c:6
[testdir]#
[testdir]# addr2line -e a.out 400532
/tmp/testdir/atest.c:12
[testdir]#
[testdir]# addr2line -e a.out 400547
/tmp/testdir/atest.c:18
Říká, že 40051d začíná na řádku číslo 6 ve zdrojovém souboru atest.c , což je řádek, kde začíná počáteční složená závorka ({ ) pro funkci1 začíná. Porovnejte výstup pro function2 a hlavní .
nm:Vypisuje symboly ze souborů objektů
Pomocí výše uvedeného programu C otestujte nm nářadí. Zkompilujte jej rychle pomocí gcc a spustit jej.
[testdir]# gcc atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
Nyní spusťte nm a grep pro informace o vašich funkcích a proměnných:
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
000000000040051d T function1
0000000000400532 T function2
000000000060102c D globalvar
U __libc_start_main@@GLIBC_2.2.5
0000000000400547 T main
Vidíte, že funkce jsou označeny T , což znamená symboly v textu sekce, zatímco proměnné jsou označeny jako D , což znamená symboly v inicializovaných datech sekce.
Představte si, jak užitečné bude spouštět tento příkaz na binárních souborech, kde nemáte zdrojový kód? To vám umožní nahlédnout dovnitř a pochopit, které funkce a proměnné se používají. Pokud ovšem nebyly binární soubory odstraněny, v takovém případě neobsahují žádné symboly, a proto nm příkaz by nebyl příliš užitečný, jak můžete vidět zde:
[testdir]# strip a.out
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
nm: a.out: no symbols
Závěr
The GNU binutils tools offer many options for anyone interested in analyzing binaries, and this has only been a glimpse of what they can do for you. Read the man pages for each tool to understand more about them and how to use them.