Bagaimana membuat backtrace () / backtrace_symbols () mencetak nama fungsi?

90

Khusus Linux backtrace()dan backtrace_symbols()memungkinkan Anda membuat jejak panggilan program. Namun, itu hanya mencetak alamat fungsi, bukan nama mereka untuk program saya. Bagaimana saya bisa membuatnya mencetak nama fungsi juga? Saya sudah mencoba menyusun program dengan -gjuga -ggdb. Kasus uji di bawah hanya mencetak ini:

    LATAR BELAKANG ------------
    ./a.out () [0x8048616]
    ./a.out () [0x8048623]
    /lib/libc.so.6(__libc_start_main+0xf3) [0x4a937413]
    ./a.out () [0x8048421]
    ----------------------
    

Saya ingin 2 item pertama juga menampilkan nama fungsi, foodanmain

Kode:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) && (errno != EINTR))
                        break;

                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

void print_backtrace(void)
{
        static const char start[] = "BACKTRACE ------------\n";
        static const char end[] = "----------------------\n";

        void *bt[1024];
        int bt_size;
        char **bt_syms;
        int i;

        bt_size = backtrace(bt, 1024);
        bt_syms = backtrace_symbols(bt, bt_size);
        full_write(STDERR_FILENO, start, strlen(start));
        for (i = 1; i < bt_size; i++) {
                size_t len = strlen(bt_syms[i]);
                full_write(STDERR_FILENO, bt_syms[i], len);
                full_write(STDERR_FILENO, "\n", 1);
        }
        full_write(STDERR_FILENO, end, strlen(end));
    free(bt_syms);
}
void foo()
{
    print_backtrace();
}

int main()
{
    foo();
    return 0;
}
Lyke
sumber
kemungkinan duplikat dari Cara mendapatkan lacak balik yang lebih rinci
Nemo
btw, function backtrace_symbols_fdmelakukan operasi yang sama seperti backtrace_symbols(), tetapi string yang dihasilkan segera ditulis ke deskriptor file fd.
nhnghia
Saya telah menguji beberapa metode secara rinci di: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

63

Simbol diambil dari tabel simbol dinamis; Anda memerlukan -rdynamicopsi untuk gcc, yang membuatnya meneruskan bendera ke linker yang memastikan bahwa semua simbol ditempatkan di tabel.

(Lihat halaman Link Options dari manual GCC , dan / atau halaman Backtraces dari manual glibc .)

Matthew Slattery
sumber
10
Ini tidak berfungsi untuk simbol statis. libunwindyang disebutkan @Nemo, berfungsi untuk fungsi statis.
Nathan Kidd
Dalam proyek CMake, ini keliru ditetapkan sebagai ADD_COMPILE_OPTIONS(-rdynamic). Sebaliknya, itu perlu ADD_LINK_OPTIONS(-rdynamic)atau setara untuk memiliki perilaku yang diinginkan.
Stéphane
30

Gunakan perintah addr2line untuk memetakan alamat yang dapat dieksekusi ke nama file kode sumber + nomor baris. Berikan -fopsi untuk mendapatkan nama fungsi juga.

Atau, coba libunwind .

Nemo
sumber
3
baris addr2 bagus karena menyertakan nama file dan nomor baris dalam output, tetapi gagal segera setelah relokasi dilakukan oleh loader.
Erwan Legrand
... dan dengan relokasi ASLR lebih umum dari sebelumnya.
Erwan Legrand
@ErwanLegrand: Sebelum ASLR, saya pikir relokasi dapat diprediksi dan addr2linebekerja dengan andal bahkan untuk alamat di objek bersama (?) Tapi ya, pada platform modern Anda perlu mengetahui alamat pemuatan sebenarnya dari objek yang dapat direlokasi bahkan untuk melakukan operasi ini pada prinsipnya .
Nemo
Saya tidak bisa memastikan seberapa baik itu bekerja sebelum ASLR. Saat ini, hanya untuk kode di dalam executable "biasa" dan gagal untuk kode di dalam DSO dan PIE (Position Independent Executables). Untungnya, libunwind tampaknya berfungsi untuk semua ini.
Erwan Legrand
Saya sudah lupa tentang diskusi ini. Libbacktrace, yang tidak saya ketahui saat itu, adalah opsi yang lebih baik. (Lihat jawaban baru saya.)
Erwan Legrand
11

Libbacktrace yang luar biasa oleh Ian Lance Taylor memecahkan masalah ini. Ini menangani pelepasan tumpukan dan mendukung simbol ELF biasa dan simbol debugging DWARF.

Libbacktrace tidak perlu mengekspor semua simbol, yang akan jelek, dan ASLR tidak merusaknya.

Libbacktrace awalnya adalah bagian dari distribusi GCC. Sekarang, versi mandiri dapat ditemukan di Github:

https://github.com/ianlancetaylor/libbacktrace

Erwan Legrand
sumber
2

jawaban di atas memiliki bug jika ret == -1 dan errno adalah EINTER Anda harus mencoba lagi, tetapi tidak menghitung ret sebagai disalin (tidak akan membuat akun hanya untuk ini, jika Anda tidak suka sulit)

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) {
                        if (errno != EINTR))
                                break;
                        //else
                        continue;
                }
                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}
Billg
sumber
0

Tingkatkan lacak balik

Sangat nyaman karena mencetak keduanya:

  • nama fungsi C ++ tidak diubah
  • nomor baris

otomatis untukmu.

Ringkasan penggunaan:

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

std::cout << boost::stacktrace::stacktrace() << std::endl;

Saya telah memberikan contoh runnable minimal untuk itu dan banyak metode lain di: print call stack di C atau C ++

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
Pertanyaannya diberi tag [c], bukan [c ++].
Yakov Galka