LINUX.ORG.RU

Коды ошибок при многомодульном приложении

 


0

2

Есть основной модуль, у него свой набор основных ошибок:

    /*        Name                Code              Description             */ \
    /* No error                                                             */ \
    SML(SUCCESS             , (0x00),        "Successfull                   ") \
    /* System general errors                                                */ \
    SML(SYSTEM_GENERALERROR , (0x01),        "Generic error                 ") \
    SML(SYSTEM_MISUSEOFBLTNS, (0x02),        "Misuse of shell built-ins     ") \
    /* Memory errors                                                        */ \
    SML(USER_BADALLOC       , (0x10 + 0x01), "Memory allocation failed      ") \
    /* Checking errors                                                      */ \
    SML(USER_BADPOINTER     , (0x20 + 0x01), "Bad pointer provided          ") \
    SML(USER_BADVALUE       , (0x20 + 0x02), "Bad value provided            ") \
    SML(USER_BADTYPE        , (0x20 + 0x03), "Bad type provided             ") \
    /* System errors                                                        */ \
    SML(SYSTEM_CANNOTEXECUTE, (0x7E),        "Command invoked cannot execute") \
    SML(SYSTEM_NOTFOUND     , (0x7F),        "Command not found             ") \
    SML(SYSTEM_INVALIDEXIT  , (0x80),        "Invalid argument to exit      ") \
    /* System signals                                                       */ \
    SML(SYSTEM_SIGHUP       , (0x80 + 0x01), "Hangup                        ") \
    SML(SYSTEM_SIGINT       , (0x80 + 0x02), "Interrupt                     ") \
    SML(SYSTEM_SIGQUIT      , (0x80 + 0x03), "Quit                          ") \
    SML(SYSTEM_SIGILL       , (0x80 + 0x04), "Illegal Instruction           ") \
    SML(SYSTEM_SIGTRAP      , (0x80 + 0x05), "Trace/Breakpoint Trap         ") \
    SML(SYSTEM_SIGABRT      , (0x80 + 0x06), "Abort                         ") \
    SML(SYSTEM_SIGEMT       , (0x80 + 0x07), "Emulation Trap                ") \
    SML(SYSTEM_SIGFPE       , (0x80 + 0x08), "Arithmetic Exception          ") \
    SML(SYSTEM_SIGKILL      , (0x80 + 0x09), "Killed                        ") \
    SML(SYSTEM_SIGBUS       , (0x80 + 0x0A), "Bus Error                     ") \
    SML(SYSTEM_SIGSEGV      , (0x80 + 0x0B), "Segmentation Fault            ") \
    SML(SYSTEM_SIGSYS       , (0x80 + 0x0C), "Bad System Call               ") \
    SML(SYSTEM_SIGPIPE      , (0x80 + 0x0D), "Broken Pipe                   ") \
    SML(SYSTEM_SIGALRM      , (0x80 + 0x0E), "Alarm Clock                   ") \
    SML(SYSTEM_SIGTERM      , (0x80 + 0x0F), "Terminated                    ") \
    SML(SYSTEM_SIGUSR1      , (0x80 + 0x10), "User Signal 1                 ") \
    SML(SYSTEM_SIGUSR2      , (0x80 + 0x11), "User Signal 2                 ") \
    SML(SYSTEM_SIGCHLD      , (0x80 + 0x12), "Child Status                  ") \
    SML(SYSTEM_SIGPWR       , (0x80 + 0x13), "Power Fail/Restart            ") \
    SML(SYSTEM_SIGWINCH     , (0x80 + 0x14), "Window Size Change            ") \
    SML(SYSTEM_SIGURG       , (0x80 + 0x15), "Urgent Socket Condition       ") \
    SML(SYSTEM_SIGPOLL      , (0x80 + 0x16), "Socket I/O Possible           ") \
    SML(SYSTEM_SIGSTOP      , (0x80 + 0x17), "Stopped (signal)              ") \
    SML(SYSTEM_SIGTSTP      , (0x80 + 0x18), "Stopped (user)                ") \
    SML(SYSTEM_SIGCONT      , (0x80 + 0x19), "Continued                     ") \
    SML(SYSTEM_SIGTTIN      , (0x80 + 0x1A), "Stopped (tty input)           ") \
    SML(SYSTEM_SIGTTOU      , (0x80 + 0x1B), "Stopped (tty output)          ") \
    SML(SYSTEM_SIGVTALRM    , (0x80 + 0x1C), "Virtual Timer Expired         ") \
    SML(SYSTEM_SIGPROF      , (0x80 + 0x1D), "Profiling Timer Expired       ") \
    SML(SYSTEM_SIGXCPU      , (0x80 + 0x1E), "CPU time limit exceeded       ") \
    SML(SYSTEM_SIGXFSZ      , (0x80 + 0x1F), "File size limit exceeded      ") \
    SML(SYSTEM_SIGWAITING   , (0x80 + 0x20), "All LWPs blocked              ") \
    SML(SYSTEM_SIGLWP       , (0x80 + 0x21), "VI Interrupt for T. Library   ") \
    SML(SYSTEM_SIGAIO       , (0x80 + 0x22), "Asynchronous I/O              ") \
                                                                               \
    SML(SYSTEM_OUTOFRANGE   , (0xFF),        "Exit status unknown           ") \
    /* Maximum is 255, as this index will be sent to exit() function        */

И есть пара модулей, каждый из которых делает какую-нибудь свою вещь, абсолютно не связанную (или наследованный от другого) с другим модулем. Выполнены в виде .so либ. К примеру модуль работы с иксами. У него свои коды ошибок:


    /* X-Server errors                                                      */ \
    SML(USER_XBADINIT       , (0x30 + 0x01), "Bad X-Server initialisation   ") \
    SML(USER_XBADVALUE      , (0x30 + 0x02), "Bad X-Resource value          ") \
    SML(USER_XBADWINDOW     , (0x30 + 0x03), "Bad X-Window                  ") \
    SML(USER_XBADPIXMAP     , (0x30 + 0x04), "Bad X-Pixmap                  ") \
    SML(USER_XBADATOM       , (0x30 + 0x05), "Bad X-Atom                    ") \
    SML(USER_XBADCURSOR     , (0x30 + 0x06), "Bad X-Cursor                  ") \
    SML(USER_XBADFONT       , (0x30 + 0x07), "Bad X-Font                    ") \
    SML(USER_XBADMATCH      , (0x30 + 0x08), "Bad X-Match                   ") \
    SML(USER_XBADDRAWABLE   , (0x30 + 0x09), "Bad X-Drawable                ") \
    SML(USER_XBADACCESS     , (0x30 + 0x0A), "Bad X-Access                  ") \
    SML(USER_XBADALLOC      , (0x30 + 0x0B), "Bad X-Alloc                   ") \
    SML(USER_XBADCOLOUR     , (0x30 + 0x0C), "Bad X-Colour                  ") \
    SML(USER_XBADGC         , (0x30 + 0x0D), "Bad X-GC                      ") \
    SML(USER_XBADID         , (0x30 + 0x0E), "Bad X-ID                      ") \
    SML(USER_XBADNAME       , (0x30 + 0x0F), "Bad X-Name                    ") \
    SML(USER_XBADLENGTH     , (0x30 + 0x10), "Bad X-Length                  ") \
    SML(USER_XBADIMPLEMENT  , (0x30 + 0x11), "Bad X-Implement               ") \

Ну для примера, возьмем еще один модуль, модуль юза шаред памяти, у него скажем тоже будет пара своих ошибок ну и пр.

И, собственно, архитектурный вопрос (с ЯП не связанный): как лучше сделать функцию, возвращающую описание ошибки по коду? У каждого модуля своя? А если он также может вернуть какую-нибудь generic ошибку? Типа bad pointer из главного модуля. Или лучше сделать функцию RegisterError(int code, char * desc), вызываемую при ините модуля? И тогда юзать единую функцию из главного модуля? А что если ошибок понадобится > 255? Сейчас модулей уже 8 штук, каждый хочет 5-8 кодов, а все это дело я хочу возвращать в совместимом с exit(code) виде (0..255).

Ну, собственно, сабж. Есть какое-нибудь общепринятое решение?

★★★★★

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

Да, это чуть-чуть более «медленный» вариант, чем коды. Но ведь мы говорим про сообщение об ошибке, это в большинстве случаев означает нештатную ситуацию, когда надо всего лишь корректно завершить работу, и тут уже требования по быстродействию куда менее актуальны, чем понятность и лаконичность кода.

Да, если хочешь заморочиться - вариант RegisterError(int code, char * desc) выглядит архитектурно красиво. Но на мой субъективный взгляд это всё-таки чрезмерное усложнение.

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 2)

В exit нужно возвращать только то на что вызывающий программу процесс может каким-то особым образом отреагировать. Вызывающему процессу абсолютно положить что там у тебя в иксах сломалось или по какому сигналу ты сдох (на самом деле информация по сигналам в waitpid и так всегда доступна). На практике единственный случай когда нужно что-то сверх «ноль/не ноль» - это когда процесс отвечает «да/нет» и нужно отличить «нет» от ошибки. Пример - grep, отвечает 0 если нашёл соответствие шаблону, 1 если не нашёл и 2 в случае ошибки. Это всё что касается кодов exit(), можешь выкидывать свою простыню.

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

При этом надо заметить что юзер всё равно ничего из статического сообщения не поймёт, потому что сообщения нужно генерировать динамически из текущих данных («не удалось открыть файл /foo/bar, ошибка EPERM»). Поэтому кто поумнее пишут sprintf-like функцию сохраняющую всю информацию об ошибке (включая данные и строку) в handle библиотеки (для библиотек) или TLS (для кода приложения, в простом случае глобальные объекты).

slovazap ★★★★★
()

А если он также может вернуть какую-нибудь generic ошибку? Типа bad pointer из главного модуля.

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

И, собственно, архитектурный вопрос ... как лучше сделать функцию, возвращающую описание ошибки по коду?

Имеет смысл выработать небольшое кол-во generic статус кодов выполнения операции, в самом простом виде это EGeneric { Ok, Fail }, и дополнительную конкретную иформацию передавать через контекст в виде элементов (ISpace, extdata...) где ISpace это реализация некоторого интерфейса который одновременно указывает на домён ошибки и может переварить extdata во что-то читабельное и полезное. Таким образом можно обойтись без глобальной регистрации кодов, неограниченно масштабироваться и даже возвращать стеки (цепочки) ошибок.

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

Но ведь мы говорим про сообщение об ошибке, это в большинстве случаев означает нештатную ситуацию

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

как вариант - единый файл с кодами ошибок и функция приведения их к строке
коды группируются по модулям с добавкой префикса, например 1ххх - коды модуля №1, 2ххх - коды модуля №2 и тд.

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

корректная обработка ошибок - это должна быть штатная часть программы, но часто на это забивают, делая её «нештатной», и часто не понять что пошло не так

С этим никто не спорит. Я говорил не про нештатность самого механизма обработки (который, естественно, должен быть), а про нештатность ситуаций, который он обрабатывает.

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

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

hobbit ★★★★★
()
Последнее исправление: hobbit (всего исправлений: 2)

Видел я один embedded проект где описания ошибок генерировались скриптом на Питоне, а 32-битный код ошибки содержал номер модуля и severity

В коде надо было писать

if (failed) {
  /* Error description one */
  ReportError(MOD_ERR_ONE);
  return MOD_ERR_ONE;
}

После этого скрипт апдейтил errors.h

#define MOD_ERR_ONE 0x00112233 /* Error description one */

И errors.c

int PrintError(int error_code) {
  switch (error_code) {
    case MOD_ERR_ONE:
      printf("Error description one");

Для embedded это было нормальным решением позволявшим иметь везде одинаковое описание ошибок и легко убирать текст описания из бинаря для уменьшения footprint'a

Для обычного проекта динамическая регистрация будет наверное предпочтительней

alx777 ★★
()

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

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

Теряется возможность однозначно установить после падения что было причиной. Если код ошибки (критичной) будет транслирован в exit(...), то появляется возможность сделать так:

./program
if [ -z "$?" -eq "0" ]; then
    ./program help "$?" # Prints ErrorAsText(ERROR_CODE)
fi

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