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