2012-04-29 13 views
9

Sin modificando el código fuente, cómo puedo rastrear qué funciones se llaman y con qué parámetros, cuando se invoca alguna función (digamos func100 en el siguiente ejemplo). Me gustaría que la salida sea la siguiente:cómo rastrear llamada de función en 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) 

¿es esto factible? o ¿cuál es la solución con una modificación mínima del código fuente?

+1

Utilice un depurador. O invoque alguna forma de registro fprintf en un archivo. Pero tal vez las últimas opciones no serían buenas ya que no desea modificar el código fuente. – Lefteris

+0

¿Tal vez un generador de perfiles para obtener un gráfico de llamadas? –

+1

¿Estás buscando algo así? http://stackoverflow.com/questions/311840/tool-to-trace-local-function-calls-in-linux – delannoyk

Respuesta

4

Si estuvo en linux, callgrind podría ayudar. Básicamente recopila estadísticas de lo que estás buscando, por lo que podría proporcionar una forma de acceder a sus datos brutos.

+0

He revisado el manual de callgrind, NO menciona cómo recopilar el valor de los parámetros al ingresar/salir de una función. – Andrew

0

Puede ver en log4cxx, un proyecto alojado por la fundación apache. Sé que log4j, la variante de Java le permitió establecer la sensibilidad, y puede rastrear cada cosa que se hizo en el programa. Tal vez la variante de C++ sea la misma, pero hay varias alternativas: hay un compilador de C++ orientado a los aspectos, y puede definir un aspecto en todas las funciones y hacer que capture e imprima las variables. Otra alternativa es usar un depurador.

En resumen: depurador, log4cxx o AOP

+0

AFAIK, para utilizar [log4cxx] (http://logging.apache.org/log4cxx/), debería modificar mi código fuente agregando un fragmento de código como "LOG4CXX_DEBUG (barlogger," Exiting func100 ");" – Andrew

+0

Ah, no lo sabía. Sí, esa rutina llamada parece la mejor solución, entonces –

13

Si utiliza gcc, se puede usar la bandera -finstrument-functions compilación. Agrega un código que llama a dos funciones, __cyg_profile_func_enter y __cyg_profile_func_exit, siempre que una función entre/salga.

Tendrá que implementar estas funciones, para hacer lo que quiera. Asegúrese de compilarlos sin la bandera, o con attribute((no_instrument_function)), para que no intenten llamarse a sí mismos.

El segundo parámetro de las funciones sería un puntero al sitio de la llamada (es decir, la dirección de retorno dentro de la función de llamada). Puede imprimirlo con %p, pero será algo difícil de usar. Puede usar nm para descubrir la función real que contiene esta dirección.

No puede obtener los parámetros de la función de esta manera.

+1

Lástima que es solo la dirección. – reader

+0

Aquí hay un [ejemplo de implementación] (https://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/). – JohnMudd

+0

no han tenido la oportunidad de probar esto todavía, pero suena poderoso. Supongo que hay un hermoso/horrendo sed/awk/nm de una sola línea que podría convertir todas las direcciones de registro a nombres de función –

12

Con la Biblioteca GNU C, puede usar el módulo backtrace. Este es un ejemplo de eso:

#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(); 
} 

compilar con la opción de compilador -g -rdynamic para cargar los símbolos

gcc -g -rdynamic Test1.c -o Test 

verá una salida similar a

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 

Puede escribir esta función de controlador y llame desde cualquier lugar de su programa en cualquier momento. Recuerde aumentar el tamaño de array según sea necesario.

3

Utilice un depurador para establecer puntos de interrupción con acciones asociadas. Por ejemplo, en gdb puede establecer un punto de interrupción al principio y al final de cada una de las funciones que desea rastrear. Usted puede dar a cada uno de esos puntos de interrupción de un comando a ejecutar, tales como:

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

Entonces, cuando se ejecuta el programa (en el depurador) que va a imprimir el texto de cada uno de sus comandos, junto con los parámetros asociados .

Eche un vistazo a relevant documentation for gdb.

+0

funciona como un encanto. El único problema es que NO sé cómo configurar un punto de interrupción al final de cada función. para el punto de ruptura al comienzo de una función, acabo de poner en mi archivo de comando el comando break seguido del nombre de la función. para punto de corte al final de una función, NO PUEDO hacerlo de esta manera. ¿Puedo usar el comando break seguido de un número de línea? entonces, ¿cómo especificar el archivo fuente en el que reside el punto de interrupción? – Andrew

+0

Puede dividirse en un número de línea específico. Además, la mayoría de los IDE decentes hacen que sea sencillo establecer puntos de interrupción, generalmente haciendo clic en el margen. No ha dicho qué herramientas está utilizando, por lo que es difícil dar consejos específicos. – Caleb

+0

uso GDB.entonces necesito preparar un archivo de comando que se pasará al gdb con la opción --command. ¿O puede Eclipse CDT ayudar a generar el archivo de comando gdb? Definitivamente necesito el archivo de comando y hacer algunos ajustes. – Andrew

0

A veces tengo que rastrear muchas llamadas a funciones, incluso para bibliotecas externas que no tengo ningún control, o no quiero modificar.

Hace algún tiempo, me di cuenta de que puedes combinar los puntos de interrupción de expresión regular de gdb (los normales también están bien) y luego ejecutar un conjunto de comandos cada vez que se activan esos puntos de interrupción. Ver: http://www.ofb.net/gnu/gdb/gdb_35.html

Por ejemplo, si desea realizar un seguimiento de todas las funciones que comienzan con "MPI_" prefijo, que puede hacer:

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

orden silenciosa se utiliza para ocultar los mensajes de GDB Cuando se encuentra un punto de interrupción . Normalmente imprimo un par de líneas vacías, para que sea más fácil de leer.

Entonces, simplemente ejecutar el programa: (BGF) corren

Una vez que el programa comienza a funcionar, el BGF imprimirá las más altas de N niveles backtrace.

#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 

Si desea información más detallada, la impresión de las variables locales de un punto de interrupción dada es también posible, basta con insertar más comandos entre command y end.

Consejo adicional: agregue todos estos a su archivo .gdbinit y canalice la ejecución en un archivo.

Cuestiones relacionadas