2012-04-29 41 views
9

Nếu không có sửa đổi mã nguồn, làm cách nào tôi có thể theo dõi chức năng nào được gọi và với tham số nào, khi một số hàm (ví dụ func100 trong ví dụ sau) được gọi. Tôi muốn kết quả như sau:cách theo dõi cuộc gọi hàm trong 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) 

là có thể làm được? hoặc giải pháp sửa đổi tối thiểu mã nguồn là gì?

+1

Sử dụng trình gỡ lỗi. Hoặc gọi một số hình thức đăng nhập fprintf vào một tập tin. Nhưng có lẽ các tùy chọn cuối cùng sẽ không tốt vì bạn không muốn sửa đổi mã nguồn. – Lefteris

+0

Có thể một profiler để có được một đồ thị cuộc gọi? –

+1

Bạn đang tìm kiếm một cái gì đó như thế? http://stackoverflow.com/questions/311840/tool-to-trace-local-function-calls-in-linux – delannoyk

Trả lời

4

Nếu bạn sử dụng Linux, callgrind có thể hữu ích. Về cơ bản, nó thu thập số liệu thống kê về những gì bạn đang tìm kiếm, vì vậy, nó có thể cung cấp một cách để truy cập dữ liệu thô của nó.

+0

tôi đã xem qua hướng dẫn callgrind, nó KHÔNG đề cập đến cách thu thập giá trị của các thông số khi vào/thoát một hàm. – Andrew

0

Bạn có thể xem log4cxx, một dự án được tổ chức bởi nền tảng apache. Tôi biết rằng log4j, biến thể java cho phép bạn thiết lập độ nhạy, và bạn có thể theo dõi mọi thứ đã được thực hiện trong chương trình. Có lẽ biến thể C++ giống nhau, nhưng có một số lựa chọn thay thế - có một trình biên dịch C++ hướng về khía cạnh, và bạn có thể định nghĩa một khía cạnh trên tất cả các hàm, và bắt nó và in các biến. Một cách khác là sử dụng trình gỡ rối.

Để tóm tắt: debugger, log4cxx hay AOP

+0

AFAIK, để sử dụng [log4cxx] (http://logging.apache.org/log4cxx/), tôi nên sửa đổi mã nguồn của mình bằng cách thêm đoạn mã như "LOG4CXX_DEBUG (barlogger," Thoát func100 ");" – Andrew

+0

Ah không biết điều đó. Yeah, cuộc gọi xay đó trông giống như giải pháp tốt nhất sau đó –

13

Nếu bạn sử dụng gcc, bạn có thể sử dụng cờ -finstrument-functions biên dịch. Nó thêm mã gọi hai hàm, __cyg_profile_func_enter__cyg_profile_func_exit, bất cứ khi nào một hàm vào/thoát.

Bạn sẽ cần phải triển khai các chức năng này, để thực hiện những gì bạn muốn. Hãy chắc chắn biên dịch chúng hoặc không có cờ hoặc với attribute((no_instrument_function)), do đó, họ sẽ không cố gắng tự gọi mình.

Tham số thứ hai của hàm sẽ là một con trỏ tới trang gọi (tức là địa chỉ trả về trong hàm gọi). Bạn chỉ có thể in nó với %p, nhưng nó sẽ hơi khó sử dụng. Bạn có thể sử dụng nm để tìm ra hàm thực có chứa địa chỉ này.

Bạn không thể nhận được các tham số chức năng theo cách này.

+1

Quá xấu nó chỉ là địa chỉ. – reader

+0

Đây là [triển khai ví dụ] (https://balau82.wordpress.com/2010/10/06/trace-and-profile-function-calls-with-gcc/). – JohnMudd

+0

havent đã có cơ hội để thử điều này, nhưng nó có vẻ mạnh mẽ. Tôi đoán có một số sed/awk/nm một lớp lót đẹp/khủng khiếp mà có thể chuyển đổi tất cả các địa chỉ nhật ký thành các tên hàm –

12

Với Thư viện GNU C, bạn có thể sử dụng mô-đun backtrace. Dưới đây là một ví dụ cho điều đó:

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

biên dịch với -g -rdynamic tùy chọn biên dịch để tải những biểu tượng

gcc -g -rdynamic Test1.c -o Test 

Bạn sẽ thấy một đầu ra tương tự như

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 

Bạn có thể viết hàm điều khiển này và gọi từ bất kỳ đâu trong chương trình của bạn ở bất kỳ thời điểm nào. Hãy nhớ tăng kích thước array theo yêu cầu.

3

Sử dụng trình gỡ lỗi để đặt điểm ngắt với các tác vụ được liên kết. Ví dụ, trong gdb bạn có thể thiết lập một điểm ngắt tại đầu và cuối của mỗi hàm mà bạn muốn theo dõi. Bạn có thể cung cấp cho mỗi người trong số những breakpoint một lệnh để thực hiện, chẳng hạn như:

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

Sau đó, khi bạn chạy chương trình (trong debugger) nó sẽ in văn bản từ mỗi lệnh của bạn cùng với các thông số liên quan .

Hãy xem qua số relevant documentation for gdb.

+0

nó hoạt động như một sự quyến rũ. Vấn đề duy nhất là tôi KHÔNG biết cách đặt điểm ngắt ở cuối mỗi chức năng. cho breakpoint ở đầu của một hàm, tôi chỉ cần đưa vào tập lệnh của tôi lệnh break theo sau là tên hàm. cho breakpoint ở phần cuối của một hàm, tôi KHÔNG THỂ làm theo cách này. tôi có thể sử dụng lệnh ngắt theo sau là số dòng không? sau đó làm thế nào để xác định tệp nguồn trong đó điểm ngắt nằm ở đâu? – Andrew

+0

Bạn có thể phá vỡ một số dòng cụ thể. Ngoài ra, hầu hết các IDE phong nha làm cho nó đơn giản để thiết lập các điểm ngắt, thường là bằng cách chỉ cần nhấp vào lề. Bạn đã không nói những công cụ bạn đang sử dụng, vì vậy thật khó để đưa ra lời khuyên cụ thể. – Caleb

+0

tôi sử dụng GDB.vì vậy tôi cần chuẩn bị một tệp lệnh sẽ được chuyển tới gdb với tùy chọn --command. Hoặc, Eclipse CDT có thể giúp tạo tệp lệnh gdb không? tôi chắc chắn cần tập tin lệnh và thực hiện một số tinh chỉnh. – Andrew

0

Thỉnh thoảng tôi phải theo dõi rất nhiều cuộc gọi hàm, ngay cả đối với thư viện bên ngoài, tôi không có bất kỳ điều khiển nào hoặc tôi không muốn sửa đổi. Cách đây không lâu, tôi nhận ra rằng bạn có thể kết hợp các điểm ngắt biểu thức chính quy của gdb (các nút thông thường là ok) và sau đó chỉ cần thực hiện một tập lệnh để chạy mỗi khi các điểm ngắt được kích hoạt. Xem: http://www.ofb.net/gnu/gdb/gdb_35.html

Ví dụ, nếu bạn muốn theo dõi tất cả các chức năng mà bắt đầu với "MPI_" tiền tố, bạn có thể làm:

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

Im lặng lệnh được sử dụng để che giấu thông điệp gdb khi một breakpoint được tìm thấy . Tôi thường in một vài dòng trống, để dễ đọc hơn.

Sau đó, bạn chỉ cần chạy chương trình: (gdb) chạy

Khi chương trình của bạn bắt đầu chạy, gdb sẽ in các mức backtrace topmost 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 

Nếu bạn muốn có thông tin chi tiết hơn, in ấn các biến cục bộ của một breakpoint được cũng có thể, chỉ cần chèn nhiều lệnh giữa commandend.

Mẹo bổ sung: thêm tất cả các tệp này vào tệp .gdbinit của bạn và thực thi đường dẫn vào tệp.

Các vấn đề liên quan