Вывести стек вызовов на C или С++

Есть ли способ сбросить стек вызовов в запущенном процессе на C или С++ каждый раз, когда вызывается определенная функция? Что я имею в виду, это примерно так:

void foo()
{
   print_stack_trace();

   // foo body

   return
}

Где print_stack_trace работает аналогично caller в Perl.

Или что-то вроде этого:

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

    // etc...
}

где register_stack_trace_function помещает какую-то внутреннюю точку останова, которая вызовет печать трассировки стека всякий раз, когда вызывается foo.

Есть ли что-нибудь подобное в некоторой стандартной библиотеке C?

Я работаю над Linux, используя GCC.


Фон

У меня есть тестовый прогон, который ведет себя по-разному на основе некоторых ключей командной строки, которые не должны влиять на это поведение. Мой код имеет генератор псевдослучайных чисел, который, как я полагаю, вызывается по-разному на основе этих переключателей. Я хочу иметь возможность запускать тест с каждым набором переключателей и посмотреть, будет ли генератор случайных чисел вызываться по-разному для каждого из них.

Ответ 1

Для решения только для Linux вы можете использовать backtrace (3), который просто возвращает массив void * (фактически каждый из этих укажите адрес возврата из соответствующего кадра стека). Чтобы перевести их на что-то полезное, backtrace_symbols (3).

Обратите внимание на раздел примечаний в backtrace (3):

Названия символов могут быть недоступны без использования специального линкера опции.        Для систем, использующих GNU-компоновщик, необходимо использовать -динамический компоновщик        вариант. Обратите внимание, что имена "статических" функций не отображаются, и не будет        доступный в backtrace.

Ответ 2

Увеличить трассировку стека

Документировано по адресу: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

Это самый удобный вариант, который я видел до сих пор, потому что он:

  • может на самом деле распечатать номера строк.

    Он просто делает вызовы addr2line, однако, что некрасиво и может быть медленным, если вы берете слишком много следов.

  • по умолчанию деманглирует

  • Boost - это только заголовок, поэтому скорее всего не нужно изменять систему сборки

main.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) {
    my_func_2();
}

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

int main() {
    my_func_1(1);   /* line 19 */
    my_func_1(2.0); /* line 20 */
}

К сожалению, это, кажется, более свежее дополнение, а пакет libboost-stacktrace-dev отсутствует в Ubuntu 16.04, только 18.04:

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

Мы должны добавить -ldl в конце, иначе сборка не удалась.

Тогда:

./main.out

дает:

 0# my_func_2() at /root/lkmc/main.cpp:7
 1# my_func_1(int) at /root/lkmc/main.cpp:16
 2# main at /root/lkmc/main.cpp:20
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./main.out

 0# my_func_2() at /root/lkmc/main.cpp:7
 1# my_func_1(double) at /root/lkmc/main.cpp:12
 2# main at /root/lkmc/main.cpp:21
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./main.out

Обратите внимание, как my_func_1(int) и my_func_1(float), , которые изуродованы из-за перегрузки функции, были хорошо разобраны для нас.

И с -O3:

 0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
 1# my_func_1(double) at /root/lkmc/main.cpp:11
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./main.out

 0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
 1# main at /root/lkmc/main.cpp:21
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./main.out

Вывод и дальнейшее объяснение в разделе "обратная трассировка glibc" ниже, что аналогично.

Имейте в виду, что следы в целом непоправимо изуродованы оптимизацией. Ярким примером этого является оптимизация вызова хвоста: Что такое оптимизация вызова хвоста?

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

Протестировано на Ubuntu 18.04, GCC 7.3.0, boost 1.65.1.

glibc backtrace

Документировано по адресу: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

main.c

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

Compile:

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

-rdynamic - это обязательный параметр ключа.

Пробег:

./main.out

Выходы:

./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]

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

Если мы попытаемся получить адреса:

addr2line -e main.out 0x4008f9 0x4008fe

мы получаем:

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

который полностью выключен.

Если мы сделаем то же самое с -O0, вместо этого ./main.out даст правильный полный след:

./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]

а затем:

addr2line -e main.out 0x400a74 0x400a79

дает:

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

так что линии отключены одним, TODO почему? Но это все еще можно использовать.

Вывод: обратные следы могут отображаться только с -O0. При оптимизации исходная трассировка в корне изменяется в скомпилированном коде.

Я не смог найти простой способ автоматически разобрать символы C++ с этим, однако, вот некоторые хаки:

Протестировано на Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

Этот помощник немного удобнее, чем backtrace_symbols, и выдает в основном идентичный вывод:

/* 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("");
}

Протестировано в Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace с C++ демаглинг-хаком 1: -export-dynamic + dladdr

Адаптировано из: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Это "взлом", потому что он требует изменения ELF с помощью -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);
}

Скомпилируйте и запустите:

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 

выход:

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]

Проверено на Ubuntu 18.04.

glibc backtrace с C++ демаглинг-хаком 2: проанализировать вывод обратной трассировки

Показано на: https://panthema.net/2008/0901-stacktrace-demangled/

Это взлом, потому что он требует разбора.

ПОЛУЧИТЕ это, чтобы скомпилировать и показать здесь.

libunwind

TODO это имеет какое-либо преимущество перед glibc backtrace? Очень похожий вывод, также требует изменения команды сборки, но не является частью glibc, поэтому требует установки дополнительного пакета.

Код адаптирован из: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.c

/* 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;
}

Скомпилируйте и запустите:

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

Либо #define _XOPEN_SOURCE 700 должен быть сверху, либо мы должны использовать -std=gnu99:

Пробег:

./main.out

Выход:

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

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

и:

addr2line -e main.out 0x4007db 0x4007e2

дает:

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

с -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)

и:

addr2line -e main.out 0x4009f3 0x4009f8

дает:

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

glibc backtrace Протестировано на Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind с C++ именем демаглингом

Код адаптирован из: 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);
}

Скомпилируйте и запустите:

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

Выход:

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)

и затем мы можем найти строки my_func_2 и my_func_1(int) с помощью:

addr2line -e unwind.out 0x400c80 0x400cb7

который дает:

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

TODO: почему линии отключены на одну?

Протестировано на Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

Автоматизация GDB

Мы также можем сделать это с помощью GDB без перекомпиляции, используя: Как выполнить определенное действие при достижении определенной точки останова в GDB?

Хотя, если вы собираетесь много печатать обратную трассировку, это, вероятно, будет менее быстрым, чем другие варианты, но, возможно, мы сможем достичь собственной скорости с compile code, но мне лень проверять это сейчас: Как вызвать сборку в 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

Скомпилируйте и запустите:

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

Выход:

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]

TODO Я хотел сделать это только с помощью -ex из командной строки, чтобы не создавать main.gdb, но я не мог заставить commands работать там.

Протестировано в Ubuntu 19.04, GDB 8.2.

Ядро Linux

Как напечатать текущую трассировку стека потоков внутри ядра Linux?

Смотрите также

Ответ 3

Есть ли способ сбросить стек вызовов в запущенном процессе на C или С++ каждый раз, когда вызывается определенная функция?

Вы можете использовать макрофункцию вместо оператора return в конкретной функции.

Например, вместо использования return

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

    ... do something ...

    return 0
}

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

#include "c-callstack.h"

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

    ... do something ...

    NL_RETURN(0);
}

Всякий раз, когда в функции возникает ошибка, вы увидите стек вызовов в стиле Java, как показано ниже.

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)

Полный исходный код доступен здесь.

c-callstack на https://github.com/Nanolat

Ответ 4

Нет стандартизованного способа сделать это. Для окон функциональность предоставляется в DbgHelp library

Ответ 5

Еще один ответ на старую ветку.

Когда мне нужно это сделать, я обычно просто использую system() и pstack

Так что-то вроде этого:

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

Это выводы

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

Это должно работать на Linux, FreeBSD и Solaris. Я не думаю, что у macOS есть pstack или простой эквивалент, но у этого потока, похоже, есть альтернатива.

Ответ 6

Вы можете реализовать свои функции самостоятельно:

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

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

Это может показаться большой работой, но очень полезно.

Ответ 7

Конечно, следующий вопрос: будет ли это достаточно?

Основным недостатком трассировки стека является то, что у вас есть точная функция, называемая вами, у вас нет ничего, например значение ее аргументов, что очень полезно для отладки.

Если у вас есть доступ к gcc и gdb, я бы предложил использовать assert для проверки конкретного условия и создать дамп памяти, если он не выполняется. Конечно, это означает, что процесс остановится, но у вас будет полноценный отчет вместо простой трассировки стека.

Если вы хотите менее навязчивый способ, вы всегда можете использовать журнал. Есть очень эффективные средства ведения журнала, например Pantheios. Что еще раз может дать вам гораздо более точное изображение того, что происходит.

Ответ 8

Вы можете использовать Poppy для этого. Обычно он используется для сбора трассировки стека во время сбоя, но он также может выводить его для запускаемой программы.

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

Ответ 9

Вы можете использовать библиотеки Boost для печати текущего стека вызовов.

#include <boost/stacktrace.hpp>

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

Человек здесь: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html

Ответ 10

Я знаю, что эта ветка старая, но я думаю, что она может быть полезна для других людей. Если вы используете gcc, вы можете использовать его функции (опция -finstrument-functions) для регистрации любого вызова функции (вход и выход). Посмотрите на это для получения дополнительной информации: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

Таким образом, вы можете, например, помещать и загружать все вызовы в стек, а когда вы хотите распечатать его, вы просто посмотрите, что у вас в стеке.

Я проверил это, он работает отлично и очень удобно

ОБНОВЛЕНИЕ: вы также можете найти информацию о опции компиляции -finstrument-functions в документе GCC, касающуюся опций Instrumentation: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

Ответ 11

Вы можете использовать профилировщик GNU. Он также показывает график вызовов! команда gprof, и вам нужно скомпилировать свой код с некоторой опцией.

Ответ 12

Есть ли способ сбросить стек вызовов в запущенном процессе на C или С++ каждый раз, когда вызывается определенная функция?

Нет, нет, хотя могут существовать решения, зависящие от платформы.