Làm thế nào để làm cho backtrace () / backtrace_symbols () in tên hàm?


90

Linux cụ thể backtrace()backtrace_symbols()cho phép bạn tạo ra một dấu vết cuộc gọi của chương trình. Tuy nhiên, nó chỉ in địa chỉ hàm chứ không in tên của chúng cho chương trình của tôi. Làm cách nào để tôi có thể in cả tên hàm? Tôi đã cố gắng biên soạn chương trình với -gcũng như -ggdb. Trường hợp thử nghiệm bên dưới chỉ in ra:

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

Tôi muốn 2 mục đầu tiên cũng hiển thị tên hàm foomain

Mã:

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

trùng lặp có thể xảy ra với Cách nhận backtrace chi tiết hơn
Nemo

btw, hàm backtrace_symbols_fdthực hiện hoạt động tương tự như backtrace_symbols(), nhưng các chuỗi kết quả ngay lập tức được ghi vào bộ mô tả tệp fd.
nhnghia, 16/07/18

Câu trả lời:


63

Các ký hiệu được lấy từ bảng ký hiệu động; bạn cần -rdynamictùy chọn để gcclàm cho nó chuyển một cờ đến trình liên kết để đảm bảo rằng tất cả các ký hiệu được đặt trong bảng.

(Xem trang Tùy chọn liên kết của sổ tay GCC và / hoặc trang Dấu lùi của sổ tay glibc .)


10
Tuy nhiên, điều này không hoạt động đối với các biểu tượng tĩnh. libunwindmà @Nemo đề cập, hoạt động cho các hàm tĩnh.
Nathan Kidd

Trong một dự án CMake, điều này đã được đặt nhầm thành ADD_COMPILE_OPTIONS(-rdynamic). Thay vào đó, nó cần phải bằng ADD_LINK_OPTIONS(-rdynamic)hoặc tương đương để có hành vi mong muốn.
Stéphane

30

Sử dụng lệnh addr2line để ánh xạ các địa chỉ thực thi với tên tệp mã nguồn + số dòng. Cung cấp -ftùy chọn để lấy tên hàm.

Ngoài ra, hãy thử libunwind .


3
Dòng addr2 rất hay vì nó bao gồm tên tệp và số dòng trong đầu ra, nhưng nó không thành công ngay khi trình tải thực hiện chuyển vị trí.
Erwan Legrand

... và với ASLR, việc chuyển vị trí trở nên phổ biến hơn bao giờ hết.
Erwan Legrand

@ErwanLegrand: Trước khi có ASLR, tôi nghĩ rằng các vị trí có thể dự đoán được và addr2linehoạt động đáng tin cậy ngay cả đối với các địa chỉ trong các đối tượng được chia sẻ (?) Nhưng vâng, trên các nền tảng hiện đại, bạn sẽ cần biết địa chỉ tải thực tế của đối tượng có thể di dời ngay cả khi thực hiện thao tác này về nguyên tắc .
Nemo

Tôi không thể biết chắc nó hoạt động tốt như thế nào trước ASLR. Ngày nay, nó sẽ chỉ dành cho mã bên trong các tệp thực thi "thông thường" và không thành công cho mã bên trong DSO và PIE (Các bảng thực thi độc lập theo vị trí). May mắn thay, libunwind dường như hoạt động cho tất cả những điều này.
Erwan Legrand

Tôi đã quên về cuộc thảo luận này. Libbacktrace, mà tôi không biết vào thời điểm đó, là một lựa chọn tốt hơn. (Xem câu trả lời mới của tôi.)
Erwan Legrand

11

Libbacktrace xuất sắc của Ian Lance Taylor giải quyết vấn đề này. Nó xử lý việc tháo cuộn ngăn xếp và hỗ trợ cả các ký hiệu ELF thông thường và các ký hiệu gỡ lỗi DWARF.

Libbacktrace không yêu cầu xuất tất cả các ký hiệu, điều này sẽ xấu và ASLR không phá vỡ nó.

Libbacktrace ban đầu là một phần của phân phối GCC. Bây giờ, một phiên bản độc lập có thể được tìm thấy trên Github:

https://github.com/ianlancetaylor/libbacktrace


2

câu trả lời ở trên cùng có lỗi nếu ret == -1 và errno là EINTER, bạn nên thử lại, nhưng không tính ret là đã sao chép (sẽ không tạo tài khoản chỉ vì điều này, nếu bạn không thích nó khó khăn)

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

0

Thúc đẩy backtrace

Rất tiện lợi vì nó in được cả hai:

  • tên hàm C ++ không bị nhầm lẫn
  • số dòng

tự động cho bạn.

Tóm tắt sử dụng:

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

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

Tôi đã cung cấp một ví dụ có thể chạy được tối thiểu cho nó và nhiều phương pháp khác tại: print call stack in C hoặc C ++


Câu hỏi được gắn thẻ [c], không phải [c ++].
Yakov Galka
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.