2012-04-29 5 views
9

Без Изменение исходного кода, как я могу отслеживать, какие функции вызывают и с какими параметрами, когда вызывается некоторая функция (например, func100 в следующем примере). Я хотел бы, чтобы этот результат был следующим:Как отслеживать функцию вызова в 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) 

это выполнимо? или каково решение с минимальной модификацией исходного кода?

+1

Используйте отладчик. Или вызовите в файл какую-либо форму fprintf. Но, возможно, последние варианты не будут хорошими, поскольку вы не хотите изменять исходный код. – Lefteris

+0

Может быть, профилировщик, чтобы получить график вызовов? –

+1

Вы ищете что-то подобное? http://stackoverflow.com/questions/311840/tool-to-trace-local-function-calls-in-linux – delannoyk

ответ

4

Если бы вы были в Linux, callgrind мог бы помочь. Он в основном собирает статистику того, что вы ищете, поэтому он может обеспечить способ доступа к своим исходным данным.

+0

Я просмотрел справочник callgrind, он не упоминает, как собирать значения параметров при входе/выходе из функции. – Andrew

0

Вы можете посмотреть в log4cxx, проект, созданный базой apache. Я знаю, что log4j, вариант java позволил вам установить чувствительность, и вы могли отслеживать все, что было сделано в программе. Возможно, что вариант C++ один и тот же, но есть несколько альтернатив - есть ориентированный на аспект C++ компилятор, и вы можете определить аспект во всех функциях и заставить его улавливать и печатать переменные. Другой вариант - использовать отладчик.

Резюмируя: отладчик, log4cxx или АОП

+0

AFAIK, чтобы использовать [log4cxx] (http://logging.apache.org/log4cxx/), я должен изменить свой исходный код, добавив фрагмент кода, например «LOG4CXX_DEBUG (barlogger,« Exiting func100 »); – Andrew

+0

А не знал об этом. Да, этот вызов grind выглядит как лучшее решение, тогда –

13

Если вы используете gcc, вы можете использовать флаг в -finstrument-functions компиляции. Он добавляет код, который вызывает две функции: __cyg_profile_func_enter и __cyg_profile_func_exit, всякий раз, когда функция входит/выходит.

Вам нужно будет реализовать эти функции, чтобы делать то, что вы хотите. Обязательно компилируйте их либо без флага, либо с помощью attribute((no_instrument_function)), чтобы они не пытались называть себя.

Второй параметр функции будет указателем на сайт вызова (т. Е. Адрес возврата в вызывающей функции). Вы можете просто распечатать его с помощью %p, но это будет несколько сложно использовать. Вы можете использовать nm для определения реальной функции, которая содержит этот адрес.

Вы не можете получить параметры функции таким образом.

+1

Слишком плохо, это просто адрес. – reader

+0

Вот пример [пример реализации] (https://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/). – JohnMudd

+0

У havent была возможность попробовать это, но это звучит мощно. Я предполагаю, что есть какой-то красивый/ужасный sed/awk/nm one-liner, который может преобразовать все адреса журналов в имена функций –

12

С библиотекой GNU C вы можете использовать модуль backtrace. Вот пример для этого:

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

компилировать с -g -rdynamic опции компилятора, чтобы загрузить символы

gcc -g -rdynamic Test1.c -o Test 

Вы увидите результат, похожий на

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 

Вы можете написать эту функцию обработчика и звонить из любой точки вашей программы в любое время. Не забудьте увеличить размер array по мере необходимости.

3

Используйте отладчик для установки контрольных точек с соответствующими действиями. Например, в gdb вы можете установить точку останова в начале и конце каждой из функций, которые вы хотите отслеживать. Вы можете дать каждому из этих точек останова команды для выполнения, например:

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

Затем, когда вы запускаете программу (в отладчике) он будет печатать текст с каждой из ваших команд вместе с соответствующими параметрами ,

Взгляните на relevant documentation for gdb.

+0

работает как шарм. Единственная проблема заключается в том, что я НЕ знаю, как установить точку останова в конце каждой функции. для точки останова в начале функции я просто вставляю в свой командный файл команду break, за которой следует имя функции. для точки останова в конце функции я НЕ МОГУ сделать это так. могу ли я использовать команду break, за которой следует номер строки? то как указать исходный файл, в котором находится точка останова? – Andrew

+0

Вы можете сломать определенный номер строки. Кроме того, большинство достойных IDE упрощают установку контрольных точек, обычно просто щелкая по краю. Вы не сказали, какие инструменты вы используете, поэтому трудно дать конкретные советы. – Caleb

+0

Я использую GDB.поэтому мне нужно подготовить командный файл, который будет передан в gdb с опцией --command. Или, может ли Eclipse CDT сгенерировать командный файл gdb? я определенно нуждаюсь в файле команды и делаю некоторые настройки. – Andrew

0

Иногда мне приходится отслеживать множество вызовов функций, даже для внешних библиотек у меня нет никакого контроля, или я не хочу изменять.

Когда-то я понял, что вы можете комбинировать контрольные точки регулярного выражения gdb (обычные тоже в порядке), а затем просто выполнить набор команд, которые будут запускаться каждый раз при срабатывании этих точек останова. См: http://www.ofb.net/gnu/gdb/gdb_35.html

Например, если вы хотите, чтобы проследить все функции, которые начинаются с префикса «MPI_», вы можете сделать:

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

Тихая команда используется для скрытия GdB сообщения, когда контрольная точка находится , Обычно я печатаю пару пустых строк, чтобы их было легче читать.

Затем вы просто запустить программу: (GDB) запустить

После того, как ваша программа начнет работать, GDB выведет 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 

Если вы хотите более подробную информацию, печать локальных переменных заданной точки останова также можно просто вставить несколько команд между command и end.

Бонусный наконечник: добавьте все это в свой файл .gdbinit и завершите его выполнение в файл.

Смежные вопросы