LINUX.ORG.RU

[с++] [autOut] километровые дебаги by design

 


0

0

- Всегда страйтесь писать программы, которые пишут программы.

@ не помню

Ведётся работа над сложным (ну и не совсем маленьким) проектом на с++. Часто возникает необходимость трассировать работу программы чуть-ли не построчно, и на большом промежутке времени, т.к. баг появляется как следствие роста «снежного кома», и вот когда его радиус достигает критической протяжённости происходит переломный момент, и при его ловле приходится идти назад по развитию ситуации которая привела к этому. Такой сценарий связан со спецификой конкретного продукта. Так вот, при отладке и добавлении модулей проявилось осознание необходимости введения отдельного механизма подробного логирования работы модулей с точностью до стека вызовов (в виде имён функций хотябы, а если они являются методами объекта — то и имя объекта +, возможно, данные его полей):

#0000123 00:05:55 [ProgramName] > [main(2,...)] > [SomeClass, field1=X, ...]::SomeClassFunc(arg1=Y, ...) > [SomeGlobalFunc] > [SomeOtherClass, field=Z]::SomeOtherClass() > Some debug out of constructor...

Хочется реализовать это таким образом, чтобы:

1) Не надо было делать всякие хитрые вступления в начале каждой функции которая использует такой вывод. Лучше вообще без (что видимо нереально для с++), но допускаю тривиальную инициализацию, типа «добавлять в начало каждого вывода ещё и такую строку» где строка чаще всего — имя функции, и возможно значения аргументов, но уж точно избежать ручного прописывания этих данных, т.к. полезно ...

2) Чтобы бывод был визуально идентичным в любом месте программы и переносимым. К примеру первая попытка реализации такой техники была основана на С-препроцессоре, и выглядела примеро так:

#define DEBUG_STREAM (std::cerr)
---< некий другой файл в котором будем использовать дебаг-поток >---
#define  _BACKUP_DEBUG_STREAM_  DEBUG_STREAM
#undef 	 DEBUG_STREAM
#define  DEBUG_STREAM           (_BACKUP_DEBUG_STREAM_ << " [ThisFileClassName, field1=" << field1 << ", field2=" << field2 << "]::")

И дальше используешь вывод следующего вида в любом месте файла:

ThisFileClassName :: Method1(...)
{
 DEBUG_STREAM << "::Method1(...) > " << "Some debug info" << std::endl << ...;
 DEBUG_STREAM.write(data,size);
}
---< в конце файла >---
#undef   DEBUG_STREAM
#define  DEBUG_STREAM  _BACKUP_DEBUG_STREAM_

// Тут, как многие наверное уже знают, проблема в том, что нельзя переопределять макрос через макрос каким-либо образом связанный с данным ранее. Даже если сделать #undef. (кстати было бы интересно почитать о работе С-препроцессора, порекомендуйте литературу, а то в начале я был уверен что это сработает), и + выводится будет не последовательность вызванных методов, а последовательность препроцессорного парсинга.

// А так же конкретно этот метод не удобен ещё и тем, что нет удобочитаемого метода автоматической добавки хвоста с именем функции (+ ещё бы вывод аргументов или чего ещё), тут его приходится писать руками что не коррелирует с пунктом 1 и вообще /0 этот пункт; либо делать ещё переопределние DEBUG_STREAM #ifdef/#endif в начале и конце функции, что противоречит вобще всякой форме морали и гуманности.

3) Отключение дебаг-режима происходило отключением какого-то глобального макроса и/или изменением некоторой переменной, и в отключенном режиме елись минимум ресурсов (если с помощью макросов, то лучше чтобы под какой-либо оптимизацией код вывода вобще вырезался с корнями. Если на примере первой макросной поделки, то не надо было обрамлять #ifdef/#endif'ами каждый вывод, а заменить корневой DEBUG_STREAM на какой-нибудь класс NullStream с пустыми оператором и write'ом, но вот появление этого паразитного кода в итоговом бинарнике для меня загадка).

Предполагаю что это можно сделать каким-нибудь образом через наследование классов, но это слишком громоздкий подход, и требует очень нетривиальных рукоприкладств. Хотя было бы очень здорово в случае реализации такой же стройной архитектуры, которую можно поднять, к примеру, для иерархии exception'ов.

Актуальная мысль заключается в использовании обёртки fprintf (а может и cerr) которая держит в памяти списко(для дебаг вывода)-стек(для push/pop) структур в которых ... Вот тут проблема... В которых каким-то образом содержатся либо строки форматирование и списки переменных для каждой из строк, либо вообще переменные (в том числе указатели на константный строки) для последовательного запихивать в поток вывода.

Так что вот в таких я раздумьях. У кого есть идеи — будет интересно.

любой серьезный инструмент вроде AQTime( win-only ) даст это все тебе без всякого изменения в коде, со стеками, диаграммами, статистикой и пр.

ahonimous
()
Ответ на: комментарий от ahonimous

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

Сдаётся мне что это не так просто сделать в профилировщиках, если нет — дайте наводу что лучше использовать, лучше для линя. К примеру умеет ли что-то подобное тот же Valgrind?

Так же некоторый вывод останется и для использования в релизе, т.е. писать логирование всё равно нужно, так что я сразу стал расчитывать на грамотную архитектуру этого дела.

//капча «name chipper» :(

name_user_login_account
() автор топика

Что касается препроцессора - может быть стоит глянуть на m4 (в википедии). Правда есть подозрение, что использовать его в серьезных проектах - ещё тот изврат.

Чуть более культурный вариант - может быть поможет Boost.Exception. Конкретно, стоит посмотреть сюда и на последний пример здесь. Правда, кода тут править придется много, но по крайней мере подход логичный.

Можно и ещё более культурно, но это уже не слету, надо подумать.

gizzka ★★
()
Ответ на: комментарий от name_user_login_account

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

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

ahonimous
()
Ответ на: комментарий от ahonimous

gizzka

Благодарю за ссылки. На сколько я понял m4 почти не имеет хождения вне линя, хотя честно говоря идея прикручивания внешнего препроцессора мне сразу не понравилась. Ещё я не смог найти ответ на вопрос полон ли по Тьюрингу сишный препроцессор? Судя по таким пирогам нет, тут m4 конечно рулит.

С бустом конечно придётся возиться долго, хотя беспокоит то, что функционал реализуется эксепшенами и вообще бустом :). Подскажите сложно ли будет выглядеть урезание вербозности вывода, очень хотелось бы чтобы оно ещё и сокращало кол-во вкомпиленого кода.

ahonimous

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

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

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

Не зря кило-метровые логи. :)

name_user_login_account
() автор топика

В Linux (а точнее в glibc) есть великолепный /usr/include/execinfo.h - если программа собрана с -g можно в процессе выполнения получить полный стек вызовов. Я как-то использовал, когда дед-локи искал.

rymis ★★
()

Так что вот в таких я раздумьях. У кого есть идеи — будет интересно.

1. использовать тестирование, лучше даже TDD (за последний год ни строчки не написал без TDD - реально помогает бороться со сложностью)

2. используй профайлер

3. если 2 предыдущих не помогли или не могут быть применены - тогда используй трейсы, алсо трейсы (частично) могут пригодиться для создания креш-репортов и логирования, но это совсем другая песня

shty ★★★★★
()

«спецификой конкретного продукта»...

маны надо читать, вот что

`-finstrument-functions'

Generate instrumentation calls for entry and exit to functions. Just after function entry and just before function exit, the following profiling functions will be called with the address of the current function and its call site. (On some platforms, `__builtin_return_address' does not work beyond the current function, so the call site information may not be available to the profiling functions otherwise.)

void __cyg_profile_func_enter (void *this_fn, void *call_site);

void __cyg_profile_func_exit (void *this_fn, void *call_site);

www_linux_org_ru ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.