Я знаю, що для цього немає стандартної функції C. Мені було цікаво, які методи цього можна зробити у Windows та * nix? (Windows XP - це моя найважливіша ОС, щоб зробити це зараз.)
Я знаю, що для цього немає стандартної функції C. Мені було цікаво, які методи цього можна зробити у Windows та * nix? (Windows XP - це моя найважливіша ОС, щоб зробити це зараз.)
Відповіді:
glibc забезпечує функцію backtrace ().
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
char ** backtrace_symbols (void *const *buffer, int size)
void backtrace_symbols_fd(void *const *buffer, int size, int fd)
який може надсилати вихід безпосередньо до stdout / err, наприклад.
backtrace_symbols()
смокче. Він вимагає експорту всіх символів і не підтримує символи DWARF (налагодження). libbacktrace - набагато кращий варіант у багатьох (більшості) випадках.
Існують backtrace () і backtrace_symbols ():
Зі сторінки користувача:
#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);
...
Одним із способів використовувати це більш зручним способом / ООП є збереження результату 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 < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
}
};
...
try {
throw MyException("Oops!");
} catch ( MyException e ) {
e.printStackTrace();
}
Та да!
Примітка: увімкнення прапорів оптимізації може призвести до отримання трасового стеку неточним. В ідеалі, цю можливість можна було б використовувати з увімкненими прапорцями налагодження та вимкненими прапорами оптимізації.
Для Windows перевірте API StackWalk64 () (також на 32-бітовій Windows). Для UNIX ви повинні використовувати рідний спосіб для ОС, щоб зробити це, або повернутися до зворотного трасування glibc (), якщо це можливо.
Однак зауважте, що використання Stacktrace у власному коді рідко є гарною ідеєю - не тому, що це неможливо, а тому, що ви зазвичай намагаєтесь досягти неправильної речі.
Найчастіше люди намагаються отримати стек за, скажімо, виняткових обставин, наприклад, коли зловити виняток, твердження не вдається або - найгірше і найбільш неправильне з усіх них - коли ви отримуєте фатальне "виняток" або подаєте сигнал як порушення сегментації.
Розглядаючи останню проблему, більшість API вимагатимуть від вас явного розподілу пам’яті або можуть робити це внутрішньо. Якщо це зробити в неміцному стані, в якому зараз знаходиться ваша програма, це може ще більше погіршити ситуацію. Наприклад, звіт про аварійне завершення роботи (або coredump) відображатиме не фактичну причину проблеми, а вашу невдалу спробу її усунення).
Я припускаю, що ви намагаєтеся досягти того, що обробляє фатальні помилки, оскільки, здається, більшість людей намагаються це зробити, коли справа доходить до отримання стека. Якщо це так, я б покладався на налагоджувач (під час розробки) і дозволяв процес спільного зливу у виробництві (або міні-дамп на вікна). Разом із належним управлінням символами, у вас не повинно виникнути труднощів із визначенням причини смертних вказівок.
Ви повинні використовувати бібліотеку розмотування .
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 ++ або GDB) і отримати корисну трасування стека.
Solaris має команду pstack , яку також скопіювали в Linux.
Ви можете зробити це, пройшовши стопку назад. Насправді, часто легше додати ідентифікатор у стек викликів на початку кожної функції та розкласти його в кінці, а потім просто пройтися, надрукувавши вміст. Це трохи ПІТА, але він працює добре і зрештою заощадить ваш час.
Останні кілька років я використовую libbacktrace Ian Lance Taylor. Це набагато чистіше, ніж функції в бібліотеці GNU C, які вимагають експорту всіх символів. Це забезпечує більше корисності для генерації зворотних слідів, ніж libunwind. І останнє, але не менш важливе: його не перемагає ASLR, як підходи, що вимагають зовнішніх інструментів, таких як addr2line
.
Спочатку Libbacktrace був частиною розповсюдження GCC, але тепер він доступний автору як окрема бібліотека за ліцензією BSD:
https://github.com/ianlancetaylor/libbacktrace
На момент написання статті я б не використовував нічого іншого, якщо мені не потрібно генерувати зворотні відстеження на платформі, яка не підтримується libbacktrace.