GNU/Linux >> Linux の 問題 >  >> Linux

C または C++ でコール スタックを出力する

Linux のみのソリューションでは、単純に void * の配列を返す backtrace(3) を使用できます。 (実際、これらのそれぞれは、対応するスタック フレームからの戻りアドレスを指します)。これらを役に立つものに変換するために、backtrace_symbols(3) があります。

backtrace(3) のメモ セクションに注意してください:

<ブロック引用>

特別なリンカ オプションを使用しないと、シンボル名を使用できない場合があります。GNU リンカを使用するシステムでは、-rdynamic リンカ オプションを使用する必要があります。 「静的」関数の名前は公開されておらず、バックトレースで使用できないことに注意してください。


<ブロック引用>

特定の関数が呼び出されるたびに、C または 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)

完全なソース コードはこちらから入手できます。

https://github.com/Nanolat の c-callstack


スタックトレースをブースト

ドキュメント: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 はヘッダーのみであるため、ビルド システムを変更する必要はほとんどありません

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

残念ながら、これは最近追加されたもののようで、パッケージ libboost-stacktrace-dev Ubuntu 16.04 には存在せず、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

-ldl を追加する必要があります そうしないと、コンパイルが失敗します。

出力:

 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

出力については、以下の「glibc バックトレース」セクションで詳しく説明されていますが、これは類似しています。

my_func_1(int) の方法に注意してください および my_func_1(float) 、関数のオーバーロードが原因でマングルされていますが、適切にデマングルされました。

最初の int に注意してください 呼び出しは 1 行ずれています (27 ではなく 28 で、2 つ目の呼び出しは 2 行ずれています (29 ではなく 27)。これは、次の命令アドレスが考慮されているためであることがコメントで示唆されており、27 が 28 になります)。 、そして 29 がループから飛び出して 27 になります。

次に、 -O3 でそれを観察します 、出力は完全に破損しています:

 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

一般に、バックトレースは最適化によって取り返しのつかないほど破壊されます。テール コールの最適化はその顕著な例です。テール コールの最適化とは何ですか?

-O3 でのベンチマーク実行 :

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

出力:

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

したがって、予想どおり、このメソッドは addr2line への外部呼び出しに対して非常に遅いことがわかります。 、限られた数の呼び出しが行われている場合にのみ実行可能になります。

各バックトレース出力には数百ミリ秒かかるように見えるため、バックトレースが頻繁に発生すると、プログラムのパフォーマンスが大幅に低下することに注意してください。

Ubuntu 19.10、GCC 9.2.1、ブースト 1.67.0 でテスト済み。

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

コンパイル:

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++ シンボルを自動的にデマングルする簡単な方法を見つけることができませんでしたが、ここにいくつかのハックがあります:

  • https://panthema.net/2008/0901-stacktrace-demangled/
  • https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

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

-export-dynamic で ELF を変更する必要があるため、これは「ハック」です。 .

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/

解析が必要なため、これはハックです。

TODO を取得してコンパイルし、ここに表示します。

リブンウィンド

TODO これには glibc バックトレースよりも利点がありますか?非常によく似た出力で、ビルド コマンドも変更する必要がありますが、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 を使用する必要があります。 :

  • タイプ `stack_t` は Linux で定義されなくなりましたか?
  • Glibc - ucontext.h にエラーがありますが、-std=c11 のみです

実行:

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

Ubuntu 16.04、GCC 6.4.0、libunwind 1.1 でテスト済み。

C++ の名前デマングリングを使用した libunwind

コードは以下から改変: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:行が 1 行ずれているのはなぜですか?

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 カーネル内の現在のスレッド スタック トレースを出力する方法は?

libdwfl

これは最初に https://stackoverflow.com/a/60713161/895245 で言及されたもので、最良の方法かもしれませんが、もう少しベンチマークする必要がありますが、その回答に賛成票を投じてください。

TODO:機能していたその回答のコードを 1 つの関数に最小化しようとしましたが、セグメンテーション違反です。誰かが理由を見つけることができる場合はお知らせください。

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

// https://stackoverflow.com/questions/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);   // line 122
        my_func_1(2.0); // line 123
    }
}

コンパイルして実行:

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

結果をより正確にするため、libunwind も必要です。省略しても実行されますが、一部の行が少し間違っていることがわかります。

出力:

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d76 my_func_1(int) at /home/ciro/test/dwfl.cpp:111
3: 0x402dd1 main at /home/ciro/test/dwfl.cpp:122
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f [email protected]@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d5f my_func_1(double) at /home/ciro/test/dwfl.cpp:106
3: 0x402de2 main at /home/ciro/test/dwfl.cpp:123
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f [email protected]@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1

ベンチマークの実行:

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

出力:

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

したがって、この方法は Boost のスタックトレースよりも 10 倍高速であるため、より多くのユースケースに適用できる可能性があることがわかります。

Ubuntu 22.04 amd64、libdw-dev 0.186、libunwind 1.3.2 でテスト済み。

libbacktrace

https://github.com/ianlancetaylor/libbacktrace

harcore ライブラリの作者を考えると、これを試してみる価値はあります。 TODO をチェックしてください。

<ブロック引用>

シンボリック バックトレースを生成するために C/C++ プログラムにリンクできる C ライブラリ

2020 年 10 月現在、libbacktrace は DWARF デバッグ情報を含む ELF、PE/COFF、Mach-O、および XCOFF 実行可能ファイルをサポートしています。つまり、GNU/Linux、*BSD、macOS、Windows、および AIX をサポートしています。このライブラリは、他のオブジェクト ファイルとデバッグ形式のサポートを簡単に追加できるように作成されています。

このライブラリは、https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html で定義されている C++ アンワインド API に依存しています。この API は GCC と clang によって提供されます。

こちらもご覧ください

  • C でスタック トレースを取得するにはどうすればよいですか?
  • backtrace()/backtrace_symbols() で関数名を出力する方法
  • スタック トレースでファイル名と行番号を取得する移植可能な/標準準拠の方法はありますか?
  • プログラム内から gdb を呼び出してスタックトレースを出力する最良の方法は?
  • 失敗時の自動スタック トレース:
    • C++ 例外の場合:C++ は例外のスタック トレースを表示します
    • generic:プログラムがクラッシュしたときにスタックトレースを自動的に生成する方法

Linux
  1. Linuxでclone()システムコールのスタックをmmapする方法は?

  2. 構造体 timespec の書式設定

  3. C++ / C++11 を使用して現在の時刻 (ミリ秒単位) を出力する方法

  1. C++ コードから C 関数を呼び出す

  2. errno をニーモニックとして出力しますか?

  3. Goでstderrにメッセージを出力するにはどうすればよいですか?

  1. Preugコマンド– SyntaxError:「print」の呼び出しに括弧がありません–解決策

  2. 単純なC++スレッドプログラムをコンパイルできませんか?

  3. C++ で C 関数を呼び出す方法、C で C++ 関数を呼び出す方法 (C と C++ の混合)