Как получить трассировку стека для C ++, используя gcc с информацией о номере строки? - PullRequest
56 голосов
/ 09 января 2011

Мы используем трассировку стека в проприетарном макросе assert для обнаружения ошибок разработчика - при обнаружении ошибки выводится трассировка стека.

Я нахожу пары gcc backtrace() / backtrace_symbols() методами недостаточно:

  1. Имена искажены
  2. Нет информации о строке

1-ю проблему можно решить с помощью abi :: __ cxa_demangle .

Однако 2-я проблема более сложная.Я нашел замену для backtrace_symbols () .Это лучше, чем gtra's backtrace_symbols (), так как он может извлекать номера строк (если они скомпилированы с -g) и вам не нужно компилировать с -rdynamic.

Hoverer, этот код лицензирован GNU, поэтому IMHO яне может использовать его в коммерческом коде.

Любое предложение?

PS

GDB способен распечатывать аргументы, передаваемые функциям.Наверное, это уже слишком много, чтобы просить:)

PS 2

Подобный вопрос (спасибо nobar)

Ответы [ 12 ]

1 голос
/ 13 января 2011

Полагаю, номера строк связаны с текущим значением eip, верно?

РЕШЕНИЕ 1:
Затем вы можете использовать что-то вроде GetThreadContext () , за исключением того, что вы работаете в Linux. Я немного погуглил и нашел что-то похожее, ptrace () :

Системный вызов ptrace () предоставляет средства, с помощью которых родительский процесс может наблюдать и контролировать выполнение другой процесс, и изучить и изменить его основной образ и регистры. [...] Родитель может инициировать трассировку вызывая fork (2) и имея В результате ребенок делает PTRACE_TRACEME, сопровождаемый (обычно) exec (3). Кроме того, родитель может начать след существующего процесса с использованием PTRACE_ATTACH.

Теперь я подумал, что вы можете создать «основную» программу, которая проверяет сигналы, посылаемые его дочернему элементу, настоящую программу, над которой вы работаете. после fork() он вызывает waitid () :

Все эти системные вызовы используются для ждать изменения состояния у ребенка вызывающий процесс, и получить информация о ребенке, чей состояние изменилось.

и, если SIGSEGV (или что-то подобное) перехватывается, звоните ptrace(), чтобы получить значение eip.

PS: я никогда не использовал эти системные вызовы (ну, на самом деле, я никогда не видел их раньше;), поэтому я не знаю, возможно ли это, и не может помочь вам. По крайней мере, я надеюсь, что эти ссылки полезны. ;)

РЕШЕНИЕ 2: Первое решение довольно сложное, верно? Я придумал гораздо более простую: используя signal () , поймайте интересующие вас сигналы и вызовите простую функцию, которая читает значение eip, хранящееся в стеке:

...
signal(SIGSEGV, sig_handler);
...

void sig_handler(int signum)
{
    int eip_value;

    asm {
        push eax;
        mov eax, [ebp - 4]
        mov eip_value, eax
        pop eax
    }

    // now you have the address of the
    // **next** instruction after the
    // SIGSEGV was received
}

Этот asm-синтаксис принадлежит Borland, просто измените его на GAS. ;)

0 голосов
/ 17 августа 2018

вот мое решение:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <zconf.h>
#include "regex"

std::string getexepath() {
    char result[PATH_MAX];
    ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
    return std::string(result, (count > 0) ? count : 0);
}

std::string sh(std::string cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
    if (!pipe) throw std::runtime_error("popen() failed!");
    while (!feof(pipe.get())) {
        if (fgets(buffer.data(), 128, pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    return result;
}


void print_backtrace(void) {
    void *bt[1024];
    int bt_size;
    char **bt_syms;
    int i;

    bt_size = backtrace(bt, 1024);
    bt_syms = backtrace_symbols(bt, bt_size);
    std::regex re("\\[(.+)\\]");
    auto exec_path = getexepath();
    for (i = 1; i < bt_size; i++) {
        std::string sym = bt_syms[i];
        std::smatch ms;
        if (std::regex_search(sym, ms, re)) {
            std::string addr = ms[1];
            std::string cmd = "addr2line -e " + exec_path + " -f -C " + addr;
            auto r = sh(cmd);
            std::regex re2("\\n$");
            auto r2 = std::regex_replace(r, re2, "");
            std::cout << r2 << std::endl;
        }
    }
    free(bt_syms);
}

void test_m() {
    print_backtrace();
}

int main() {
    test_m();
    return 0;
}

вывод:

/home/roroco/Dropbox/c/ro-c/cmake-build-debug/ex/test_backtrace_with_line_number
test_m()
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:57
main
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:61
??
??:0

"??"и "??: 0", так как эта трассировка находится в libc, а не в моем источнике

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...