2012-04-29 16 views
9

Senza che modifica il codice sorgente, come posso rintracciare quali funzioni sono chiamate e con quali parametri, quando viene invocata una funzione (ad esempio func100 nell'esempio seguente). Vorrei che l'output fosse il seguente:come rintracciare la chiamata di funzione in C?

enter func100(p1001=xxx,p1002=xxx) 
     enter func110(p1101=xxx,p1102=xxx) 
     exit func110(p1101=xxx,p1102=xxx) 
     enter func120(p1201=xxx,p1202=xxx,p1203=xxx) 
       enter func121(p1211=xxx) 
       exit func121(p1211=xxx) 
     exit func120(p1201=xxx,p1202=xxx,p1203=xxx) 
exit func100(p1001=xxx,p1002=xxx) 

è possibile? o qual è la soluzione con modifica minima del codice sorgente?

+1

Utilizzare un debugger. Oppure invocare qualche forma di registrazione fprintf in un file. Ma forse le ultime opzioni non sarebbero buone visto che non si desidera modificare il codice sorgente. – Lefteris

+0

Forse un profiler per ottenere un grafico delle chiamate? –

+1

Stai cercando qualcosa del genere? http://stackoverflow.com/questions/311840/tool-to-trace-local-function-calls-in-linux – delannoyk

risposta

4

Se si fosse su Linux, callgrind potrebbe essere d'aiuto. Raccoglie fondamentalmente le statistiche di ciò che stai cercando, quindi potrebbe fornire un modo per accedere ai suoi dati non elaborati.

+0

ho controllato il manuale di callgrind, NON menziona come raccogliere il valore dei parametri quando si entra/si esce da una funzione. – Andrew

0

È possibile esaminare log4cxx, un progetto ospitato dalla base di apache. So che log4j, la variante java ti ha permesso di impostare la sensibilità, e puoi monitorare ogni singola cosa che è stata fatta nel programma. Forse la variante C++ è la stessa, ma ci sono diverse alternative: c'è un compilatore C++ orientato all'aspetto, e puoi definire un aspetto su tutte le funzioni, e farlo catturare e stampare le variabili. Un'altra alternativa è usare un debugger.

In sintesi: debugger, log4cxx o AOP

+0

AFAIK, per utilizzare [log4cxx] (http://logging.apache.org/log4cxx/), dovrei modificare il mio codice sorgente aggiungendo lo snippet di codice come "LOG4CXX_DEBUG (barlogger," Exiting func100 ");" – Andrew

+0

Ah non lo sapevo. Sì, quella chiamata grind sembra la soluzione migliore allora –

13

Se si utilizza gcc, è possibile utilizzare il flag di compilazione -finstrument-functions. Aggiunge il codice che chiama due funzioni, __cyg_profile_func_enter e __cyg_profile_func_exit, ogni volta che una funzione entra/esce.

È necessario implementare queste funzioni, per fare ciò che si desidera. Assicurati di compilarli senza il flag o con attribute((no_instrument_function)), in modo che non provino a chiamarsi.

Il secondo parametro delle funzioni sarebbe un puntatore al sito di chiamata (ovvero l'indirizzo di ritorno all'interno della funzione di chiamata). Puoi semplicemente stamparlo con %p, ma sarà alquanto difficile da usare. È possibile utilizzare nm per capire la funzione reale che contiene questo indirizzo.

Non è possibile ottenere i parametri di funzione in questo modo.

+1

Peccato che sia solo l'indirizzo. – reader

+0

Ecco un [esempio di implementazione] (https://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/). – JohnMudd

+0

havent ha avuto la possibilità di provare ancora, ma sembra potente. Presumo che ci sia un bellissimo/orrendo sed/awk/nm one-liner in grado di convertire tutti gli indirizzi di registro in nomi di funzioni –

12

Con la libreria GNU C, è possibile utilizzare il modulo backtrace. Ecco un esempio per questo:

#include <stdio.h> 
#include <execinfo.h> 
#include <stdlib.h> 


void handler(char *caller) { 
    void *array[10]; 
    size_t size; 
    printf("Stack Trace Start for %s\n",caller); 
    size = backtrace(array, 10); 
    backtrace_symbols_fd(array, size, 2); 
    printf("Stack Trace End\n"); 
} 

void car() { 
    handler("car()"); 
    printf("Continue Execution"); 
} 
void baz() {car(); } 

void bar() { baz(); } 
void foo() { bar(); } 


int main(int argc, char **argv) { 
    foo(); 
} 

compilare con -g -rdynamic opzione del compilatore per caricare i simboli

gcc -g -rdynamic Test1.c -o Test 

si vedrà un output simile al

Stack Trace Start for car() 
./Test(handler+0x2d)[0x80486f1] 
./Test(car+0x12)[0x804872e] 
./Test(baz+0xb)[0x8048747] 
./Test(bar+0xb)[0x8048754] 
./Test(foo+0xb)[0x8048761] 
./Test(main+0xb)[0x804876e] 
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0x126e37] 
./Test[0x8048631] 
Stack Trace End 
Continue Execution in car 

È possibile scrivere questa funzione gestore e chiama da qualsiasi parte del tuo programma in qualsiasi momento. Ricordarsi di aumentare la dimensione array come richiesto.

3

Utilizzare un debugger per impostare i punti di interruzione con le azioni associate. Ad esempio, in gdb è possibile impostare un punto di interruzione all'inizio e alla fine di ciascuna funzione che si desidera tracciare. Si può dare a ciascuno di questi punti di interruzione di un comando da eseguire, come ad esempio:

printf("Enter func100(p1001=%d, p1002=%d)", p1001, p1002) 

Poi, quando si esegue il programma (nel debugger) che sarà stampare il testo da ognuno dei vostri comandi con i parametri associati .

Dai uno sguardo allo relevant documentation for gdb.

+0

funziona come un fascino. L'unico problema è che NON so come impostare un breakpoint alla fine di ogni funzione. per breakpoint all'inizio di una funzione, ho appena inserito nel mio file di comando il comando break seguito dal nome della funzione. per breakpoint alla fine di una funzione, NON POSSO farlo in questo modo. posso usare il comando break seguito da un numero di riga? allora come specificare il file sorgente in cui risiede il punto di interruzione? – Andrew

+0

È possibile interrompere un numero di riga specifico. Inoltre, gli IDE più decenti rendono semplice impostare i punti di interruzione, in genere facendo semplicemente clic sul margine. Non hai detto quali strumenti stai usando, quindi è difficile dare consigli specifici. – Caleb

+0

io uso GDB.quindi ho bisogno di preparare un file di comando che verrà passato al gdb con l'opzione --command. Oppure, Eclipse CDT può aiutare a generare il file di comando gdb? ho sicuramente bisogno del file di comando e fare qualche modifica. – Andrew

0

A volte devo rintracciare molte chiamate di funzione, anche per le librerie esterne non ho alcun controllo, o non voglio modificare.

Un po 'di tempo fa, mi sono reso conto che è possibile combinare i punti di interruzione delle espressioni regolari di gdb (anche quelli regolari sono ok) e quindi eseguire una serie di comandi da eseguire ogni volta che vengono attivati ​​questi punti di interruzione. Vedere: http://www.ofb.net/gnu/gdb/gdb_35.html

Ad esempio, se si vuole tracciare tutte le funzioni che iniziano con il prefisso "MPI_", si può fare:

(gdb) rbreak MPI_ 
[...] 
(gdb) command 1-XX 
(gdb) silent 
(gdb) bt 1 
(gdb) echo \n\n 
(gdb) continue 
(gdb) end 

comando silenzioso viene utilizzata per nascondere i messaggi gdb quando viene trovato un punto di interruzione . Di solito stampo un paio di righe vuote, in modo che sia più facile da leggere.

Poi, basta eseguire il programma: (gdb) gestita

Una volta che il programma inizia l'esecuzione, gdb stamperà i più alti livelli di Backtrace N.

#0 0x000000000040dc60 in [email protected]() 


#0 PMPI_Initialized (flag=0x7fffffffba78) at ../../src/mpi/init/initialized.c:46 


#0 0x000000000040d9b0 in [email protected]() 


#0 PMPI_Init_thread (argc=0x7fffffffbe78, argv=0x7fffffffbde0, required=3, provided=0x7fffffffba74) at ../../src/mpi/init/initthread.c:946 


#0 0x000000000040e390 in [email protected]() 


#0 PMPI_Comm_rank (comm=1140850688, rank=0x7fffffffba7c) at ../../src/mpi/comm/comm_rank.c:53 


#0 0x000000000040e050 in [email protected]() 


#0 PMPI_Type_create_struct (count=3, array_of_blocklengths=0x7fffffffba90, array_of_displacements=0x7fffffffbab0, array_of_types=0x7fffffffba80, newtype=0x69de20) at ../../src/mpi/datatype/type_create_struct.c:116 


#0 0x000000000040e2a0 in [email protected]() 


#0 PMPI_Type_commit (datatype=0x69de20) at ../../src/mpi/datatype/type_commit.c:75 

Se volete informazioni più dettagliate, la stampa di variabili locali di un dato punto di interruzione è anche possibile, basta inserire più comandi tra command e end.

Suggerimento bonus: aggiungi tutti questi al tuo file .gdbinit e conduci l'esecuzione in un file.