Pull to refresh

Comments 42

Спасибо — в закладки! Интересно, а в mingw есть какие-нибудь механизмы для поддержания syslog || dtrace(systemtap)?
в reportTrouble надо i объявить.

Большое спасибо, честно говоря. Среди себя поднимал вопрос давным давно, сравнивая отлаживаемость Java с голыми С. И к тому же backtrace приходил, но сильно далеко не убежал.

А ваш код прямо сейчас скопипастил, спасибо :-)
Только ещё сразу лучше к имени файла репорта добавлять pid
Это уже на усмотрение разработчика. В моей программе вообще добавляется куча информации о системе (версия, битность, язык и тп), а так же уникальный GUID системы.
А откуда backtrace возьмет имена функций на стрипнутом экзешнеке?
Как минимум, по классам он всё показывает:

0   segfault                            0x0000000102b1a8c8 segfault + 2248
1   segfault                            0x0000000102b1a9d0 segfault + 2512
2   libsystem_c.dylib                   0x00007fff99b5dcfa _sigtramp + 26
3   ???                                 0x0000040000000000 0x0 + 4398046511104
4   segfault                            0x0000000102b1ac67 _ZN7Crasher11doSomethingEv + 71
5   segfault                            0x0000000102b1ac1a _ZN7Crasher18doSomethingPrivateEv + 208
6   segfault                            0x0000000102b1ac67 _ZN7Crasher11doSomethingEv + 71
7   segfault                            0x0000000102b1a880 segfault + 2176
8   segfault                            0x0000000102b1aac4 segfault + 2756
9   segfault                            0x0000000102b1ab33 segfault + 2867
10  segfault                            0x0000000102b1a854 segfault + 2132

Чаще всего в плюсовой программе именно это и требуется.
А после того, как над экзешником поработал strip?
Это и есть после стрипа:

$ strip segfault 
$ ./segfault 
That's a function!
That's a private function! myPrivateInteger == 1752392050
myPrivateDoubles[1] == 60993401604041306737928347282702617388988841504491171140800281285302442927306116721201046092641903128620672849302937378251940003901836219046866981678295779355600933772275817062376375849852470059862498765690530537583237171035779906888043337758015488.000000
myPrivateString == 0x63202c6f6c6c6548
That's a function!
0   segfault                            0x0000000102b1a8c8 segfault + 2248
1   segfault                            0x0000000102b1a9d0 segfault + 2512
2   libsystem_c.dylib                   0x00007fff99b5dcfa _sigtramp + 26
3   ???                                 0x0000040000000000 0x0 + 4398046511104
4   segfault                            0x0000000102b1ac67 _ZN7Crasher11doSomethingEv + 71
5   segfault                            0x0000000102b1ac1a _ZN7Crasher18doSomethingPrivateEv + 208
6   segfault                            0x0000000102b1ac67 _ZN7Crasher11doSomethingEv + 71
7   segfault                            0x0000000102b1a880 segfault + 2176
8   segfault                            0x0000000102b1aac4 segfault + 2756
9   segfault                            0x0000000102b1ab33 segfault + 2867
10  segfault                            0x0000000102b1a854 segfault + 2132
Впишите в начало main.cpp
pragma GCC visibility push(hidden)
и повторите эксперимент.
У вас по-умолчанию все символы доступны для динамической загрузки с помощью dladdr.
Результат аналогичный указанному в комменте выше: все символы скрыты, кроме методов класса.
diff --git a/main.cpp b/main.cpp
index 9f89031..1e8ba47 100644
--- a/main.cpp
+++ b/main.cpp
@@ -3,6 +3,8 @@
 #include <signal.h>
 #include <execinfo.h>
 
+#pragma GCC visibility push(hidden)
+
 #define P_DOUBLE_COUNT   10000
 
 // if set to 1, cat is used instead of curl'ing file to server
-- 


Результат (после стрипа):
0   segfault                            0x0000000100000a7c 0x0 + 4294969980
1   segfault                            0x0000000100000b19 0x0 + 4294970137
2   libSystem.B.dylib                   0x00007fff831fd1ba _sigtramp + 26
3   ???                                 0x3838383630393937 0x0 + 4051049670208207159
4   segfault                            0x0000000100000c9b 0x0 + 4294970523
5   segfault                            0x0000000100000c61 0x0 + 4294970465
6   segfault                            0x0000000100000c9b 0x0 + 4294970523
7   segfault                            0x0000000100000a59 0x0 + 4294969945
8   segfault                            0x0000000100000b99 0x0 + 4294970265
9   segfault                            0x0000000100000bd5 0x0 + 4294970325
10  segfault                            0x0000000100000a34 0x0 + 4294969908
11  ???                                 0x0000000000000001 0x0 + 1
Да, действительно, прошу прощения за допущенную ошибку — забыл собрать проект после вставки прагмы.

Кстати, не знал о такой. Часто ли используется в реальных проектах?
Обычно используется linker script чтобы что попало не экспортировалось.
Что становится нетривиальной задачей в случае C++.
Есть проблемы с explicitly instantiated templates, но такие вещи почти никогда не экспортируются. Всё остальное нормально работает.
Ну вот хотим мы класс экспортировать, не дай бог в неймспейсе, с кучей операторов и перегруженных функций. А еще хорошо бы экспортировать vtable и typeinfo от этого класса. Руками я это прописывать в линкер-скрипте не решусь.
Может мы о разных вещах говорим. Я имею ввиду ld --version-script=version-scriptfile который вполне успешно понимает C++ неймспейсы и классы.

Например экспорт всего класса Bar из namespace Foo, кроме символов начинающихся с private_stuff.
LIBNAME_123 {
  global:
    # C++ functions
    extern "C++" {
        Foo::Bar::*;
    };
    # C functions
    cfoo; cbar;
  local: *;
    extern "C++" {
        Foo::Bar::private_stuff*;
    };
};
Посыпаю голову пеплом; о такой возможности я не знал.
Не далее чем на прошедшей неделе обнаружил в составе Mac OS X 10.7.4 модуль на C++ с той же самой проблемой — экспортируются абсолютно все C++ символы. При чем речь идет о модуле, исходники которого не доступны.

С символами код элементарно реверсится по дизасму (например всякие STL-ные вещи сразу же отбрасываются и время на анализ их кода не тратится).

Если вы комерческий вендор, и ваши исходные тексты закрыты, врядли вы захотите настолько облегчить реверсерам жизнь.
fopen, насколько я помню, работает с динамической памятью. Я бы не стал такое использовать в обработчике SIGSEGV — ведь сам краш мог произойти из-за расстрела или нехватки памяти.
А что тогда использовать?
Интересуюсь с целью улучшить свою реализацию.
в своем коде — переменные на стеке и статически аллоцированные. для общения с системой — open()/read()/write() и другие API которые заведомо не используют динамическую память
Поглядите в статью Практика работы с сигналами, в особенности в комментарии. На обработчики сигнала наложены жесточайшие ограничения :)

НО! В случае полного краха приложения, уж лучше что то (шанс что весь ваш обработчик отработаете и вы получите репорт) чем ничего (вы даже не пытаетесь его отправит, потому что следуете всем правилам написания обработчика сигналов)
Я читал, да, спасибо.
Перечитаю ещё разок — уж очень интересная статейка.

И с Вашим «НО!» я полностью согласен, именно из этих соображений я и исходил при написании сего поста.
Ну, сразу в файл хорошо писать если ничего кроме bt там не будет. К примеру, я bt упаковываю в xml вместе с последними логами и инфо о системе.
ЕМНИП, stdio работает поверх выделенных заранее буферов. Но вопрос юзания сложных функций из обработчиков сигнала — конечно, тоже занятен :-)
Для создания краш-репортов можно использовать google-breakpad, разработанный специально для этих целей. К тому же он кросс-платформенный.
Спасибо, обязательно посмотрю в его сторону.
Я иногда делаю так

static int inSigSegvHandler = 0;
static void sigsegvHandler(int sig) {
    ++inSigSegvHandler;
    if(inSigSegvHandler > 1) { // защита от двойного попадания, сигнал ставлю sigaction + SA_RESTART
        fprintf(stderr, "*** SIG %d TWICE ***\n", sig);
        _exit(2);
    }

    invokeDebuggerToSelf();
    _exit(1);
}


Где функция

void invokeDebuggerToSelf() {
    int pid = getpid();
    char link[MAX_PATH];
    char buf[MAX_PATH]; 
    char bufexe[MAX_PATH]; 
    char pidbuf[16];
    char cmdline[MAX_PATH]; 

    memset(bufexe, 0, sizeof(bufexe));
    memset(link, 0, sizeof(link));
    sprintf(link, "/proc/%d/exe", pid);
    if(readlink(link, bufexe, MAX_PATH) <=0)
        strcpy(bufexe, "где-там-оно-лежит"); // вдруг звезды сойдутся неудачно

    sprintf(pidbuf, "%d", pid);

    int wpid;
    if((wpid=fork()) == 0) {
        execlp("gdb", "gdb", "-batch", "-q", "-x", gdbFile, bufexe, pidbuf, NULL);
    }
    else if(wpid > 0) {
        int status;
	waitpid(wpid, &status, WUNTRACED);
	_exit(0);
    }
}


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

В демоне — очень даже помогает, тут и деманглинг и прочее, и 3rd party код показывает с именами и номерами строк. cmdFile содержит список нужных мне команд, в общем случае там bt + quit.

Главное — strip не увлекаться ;-)
Да, интересный подход, я думал о вызове gdb. Проблема в том, что не у всех конечных пользователей он есть. Хотя, для демона вызов gdb + отправка репорта + перезапуск — это очень даже хорошо.
UFO just landed and posted this here
О как, спасибо, буду знать! И попробую в проекте заимплементить.
В винде можно не просто подойти к этой проблеме, а заюзать DbgHelp API. Если собирать проект студией, то она создает приличный по объему файл с символами *.pdb, который содержит символы и их привязки к релизному коду. Сам релизный код символов не содержит (кроме экспортных). При краше мы используем функцию MiniDumpWriteDump. Имея *.pdb файл символов, исходники и релизный образ у себя на машине открываем в студии дамп и получаем интерфейс отладчика, с состоянием, как будто бы крэш случился только что на нашей машине под отладчиком. Помимо стек-трейса, можно просмотреть значения локальных переменных, потоки и пр. инфу. Если использовать флаг MiniDumpWithFullMemory, то (судя по справке) вообще всю user-mode память процесса можно получить.
Крэша в случае вызова метода через nullptr не будет только если метод невиртуальный, если виртуальный — тоже будет крэш. Ну это так, дополнение.
Sign up to leave a comment.

Articles