Tôi biết không có hàm C tiêu chuẩn nào để làm điều này. Tôi đã tự hỏi các kỹ thuật để thực hiện việc này trên Windows và * nix là gì? (Windows XP là hệ điều hành quan trọng nhất của tôi để thực hiện điều này ngay bây giờ.)
Tôi biết không có hàm C tiêu chuẩn nào để làm điều này. Tôi đã tự hỏi các kỹ thuật để thực hiện việc này trên Windows và * nix là gì? (Windows XP là hệ điều hành quan trọng nhất của tôi để thực hiện điều này ngay bây giờ.)
Câu trả lời:
glibc cung cấp hàm backtrace ().
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
char ** backtrace_symbols (void *const *buffer, int size)
void backtrace_symbols_fd(void *const *buffer, int size, int fd)
có thể gửi đầu ra trực tiếp đến stdout / err chẳng hạn.
backtrace_symbols()
tệ quá. Nó yêu cầu xuất tất cả các ký hiệu và nó không hỗ trợ các ký hiệu DWARF (gỡ lỗi). libbacktrace là một lựa chọn tốt hơn nhiều trong nhiều (hầu hết) trường hợp.
Có backtrace () và backtrace_symbols ():
Từ trang người đàn ông:
#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
...
Một cách để sử dụng điều này theo cách thuận tiện hơn / OOP là lưu kết quả của backtrace_symbols () trong một hàm tạo lớp ngoại lệ. Vì vậy, bất cứ khi nào bạn ném loại ngoại lệ đó, bạn sẽ có dấu vết ngăn xếp. Sau đó, chỉ cần cung cấp một chức năng để in nó ra. Ví dụ:
class MyException : public std::exception {
char ** strs;
MyException( const std::string & message ) {
int i, frames = backtrace(callstack, 128);
strs = backtrace_symbols(callstack, frames);
}
void printStackTrace() {
for (i = 0; i < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
}
};
...
try {
throw MyException("Oops!");
} catch ( MyException e ) {
e.printStackTrace();
}
Ta da!
Lưu ý: việc bật cờ tối ưu hóa có thể làm cho dấu vết ngăn xếp kết quả không chính xác. Lý tưởng nhất là người ta sẽ sử dụng khả năng này khi bật cờ gỡ lỗi và tắt cờ tối ưu hóa.
Đối với Windows, hãy kiểm tra API StackWalk64 () (cũng trên Windows 32bit). Đối với UNIX, bạn nên sử dụng cách gốc của hệ điều hành để làm điều đó hoặc dự phòng cho backtrace của glibc (), nếu có sẵn.
Tuy nhiên, lưu ý rằng sử dụng Stacktrace trong mã gốc hiếm khi là một ý tưởng hay - không phải vì nó không thể thực hiện được, mà bởi vì bạn đang cố gắng đạt được điều sai lầm.
Hầu hết thời gian mọi người cố gắng có được một ngăn xếp, chẳng hạn như một trường hợp ngoại lệ, chẳng hạn như khi bắt được một ngoại lệ, một xác nhận không thành công hoặc - tệ nhất và sai nhất - khi bạn nhận được một "ngoại lệ" nghiêm trọng hoặc tín hiệu như Vi phạm phân đoạn.
Xem xét vấn đề cuối cùng, hầu hết các API sẽ yêu cầu bạn cấp phát bộ nhớ một cách rõ ràng hoặc có thể thực hiện điều đó trong nội bộ. Làm như vậy trong tình trạng mỏng manh mà chương trình của bạn hiện đang ở, về mặt thực tế có thể khiến mọi thứ thậm chí còn tồi tệ hơn. Ví dụ: báo cáo sự cố (hoặc coredump) sẽ không phản ánh nguyên nhân thực sự của sự cố mà do bạn đã không xử lý được).
Tôi cho rằng bạn đang cố gắng đạt được điều đó để xử lý lỗi nghiêm trọng, vì hầu hết mọi người dường như thử điều đó khi nói đến việc có được một stacktrace. Nếu vậy, tôi sẽ dựa vào trình gỡ lỗi (trong quá trình phát triển) và để quá trình coredump trong sản xuất (hoặc kết xuất nhỏ trên windows). Cùng với việc quản lý biểu tượng thích hợp, bạn sẽ không gặp khó khăn khi tìm ra hướng dẫn gây ra sau khi khám nghiệm tử thi.
Bạn nên sử dụng thư viện thư giãn .
unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;
while (unw_step(&cursor) > 0) {
unw_get_reg(&cursor, UNW_REG_IP, &ip);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
if (ctr >= 10) break;
a[ctr++] = ip;
}
Cách tiếp cận của bạn cũng sẽ hoạt động tốt trừ khi bạn thực hiện cuộc gọi từ thư viện được chia sẻ.
Bạn có thể sử dụng addr2line
lệnh trên Linux để lấy hàm nguồn / số dòng của PC tương ứng.
Không có cách nào độc lập với nền tảng để làm điều đó.
Điều gần nhất bạn có thể làm là chạy mã mà không cần tối ưu hóa. Bằng cách đó, bạn có thể đính kèm vào quy trình (sử dụng trình gỡ lỗi c ++ trực quan hoặc GDB) và nhận dấu vết ngăn xếp có thể sử dụng.
Solaris có lệnh pstack , lệnh này cũng đã được sao chép vào Linux.
Bạn có thể làm điều đó bằng cách đi ngược lại ngăn xếp. Tuy nhiên, trong thực tế, việc thêm số nhận dạng vào ngăn xếp lệnh gọi ở đầu mỗi hàm và bật nó vào cuối mỗi hàm thường dễ dàng hơn, sau đó chỉ cần thực hiện thao tác in nội dung. Nó hơi giống PITA, nhưng nó hoạt động tốt và cuối cùng sẽ giúp bạn tiết kiệm thời gian.
Trong vài năm qua, tôi đã sử dụng libbacktrace của Ian Lance Taylor. Nó gọn gàng hơn nhiều so với các hàm trong thư viện GNU C yêu cầu xuất tất cả các ký hiệu. Nó cung cấp nhiều tiện ích hơn cho việc tạo backtraces so với libunwind. Và cuối cùng nhưng không kém phần quan trọng, nó không bị đánh bại bởi ASLR cũng như các phương pháp tiếp cận yêu cầu các công cụ bên ngoài như addr2line
.
Libbacktrace ban đầu là một phần của bản phân phối GCC, nhưng bây giờ nó đã được tác giả cung cấp dưới dạng thư viện độc lập theo giấy phép BSD:
https://github.com/ianlancetaylor/libbacktrace
Tại thời điểm viết bài, tôi sẽ không sử dụng bất kỳ thứ gì khác trừ khi tôi cần tạo dấu vết trên nền tảng không được hỗ trợ bởi libbacktrace.