Как получить трассировку стека для C++ с помощью gcc с информацией о номере строки?



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



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




  1. имена исковеркали

  2. нет информации строка


1-я проблема может быть решена путем abi::_ _ cxa_demangle.



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



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



какие предложения?



П. С.



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



PS 2



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

1269   12  

12 ответов:

Не так давно Я ответил на аналогичный вопрос. Вы должны взглянуть на исходный код, доступный в методе №4, который также печатает номера строк и имена файлов.

Итак, вы хотите автономная функция, которая печатает трассировку стека со всеми функциями, которые имеют трассировки стека gdb, и это не завершает ваше приложение. Ответ заключается в автоматизации запуска gdb в неинтерактивном режиме для выполнения только тех задач, которые вы хотите.

это делается путем выполнения gdb в дочернем процессе, используя fork (), и сценариев его для отображения трассировки стека, пока приложение ждет его завершения. Это может быть выполнено без использование core-дампа и без прерывания приложения. Я узнал, как это сделать, глядя на этот вопрос:как лучше вызвать gdb из программы, чтобы распечатать его stacktrace?

пример, опубликованный с этим вопросом, не работал для меня точно так, как написано, поэтому вот моя "исправленная" версия (я запустил ее на Ubuntu 9.04).

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    int child_pid = fork();
    if (!child_pid) {           
        dup2(2,1); // redirect output to stderr
        fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
        execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
        abort(); /* If gdb failed to start */
    } else {
        waitpid(child_pid,NULL,0);
    }
}

как показано в указанном вопросе, gdb предоставляет дополнительные параметры, которые можно использовать. Например, с помощью "bt full "вместо" bt " выдает еще более подробный отчет (локальные переменные включены в вывод). Manpages для gdb - это своего рода свет, но полная документация доступна здесь.

Так как это основано на gdb, выход включает demangled names,строке-числа,аргументов функции, и при необходимости даже локальные переменные. Кроме того, gdb поддерживает потоки, поэтому вы должны иметь возможность извлечь некоторые метаданные для конкретных потоков.

вот пример трассировки стека, что я вижу с помощью этого метода.

0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0  0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1  0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2  0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3  0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4  0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5  0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70

Примечание: я обнаружил, что это несовместимо с использованием отчет (вероятно, из-за использования виртуальной машины Valgrind). Он также не работает, когда вы запускаете программу внутри сеанса gdb (не можете применить второй экземпляр "ptrace" к процессу).

существует надежное обсуждение по существу того же вопроса по адресу: как создать stacktrace, когда мое приложение gcc C++ аварийно завершает работу. Многие предложения, в том числе много дискуссий о том, как создать трассировку стека во время выполнения.

мой любимый ответ!--5--> из этого потока было включить дамп, который позволяет просматривать полное состояние приложения на момент аварии (включая функцию аргументы, номера строк и незамутненные имена). Дополнительным преимуществом такого подхода является то, что он работает не только для утверждает, но и ошибки сегментации и необработанных исключений.

разные оболочки Linux используют разные команды для включения дампов ядра, но вы можете сделать это из своего кода приложения с чем-то вроде этого...

#include <sys/resource.h>
...
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds

после сбоя Запустите свой любимый отладчик, чтобы изучить программу государство.

$ kdbg executable core

вот некоторые примеры вывода...

alt text

также можно извлечь трассировку стека из дампа ядра в командной строке.

$ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core )
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 22857]
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#0  0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#1  0x00007f4189be7bc3 in abort () from /lib/libc.so.6
#2  0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6
#3  0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18
#4  0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19
#5  0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19
#6  0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19
#7  0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19
#8  0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19
#9  0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26

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

используйте для этого библиотеку google glog. Он имеет новую лицензию BSD.

Он содержит функцию GetStackTrace в stacktrace.H-файл.

EDIT

Я нашел здесь http://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/ что существует утилита addr2line, которая переводит адреса программ в имена файлов и строки числа.

http://linuxcommand.org/man_pages/addr2line1.html

вот альтернативный подход. А debug_assert() макрос программно задает условная точка останова. Если вы работаете в отладчике, вы нажмете точку останова, когда выражение assert будет false -- и вы можете анализировать живой стек (программа не прекращает). Если вы не работаете в отладчике, сбой debug_assert () вызывает прерывание программы и вы получаете дамп ядра, из которого можно анализировать стек (см. мой предыдущий ответ).

преимущество этого подхода, по сравнению с обычными утверждениями, заключается в том, что вы можете продолжать запускать программу после запуска debug_assert (при запуске в отладчике). Другими словами, debug_assert() является немного более гибким, чем assert().

   #include <iostream>
   #include <cassert>
   #include <sys/resource.h> 

// note: The assert expression should show up in
// stack trace as parameter to this function
void debug_breakpoint( char const * expression )
   {
   asm("int3"); // x86 specific
   }

#ifdef NDEBUG
   #define debug_assert( expression )
#else
// creates a conditional breakpoint
   #define debug_assert( expression ) \
      do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0)
#endif

void recursive( int i=0 )
   {
   debug_assert( i < 5 );
   if ( i < 10 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
   setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
   recursive();
   }

Примечание: иногда установка "условных точек останова" в отладчиках может быть медленной. Устанавливая точку останова программно, производительность этого метода должна быть эквивалентно нормальному assert ().

Примечание: как написано, это специфично для архитектуры Intel x86 - другие процессоры могут иметь разные инструкции для создания точки останова.

немного поздно, но вы можете использовать libbfb чтобы получить имя файла и номер строки, как refdbg делает в symsnarf.c. libbfb внутренне используется addr2line и gdb

одним из решений является запуск gdb с помощью"bt" -script в обработчике failed assert. Не очень легко интегрировать такой GDB-запуск, но он даст вам как обратные пути, так и args и имена demangle (или вы можете передать вывод gdb через программу filt c++).

обе программы (gdb и C++filt) не будут связаны с вашим приложением, поэтому GPL не потребует от вас полного приложения с открытым исходным кодом.

тот же подход (exec программа GPL) вы можете использовать с след-символы. Просто создайте список ascii %eip и карту файла exec (/proc/self/maps) и передайте его в отдельный двоичный файл.

можно использовать DeathHandler - небольшой класс C++, который делает все для вас, надежный.

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

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

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

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

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

и если SIGSEGV (или что-то подобное) пойман вызов ptrace() получить eip's значение.

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

решение 2: Первое решение довольно сложное, не так ли? Я придумал гораздо более простой: используя сигнал() поймайте интересующие вас сигналы и вызовите простую функцию, которая считывает 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. ;)

вот мой третий ответ-все еще пытается воспользоваться основными дампами.

в вопросе не было полностью ясно, должны ли макросы "assert-like" завершать приложение (как это делает assert) или они должны были продолжать выполняться после создания их трассировки стека.

в этом ответе я обращаюсь к случаю, когда вы хотите показать трассировку стека и продолжить выполнение. Я написал coredump (функция) ниже в создать дамп ядра, автоматическое извлечение трассировке стека от него, а затем продолжить выполнение программы.

использование такое же, как у assert(). Разница, конечно, в том, что assert () завершает программу, но coredump_assert() не.

   #include <iostream>
   #include <sys/resource.h> 
   #include <cstdio>
   #include <cstdlib>
   #include <boost/lexical_cast.hpp>
   #include <string>
   #include <sys/wait.h>
   #include <unistd.h>

   std::string exename;

// expression argument is for diagnostic purposes (shows up in call-stack)
void coredump( char const * expression )
   {

   pid_t childpid = fork();

   if ( childpid == 0 ) // child process generates core dump
      {
      rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
      setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
      abort(); // terminate child process and generate core dump
      }

// give each core-file a unique name
   if ( childpid > 0 ) waitpid( childpid, 0, 0 );
   static int count=0;
   using std::string;
   string pid = boost::lexical_cast<string>(getpid());
   string newcorename = "core-"+boost::lexical_cast<string>(count++)+"."+pid;
   string rawcorename = "core."+boost::lexical_cast<string>(childpid);
   int rename_rval = rename(rawcorename.c_str(),newcorename.c_str()); // try with core.PID
   if ( rename_rval == -1 ) rename_rval = rename("core",newcorename.c_str()); // try with just core
   if ( rename_rval == -1 ) std::cerr<<"failed to capture core file\n";

  #if 1 // optional: dump stack trace and delete core file
   string cmd = "( CMDFILE=$(mktemp); echo 'bt' >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} "+exename+" "+newcorename+" ; unlink ${CMDFILE} )";
   int system_rval = system( ("bash -c '"+cmd+"'").c_str() );
   if ( system_rval == -1 ) std::cerr.flush(), perror("system() failed during stack trace"), fflush(stderr);
   unlink( newcorename.c_str() );
  #endif

   }

#ifdef NDEBUG
   #define coredump_assert( expression ) ((void)(expression))
#else
   #define coredump_assert( expression ) do { if ( !(expression) ) { coredump( #expression ); } } while (0)
#endif

void recursive( int i=0 )
   {
   coredump_assert( i < 2 );
   if ( i < 4 ) recursive(i+1);
   }

int main( int argc, char * argv[] )
   {
   exename = argv[0]; // this is used to generate the stack trace
   recursive();
   }

когда я запускаю программу, она показывает три трассировки стека...

Core was generated by `./temp.exe'.                                         
Program terminated with signal 6, Aborted.
[New process 24251]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=2) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#6  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24259]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=3) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#6  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#7  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24267]
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0  0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1  0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2  0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3  0x0000000000401f5f in recursive (i=4) at ./demo3.cpp:60
#4  0x0000000000401f70 in recursive (i=3) at ./demo3.cpp:61
#5  0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#6  0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#7  0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#8  0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66

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

#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, а не в моем источнике

Comments

    Ничего не найдено.