Как получить трассировку стека для C++ с помощью gcc с информацией о номере строки?
мы используем трассировки стека в proprietary assert как макрос, чтобы поймать ошибки разработчика - когда ошибка поймана, трассировка стека печатается.
Я нахожу пару gcc backtrace()/backtrace_symbols() методы недостаточны:
- имена исковеркали
- нет информации строка
1-я проблема может быть решена путем abi::_ _ cxa_demangle.
однако 2-я проблема более жесткая. Я нашел замена backtrace_symbols ().
Это лучше, чем GCC backtrace_symbols (), поскольку он может извлекать номера строк (если скомпилирован с-g), и вам не нужно компилировать с-rdynamic.
Hoverer код имеет лицензию GNU, поэтому IMHO я не могу использовать его в коммерческом коде.
какие предложения?
П. С.
gdb способен печатать аргументы, передаваемые в функции.
Наверное, это уже слишком много, чтобы попросить :)
PS 2
подобный вопрос (спасибо nobar)
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вот некоторые примеры вывода...
также можно извлечь трассировку стека из дампа ядра в командной строке.
$ ( 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, которая переводит адреса программ в имена файлов и строки числа.
вот альтернативный подход. А 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