Вопрос по cross-platform, windows, stack-trace, c, debugging – Как можно получить трассировку стека в C?

77

Я знаю, что нет стандартной функции C, чтобы сделать это. Мне было интересно, какие методы для этого в Windows и * nix? (Windows XP - моя самая важная ОС, чтобы сделать это прямо сейчас.)

Ваш Ответ

11   ответов
4

CaptureStackBackTrace() также опция, которая требует меньше подготовительного кода на стороне пользователя, чемStackWalk64() делает. (Также для похожего сценария, который у меня был,CaptureStackBackTrace() работал лучше (надежнее), чемStackWalk64().)

22

https: //www.codeproject.com/kb/threads/stackwalker.asp

Код немного ИМХО, но он работает хорошо. Только для Windows.

Link не работает. Должно ли это быть Codeproject.com / KB / темы / StackWalker.aspx ? squelart
Меня устраивае Vladislav Rastrusny
Кстати, более свежий код сохраняется @ Stackwalker.codeplex.com но страница кода проекта по-прежнему полезна в качестве основной документации. PeterT
78
glibc FTW ... снова. (Это еще одна причина, почему я считаю glibc абсолютным золотым стандартом, когда речь заходит о программировании на C (как и компиляторе, который идет с ним).) Trevor Boyd Smith
Но подождите, это еще не все! Функция backtrace () предоставляет только массив указателей void *, представляющих функции стека вызовов. «Это не очень полезно. Арг.» Не бойся! glibc предоставляет функцию, которая преобразует все адреса void * (адреса функции callstack) в читаемые человеком строковые символы.char ** backtrace_symbols (void *const *buffer, int size) Trevor Boyd Smith
Caveat: я думаю, что работает только для C-функций. @Trevor: это просто поиск символов по адресу в таблице ELF. Conrad Meyer
Существует такжеvoid backtrace_symbols_fd(void *const *buffer, int size, int fd) который может отправить вывод напрямую, например, в stdout / err. wkz
backtrace_symbols() отстой. Он требует экспорта всех символов и не поддерживает символы DWARF (отладки). libbacktrace - намного лучший вариант во многих (большинстве) случаях. Erwan Legrand
28

Со страницы руководства:

     #include <execinfo.h>
     #include <stdio.h>
     ...
     void* callstack[128];
     int i, frames = backtrace(callstack, 128);
     char** strs = backtrace_symbols(callstack, frames);
     for (i = 0; i < frames; ++i) {
         printf("%s\n", strs[i]);
     }
     free(strs);
     ...

Один из способов использовать это более удобным способом / OOP - сохранить результат backtrace_symbols () в конструкторе класса исключений. Таким образом, всякий раз, когда вы генерируете исключение такого типа, у вас есть трассировка стека. Затем просто предоставьте функцию для распечатки. Например


class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i 

...


try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

Та да!

Примечание: включение флагов оптимизации может сделать полученную трассировку стека неточной. В идеале эту функцию можно использовать при включенных флагах отладки и отключенных флагах оптимизации.

gcc требует-rdynamic аргумент, чтобы это работало shuckc
@ shuckc только для преобразования адреса в символьную строку, что может быть выполнено извне с использованием других инструментов, если это необходимо. Woodrow Barlow
20

Для UNIX вы должны использовать родной способ ОС или сделать возврат к glibc backtrace (), если таковой имеется.

Заметим, однако, что использование Stacktrace в нативном коде редко бывает хорошей идеей - не потому, что это невозможно, а потому, что вы обычно пытаетесь достичь неправильного результата.

В большинстве случаев люди пытаются получить стековую трассировку, скажем, в исключительных обстоятельствах, например, при обнаружении исключения, сбое утверждения или - наихудшем и наиболее неправильном из них - когда вы получаете фатальное «исключение» или сигнал типа нарушение сегментации.

Учитывая последнюю проблему, большинство API-интерфейсов потребует от вас явного выделения памяти или может сделать это внутренне. Выполнение этого в хрупком состоянии, в котором ваша программа может находиться в данный момент, может на самом деле ухудшить ситуацию. Например, отчет о сбое (или coredump) не будет отражать фактическую причину проблемы, но ваша неудачная попытка ее устранить).

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

Вы правы в том, что пытаетесь выделить память в обработчике сигналов или исключений. Одним из возможных выходов является выделение фиксированного количества «аварийного» пространства при запуске программы или использование статического буфера. j_random_hacker
Другим выходом является создание службы coredump, которая работает независимо Kobor42
4

Ближайшее, что вы можете сделать, - запустить код без оптимизации. Таким образом, вы можете подключиться к процессу (используя отладчик Visual C ++ или GDB) и получить полезную трассировку стека.

Это не помогает мне, когда происходит сбой на встроенном компьютере в полевых условиях. : Kevin
@ Кевин: Даже на встроенных машинах обычно есть способ получить заглушку удаленного отладчика или, по крайней мере, дамп ядра. Может быть, не раз он будет развернут на поле, хотя ... ephemient
если вы используете gcc-glibc на выбранной вами платформе windows / linux / mac ... тогда backtrace () и backtrace_symbols () будут работать на всех трех платформах. Учитывая это утверждение, я бы использовал слова «нет [портативного] способа сделать это». Trevor Boyd Smith
4

Ты должен использоватьunwind library.

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

Ваш подход также будет работать нормально, если вы не позвоните из общей библиотеки.

Вы можете использоватьaddr2line команда в Linux, чтобы получить исходную функцию / номер строки соответствующего ПК.

"функция источника / номер строки"? Что если ссылки оптимизированы для уменьшения размера кода? Я скажу, однако, что это выглядит как полезный проект. Жаль, что нет возможности получить регистры. Я обязательно посмотрю на это. Вы знаете, что он абсолютно независим от процессора? Просто работает на всем, что имеет компилятор C? Mawg
Хорошо, этот комментарий стоил того, хотя бы из-за упоминания полезной команды addr2line! Ogre Psalm33
addr2line не подходит для перемещаемого кода в системах с ASLR (т. е. большинство из того, что люди использовали в течение последнего десятилетия). Erwan Legrand
2

Pstack команда, которая также была скопирована в Linux.

Полезно, но не совсем C (это внешняя утилита). ephemient
также из описания (раздел: ограничения) ": pstack в настоящее время работает только в Linux, только на компьютере x86 с 32-разрядными двоичными файлами ELF (64-разрядная версия не поддерживается)" Ciro Costa
2

Post Mortem Debugging

Хотя сейчас у меня проблемы с x64 реализация этого.

0

то намного чище, чем функции в библиотеке GNU C, которые требуют экспорта всех символов. Он предоставляет больше полезности для генерации обратных трасс, чем libunwind. И последнее, но не менее важное, он не побежден ASLR, как и подходы, требующие внешних инструментов, таких какaddr2line.

Libbacktrace изначально была частью дистрибутива GCC, но теперь она доступна автору как отдельная библиотека под лицензией BSD:

https: //github.com/ianlancetaylor/libbacktrac

На момент написания я не стал бы использовать что-либо еще, если бы мне не нужно было генерировать обратные трассировки на платформе, которая не поддерживается libbacktrace.

-1

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

Не могли бы вы объяснить, как "прогуливать стопку назад" более подробно? Spidey

Похожие вопросы