GNU/Linux >> Belajar Linux >  >> Linux

cetak tumpukan panggilan dalam C atau C++

Untuk solusi khusus linux, Anda dapat menggunakan backtrace(3) yang hanya mengembalikan larik void * (sebenarnya masing-masing menunjuk ke alamat pengirim dari bingkai tumpukan yang sesuai). Untuk menerjemahkannya menjadi sesuatu yang berguna, ada backtrace_symbols(3).

Perhatikan bagian catatan di backtrace(3):

Nama simbol mungkin tidak tersedia tanpa penggunaan opsi tautan khusus. Untuk sistem yang menggunakan tautan GNU, perlu menggunakan opsi tautan-rdinamis. Perhatikan bahwa nama fungsi "statis" tidak diekspos, dan tidak akan tersedia di backtrace.


Apakah ada cara untuk membuang tumpukan panggilan dalam proses yang sedang berjalan di C atau C++ setiap kali fungsi tertentu dipanggil?

Anda dapat menggunakan fungsi makro sebagai ganti pernyataan pengembalian dalam fungsi tertentu.

Misalnya, alih-alih menggunakan return,

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

    ... do something ...

    return 0
}

Anda dapat menggunakan fungsi makro.

#include "c-callstack.h"

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

    ... do something ...

    NL_RETURN(0);
}

Setiap kali terjadi kesalahan dalam suatu fungsi, Anda akan melihat tumpukan panggilan bergaya Java seperti yang ditunjukkan di bawah ini.

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)

Kode sumber lengkap tersedia di sini.

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


Tingkatkan stacktrace

Didokumentasikan di:https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

Ini adalah opsi paling nyaman yang pernah saya lihat sejauh ini, karena ini:

  • sebenarnya dapat mencetak nomor baris.

    Itu hanya membuat panggilan ke addr2line namun, yang menambah ketergantungan eksternal yang jelek dan akan sangat memperlambat kode Anda jika Anda membuat banyak jejak

  • demangles secara default

  • Boost hanya header, jadi kemungkinan besar tidak perlu memodifikasi sistem build Anda

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

Sayangnya, ini tampaknya merupakan tambahan yang lebih baru, dan paket libboost-stacktrace-dev tidak ada di Ubuntu 16.04, hanya 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

Kita harus menambahkan -ldl pada akhirnya atau kompilasi gagal.

Keluaran:

 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

Output dan dijelaskan lebih lanjut pada bagian "glibc backtrace" di bawah, yang serupa.

Perhatikan bagaimana my_func_1(int) dan my_func_1(float) , yang rusak karena kelebihan fungsi, telah dibongkar dengan baik untuk kami.

Perhatikan bahwa int pertama panggilan dimatikan oleh satu baris (28 bukannya 27 dan yang kedua dimatikan oleh dua baris (27 bukannya 29). Disarankan dalam komentar bahwa ini karena alamat instruksi berikut sedang dipertimbangkan, yang membuat 27 menjadi 28 , dan 29 melompat dari lingkaran dan menjadi 27.

Kami kemudian mengamatinya dengan -O3 , hasilnya benar-benar dimutilasi:

 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

Jejak mundur pada umumnya dimutilasi secara tidak dapat diperbaiki oleh pengoptimalan. Pengoptimalan panggilan ekor adalah contoh penting dari itu:Apa itu pengoptimalan panggilan ekor?

Tolok ukur dijalankan pada -O3 :

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

Keluaran:

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

Jadi seperti yang diharapkan, kami melihat bahwa metode ini cenderung sangat lambat untuk panggilan eksternal ke addr2line , dan hanya dapat dilakukan jika sejumlah panggilan dilakukan.

Setiap pencetakan backtrace tampaknya memakan waktu ratusan milidetik, jadi berhati-hatilah bahwa jika backtrace sangat sering terjadi, kinerja program akan menurun secara signifikan.

Diuji di Ubuntu 19.10, GCC 9.2.1, tingkatkan 1.67.0.

glibc backtrace

Didokumentasikan di: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;
}

Kompilasi:

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

-rdynamic adalah opsi kunci yang diperlukan.

Jalankan:

./main.out

Keluaran:

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

Jadi kami segera melihat bahwa pengoptimalan sebaris terjadi, dan beberapa fungsi hilang dari pelacakan.

Jika kami mencoba mendapatkan alamat:

addr2line -e main.out 0x4008f9 0x4008fe

kami memperoleh:

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

yang benar-benar mati.

Jika kita melakukan hal yang sama dengan -O0 sebagai gantinya, ./main.out memberikan jejak lengkap yang benar:

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

lalu:

addr2line -e main.out 0x400a74 0x400a79

memberikan:

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

jadi antrean terputus satu saja, TODO kenapa? Tapi ini mungkin masih bisa digunakan.

Kesimpulan:backtrace hanya dapat ditampilkan dengan sempurna dengan -O0 . Dengan pengoptimalan, backtrace asli dimodifikasi secara mendasar dalam kode yang dikompilasi.

Saya tidak dapat menemukan cara sederhana untuk mendemangle simbol C++ secara otomatis dengan ini, namun berikut beberapa peretasan:

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

Diuji pada Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

Pembantu ini sedikit lebih nyaman daripada backtrace_symbols , dan menghasilkan keluaran yang pada dasarnya identik:

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

Diuji pada Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace dengan C++ demangling hack 1:-export-dynamic + dladdr

Diadaptasi dari:https://Gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

Ini adalah "peretasan" karena memerlukan perubahan ELF dengan -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);
}

Kompilasi dan jalankan:

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 

keluaran:

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]

Diuji pada Ubuntu 18.04.

glibc backtrace dengan C++ demangling hack 2:parse backtrace output

Ditampilkan di:https://panthema.net/2008/0901-stacktrace-demangled/

Ini adalah peretasan karena membutuhkan penguraian.

TODO mendapatkannya untuk dikompilasi dan ditampilkan di sini.

libunwind

TODO apakah ini memiliki keunggulan dibandingkan glibc backtrace? Output yang sangat mirip, juga memerlukan modifikasi perintah build, tetapi bukan bagian dari glibc sehingga memerlukan instalasi paket tambahan.

Kode diadaptasi dari: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;
}

Kompilasi dan jalankan:

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

Baik #define _XOPEN_SOURCE 700 harus di atas, atau kita harus menggunakan -std=gnu99 :

  • Apakah tipe `stack_t` tidak lagi ditentukan di linux?
  • Glibc - kesalahan di ucontext.h, tetapi hanya dengan -std=c11

Jalankan:

./main.out

Keluaran:

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

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

dan:

addr2line -e main.out 0x4007db 0x4007e2

memberikan:

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

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

dan:

addr2line -e main.out 0x4009f3 0x4009f8

memberikan:

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

Diuji pada Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

libunwind dengan demangling nama C++

Kode diadaptasi dari:https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

bersantai.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);
}

Kompilasi dan jalankan:

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

Keluaran:

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)

dan kemudian kita dapat menemukan baris my_func_2 dan my_func_1(int) dengan:

addr2line -e unwind.out 0x400c80 0x400cb7

yang memberikan:

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

TODO:mengapa garis terputus satu?

Diuji di Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.

otomatisasi GDB

Kita juga dapat melakukan ini dengan GDB tanpa mengkompilasi ulang dengan menggunakan:Bagaimana cara melakukan tindakan tertentu saat breakpoint tertentu terjadi di GDB?

Meskipun jika Anda akan sering mencetak backtrace, ini kemungkinan akan kurang cepat dibandingkan opsi lainnya, tetapi mungkin kita dapat mencapai kecepatan asli dengan compile code , tapi saya malas untuk mengujinya sekarang:Bagaimana cara memanggil perakitan di 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

Kompilasi dan jalankan:

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

Keluaran:

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 Saya ingin melakukan ini hanya dengan -ex dari baris perintah untuk tidak membuat main.gdb tapi saya tidak bisa mendapatkan commands untuk bekerja di sana.

Diuji di Ubuntu 19.04, GDB 8.2.

kernel Linux

Bagaimana cara mencetak jejak tumpukan utas saat ini di dalam kernel Linux?

libdwfl

Ini awalnya disebutkan di:https://stackoverflow.com/a/60713161/895245 dan ini mungkin metode terbaik, tetapi saya harus membuat tolok ukur sedikit lebih banyak, tetapi harap upvote jawaban itu.

TODO:Saya mencoba meminimalkan kode dalam jawaban itu, yang berfungsi, menjadi satu fungsi, tetapi segfaulting, beri tahu saya jika ada yang dapat menemukan alasannya.

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

Kompilasi dan jalankan:

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

Kami juga membutuhkan libunwind karena itu membuat hasil lebih tepat. Jika Anda melakukannya tanpa itu, itu berjalan, tetapi Anda akan melihat bahwa beberapa baris agak salah.

Keluaran:

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

Uji tolok ukur:

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

Keluaran:

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

Jadi kami melihat bahwa metode ini 10x lebih cepat daripada stacktrace Boost, dan karena itu mungkin dapat diterapkan untuk lebih banyak kasus penggunaan.

Diuji di Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.

libbacktrace

https://github.com/ianlancetaylor/libbacktrace

Mempertimbangkan penulis perpustakaan harcore, ini patut dicoba, mungkin The One. TODO periksa.

Pustaka C yang mungkin ditautkan ke program C/C++ untuk menghasilkan jejak balik simbolis

Mulai Oktober 2020, libbacktrace mendukung executable ELF, PE/COFF, Mach-O, dan XCOFF dengan informasi debug DWARF. Dengan kata lain, ini mendukung GNU/Linux, *BSD, macOS, Windows, dan AIX. Pustaka ini ditulis untuk mempermudah penambahan dukungan untuk file objek lain dan format debug.

Pustaka bergantung pada C++ unwind API yang ditentukan di https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html API ini disediakan oleh GCC dan dentang.

Lihat juga

  • Bagaimana cara mengambil pelacakan tumpukan di C?
  • Bagaimana cara membuat backtrace()/backtrace_symbols() mencetak nama fungsi?
  • Apakah ada cara portabel/sesuai standar untuk mendapatkan nama file dan nomor baris dalam pelacakan tumpukan?
  • Cara terbaik untuk memanggil gdb dari dalam program untuk mencetak stacktrace-nya?
  • pelacakan tumpukan otomatis saat gagal:
    • pada pengecualian C++:C++ menampilkan pelacakan tumpukan pada pengecualian
    • generik:Cara membuat stacktrace secara otomatis saat program saya mogok

Linux
  1. Bagaimana cara memetakan tumpukan untuk panggilan sistem clone () di linux?

  2. Cara mencetak waktu saat ini (dengan milidetik) menggunakan C++ / C++11

  3. Memanggil fungsi C dari kode C++

  1. Cetak nilai penunjuk tumpukan

  2. Cetak errno sebagai mnemonik?

  3. Bagaimana cara mencetak pesan ke stderr di Go?

  1. Perintah Preug – SyntaxError:Tanda kurung hilang saat memanggil 'print' – Solusi

  2. 2 Printer 1 Antrian

  3. Bisakah saya memanggil pushd/popd dan mencegahnya mencetak tumpukan?