Immagina di non avere accesso al codice sorgente di un software ma di essere comunque in grado di capire come viene implementato il software, trovare le vulnerabilità in esso e, meglio ancora, correggere i bug. Tutto questo in forma binaria. Sembra avere dei superpoteri, vero?
Anche tu puoi possedere tali superpoteri e le utilità binarie GNU (binutils) sono un buon punto di partenza. Le binutils GNU sono una raccolta di strumenti binari installati per impostazione predefinita su tutte le distribuzioni Linux.
L'analisi binaria è l'abilità più sottovalutata nell'industria dei computer. Viene utilizzato principalmente da analisti di malware, reverse engineer e persone
che lavorano su software di basso livello.
Questo articolo esplora alcuni degli strumenti disponibili tramite binutils. Sto usando RHEL ma questi esempi dovrebbero essere eseguiti su qualsiasi distribuzione Linux.
[~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.6 (Maipo)
[~]#
[~]# uname -r
3.10.0-957.el7.x86_64
[~]#
Nota che alcuni comandi di packaging (come rpm ) potrebbe non essere disponibile su distribuzioni basate su Debian, quindi usa l'equivalente dpkg comando ove applicabile.
Sviluppo software 101
Nel mondo open source, molti di noi si concentrano sul software in forma sorgente; quando il codice sorgente del software è prontamente disponibile, è facile ottenere semplicemente una copia del codice sorgente, aprire il tuo editor preferito, prendere una tazza di caffè e iniziare a esplorare.
Ma il codice sorgente non è ciò che viene eseguito sulla CPU; sono le istruzioni binarie o in linguaggio macchina che vengono eseguite sulla CPU. Il file binario o eseguibile è ciò che ottieni quando compili il codice sorgente. Le persone esperte nel debug spesso ottengono il loro vantaggio comprendendo questa differenza.
Compilazione 101
Prima di approfondire il pacchetto binutils stesso, è bene comprendere le basi della compilazione.
La compilazione è il processo di conversione di un programma dalla sua forma di testo o sorgente in un determinato linguaggio di programmazione (C/C++) in codice macchina.
Il codice macchina è la sequenza di 1 e 0 che viene compresa da una CPU (o dall'hardware in generale) e quindi può essere eseguita o eseguita dalla CPU. Questo codice macchina viene salvato in un file in un formato specifico che viene spesso definito file eseguibile o file binario. Su Linux (e BSD, quando si utilizza Linux Binary Compatibility), questo si chiama ELF (Executable and Linkable Format).
Il processo di compilazione passa attraverso una serie di passaggi complicati prima di presentare un file eseguibile o binario per un determinato file sorgente. Considera questo programma sorgente (codice C) come esempio. Apri il tuo editor preferito e digita questo programma:
#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}
Fase 1:preelaborazione con cpp
Il preprocessore C (cpp ) viene utilizzato per espandere tutte le macro e includere i file di intestazione. In questo esempio, il file di intestazione stdio.h sarà incluso nel codice sorgente. stdio.h è un file di intestazione che contiene informazioni su un printf funzione utilizzata all'interno del programma. cpp viene eseguito sul codice sorgente e le istruzioni risultanti vengono salvate in un file chiamato hello.i . Apri il file con un editor di testo per vederne il contenuto. Il codice sorgente per la stampa di hello world si trova in fondo al file.
[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]#
Fase 2:compilazione con gcc
Questa è la fase in cui il codice sorgente preelaborato del passaggio 1 viene convertito in istruzioni in linguaggio assembly senza creare un file oggetto. Utilizza la raccolta del compilatore GNU (gcc ). Dopo aver eseguito il gcc comando con la -S opzione su hello.i file, crea un nuovo file chiamato hello.s . Questo file contiene le istruzioni in linguaggio assembly per il programma C.
Puoi visualizzare i contenuti utilizzando qualsiasi editor o il gatto comando.
[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]#
Fase 3:assemblaggio con as
Lo scopo di un assembler è convertire le istruzioni in linguaggio assembly in codice in linguaggio macchina e generare un file oggetto con un .o estensione. Usa l'assemblatore GNU come disponibile per impostazione predefinita su tutte le piattaforme 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]#
Ora hai il tuo primo file nel formato ELF; tuttavia, non è ancora possibile eseguirlo. Successivamente vedrai la differenza tra un file oggetto e un file eseguibile .
[testdir]# file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
Fase 4:collegamento con ld
Questa è la fase finale della compilazione, quando i file oggetto sono collegati per creare un eseguibile. Un eseguibile di solito richiede funzioni esterne che spesso provengono dalle librerie di sistema (libc ).
Puoi richiamare direttamente il linker con il ld comando; tuttavia, questo comando è alquanto complicato. Invece, puoi utilizzare il gcc compilatore con -v flag (verboso) per capire come avviene il collegamento. (Utilizzando il ld il comando per il collegamento è un esercizio da esplorare.)
[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]#
Dopo aver eseguito questo comando, dovresti vedere un file eseguibile chiamato 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
Esecuzione del file comando su a.out mostra che si tratta effettivamente di un eseguibile 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
Esegui il tuo file eseguibile per vedere se funziona come indicato dal codice sorgente:
[testdir]# ./a.out
Hello World
Lo fa! Succedono così tante cose dietro le quinte solo per stampare Hello World sullo schermo. Immagina cosa succede in programmi più complicati.
Esplora gli strumenti di binutils
Questo esercizio ha fornito una buona base per l'utilizzo degli strumenti presenti nel pacchetto binutils. Il mio sistema ha binutils versione 2.27-34; potresti avere una versione diversa a seconda della tua distribuzione Linux.
[~]# rpm -qa | grep binutils
binutils-2.27-34.base.el7.x86_64
I seguenti strumenti sono disponibili nei pacchetti 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
L'esercizio di compilazione sopra ha già esplorato due di questi strumenti:il as il comando è stato utilizzato come assemblatore e il ld comando è stato utilizzato come linker. Continua a leggere per conoscere gli altri sette strumenti del pacchetto GNU binutils evidenziati in grassetto sopra.
readelf:mostra le informazioni sui file ELF
L'esercizio sopra menzionato i termini file oggetto e file eseguibile . Utilizzando i file di quell'esercizio, inserisci readalf utilizzando -h (intestazione) per scaricare l'intestazione ELF dei file sullo schermo. Nota che il file oggetto termina con .o l'estensione viene mostrata come Tipo:REL (file trasferibile) :
[testdir]# readelf -h hello.o
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 [...]
[...]
Type: REL (Relocatable file)
[...]
Se provi a eseguire questo file, riceverai un errore che dice che non può essere eseguito. Ciò significa semplicemente che non dispone ancora delle informazioni necessarie per l'esecuzione sulla CPU.
Ricorda, devi aggiungere la x o bit eseguibile sul file oggetto prima usando chmod comando altrimenti otterrai un Autorizzazione negata errore.
[testdir]# ./hello.o
bash: ./hello.o: Permission denied
[testdir]# chmod +x ./hello.o
[testdir]#
[testdir]# ./hello.o
bash: ./hello.o: cannot execute binary file
Se provi lo stesso comando su a.out file, vedi che il suo tipo è un EXEC (file eseguibile) .
[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)
Come visto prima, questo file può essere eseguito direttamente dalla CPU:
[testdir]# ./a.out
Hello World
Il ricerca comando fornisce una vasta gamma di informazioni su un binario. Qui, ti dice che è in formato ELF64 bit, il che significa che può essere eseguito solo su una CPU a 64 bit e non funzionerà su una CPU a 32 bit. Ti dice anche che è pensato per essere eseguito su architettura X86-64 (Intel/AMD). Il punto di ingresso nel binario è all'indirizzo 0x400430, che è solo l'indirizzo del principale funzione all'interno del programma sorgente C.
Prova il readalf comando sugli altri binari di sistema che conosci, come ls . Tieni presente che il tuo output (in particolare Digita: ) potrebbe differire sui sistemi RHEL 8 o Fedora 30 e versioni successive a causa di modifiche all'eseguibile indipendente dalla posizione (PIE) apportate per motivi di sicurezza.
[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)
Scopri quali librerie di sistema le ls il comando dipende dall'utilizzo di ldd comando, come segue:
[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)
Esegui readalf sulla libc libreria per vedere che tipo di file è. Come sottolinea, è un DYN (file oggetto condiviso) , il che significa che non può essere eseguito direttamente da solo; deve essere utilizzato da un file eseguibile che utilizza internamente le funzioni messe a disposizione dalla libreria.
[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)
size:elenca le dimensioni delle sezioni e le dimensioni totali
La dimensione il comando funziona solo su oggetti e file eseguibili, quindi se provi a eseguirlo su un semplice file ASCII, verrà generato un errore che dice Formato file non riconosciuto .
[testdir]# echo "test" > file1
[testdir]# cat file1
test
[testdir]# file file1
file1: ASCII text
[testdir]# size file1
size: file1: File format not recognized
Ora, esegui dimensione nel file oggetto e il file eseguibile dall'esercizio di cui sopra. Si noti che il file eseguibile (a.out ) contiene molte più informazioni rispetto al file oggetto (hello.o ), in base all'output del comando 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
Ma cosa significa il testo , dati e bss sezioni significano?
Il testo le sezioni si riferiscono alla sezione del codice del binario, che contiene tutte le istruzioni eseguibili. I dati le sezioni sono dove si trovano tutti i dati inizializzati e bss è dove vengono archiviati tutti i dati non inizializzati.
Confronta taglia con alcuni degli altri binari di sistema disponibili.
Per le ls comando:
[testdir]# size /bin/ls
text data bss dec hex filename
103119 4768 3360 111247 1b28f /bin/ls
Puoi vedere quel gcc e gb sono programmi molto più grandi di ls semplicemente guardando l'output della dimensione comando:
[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
stringhe:stampa le stringhe di caratteri stampabili nei file
Spesso è utile aggiungere il -d flag alle stringhe comando per mostrare solo i caratteri stampabili dalla sezione dati.
ciao.o è un file oggetto che contiene le istruzioni per stampare il testo Hello World . Quindi, l'unico output dalle stringhe il comando è Hello World .
[testdir]# strings -d hello.o
Hello World
Esecuzione di stringhe su a.out (un eseguibile), invece, mostra informazioni aggiuntive che sono state incluse nel binario durante la fase di collegamento:
[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$"
Ricordiamo che la compilazione è il processo di conversione delle istruzioni del codice sorgente in codice macchina. Il codice macchina è composto solo da 1 e 0 ed è difficile da leggere per gli esseri umani. Pertanto, è utile presentare il codice macchina come istruzioni in linguaggio assembly. Che aspetto hanno i linguaggi assembly? Ricorda che il linguaggio assembly è specifico dell'architettura; poiché sto usando l'architettura Intel o x86-64, le istruzioni saranno diverse se utilizzi l'architettura ARM per compilare gli stessi programmi.
objdump:visualizza le informazioni dai file oggetto
Un altro strumento binutils che può scaricare le istruzioni in linguaggio macchina dal binario è chiamato objdump .
Utilizza -d opzione, che disassembla tutte le istruzioni di assemblaggio dal binario.
[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
Questo risultato all'inizio sembra intimidatorio, ma prenditi un momento per capirlo prima di andare avanti. Ricorda che il .text la sezione contiene tutte le istruzioni del codice macchina. Le istruzioni di montaggio possono essere visualizzate nella quarta colonna (ad esempio, push , movimento , callq , pop , richiedi ). Queste istruzioni agiscono sui registri, che sono locazioni di memoria integrate nella CPU. I registri in questo esempio sono rbp , risp , edi , facile , ecc., e ogni registro ha un significato speciale.
Ora esegui objdump sul file eseguibile (a.out ) e guarda cosa ottieni. L'output di objdump sull'eseguibile può essere grande, quindi l'ho ristretto al principale funzione utilizzando il grep comando:
[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
Nota che le istruzioni sono simili al file oggetto hello.o , ma contengono alcune informazioni aggiuntive:
- Il file oggetto hello.o ha la seguente istruzione:
callq e
- L'eseguibile a.out consiste nella seguente istruzione con un indirizzo e una funzione:
callq 400400 <puts@plt>
L'istruzione assembly di cui sopra sta chiamando un puts funzione. Ricorda che hai utilizzato un printf funzione nel codice sorgente. Il compilatore ha inserito una chiamata alle put funzione di libreria per produrre Hello World sullo schermo.
Guarda le istruzioni per una riga sopra puts :
- Il file oggetto hello.o ha l'istruzione mov :
mov $0x0,%edi
- L'istruzione mov per l'eseguibile a.out ha un indirizzo effettivo ($0x4005d0 ) invece di $0x0 :
mov $0x4005d0,%edi
Questa istruzione sposta tutto ciò che è presente all'indirizzo $0x4005d0 all'interno del binario al registro denominato edi .
Cos'altro potrebbe esserci nel contenuto di quella posizione di memoria? Sì, avete indovinato:non è altro che il testo Hello, World . Come puoi esserne sicuro?
Il ricerca Il comando consente di eseguire il dump di qualsiasi sezione del file binario (a.out ) sullo schermo. Quanto segue gli chiede di scaricare .rodata , che sono dati di sola lettura, sullo schermo:
[testdir]# readelf -x .rodata a.out
Hex dump of section '.rodata':
0x004005c0 01000200 00000000 00000000 00000000 ....
0x004005d0 48656c6c 6f20576f 726c6400 Hello World.
Puoi vedere il testo Hello World a destra e il suo indirizzo in binario a sinistra. Corrisponde all'indirizzo che hai visto nel mov istruzioni sopra? Sì, lo fa.
strip:scarta i simboli dai file oggetto
Questo comando viene spesso utilizzato per ridurre le dimensioni del file binario prima di inviarlo ai clienti.
Ricorda che ostacola il processo di debug poiché le informazioni vitali vengono rimosse dal binario; tuttavia, il binario viene eseguito in modo impeccabile.
Eseguilo sul tuo a.out eseguibile e nota cosa succede. Innanzitutto, assicurati che il file binario non venga rimosso eseguendo il comando seguente:
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] not stripped
Inoltre, tieni traccia del numero di byte originariamente nel binario prima di eseguire la striscia comando:
[testdir]# du -b a.out
8440 a.out
Ora esegui la striscia comando sul tuo eseguibile e assicurati che funzioni utilizzando il file comando:
[testdir]# strip a.out
[testdir]# file a.out
a.out: ELF 64-bit LSB executable, x86-64, [......] stripped
Dopo aver rimosso il file binario, le sue dimensioni sono scese a 6296 dal precedente 8440 byte per questo piccolo programma. Con così tanti risparmi per un programma minuscolo, non c'è da stupirsi che i programmi di grandi dimensioni vengano spesso eliminati.
[testdir]# du -b a.out
6296 a.out
addr2line:converte gli indirizzi in nomi di file e numeri di riga
La addr2line Lo strumento cerca semplicemente gli indirizzi nel file binario e li abbina alle righe nel programma del codice sorgente C. Abbastanza bello, vero?
Scrivi un altro programma di test per questo; solo che questa volta assicurati di compilarlo con -g segnala per gcc , che aggiunge ulteriori informazioni di debug per il binario e aiuta anche includendo i numeri di riga (forniti nel codice sorgente qui):
[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 }
Compila con -g contrassegnare ed eseguirlo. Nessuna sorpresa qui:
[testdir]# gcc -g atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
Ora usa objdump per identificare gli indirizzi di memoria dove iniziano le tue funzioni. Puoi usare grep comando per filtrare le righe specifiche che desideri. Gli indirizzi per le tue funzioni sono evidenziati di seguito:
[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
Ora usa la addr2line strumento per mappare questi indirizzi dal binario in modo che corrispondano a quelli del codice sorgente 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
Dice che 40051d inizia alla riga numero 6 nel file sorgente atest.c , che è la riga in cui si trova la parentesi graffa iniziale ({ ) per funzione1 inizia. Abbina l'output per function2 e principale .
nm:elenca i simboli dai file oggetto
Usa il programma C sopra per testare il nm attrezzo. Compilalo rapidamente utilizzando gcc ed eseguilo.
[testdir]# gcc atest.c
[testdir]# ./a.out
Within function1
Within function2
Within main
Ora esegui nm e grep per informazioni sulle tue funzioni e variabili:
[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
Puoi vedere che le funzioni sono contrassegnate con T , che sta per simboli nel testo sezione, mentre le variabili sono contrassegnate come D , che sta per simboli nei dati inizializzati sezione.
Immagina quanto sarà utile eseguire questo comando su binari in cui non hai il codice sorgente? Ciò consente di sbirciare all'interno e capire quali funzioni e variabili vengono utilizzate. A meno che, ovviamente, i binari non siano stati rimossi, nel qual caso non contengono simboli, e quindi nm comando non sarebbe molto utile, come puoi vedere qui:
[testdir]# strip a.out
[testdir]# nm a.out | grep -Ei 'function|main|globalvar'
nm: a.out: no symbols
Conclusione
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.