in ngăn xếp cuộc gọi trong C hoặc C ++


120

Có cách nào để kết xuất ngăn xếp cuộc gọi trong một tiến trình đang chạy bằng C hoặc C ++ mỗi khi một hàm nhất định được gọi không? Những gì tôi có trong đầu là một cái gì đó như thế này:

void foo()
{
   print_stack_trace();

   // foo's body

   return
}

Nơi print_stack_tracehoạt động tương tự như callerở Perl.

Hoặc thứ gì đó giống thế này:

int main (void)
{
    // will print out debug info every time foo() is called
    register_stack_trace_function(foo); 

    // etc...
}

nơi register_stack_trace_functionđặt một số loại điểm ngắt bên trong sẽ tạo ra một dấu vết ngăn xếp được in bất cứ khi nào foođược gọi.

Có bất cứ điều gì như thế này tồn tại trong một số thư viện C tiêu chuẩn không?

Tôi đang làm việc trên Linux, sử dụng GCC.


Lý lịch

Tôi có một lần chạy thử nghiệm hoạt động khác nhau dựa trên một số công tắc dòng lệnh không ảnh hưởng đến hành vi này. Mã của tôi có một trình tạo số giả ngẫu nhiên mà tôi cho rằng đang được gọi khác nhau dựa trên các công tắc này. Tôi muốn có thể chạy thử nghiệm với từng bộ công tắc và xem liệu trình tạo số ngẫu nhiên có được gọi khác nhau cho từng bộ công tắc hay không.


1
@Armen, bạn có quen với cái nào trong số này không?
Nathan Fellman

1
@Nathan: Nếu trình gỡ lỗi của bạn là gdb, nó có thể xử lý trường hợp đó . Tôi không thể cho bạn biết về những người khác, nhưng tôi cho rằng gdb không đơn độc có chức năng này. Bên cạnh: Tôi chỉ xem bình luận trước đó của tôi. :: gag :: s/easier/either/làm thế quái nào mà điều đó xảy ra?
dmckee --- cựu điều hành kitten

2
@dmckee: Thực tế là như vậy s/either/easier. Những gì tôi cần làm với gdb là viết một tập lệnh ngắt trên hàm đó và in ra dấu vết ngăn xếp, sau đó tiếp tục. Bây giờ tôi nghĩ về nó, có lẽ đã đến lúc tôi học về gdb scripting.
Nathan Fellman

1
Gah! Đi ngủ một chút. Có thật ngay bây giờ ...
dmckee --- ex-moderator kitten

Câu trả lời:


79

Đối với một giải pháp chỉ dành cho linux, bạn có thể sử dụng backtrace (3) chỉ đơn giản là trả về một mảng void *(thực tế là mỗi điểm trong số này đến địa chỉ trả về từ khung ngăn xếp tương ứng). Để dịch những thứ này sang thứ gì đó sử dụng, có backtrace_symbols (3) .

Hãy chú ý đến phần ghi chú trong backtrace (3) :

Các tên ký hiệu có thể không có sẵn nếu không sử dụng các tùy chọn trình liên kết đặc biệt. Đối với các hệ thống sử dụng trình liên kết GNU, cần sử dụng tùy chọn trình liên kết -rdynamic. Lưu ý rằng tên của các hàm "tĩnh" không được hiển thị và sẽ không có sẵn trong backtrace.


10
FWIW, chức năng này cũng tồn tại trên Mac OS X: developer.apple.com/library/mac/#documentation/Darwin/Reference/…
EmeryBerger Ngày



glibcRất tiếc, trên Linux với các backtrace_symbolshàm không cung cấp tên hàm, tên tệp nguồn và số dòng.
Maxim Egorushkin

Ngoài việc sử dụng -rdynamic, hãy kiểm tra xem hệ thống xây dựng của bạn có thêm -fvisibility=hiddentùy chọn hay không! (vì nó sẽ loại bỏ hoàn toàn hiệu ứng của -rdynamic)
Dima Litvinov

36

Tăng stacktrace

Tài liệu tại: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

Đây là tùy chọn thuận tiện nhất mà tôi đã thấy cho đến nay, bởi vì nó:

  • thực sự có thể in ra số dòng.

    Tuy nhiên , nó chỉ thực hiện các cuộc gọiaddr2line , điều này xấu và có thể chậm nếu bạn đang theo dõi quá nhiều dấu vết.

  • hình tam giác theo mặc định

  • Boost chỉ là tiêu đề, vì vậy rất có thể không cần sửa đổi hệ thống xây dựng của bạn

boost_stacktrace.cpp

#include <iostream>

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

void my_func_2(void) {
    std::cout << boost::stacktrace::stacktrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);   // line 28
        my_func_1(2.0); // line 29
    }
}

Thật không may, nó có vẻ là một bổ sung gần đây hơn và gói libboost-stacktrace-devnày không có trong Ubuntu 16.04, chỉ có 18.04:

sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
  -Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
./boost_stacktrace.out

Chúng tôi phải thêm -ldlvào cuối nếu không quá trình biên dịch không thành công.

Đầu ra:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
 2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
 2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./boost_stacktrace.out

Đầu ra và được giải thích thêm trong phần "glibc backtrace" bên dưới, tương tự.

Lưu ý cách my_func_1(int)my_func_1(float), những thứ bị sai lệch do quá tải chức năng , đã được gỡ rối một cách độc đáo cho chúng tôi.

Lưu ý rằng các intcuộc gọi đầu tiên bị tắt bởi một dòng (28 thay vì 27 và cuộc gọi thứ hai bị tắt bởi hai dòng (27 thay vì 29). Nó được đề xuất trong các nhận xét rằng điều này là do địa chỉ hướng dẫn sau đang được xem xét, làm cho 27 trở thành 28, và 29 nhảy ra khỏi vòng lặp và trở thành 27.

Sau đó, chúng tôi quan sát thấy rằng với -O3, đầu ra hoàn toàn bị cắt xén:

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

 0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
 1# main at /home/ciro/test/boost_stacktrace.cpp:31
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./boost_stacktrace.out

Nói chung, các dấu hiệu tồn đọng bị cắt xén không thể sửa chữa được bằng cách tối ưu hóa. Tối ưu hóa cuộc gọi đuôi là một ví dụ đáng chú ý về điều đó: Tối ưu hóa cuộc gọi đuôi là gì?

Điểm chuẩn chạy trên -O3:

time  ./boost_stacktrace.out 1000 >/dev/null

Đầu ra:

real    0m43.573s
user    0m30.799s
sys     0m13.665s

Vì vậy, như mong đợi, chúng tôi thấy rằng phương pháp này có khả năng cực kỳ chậm đối với các cuộc gọi bên ngoài addr2linevà sẽ chỉ khả thi nếu một số lượng giới hạn cuộc gọi được thực hiện.

Mỗi lần in backtrace dường như mất hàng trăm mili giây, vì vậy hãy cảnh báo rằng nếu việc in backtrace thường xuyên xảy ra, hiệu suất chương trình sẽ bị ảnh hưởng đáng kể.

Đã thử nghiệm trên Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.

glibc backtrace

Tài liệu tại: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

C chính

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

/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
    char **strings;
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    strings = backtrace_symbols(array, size);
    for (i = 0; i < size; i++)
        printf("%s\n", strings[i]);
    puts("");
    free(strings);
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 33 */
    my_func_2(); /* line 34 */
    return 0;
}

Biên dịch:

gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
  -Wall -Wextra -pedantic-errors main.c

-rdynamic là tùy chọn bắt buộc quan trọng.

Chạy:

./main.out

Kết quả đầu ra:

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]

Vì vậy, chúng tôi ngay lập tức thấy rằng một tối ưu hóa nội tuyến đã xảy ra và một số chức năng đã bị mất khỏi dấu vết.

Nếu chúng tôi cố gắng lấy địa chỉ:

addr2line -e main.out 0x4008f9 0x4008fe

chúng tôi đạt được:

/home/ciro/main.c:21
/home/ciro/main.c:36

mà hoàn toàn tắt.

Nếu chúng tôi làm tương tự với -O0thay vào đó, hãy ./main.outcung cấp dấu vết đầy đủ chính xác:

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]

và sau đó:

addr2line -e main.out 0x400a74 0x400a79

cho:

/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35

vì vậy các dòng chỉ bị tắt bởi một, CẦN LÀM tại sao? Nhưng điều này vẫn có thể sử dụng được.

Kết luận: dấu vết chỉ có thể hiển thị hoàn hảo với -O0. Với tối ưu hóa, backtrace ban đầu được sửa đổi về cơ bản trong mã đã biên dịch.

Tuy nhiên, tôi không thể tìm thấy một cách đơn giản nào để tự động gỡ bỏ các ký hiệu C ++ bằng cách này, đây là một số thủ thuật:

Đã thử nghiệm trên Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

Trình trợ giúp này thuận tiện hơn một chút backtrace_symbolsvà tạo ra đầu ra về cơ bản giống hệt nhau:

/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
    size_t i, size;
    enum Constexpr { MAX_SIZE = 1024 };
    void *array[MAX_SIZE];
    size = backtrace(array, MAX_SIZE);
    backtrace_symbols_fd(array, size, STDOUT_FILENO);
    puts("");
}

Đã thử nghiệm trên Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtracevới C ++ demangling hack 1: -export-dynamic+dladdr

Phỏng theo: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Đây là một "hack" vì nó yêu cầu thay đổi ELF với -export-dynamic.

glibc_ldl.cpp

#include <dlfcn.h>     // for dladdr
#include <cxxabi.h>    // for __cxa_demangle

#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>

// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
    void *callstack[128];
    const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
    char buf[1024];
    int nFrames = backtrace(callstack, nMaxFrames);
    char **symbols = backtrace_symbols(callstack, nFrames);

    std::ostringstream trace_buf;
    for (int i = skip; i < nFrames; i++) {
        Dl_info info;
        if (dladdr(callstack[i], &info)) {
            char *demangled = NULL;
            int status;
            demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
            std::snprintf(
                buf,
                sizeof(buf),
                "%-3d %*p %s + %zd\n",
                i,
                (int)(2 + sizeof(void*) * 2),
                callstack[i],
                status == 0 ? demangled : info.dli_sname,
                (char *)callstack[i] - (char *)info.dli_saddr
            );
            free(demangled);
        } else {
            std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                i, (int)(2 + sizeof(void*) * 2), callstack[i]);
        }
        trace_buf << buf;
        std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
        trace_buf << buf;
    }
    free(symbols);
    if (nFrames == nMaxFrames)
        trace_buf << "[truncated]\n";
    return trace_buf.str();
}

void my_func_2(void) {
    std::cout << backtrace() << std::endl;
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Biên dịch và chạy:

g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
  -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out 

đầu ra:

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3             0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

1             0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2             0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3             0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4       0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5             0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]

Đã thử nghiệm trên Ubuntu 18.04.

glibc backtracevới C ++ demangling hack 2: phân tích cú pháp đầu ra backtrace

Được hiển thị tại: https://panthema.net/2008/0901-stacktrace-demangled/

Đây là một hack vì nó yêu cầu phân tích cú pháp.

CẦN LÀM nó để biên dịch và hiển thị nó ở đây.

libunwind

VIỆC CẦN LÀM điều này có bất kỳ lợi thế nào so với nền tảng glibc không? Đầu ra tương tự, cũng yêu cầu sửa đổi lệnh xây dựng, nhưng không phải là một phần của glibc, vì vậy yêu cầu cài đặt gói bổ sung.

Mã chuyển thể từ: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

C chính

/* This must be on top. */
#define _XOPEN_SOURCE 700

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

/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
    char sym[256];
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
            break;
        }
        printf("0x%lx:", pc);
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
            printf(" (%s+0x%lx)\n", sym, offset);
        } else {
            printf(" -- error: unable to obtain symbol name for this frame\n");
        }
    }
    puts("");
}

void my_func_3(void) {
    print_trace();
}

void my_func_2(void) {
    my_func_3();
}

void my_func_1(void) {
    my_func_3();
}

int main(void) {
    my_func_1(); /* line 46 */
    my_func_2(); /* line 47 */
    return 0;
}

Biên dịch và chạy:

sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
  -Wall -Wextra -pedantic-errors main.c -lunwind

Hoặc #define _XOPEN_SOURCE 700phải ở trên cùng, hoặc chúng ta phải sử dụng -std=gnu99:

Chạy:

./main.out

Đầu ra:

0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)

và:

addr2line -e main.out 0x4007db 0x4007e2

cho:

/home/ciro/main.c:34
/home/ciro/main.c:49

Với -O0:

0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)

và:

addr2line -e main.out 0x4009f3 0x4009f8

cho:

/home/ciro/main.c:47
/home/ciro/main.c:48

Đã thử nghiệm trên Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind với cách tách tên C ++

Mã chuyển thể từ: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

unwind.cpp

#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>

void backtrace() {
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize cursor to current frame for local unwinding.
  unw_getcontext(&context);
  unw_init_local(&cursor, &context);

  // Unwind frames one by one, going up the frame stack.
  while (unw_step(&cursor) > 0) {
    unw_word_t offset, pc;
    unw_get_reg(&cursor, UNW_REG_IP, &pc);
    if (pc == 0) {
      break;
    }
    std::printf("0x%lx:", pc);

    char sym[256];
    if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
      char* nameptr = sym;
      int status;
      char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
      if (status == 0) {
        nameptr = demangled;
      }
      std::printf(" (%s+0x%lx)\n", nameptr, offset);
      std::free(demangled);
    } else {
      std::printf(" -- error: unable to obtain symbol name for this frame\n");
    }
  }
}

void my_func_2(void) {
    backtrace();
    std::cout << std::endl; // line 43
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}  // line 54

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

Biên dịch và chạy:

sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
  -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out

Đầu ra:

0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)

và sau đó chúng ta có thể tìm thấy các dòng của my_func_2my_func_1(int)với:

addr2line -e unwind.out 0x400c80 0x400cb7

mang lại:

/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54

VIỆC CẦN LÀM: tại sao các dòng lại bị lệch nhau?

Đã thử nghiệm trên Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

Tự động hóa GDB

Chúng tôi cũng có thể làm điều này với GDB mà không cần biên dịch lại bằng cách sử dụng: Làm thế nào để thực hiện một hành động cụ thể khi một điểm ngắt nhất định bị chạm trong GDB?

Mặc dù nếu bạn định in backtrace nhiều, điều này có thể sẽ kém nhanh hơn so với các tùy chọn khác, nhưng có lẽ chúng ta có thể đạt được tốc độ gốc compile code, nhưng tôi lười thử nghiệm nó ngay bây giờ: Làm thế nào để gọi assembly trong gdb?

main.cpp

void my_func_2(void) {}

void my_func_1(double f) {
    my_func_2();
}

void my_func_1(int i) {
    my_func_2();
}

int main() {
    my_func_1(1);
    my_func_1(2.0);
}

main.gdb

start
break my_func_2
commands
  silent
  backtrace
  printf "\n"
  continue
end
continue

Biên dịch và chạy:

g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out

Đầu ra:

Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.

Temporary breakpoint 1, main () at main.cpp:12
12          my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0  my_func_2 () at main.cpp:1
#1  0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2  0x0000555555555162 in main () at main.cpp:12

#0  my_func_2 () at main.cpp:1
#1  0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2  0x000055555555516f in main () at main.cpp:13

[Inferior 1 (process 14193) exited normally]

CẦN LÀM Tôi muốn làm điều này chỉ với -exdòng lệnh để không phải tạo main.gdbnhưng tôi không thể commandslàm việc ở đó.

Đã thử nghiệm trong Ubuntu 19.04, GDB 8.2.

nền tảng Linux

Làm cách nào để in dấu vết ngăn xếp luồng hiện tại bên trong nhân Linux?

libdwfl

Điều này ban đầu được đề cập tại: https://stackoverflow.com/a/60713161/895245 và nó có thể là phương pháp tốt nhất, nhưng tôi phải đánh giá điểm chuẩn hơn một chút, nhưng hãy ủng hộ câu trả lời đó.

VIỆC CẦN LÀM: Tôi đã cố gắng thu nhỏ mã trong câu trả lời đó, đang hoạt động, thành một hàm duy nhất, nhưng nó đang ở chế độ mặc định, hãy cho tôi biết nếu ai đó có thể tìm thấy lý do.

dwfl.cpp

#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>

#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid

// /programming/281818/unmangling-the-result-of-stdtype-infoname
std::string demangle(const char* name) {
    int status = -4;
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}

std::string debug_info(Dwfl* dwfl, void* ip) {
    std::string function;
    int line = -1;
    char const* file;
    uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
    Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
    char const* name = dwfl_module_addrname(module, ip2);
    function = name ? demangle(name) : "<unknown>";
    if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
        Dwarf_Addr addr;
        file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
    }
    std::stringstream ss;
    ss << ip << ' ' << function;
    if (file)
        ss << " at " << file << ':' << line;
    ss << std::endl;
    return ss.str();
}

std::string stacktrace() {
    // Initialize Dwfl.
    Dwfl* dwfl = nullptr;
    {
        Dwfl_Callbacks callbacks = {};
        char* debuginfo_path = nullptr;
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;
        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);
        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    // Loop over stack frames.
    std::stringstream ss;
    {
        void* stack[512];
        int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
        for (int i = 0; i < stack_size; ++i) {
            ss << i << ": ";

            // Works.
            ss << debug_info(dwfl, stack[i]);

#if 0
            // TODO intended to do the same as above, but segfaults,
            // so possibly UB In above function that does not blow up by chance?
            void *ip = stack[i];
            std::string function;
            int line = -1;
            char const* file;
            uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
            Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
            char const* name = dwfl_module_addrname(module, ip2);
            function = name ? demangle(name) : "<unknown>";
            // TODO if I comment out this line it does not blow up anymore.
            if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
              Dwarf_Addr addr;
              file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
            }
            ss << ip << ' ' << function;
            if (file)
                ss << " at " << file << ':' << line;
            ss << std::endl;
#endif
        }
    }
    dwfl_end(dwfl);
    return ss.str();
}

void my_func_2() {
    std::cout << stacktrace() << std::endl;
    std::cout.flush();
}

void my_func_1(double f) {
    (void)f;
    my_func_2();
}

void my_func_1(int i) {
    (void)i;
    my_func_2();
}

int main(int argc, char **argv) {
    long long unsigned int n;
    if (argc > 1) {
        n = strtoul(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    for (long long unsigned int i = 0; i < n; ++i) {
        my_func_1(1);
        my_func_1(2.0);
    }
}

Biên dịch và chạy:

sudo apt install libdw-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
./dwfl.out

Đầu ra:

0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d7d my_func_1(int) at /home/ciro/test/dwfl.cpp:112
3: 0x402de0 main at /home/ciro/test/dwfl.cpp:123
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1

0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d66 my_func_1(double) at /home/ciro/test/dwfl.cpp:107
3: 0x402df1 main at /home/ciro/test/dwfl.cpp:121
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1

Điểm chuẩn chạy:

g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null

Đầu ra:

real    0m3.751s
user    0m2.822s
sys     0m0.928s

Vì vậy, chúng tôi thấy rằng phương pháp này nhanh hơn 10 lần so với stacktrace của Boost và do đó có thể áp dụng cho nhiều trường hợp sử dụng hơn.

Đã thử nghiệm trong Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.

Xem thêm


1
Tất cả các "VIỆC CẦN LÀM: các dòng tắt từng dòng" là do số dòng được lấy từ đầu của biểu thức tiếp theo.
SS Anne

6

Không có cách tiêu chuẩn hóa để làm điều đó. Đối với các cửa sổ, chức năng được cung cấp trong thư viện DbgHelp


6

Có cách nào để kết xuất ngăn xếp cuộc gọi trong một tiến trình đang chạy bằng C hoặc C ++ mỗi khi một hàm nhất định được gọi không?

Bạn có thể sử dụng một hàm macro thay vì câu lệnh trả về trong một hàm cụ thể.

Ví dụ: thay vì sử dụng return,

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

Bạn có thể sử dụng một hàm macro.

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

Bất cứ khi nào có lỗi xảy ra trong một hàm, bạn sẽ thấy ngăn xếp cuộc gọi kiểu Java như hình dưới đây.

Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)

Mã nguồn đầy đủ có sẵn ở đây.

c-callstack tại https://github.com/Nanolat


6

Một câu trả lời khác cho một chủ đề cũ.

Khi cần làm điều này, tôi thường chỉ sử dụng system()pstack

Vì vậy, một cái gì đó như thế này:

#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>

void f()
{
    pid_t myPid = getpid();
    std::string pstackCommand = "pstack ";
    std::stringstream ss;
    ss << myPid;
    pstackCommand += ss.str();
    system(pstackCommand.c_str());
}

void g()
{
   f();
}


void h()
{
   g();
}

int main()
{
   h();
}

Kết quả này

#0  0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1  0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2  0x0000000000400c3c in f() ()
#3  0x0000000000400cc5 in g() ()
#4  0x0000000000400cd1 in h() ()
#5  0x0000000000400cdd in main ()

Điều này sẽ hoạt động trên Linux, FreeBSD và Solaris. Tôi không nghĩ rằng macOS có pstack hoặc tương đương đơn giản, nhưng chủ đề này dường như có một giải pháp thay thế .

Nếu bạn đang sử dụng C, thì bạn sẽ cần sử dụng Ccác hàm chuỗi.

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

void f()
{
    pid_t myPid = getpid();
    /*
      length of command 7 for 'pstack ', 7 for the PID, 1 for nul
    */
    char pstackCommand[7+7+1];
    sprintf(pstackCommand, "pstack %d", (int)myPid);
    system(pstackCommand);
}

Tôi đã sử dụng 7 cho số chữ số tối đa trong PID, dựa trên bài đăng này .


Điểm tốt, vì chủ thể yêu cầu C. Không, nó sẽ cần điều chỉnh, vì std :: string chỉ là C ++. Tôi sẽ cập nhật câu trả lời của mình bằng phiên bản C.
Paul Floyd

5

Linux cụ thể, TLDR:

  1. backtracetrong chỉ glibctạo ra các đường xếp chồng chính xác khi -lunwindđược liên kết (tính năng dành riêng cho nền tảng không có tài liệu).
  2. Để xuất ra tên hàm , sử dụng tệp nguồnsố dòng#include <elfutils/libdwfl.h> (thư viện này chỉ được ghi lại trong tệp tiêu đề của nó). backtrace_symbolsbacktrace_symbolsd_fdít thông tin nhất.

Trên Linux hiện đại, bạn có thể lấy địa chỉ stacktrace bằng hàm backtrace. Cách không có giấy tờ để tạo backtraceđịa chỉ chính xác hơn trên các nền tảng phổ biến là liên kết với -lunwind( libunwind-devtrên Ubuntu 18.04) (xem ví dụ đầu ra bên dưới). backtracesử dụng chức năng _Unwind_Backtracevà theo mặc định, cái sau xuất phát libgcc_s.so.1và việc triển khai đó là dễ di chuyển nhất. Khi -lunwindđược liên kết, nó cung cấp phiên bản chính xác hơn _Unwind_Backtracenhưng thư viện này ít di động hơn (xem các kiến ​​trúc được hỗ trợ trong libunwind/src).

Thật không may, đồng hành backtrace_symbolsdvà các backtrace_symbols_fdhàm đã không thể phân giải địa chỉ stacktrace thành tên hàm với tên tệp nguồn và số dòng trong một thập kỷ gần đây (xem đầu ra ví dụ bên dưới).

Tuy nhiên, có một phương pháp khác để phân giải địa chỉ thành ký hiệu và nó tạo ra các dấu vết hữu ích nhất với tên hàm , tệp nguồnsố dòng . Phương pháp là đến #include <elfutils/libdwfl.h>và liên kết với -ldw( libdw-devtrên Ubuntu 18.04).

Làm việc ví dụ C ++ ( test.cc):

#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>

#include <boost/core/demangle.hpp>

#include <execinfo.h>
#include <elfutils/libdwfl.h>

struct DebugInfoSession {
    Dwfl_Callbacks callbacks = {};
    char* debuginfo_path = nullptr;
    Dwfl* dwfl = nullptr;

    DebugInfoSession() {
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;

        dwfl = dwfl_begin(&callbacks);
        assert(dwfl);

        int r;
        r = dwfl_linux_proc_report(dwfl, getpid());
        assert(!r);
        r = dwfl_report_end(dwfl, nullptr, nullptr);
        assert(!r);
        static_cast<void>(r);
    }

    ~DebugInfoSession() {
        dwfl_end(dwfl);
    }

    DebugInfoSession(DebugInfoSession const&) = delete;
    DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};

struct DebugInfo {
    void* ip;
    std::string function;
    char const* file;
    int line;

    DebugInfo(DebugInfoSession const& dis, void* ip)
        : ip(ip)
        , file()
        , line(-1)
    {
        // Get function name.
        uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
        Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
        char const* name = dwfl_module_addrname(module, ip2);
        function = name ? boost::core::demangle(name) : "<unknown>";

        // Get source filename and line number.
        if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
            Dwarf_Addr addr;
            file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
        }
    }
};

std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
    s << di.ip << ' ' << di.function;
    if(di.file)
        s << " at " << di.file << ':' << di.line;
    return s;
}

void terminate_with_stacktrace() {
    void* stack[512];
    int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);

    // Print the exception info, if any.
    if(auto ex = std::current_exception()) {
        try {
            std::rethrow_exception(ex);
        }
        catch(std::exception& e) {
            std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
        }
        catch(...) {
            std::cerr << "Fatal unknown exception.\n";
        }
    }

    DebugInfoSession dis;
    std::cerr << "Stacktrace of " << stack_size << " frames:\n";
    for(int i = 0; i < stack_size; ++i) {
        std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
    }
    std::cerr.flush();

    std::_Exit(EXIT_FAILURE);
}

int main() {
    std::set_terminate(terminate_with_stacktrace);
    throw std::runtime_error("test exception");
}

Được biên dịch trên Ubuntu 18.04.4 LTS với gcc-8.3:

g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind

Kết quả đầu ra:

Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start

Khi không có -lunwindliên kết nào được liên kết, nó tạo ra một đường xếp chồng kém chính xác hơn:

0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start

Để so sánh, backtrace_symbols_fdđầu ra cho cùng một stacktrace ít thông tin nhất:

/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]

Trong một phiên bản sản xuất (cũng như phiên bản ngôn ngữ C), bạn có thể muốn làm cho mã này thêm mạnh mẽ bằng cách thay thế boost::core::demangle, std::stringstd::coutvới các cuộc gọi cơ bản của họ.

Bạn cũng có thể ghi đè __cxa_throwđể nắm bắt stacktrace khi một ngoại lệ được ném ra và in nó khi ngoại lệ được bắt. Vào thời điểm nó đi vào catchkhối, ngăn xếp vẫn chưa được liên kết, vì vậy đã quá muộn để gọi backtrace, và đây là lý do tại sao ngăn xếp phải được nắm bắt trên throwđó được thực hiện bởi hàm __cxa_throw. Lưu ý rằng trong một chương trình đa luồng __cxa_throwcó thể được gọi đồng thời bởi nhiều luồng, vì vậy nếu nó nắm bắt stacktrace thành một mảng toàn cục thì phải như vậy thread_local.


1
Câu trả lời hay! Cũng được nghiên cứu kỹ lưỡng.
SS Anne

@SSAnne Rất tốt, cảm ơn bạn. -lunwindVấn đề đó đã được phát hiện khi thực hiện bài đăng này, trước đây tôi đã sử dụng libunwindtrực tiếp để lấy stacktrace và sẽ đăng nó, nhưng backtracenó xảy ra với tôi khi -lunwindđược liên kết.
Maxim Egorushkin

1
@SSAnne Có thể là do tác giả ban đầu của thư viện David Mosberger ban đầu tập trung vào IA-64 nhưng sau đó thư viện có nhiều lực kéo hơn nongnu.org/libunwind/people.html . gcckhông làm lộ API, có đúng không?
Maxim Egorushkin

3

Bạn có thể tự triển khai chức năng:

Sử dụng một ngăn xếp (chuỗi) toàn cục và khi bắt đầu mỗi hàm đẩy tên hàm và các giá trị khác như vậy (ví dụ: tham số) vào ngăn xếp này; khi thoát khỏi chức năng bật lại.

Viết một hàm sẽ in ra nội dung ngăn xếp khi nó được gọi và sử dụng hàm này trong hàm mà bạn muốn xem ngăn xếp.

Điều này nghe có vẻ nhiều việc nhưng lại khá hữu ích.


2
Đúng ra tôi không nên làm vậy. Thay vào đó, tôi sẽ tạo một trình bao bọc sử dụng các API cụ thể của nền tảng cơ bản (xem bên dưới). Số lượng công việc có thể giống nhau, nhưng khoản đầu tư sẽ nhanh hơn.
Paul Michalik

3
@paul: câu trả lời của bạn đề cập đến cửa sổ khi OP chỉ định rõ ràng linux ... nhưng có thể hữu ích cho những người dùng cửa sổ xuất hiện ở đây.
slashmais

Đúng vậy, tôi đã bỏ qua điều đó..Hm, đó là câu cuối cùng của câu hỏi, vì vậy có lẽ người đăng nên sửa đổi yêu cầu của mình để đề cập đến nền tảng mục tiêu của anh ấy / cô ấy ở một nơi nổi bật hơn.
Paul Michalik

1
Đây sẽ là một ý tưởng hay, ngoại trừ codebase của tôi bao gồm vài chục tệp chứa vài trăm (nếu không phải là vài nghìn) tệp, vì vậy điều này là không khả thi.
Nathan Fellman

có thể không nếu bạn hack một script sed / perl để thêm vào sau mỗi khai báo hàm call_registror MY_SUPERSECRETNAME(__FUNCTION__);, nó đẩy đối số trong hàm tạo của nó và bật lên trong hàm hủy FUNCTION luôn đại diện cho tên của hàm hiện tại.
flownt

2

Tất nhiên câu hỏi tiếp theo là: liệu điều này có đủ không?

Nhược điểm chính của dấu vết ngăn xếp là lý do tại sao bạn có hàm chính xác được gọi là bạn không có bất kỳ thứ gì khác, như giá trị của các đối số của nó, rất hữu ích cho việc gỡ lỗi.

Nếu bạn có quyền truy cập vào gcc và gdb, tôi khuyên bạn nên sử dụng assertđể kiểm tra một điều kiện cụ thể và tạo kết xuất bộ nhớ nếu nó không được đáp ứng. Tất nhiên điều này có nghĩa là quá trình sẽ dừng lại, nhưng bạn sẽ có một báo cáo chính thức đầy đủ thay vì chỉ là một dấu vết ngăn xếp.

Nếu bạn muốn một cách ít gây khó chịu hơn, bạn luôn có thể sử dụng ghi nhật ký. Có những phương tiện ghi nhật ký rất hiệu quả, như Pantheios chẳng hạn. Điều này một lần nữa có thể cung cấp cho bạn hình ảnh chính xác hơn nhiều về những gì đang diễn ra.


1
Tất nhiên nó có thể là không đủ, nhưng nếu tôi có thể thấy rằng chức năng được gọi đúng chỗ với một cấu hình chứ không phải với cấu hình khác, thì đó là một nơi khá tốt để bắt đầu.
Nathan Fellman

2

Bạn có thể sử dụng Poppy cho việc này. Nó thường được sử dụng để thu thập dấu vết ngăn xếp trong sự cố nhưng nó cũng có thể xuất ra cho một chương trình đang chạy.

Bây giờ đây là phần tốt: nó có thể xuất ra các giá trị tham số thực tế cho từng hàm trên ngăn xếp và thậm chí cả các biến cục bộ, bộ đếm vòng lặp, v.v.


2

Tôi biết chủ đề này đã cũ, nhưng tôi nghĩ nó có thể hữu ích cho những người khác. Nếu bạn đang sử dụng gcc, bạn có thể sử dụng các tính năng công cụ của nó (tùy chọn chức năng-kinh nguyệt) để ghi lại bất kỳ lệnh gọi chức năng nào (vào và ra). Hãy xem phần này để biết thêm thông tin: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

Vì vậy, bạn có thể ví dụ như đẩy và đưa mọi cuộc gọi vào một ngăn xếp, và khi bạn muốn in nó, bạn chỉ cần xem những gì bạn có trong ngăn xếp của mình.

Tôi đã thử nghiệm nó, nó hoạt động hoàn hảo và rất tiện dụng

CẬP NHẬT: bạn cũng có thể tìm thấy thông tin về tùy chọn biên dịch -finticment-functions trong tài liệu GCC liên quan đến các tùy chọn Instrumentation: https://gcc.gnu.org/onlineocs/gcc/Inticmentation-Options.html


Bạn cũng nên liên kết đến tài liệu GCC trong trường hợp bài viết bị hỏng.
HolyBlackCat

Cảm ơn bạn, bạn đã đúng. Vì vậy tôi đã thêm một UPDATE trong bài viết của tôi với một liên kết đến doc gcc
François

2

Bạn có thể sử dụng thư viện Boost để in callstack hiện tại.

#include <boost/stacktrace.hpp>

// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

Man ở đây: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html


Tôi gặp lỗi cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dlltrên Win10.
zwcloud

0

Bạn có thể sử dụng trình biên dịch GNU. Nó cũng hiển thị biểu đồ cuộc gọi! lệnh là gprofvà bạn cần phải biên dịch mã của mình với một số tùy chọn.


-6

Có cách nào để kết xuất ngăn xếp cuộc gọi trong một tiến trình đang chạy bằng C hoặc C ++ mỗi khi một hàm nhất định được gọi không?

Không, không có, mặc dù có thể tồn tại các giải pháp phụ thuộc vào nền tảng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.