Apakah ada cara untuk membuang tumpukan panggilan dalam proses yang sedang berjalan di C atau C ++ setiap kali fungsi tertentu dipanggil? Yang ada dalam pikiran saya adalah seperti ini:
void foo()
{
print_stack_trace();
// foo's body
return
}
Dimana print_stack_trace
cara kerjanya mirip dengan caller
di Perl.
Atau sesuatu seperti ini:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
di mana register_stack_trace_function
menempatkan semacam breakpoint internal yang akan menyebabkan jejak tumpukan dicetak setiap kali foo
dipanggil.
Apakah yang seperti ini ada di beberapa pustaka C standar?
Saya bekerja di Linux, menggunakan GCC.
Latar Belakang
Saya memiliki uji coba yang berperilaku berbeda berdasarkan beberapa sakelar baris perintah yang seharusnya tidak memengaruhi perilaku ini. Kode saya memiliki generator nomor pseudo-acak yang saya asumsikan dipanggil berbeda berdasarkan sakelar ini. Saya ingin dapat menjalankan pengujian dengan setiap set sakelar dan melihat apakah generator nomor acak dipanggil berbeda untuk masing-masing.
s/easier/either/
bagaimana sih itu bisa terjadi?s/either/easier
. Yang perlu saya lakukan dengan gdb adalah menulis skrip yang melanggar fungsi itu dan mencetak jejak tumpukan, lalu melanjutkan. Sekarang setelah dipikir-pikir, mungkin sudah waktunya saya belajar tentang gdb scripting.Jawaban:
Untuk solusi khusus linux, Anda dapat menggunakan lacak balik (3) yang hanya mengembalikan larik
void *
(sebenarnya masing-masing titik ini mengarah ke alamat pengirim dari bingkai tumpukan yang sesuai). Untuk menerjemahkan ini menjadi sesuatu yang berguna, ada backtrace_symbols (3) .Perhatikan bagian catatan di lacak balik (3) :
sumber
glibc
Sayangnya, di Linux denganbacktrace_symbols
fungsi tidak menyediakan nama fungsi, nama file sumber, dan nomor baris.-rdynamic
, periksa juga apakah sistem build Anda tidak menambahkan-fvisibility=hidden
opsi! (karena itu akan benar-benar membuang efek-rdynamic
)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:
benar-benar dapat mencetak nomor baris.
Itu hanya membuat panggilan ke
addr2line
Namun , yang jelek dan mungkin lambat jika Anda mengambil terlalu banyak jejak.demangles secara default
Boost hanya untuk tajuk, jadi kemungkinan besar tidak perlu mengubah sistem build Anda
boost_stacktrace.cpp
Sayangnya, ini tampaknya merupakan tambahan yang lebih baru, dan paket
libboost-stacktrace-dev
tersebut tidak ada di Ubuntu 16.04, hanya 18.04:Kami harus menambahkan
-ldl
di akhir atau kompilasi gagal.Keluaran:
Keluaran dan selanjutnya dijelaskan pada bagian "lacak balik glibc" di bawah ini, yang analog.
Perhatikan bagaimana
my_func_1(int)
danmy_func_1(float)
, yang rusak karena kelebihan beban fungsi , telah dirusak dengan baik untuk kami.Perhatikan bahwa
int
panggilan pertama dimatikan oleh satu baris (28 bukannya 27 dan yang kedua terputus oleh dua baris (27 bukannya 29). Disarankan dalam komentar bahwa ini karena alamat instruksi berikut sedang dipertimbangkan, yang mana membuat 27 menjadi 28, dan 29 melompat keluar lingkaran dan menjadi 27.Kami kemudian mengamati bahwa dengan
-O3
, output sepenuhnya dimutilasi:Backtrack pada umumnya tidak dapat diperbaiki lagi oleh pengoptimalan. Pengoptimalan tail call adalah contoh penting dari itu: Apa itu pengoptimalan panggilan ekor?
Tolok ukur berjalan pada
-O3
:Keluaran:
Jadi seperti yang diharapkan, kami melihat bahwa metode ini kemungkinan besar sangat lambat untuk panggilan eksternal ke
addr2line
, dan hanya dapat dilakukan jika sejumlah panggilan sedang dibuat.Setiap pencetakan lacak balik tampaknya memakan waktu ratusan milidetik, jadi berhati-hatilah bahwa jika lacak balik sangat sering terjadi, kinerja program akan sangat terganggu.
Diuji pada Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.
glibc
backtrace
Didokumentasikan di: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
main.c
Menyusun:
-rdynamic
adalah pilihan kunci yang diperlukan.Lari:
Keluaran:
Jadi kami segera melihat bahwa pengoptimalan sebaris terjadi, dan beberapa fungsi hilang dari pelacakan.
Jika kami mencoba mendapatkan alamat:
kami memperoleh:
yang benar-benar mati.
Jika kita melakukan hal yang sama
-O0
sebagai gantinya,./main.out
berikan jejak lengkap yang benar:lalu:
memberikan:
jadi garisnya putus hanya satu, TODO kenapa? Tapi ini mungkin masih bisa digunakan.
Kesimpulan: backtraces hanya mungkin dapat ditampilkan dengan sempurna
-O0
. Dengan pengoptimalan, lacak balik asli secara fundamental diubah dalam kode yang dikompilasi.Saya tidak dapat menemukan cara sederhana untuk secara otomatis menghapus simbol C ++ dengan ini, namun berikut beberapa retasan:
Diuji pada Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace_symbols_fd
Helper ini sedikit lebih nyaman daripada
backtrace_symbols
, dan menghasilkan keluaran yang pada dasarnya identik: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 "retasan" karena memerlukan penggantian ELF dengan
-export-dynamic
.glibc_ldl.cpp
Kompilasi dan jalankan:
keluaran:
Diuji di Ubuntu 18.04.
glibc
backtrace
with C ++ demangling hack 2: parse backtrace outputDitampilkan di: https://panthema.net/2008/0901-stacktrace-demangled/
Ini adalah peretasan karena membutuhkan penguraian.
TODO mendapatkannya untuk dikompilasi dan menunjukkannya di sini.
libunwind
TODO, apakah ini memiliki keunggulan dibandingkan glibc backtrace? Keluaran yang sangat mirip, juga membutuhkan modifikasi perintah build, tetapi bukan bagian dari glibc sehingga membutuhkan instalasi paket tambahan.
Kode diadaptasi dari: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
main.c
Kompilasi dan jalankan:
Entah
#define _XOPEN_SOURCE 700
harus berada di atas, atau kita harus menggunakan-std=gnu99
:Lari:
Keluaran:
dan:
memberikan:
Dengan
-O0
:dan:
memberikan:
Diuji pada Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind dengan nama C ++ demangling
Kode diadaptasi dari: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
unwind.cpp
Kompilasi dan jalankan:
Keluaran:
dan kemudian kita dapat menemukan baris dari
my_func_2
danmy_func_1(int)
dengan:pemberian yang mana:
TODO: mengapa garis terputus satu per satu?
Diuji pada Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
Otomatisasi GDB
Kami juga dapat melakukan ini dengan GDB tanpa kompilasi ulang dengan menggunakan: Bagaimana melakukan tindakan tertentu ketika breakpoint tertentu tercapai di GDB?
Meskipun jika Anda akan banyak mencetak backtrace, ini kemungkinan akan kurang cepat daripada opsi lain, tetapi mungkin kami dapat mencapai kecepatan asli dengan
compile code
, tetapi saya malas untuk mengujinya sekarang: Bagaimana memanggil assembly di gdb?main.cpp
main.gdb
Kompilasi dan jalankan:
Keluaran:
TODO Saya ingin melakukan ini dengan hanya
-ex
dari baris perintah agar tidak perlu membuatmain.gdb
tetapi saya tidakcommands
dapat mengerjakannya di sana.Diuji di Ubuntu 19.04, GDB 8.2.
Kernel Linux
Bagaimana cara mencetak jejak tumpukan benang saat ini di dalam kernel Linux?
libdwfl
Ini awalnya disebutkan di: https://stackoverflow.com/a/60713161/895245 dan itu mungkin metode terbaik, tetapi saya harus melakukan tolok ukur lebih banyak, tetapi tolong naikkan jawaban itu.
TODO: Saya mencoba meminimalkan kode dalam jawaban itu, yang berfungsi, menjadi satu fungsi, tetapi itu segfault, beri tahu saya jika ada yang dapat menemukan alasannya.
dwfl.cpp
Kompilasi dan jalankan:
Keluaran:
Menjalankan benchmark:
Keluaran:
Jadi kami melihat bahwa metode ini 10x lebih cepat daripada stacktrace Boost, dan oleh karena itu mungkin berlaku untuk lebih banyak kasus penggunaan.
Diuji di Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.
Lihat juga
sumber
Tidak ada cara standar untuk melakukan itu. Untuk windows fungsionalitas disediakan di perpustakaan DbgHelp
sumber
Anda dapat menggunakan fungsi makro sebagai ganti pernyataan pengembalian dalam fungsi tertentu.
Misalnya, daripada menggunakan return,
Anda dapat menggunakan fungsi makro.
Setiap kali terjadi kesalahan dalam suatu fungsi, Anda akan melihat tumpukan panggilan gaya Java seperti yang ditunjukkan di bawah ini.
Kode sumber lengkap tersedia di sini.
c-callstack di https://github.com/Nanolat
sumber
Jawaban lain untuk utas lama.
Ketika saya perlu melakukan ini, saya biasanya hanya menggunakan
system()
danpstack
Jadi seperti ini:
Output ini
Ini harus bekerja di Linux, FreeBSD dan Solaris. Saya tidak berpikir bahwa macOS memiliki pstack atau padanan sederhana, tetapi utas ini tampaknya memiliki alternatif .
Jika Anda menggunakan
C
, maka Anda perlu menggunakanC
fungsi string.Saya telah menggunakan 7 untuk jumlah digit maksimal di PID, berdasarkan posting ini .
sumber
Khusus Linux, TLDR:
backtrace
inglibc
menghasilkan stacktraces yang akurat hanya jika-lunwind
ditautkan (fitur khusus platform yang tidak berdokumen).#include <elfutils/libdwfl.h>
(pustaka ini hanya didokumentasikan di file header).backtrace_symbols
danbacktrace_symbolsd_fd
paling tidak informatif.Di Linux modern, Anda bisa mendapatkan alamat stacktrace menggunakan fungsi
backtrace
. Cara tidak terdokumentasi untukbacktrace
menghasilkan alamat yang lebih akurat pada platform populer adalah dengan menautkan-lunwind
(libunwind-dev
di Ubuntu 18.04) (lihat contoh keluaran di bawah).backtrace
menggunakan fungsi_Unwind_Backtrace
dan secara default yang terakhir berasallibgcc_s.so.1
dan implementasinya paling portabel. Ketika-lunwind
ditautkan, ia memberikan versi yang lebih akurat_Unwind_Backtrace
tetapi pustaka ini kurang portabel (lihat arsitektur yang didukung dilibunwind/src
).Sayangnya, pendamping
backtrace_symbolsd
danbacktrace_symbols_fd
fungsi belum dapat menyelesaikan alamat stacktrace ke nama fungsi dengan nama file sumber dan nomor baris mungkin selama satu dekade sekarang (lihat contoh keluaran di bawah).Namun, ada metode lain untuk menyelesaikan alamat ke simbol dan menghasilkan jejak yang paling berguna dengan nama fungsi , file sumber dan nomor baris . Metodenya adalah
#include <elfutils/libdwfl.h>
dan ditautkan dengan-ldw
(libdw-dev
di Ubuntu 18.04).Bekerja C ++ contoh (
test.cc
):Dikompilasi pada Ubuntu 18.04.4 LTS dengan gcc-8.3:
Keluaran:
Jika tidak ada
-lunwind
yang ditautkan, ini menghasilkan pelacakan tumpukan yang kurang akurat:Sebagai perbandingan,
backtrace_symbols_fd
keluaran untuk stacktrace yang sama paling tidak informatif:Dalam versi produksi (serta versi bahasa C) Anda mungkin ingin membuat kode ini lebih kuat dengan mengganti
boost::core::demangle
,std::string
danstd::cout
dengan panggilan dasarnya.Anda juga dapat mengganti
__cxa_throw
untuk menangkap stacktrace saat pengecualian dilemparkan dan mencetaknya saat pengecualian tertangkap. Pada saat memasukicatch
blok, tumpukan telah dibatalkan, jadi sudah terlambat untuk dipanggilbacktrace
, dan inilah mengapa tumpukan harus ditangkapthrow
yang diimplementasikan oleh fungsi__cxa_throw
. Perhatikan bahwa dalam program multi-threaded__cxa_throw
dapat dipanggil secara bersamaan oleh beberapa thread, sehingga jika ia menangkap stacktrace ke dalam array global yang harusthread_local
.sumber
-lunwind
masalah mendapat ditemukan saat membuat posting ini, saya sebelumnya digunakanlibunwind
langsung untuk mendapatkan stacktrace dan akan posting, tapibacktrace
tidak untuk saya ketika-lunwind
terhubung.gcc
tidak mengekspos API, bukan?Anda dapat mengimplementasikan sendiri fungsionalitas tersebut:
Gunakan tumpukan global (string) dan di awal setiap fungsi, dorong nama fungsi dan nilai lainnya (misalnya parameter) ke tumpukan ini; saat keluar dari fungsi pop lagi.
Tulis fungsi yang akan mencetak konten tumpukan saat dipanggil, dan gunakan ini di fungsi tempat Anda ingin melihat tumpukan panggilan.
Ini mungkin terdengar seperti pekerjaan yang berat, tetapi cukup berguna.
sumber
call_registror MY_SUPERSECRETNAME(__FUNCTION__);
yang mendorong argumen dalam konstruktornya dan muncul di destruktornya. FUNCTION selalu mewakili nama fungsi saat ini.Tentu pertanyaan selanjutnya adalah: apakah ini cukup?
Kerugian utama dari stack-traces adalah mengapa Anda memiliki fungsi yang tepat yang dipanggil, Anda tidak memiliki yang lain, seperti nilai argumennya, yang sangat berguna untuk debugging.
Jika Anda memiliki akses ke gcc dan gdb, saya sarankan untuk menggunakan
assert
untuk memeriksa kondisi tertentu, dan menghasilkan dump memori jika tidak terpenuhi. Tentu saja ini berarti prosesnya akan berhenti, tetapi Anda akan memiliki laporan lengkap dan bukan hanya jejak tumpukan.Jika Anda menginginkan cara yang tidak terlalu mencolok, Anda selalu dapat menggunakan logging. Ada fasilitas logging yang sangat efisien di luar sana, seperti Pantheios misalnya. Yang sekali lagi bisa memberi Anda gambaran yang jauh lebih akurat tentang apa yang sedang terjadi.
sumber
Anda dapat menggunakan Poppy untuk ini. Ini biasanya digunakan untuk mengumpulkan jejak tumpukan selama crash tetapi juga dapat mengeluarkannya untuk program yang sedang berjalan.
Sekarang, inilah bagian baiknya: ia dapat menampilkan nilai parameter aktual untuk setiap fungsi pada stack, dan bahkan variabel lokal, penghitung loop, dll.
sumber
Saya tahu utas ini sudah lama, tetapi menurut saya utas ini bisa berguna bagi orang lain. Jika Anda menggunakan gcc, Anda dapat menggunakan fitur instrumennya (opsi -finstrument-functions) untuk mencatat panggilan fungsi apa pun (masuk dan keluar). Lihat ini untuk informasi lebih lanjut: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
Dengan demikian, Anda dapat, misalnya, mendorong dan memunculkan setiap panggilan ke dalam tumpukan, dan ketika Anda ingin mencetaknya, Anda cukup melihat apa yang ada di tumpukan Anda.
Saya sudah mengujinya, ini bekerja dengan sempurna dan sangat berguna
UPDATE: Anda juga dapat menemukan informasi tentang opsi kompilasi -finstrument-functions di dokumen GCC mengenai opsi Instrumentasi: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
sumber
Anda dapat menggunakan pustaka Boost untuk mencetak callstack saat ini.
Laki-laki di sini: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
sumber
cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dll
pada Win10.Anda dapat menggunakan profiler GNU. Ini menunjukkan grafik panggilan juga! perintahnya adalah
gprof
dan Anda perlu mengkompilasi kode Anda dengan beberapa opsi.sumber
Tidak, tidak ada, meskipun solusi yang bergantung pada platform mungkin ada.
sumber