Spec-Zone.ru › Perl 5.28

perlhacktips

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
  • ОБЩИЕ ПРОБЛЕМЫ
    • Проблемы с окружением Perl
    • Проблемы переносимости
    • Проблемные системные интерфейсы
    • Проблемы безопасности
  • ОТЛАДКА
    • Изучение Perl
    • Использование отладчика на уровне исходного кода
    • Поддержка макросов gdb
    • Вывод структур данных Perl
    • Использование gdb для просмотра определённых частей программы
    • Использование gdb для просмотра того, что делают парсер/лексер
  • СТАТИЧЕСКИЙ АНАЛИЗ ИСТОЧНИКА
    • lint
    • Coverity
    • HP-UX cadvise (Консультант по коду)
    • cpd (детектор копирования-вставки)
    • Предупреждения gcc
    • Предупреждения других компиляторов C
  • ОТЛАДЧИКИ ПАМЯТИ
    • valgrind
    • AddressSanitizer
  • ПРОФИЛИРОВАНИЕ
    • Профилирование Gprof
    • Профилирование GCC gcov
  • РАЗЛИЧНЫЕ ХИТРОСТИ
    • PERL_DESTRUCT_LEVEL
    • PERL_MEM_LOG
    • DDD через gdb
    • C стек вызова
    • Poison
    • Только для чтения optrees
    • Когда bool не является bool?
    • Цели .i
  • АВТОР

НАЗВАНИЕ

perlhacktips - Советы по разработке кода Perl на C

ОПИСАНИЕ

Этот документ поможет вам освоить лучшие способы разработки кода Perl на C. Он охватывает общие проблемы, отладку, профилирование и многое другое.

Если вы ещё не читали perlhack и perlhacktut, возможно, стоит сделать это сначала.

ОБЩИЕ ПРОБЛЕМЫ

Исходный код Perl подчиняется правилам ANSI C89: нет расширений C99 (или C++). Вам неважно, что какая-то конкретная платформа имеет повреждённый Perl? Я слышал, что до сих пор есть сильный спрос на программистов J2EE.

Проблемы с окружением Perl

  • Не компилируется с потоками

    Компиляция с потоками (-Duseithreads) полностью переписывает прототипы функций Perl. Вам лучше попробовать изменения с этим. Связано с этим и различие между "Perl_-less" и "Perl_-ly" API, например:

    Perl_sv_setiv(aTHX_ ...);
    sv_setiv(...);

    Первый явно передаёт контекст, который необходим, например, для многопоточных сборок. Второй делает это неявно; не смешивайте их. Если вы не передаёте aTHX_, вам нужно выполнить dTHX (или dVAR) как первое действие в функции.

    См. "Как поддерживаются несколько интерпретаторов и одновременность" в perlguts для дальнейшего обсуждения контекста.

  • Не компилируется с -DDEBUGGING

    Определение DEBUGGING предоставляет компилятору больше кода, следовательно, больше способов возникновения проблем. Вам следует попробовать это.

  • Ввод глобальных переменных (не только для чтения)

    Не вводите глобальных переменных, глобальных или статических в файлах. Это плохая форма и усложняет многопоточность и другие виды параллельности. Правильный способ – ввести их как новые переменные интерпретатора, см. intrpvar.h (в самом конце для двоичной совместимости).

    Ввод глобальных переменных только для чтения (const) допустим, если вы проверяете, например, nm libperl.a|egrep -v ' [TURtr] ' (если ваш nm имеет вывод в стиле BSD), что добавленные данные действительно только для чтения. (Если это так, то они не должны отображаться в выводе этой команды.)

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

    static const char etc[] = "...";

    Если вы хотите иметь массивы константных строк, внимательно обратите внимание на правильную комбинацию const:

    static const char * const yippee[] =
        {"hi", "ho", "silver"};

    Существует способ полностью скрыть все изменяемые глобальные переменные (все они перемещаются в кучу), настройка компиляции -DPERL_GLOBAL_STRUCT_PRIVATE. Обычно она не используется, но может использоваться для тестирования, подробнее об этом в "Предпосылки и PERL_IMPLICIT_CONTEXT" в perlguts.

  • Не экспортируете новую функцию

    Некоторые платформы (Win32, AIX, VMS, OS/2 и др.) требуют, чтобы любая функция, являющаяся частью публичного API (разделяемой библиотеки Perl), была явно отмечена как экспортируемая. См. обсуждение embed.pl в perlguts.

  • Экспортируете новую функцию

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

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

    Если функция используется только в одном файле исходного кода, сделайте её статической. См. обсуждение embed.pl в perlguts.

    Если функция используется в нескольких файлах, но предназначена только для внутреннего использования Perl (и это должно быть общим случаем), не экспортируйте её в публичный API. См. обсуждение embed.pl в perlguts.

Проблемы переносимости

Следующие являются общими причинами сбоев компиляции и/или выполнения, не характерными для Perl как такового. C FAQ – хорошая книга для чтения перед сном. Пожалуйста, протестируйте свои изменения с максимально возможным количеством компиляторов C и платформ; мы, в любом случае, будем это делать, и приятно избавиться от публичных конфузов.

При использовании gcc можно добавить параметр -std=c89, который, надеюсь, поймает большинство таких проблем переносимости. (Однако он также может поймать несовместимости в заголовочных файлах вашей системы.)

Используйте флаг конфигурации -Dgccansipedantic для включения флагов gcc -ansi -pedantic, которые обеспечивают более строгие правила ANSI.

При использовании gcc -Wall обратите внимание, что не все возможные предупреждения (например, -Wuninitialized) не выдаются, если вы также не скомпилируете с -O.

Обратите внимание, что если используется gcc, начиная с Perl 5.9.5, файлы исходного кода Perl (те, что находятся на верхнем уровне дистрибутива исходного кода, но не, например, расширения в ext/) автоматически компилируются с максимально возможным количеством флагов -std=c89, -ansi, -pedantic и выборочным набором флагов -W (см. cflags.SH).

Также внимательно изучите perlport, чтобы избежать ложных предположений об операционной системе, файловой системе, кодировке символов и т.д.

Вы можете иногда попробовать "make microperl", чтобы посмотреть, можем ли мы все ещё скомпилировать Perl только с минимальным набором интерфейсов. (См. README.micro.)

Не предполагайте, что операционная система указывает на определённый компилятор.

END_OF_DOCUMENT_MARKER
  • Приведение указателей к целым числам или приведение целых чисел к указателям

    void castaway(U8* p)
    {
      IV i = p;

    или

    void castaway(U8* p)
    {
      IV i = (IV)p;

    Оба варианта плохи, ненадёжны и не переносимы. Используйте макрос PTR2IV(), который делает это правильно. (Аналогично, существуют PTR2UV(), PTR2NV(), INT2PTR() и NUM2PTR()).

  • Приведение функций к указателям данных и наоборот

    Технически приведение указателей на функции к указателям на данные и наоборот не переносимо и неопределено, но практически может работать, но вы должны использовать макросы FPTR2DPTR() и DPTR2FPTR(). Иногда можно также использовать объединения.

  • Предположение, что sizeof(int) == sizeof(long)

    Существуют платформы, где long имеет 64 бита, и платформы, где int имеет 64 бита, и даже, к вашему удивлению, платформы, где short имеет 64 бита. Всё это законно согласно стандарту C. (Другими словами, "long long" не является переносимым способом указать 64 бита, и "long long" даже не гарантируется, что будет шире, чем "long").

    Вместо этого используйте определения IV, UV, IVSIZE, I32SIZE и так далее. Избегайте таких вещей, как I32, поскольку они не гарантированно являются точно 32 битами, они по крайней мере 32 бита, а также не гарантируется, что они int или long. Если вам действительно нужны переменные размером 64 бита, используйте I64 и U64, но только если это защищено HAS_QUAD.

  • Предположение, что любой тип указателя можно разыменовать для любого типа данных

    char *p = ...;
    long pony = *(long *)p;    /* BAD */

    Многие платформы, совершенно справедливо, выдадут вам дамп ядра вместо пони, если p не правильно выровнен.

  • Приведение lvalue

    (int)*p = ...;    /* BAD */

    Просто не переносимо. Сделайте ваш lvalue нужного типа, или, возможно, используйте временные переменные или грязные трюки с объединениями.

  • Предположения о структурах (особенно тех, которые вы не контролируете, например, из системных заголовков)

    • Что определённое поле существует в структуре

    • Что нет других полей, кроме известных

    • Что поле имеет определённый знак, размер или тип

    • Что поля расположены в определённом порядке

      • Хотя C гарантирует порядок, указанный в определении структуры, порядок может отличаться на разных платформах

    • Что sizeof(struct) или выравнивание одинаковы везде

      • Между полями могут быть байты заполнения для выравнивания полей — эти байты могут быть любыми

      • Структуры должны быть выровнены по максимальному выравниванию, необходимому для полей — которое для встроенных типов обычно эквивалентно sizeof() поля

  • Предположение, что кодовая страница — ASCII

    Perl может компилироваться и выполняться на платформах EBCDIC. См. perlebcdic. В большинстве случаев это прозрачно, но из-за различий в кодовых страницах не следует использовать числовые (десятичные, восьмеричные или шестнадцатеричные) константы для ссылок на символы. Вы можете безопасно сказать 'A', но не 0x41. Вы можете безопасно сказать '\n', но не \012. Однако вы можете использовать макросы, определённые в utf8.h, для указания любого кодового значения переносимым способом. LATIN1_TO_NATIVE(0xDF) будет кодовым значением, обозначающим LATIN SMALL LETTER SHARP S на той или иной платформе (на платформах ASCII он компилируется без добавления дополнительного кода, поэтому нет потери производительности). Допустимые значения для LATIN1_TO_NATIVE находятся в диапазоне от 0x00 до 0xFF. Если вход не гарантированно находится в этом диапазоне, используйте UNICODE_TO_NATIVE вместо него. NATIVE_TO_LATIN1 и NATIVE_TO_UNICODE выполняют преобразование в обратном направлении.

    Если вам нужно строковое представление символа, у которого нет мнемонического имени в C, вы должны добавить его в список в regen/unicode_constants.pl, чтобы Perl создал #define для вас на основе текущей платформы.

    Обратите внимание, что макросы isFOO и toFOO в handy.h корректно работают с кодовыми точками и строками.

    Также диапазон 'A' - 'Z' в ASCII представляет собой непрерывную последовательность из 26 заглавных букв. Это неверно для EBCDIC. Также неверно для 'a' - 'z'. Но '0' - '9' — непрерывный диапазон в обеих системах. Не делайте предположений о других диапазонах. (Обратите внимание, что специальная обработка диапазонов в шаблонах регулярных выражений и транслитерациях создаёт для кода Perl иллюзию, что вышеупомянутые диапазоны непрерывны).

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

    UTF-8 и UTF-EBCDIC — это два разных кодирования, используемые для представления кодовых точек Unicode в виде последовательностей байтов. Макросы с одинаковыми именами (но с разными определениями) в utf8.h и utfebcdic.h используются, чтобы позволить вызывающему коду думать, что существует только одно такое кодирование. Это практически всегда обозначается как utf8, но это относится и к EBCDIC версии. Опять же, комментарии в коде могут быть неверными, даже если сам код правильный. Например, концепция UTF-8 invariant characters отличается между ASCII и EBCDIC. На платформах ASCII неизменными являются только символы, у которых бит старшего разряда не установлен (т. е. символы строкового ASCII, 0-127), и документация и комментарии в коде могут предполагать это, часто ссылаясь на что-либо вроде, скажем, hibit. На машинах EBCDIC ситуация отличается и не так проста, но, пока код сам корректно использует макрос NATIVE_IS_INVARIANT(), он работает, даже если комментарии неверны.

    Как отмечено в "TESTING" в perlhack, когда вы пишете тестовые скрипты, файл t/charset_tools.pl содержит полезные функции для написания тестов, работающих на платформах ASCII и EBCDIC. Иногда же тест не может использовать функцию, и неудобно иметь разные версии тестов в зависимости от платформы. Существует 20 кодовых точек, одинаковых во всех 4 кодовых страницах, которые в настоящее время распознаются Perl (3 кодовые страницы EBCDIC плюс ISO 8859-1 (ASCII/Latin1)). Эти кодовые точки можно использовать в таких тестах, хотя существует небольшая вероятность, что Perl станет доступным ещё на одной кодовой странице, сломав ваш тест. Все, кроме одной, из этих кодовых точек — управляющие символы C0. Наиболее значимые управляющие символы, которые одинаковы, — \0, \r и \N{VT} (также задаваемые как \cK, \x0B, \N{U+0B} или \013). Единственный не управляющий — PILCROW SIGN U+00B6. Управляющие символы, которые одинаковы, имеют одинаковый битовый шаблон во всех 4 кодовых страницах, независимо от UTF8-ности строки, содержащей их. Битовый шаблон U+B6 одинаков во всех 4 кодовых страницах для строк, не являющихся UTF-8, но отличается в каждой из них, когда содержащая их строка закодирована в UTF-8. Единственные другие кодовые точки, которые имеют некоторую общность в четырёх кодовых страницах, — это пара 0xDC и 0xFC. Вместе они представляют заглавную и строчную LATIN LETTER U WITH DIAERESIS, но большая и маленькая могут быть перевёрнуты: 0xDC — заглавная буква в Latin1, а 0xFC — строчная, в то время как 0xFC — заглавная буква в EBCDIC, а 0xDC — строчная. Этот факт можно использовать для написания тестов, не чувствительных к регистру, которые одинаковы для всех четырёх кодовых страниц.

  • Предположение, что кодовая страница — только ASCII

    ASCII — 7-битовое кодирование, но байты содержат 8 бит. 128 дополнительных символов имеют разное значение в зависимости от локали. Отсутствие локали в настоящее время означает, что эти дополнительные символы обычно считаются незанятыми, что создало некоторые проблемы. Эта ситуация начала изменяться начиная с версии 5.12, чтобы эти символы можно было считать Latin-1 (ISO-8859-1).

  • Смешивание #define и #ifdef

    #define BURGLE(x) ... \
    #ifdef BURGLE_OLD_STYLE        /* BAD */
    ... do it the old way ... \
    #else
    ... do it the new way ... \
    #endif

    Вы не можете переносимым образом «накладывать» директивы препроцессора C. Например, в приведённом выше примере вам нужны два отдельных #define для BURGLE(), по одному для каждого #ifdef.

  • Добавление некомментариев после #endif или #else

    #ifdef SNOSH
    ...
    #else !SNOSH    /* BAD */
    ...
    #endif SNOSH    /* BAD */

    После #endif и #else не может быть ничего, кроме комментариев. Если вы хотите задокументировать, что происходит (что является хорошей идеей, особенно если ветви длинные), используйте (C)-комментарии:

    #ifdef SNOSH
    ...
    #else /* !SNOSH */
    ...
    #endif /* SNOSH */

    Опция gcc -Wendif-labels предупреждает о плохом варианте (по умолчанию начиная с Perl 5.9.4).

  • Запятая после последнего элемента списка перечислений

    enum color {
      CERULEAN,
      CHARTREUSE,
      CINNABAR,     /* BAD */
    };

    не является переносимой. Оставьте последнюю запятую.

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

  • Использование //-комментариев

    // This function bamfoodles the zorklator.   /* BAD */

    Это C99 или C++. Perl — C89. Использование //-комментариев без проблем разрешено многими компиляторами C, но включение жёстких ограничений ANSI C89 (что мы предпочитаем) приводит к ошибке компиляции.

  • Смешивание объявления и кода

    void zorklator()
    {
      int n = 3;
      set_zorkmids(n);    /* BAD */
      int q = 4;

    Это C99 или C++. Некоторые компиляторы C допускают это, но делать этого не следует.

    Опция gcc -Wdeclaration-after-statements обнаруживает такие проблемы (по умолчанию начиная с Perl 5.9.4).

  • Введение переменных внутри for()

    for(int i = ...; ...; ...) {    /* BAD */

    Это C99 или C++. Хотя было бы замечательно иметь это и в C89, чтобы ограничить область видимости переменной цикла, к сожалению, это невозможно.

  • Смешивание указателей на signed char и unsigned char

    int foo(char *s) { ... }
    ...
    unsigned char *t = ...; /* Or U8* t = ... */
    foo(t);   /* BAD */

    Хотя это законно, это сомнительно и даже смертельно опасно по крайней мере на одной платформе: например, VMS cc рассматривает это как ошибку. Одна из причин, по которой люди часто допускают эту ошибку, заключается в том, что у «голого char» и, следовательно, у разыменования «голого указателя char» неопределённый знак: он зависит от компилятора, флагов компилятора и базовой платформы, является ли результат знаковым или беззнаковым. По той же причине использование 'char' в качестве индекса массива — плохая практика.

  • Макросы, содержащие строковые константы и их аргументы как подстроки строковых констант

    #define FOO(n) printf("number = %d\n", n)    /* BAD */
    FOO(10);

    Семантика до ANSI для этого эквивалентна

    printf("10umber = %d\10");

    что, вероятно, не то, чего вы ожидали. К сожалению, по крайней мере один достаточно распространённый и современный компилятор C поддерживает «настоящую обратную совместимость» здесь; в AIX это всё ещё происходит, хотя остальная часть компилятора AIX вполне довольна C89.

  • Использование форматов printf для нестандартных типов C

    IV i = ...;
    printf("i = %d\n", i);    /* BAD */

    Хотя это, возможно, случайно сработает на некоторых платформах (где IV оказывается int), в общем случае это невозможно. IV может быть чем-то большим. Еще хуже ситуация с более специфическими типами (определяемыми этапом конфигурации Perl в config.h):

    Uid_t who = ...;
    printf("who = %d\n", who);    /* BAD */

    Проблема здесь в том, что Uid_t может быть не только не int-разрядным, но и беззнаковым, в этом случае большие uid будут выводиться как отрицательные значения.

    Простое решение отсутствует из-за ограниченной интеллектуальной способности printf(), но для многих типов доступен правильный формат с суффиксом 'f' или '_f', например:

    IVdf /* IV in decimal */
    UVxf /* UV is hexadecimal */
    
    printf("i = %"IVdf"\n", i); /* The IVdf is a string constant. */
    
    Uid_t_f /* Uid_t in decimal */
    
    printf("who = %"Uid_t_f"\n", who);

    Или можно попробовать привести к типу «достаточно широкому»:

    printf("i = %"IVdf"\n", (IV)something_very_small_and_signed);

    См. «Форматированный вывод Size_t и SSize_t» в perlguts, чтобы узнать, как вывести эти значения.

    Также помните, что формат %p действительно требует указателя на void:

    U8* p = ...;
    printf("p = %p\n", (void*)p);

    Опция gcc -Wformat ищет такие проблемы.

  • Слепое использование макросов с переменным числом аргументов

    В gcc они уже некоторое время существуют со своей собственной синтаксической конструкцией, а C99 ввел их со стандартизованным синтаксисом. Не используйте первый вариант, и используйте второй только в случае, если определено HAS_C99_VARIADIC_MACROS.

  • Слепое передача va_list

    Не все платформы поддерживают передачу va_list в другие функции varargs (stdarg). Правильным подходом является копирование va_list с помощью Perl_va_copy(), если определено NEED_VA_COPY.

  • Использование выражений-утверждений gcc

    val = ({...;...;...});    /* BAD */

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

  • Связывание нескольких инструкций в макрос

    Используйте макросы STMT_START и STMT_END.

    STMT_START {
       ...
    } STMT_END
  • Проверка операционных систем или версий, когда следует проверять наличие функций

    #ifdef __FOONIX__    /* BAD */
    foo = quux();
    #endif

    Если вы не уверены на 100%, что функция quux() доступна только для операционной системы «Foonix» и что она доступна и работает корректно для всех прошлых, настоящих и будущих версий «Foonix», то вышеприведённый код очень некорректный. Вот более правильный вариант (хотя и не идеальный, поскольку нижеприведённый код — это проверка на этапе компиляции):

    #ifdef HAS_QUUX
    foo = quux();
    #endif

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

    В крайнем случае, если вы не можете дождаться, пока Configure будет проинструктирован, или у вас есть обоснованное предположение о том, где quux() может быть доступен, вы можете временно попробовать следующее:

    #if (defined(__FOONIX__) || defined(__BARNIX__))
    # define HAS_QUUX
    #endif
    
    ...
    
    #ifdef HAS_QUUX
    foo = quux();
    #endif

    Но в любом случае старайтесь разделять функции и операционные системы.

    Полезным ресурсом по предопределённым макросам для различных операционных систем, компиляторов и так далее является http://sourceforge.net/p/predef/wiki/Home/

  • Предположение, что содержимое статической памяти, на которую ссылаются возвращаемые значения оберток Perl для функций библиотеки C, не изменяется. Многие функции библиотеки C возвращают указатели на статическое хранилище, которое может быть перезаписано последующими вызовами той же или связанных функций. Perl имеет лёгкие обёртки для некоторых из этих функций, которые не создают копий статической памяти. Хорошим примером является интерфейс к переменным среды, действующим для программы. Perl имеет PerlEnv_getenv для получения значений из среды. Но возвращаемое значение — это указатель на статическую память в библиотеке C. Если вы используете значение для немедленной проверки, это нормально, но если вы сохраняете значение и ожидаете, что оно не изменится в ходе дальнейшей обработки, то вы ошибаетесь, хотя, возможно, вы этого не заметите, поскольку различные реализации библиотек C ведут себя по-разному, и та, что на вашей тестовой платформе, может подойти для вашей ситуации. Но на некоторых платформах последующий вызов PerlEnv_getenv или связанной функции будет перезаписывать память, на которую ссылается ваш первый вызов. Это привело к некоторым трудно отлаживаемым проблемам. Используйте "savepv" в perlapi, чтобы создать копию, тем самым избежав этих проблем. Вам нужно будет освободить копию, когда вы закончите, чтобы избежать утечки памяти. Если вы не контролируете, когда она освобождается, вам нужно будет создать копию в смертном скаляре, как в примере:

    if ((s = PerlEnv_getenv("foo") == NULL) {
       ... /* handle NULL case */
    }
    else {
        s = SvPVX(sv_2mortal(newSVpv(s, 0)));
    }

    Приведённый пример работает только в том случае, если "s" NUL; в противном случае вам необходимо передать его длину в newSVpv.

Проблемные системные интерфейсы

  • malloc(0), realloc(0), calloc(0, 0) не являются переносимыми. Для переносимости необходимо выделять как минимум один байт. (В целом, вам редко нужно работать на таком низком уровне, вместо этого используйте различные обёртки malloc.)

  • snprintf() — возвращаемый тип не является переносимым. Используйте my_snprintf() вместо него.

Проблемы безопасности

И наконец, вот несколько советов по более безопасному кодированию. См. также perlclib для замены libc/stdio, которые следует использовать.

  • Не используйте gets()

    Или мы вас публично осмеём. Серьёзно.

  • Не используйте tmpfile()

    Используйте mkstemp() вместо него.

  • Не используйте strcpy() или strcat() или strncpy() или strncat()

    Используйте my_strlcpy() и my_strlcat() вместо них: они либо используют встроенную реализацию, либо собственную реализацию Perl (взятую из общедоступной реализации INN).

  • Не используйте sprintf() или vsprintf()

    Если вам действительно нужны только обычные байтовые строки, используйте my_snprintf() и my_vsnprintf() вместо них, которые попытаются использовать snprintf() и vsnprintf(), если эти более безопасные API доступны. Если вам нужно что-то более сложное, чем простая байтовая строка, используйте Perl_form() или SVs и Perl_sv_catpvf().

    Обратите внимание, что glibc printf(), sprintf() и т.д. содержат ошибки до версии glibc 2.17. Они не будут допускать формат %.s с точностью для создания строки, которая не является допустимым UTF-8, если текущий локальный стандарт программы — UTF-8. Происходит то, что %s и его операнд просто пропускаются без каких-либо сообщений. https://sourceware.org/bugzilla/show_bug.cgi?id=6530.

  • Не используйте atoi()

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

  • Не используйте strtol() или strtoul()

    Используйте grok_atoUV() вместо них. strtol() или strtoul() (или их аналогичные макросы IV/UV, Strtol() и Strtoul(), или Atol() и Atoul()) зависят от локального стандарта, что плохо.

ОТЛАДКА

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

Работа с Perl

Для работы с Perl вам, вероятно, потребуется скомпилировать Perl для отладки, например, так:

./Configure -d -DDEBUGGING
make

-DDEBUGGING включает флаг -g компилятора C для создания отладочной информации, что позволит нам выполнить пошаговую отладку программы и увидеть, в какой функции C мы находимся (без отладочной информации мы увидим только числовые адреса функций, что не очень полезно). Он также включит символ компиляции DEBUGGING, который активирует весь внутренний отладочный код в Perl. Существует множество вещей, которые можно отладить с помощью этого: perlrun перечисляет их все, и лучший способ узнать о них — поиграться с ними. Вероятно, наиболее полезными опциями являются

l  Context (loop) stack processing
s  Stack snapshots (with v, displays all stacks)
t  Trace execution
o  Method and overloading resolution
c  String/numeric conversions

Например

$ perl -Dst -e '$a + 1'
....
(-e:1)      gvsv(main::a)
    =>  UNDEF
(-e:1)      const(IV(1))
    =>  UNDEF  IV(1)
(-e:1)      add
    =>  NV(1)

Некоторую функциональность отладочного кода можно реализовать в неотладочном Perl с помощью модулей XS:

-Dr => use re 'debug'
-Dx => use O 'Debug'

Использование отладчика уровня исходного кода

Если вывод отладки -D вам не помогает, пришло время выполнить пошаговую отладку выполнения Perl с помощью отладчика уровня исходного кода.

  • В наших примерах мы будем использовать gdb; принципы применимы к любому отладчику (многие поставщики называют свой отладчик dbx), но просмотрите руководство по использованию того, который вы используете.

Чтобы запустить отладчик, введите

gdb ./perl

Или если у вас есть дамп ядра:

gdb ./perl core

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

(gdb)

help позволит вам попасть в документацию, но вот самые полезные команды:

  • run [args]

    Запустить программу с заданными аргументами.

  • break function_name

  • break source.c:xxx

    Сообщает отладчику, что мы хотим приостановить выполнение, когда мы достигнем либо указанной функции (но см. "Внутренние функции" в perlguts!), либо заданной строки в указанном файле исходного кода.

  • step

    Пошаговое выполнение программы по одной строке.

  • next

    Пошаговое выполнение программы по одной строке, без спуска в функции.

  • continue

    Продолжить выполнение до следующей точки останова.

  • finish

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

  • 'enter'

    Просто нажатие Enter повторно выполнит последнее действие — это благодать при пошаговой проработке километров исходного кода.

  • ptype

    Выводит C-определение переданного аргумента.

    (gdb) ptype PL_op
    type = struct op {
        OP *op_next;
        OP *op_sibparent;
        OP *(*op_ppaddr)(void);
        PADOFFSET op_targ;
        unsigned int op_type : 9;
        unsigned int op_opt : 1;
        unsigned int op_slabbed : 1;
        unsigned int op_savefree : 1;
        unsigned int op_static : 1;
        unsigned int op_folded : 1;
        unsigned int op_spare : 2;
        U8 op_flags;
        U8 op_private;
    } *
  • print

    Выполнить заданный C-код и вывести результаты. ПРЕДУПРЕЖДЕНИЕ: Perl активно использует макросы, и gdb не обязательно поддерживает макросы (см. далее "поддержка макросов gdb"). Вам нужно будет заменить их сами или вызвать cpp на файлах исходного кода (см. "Цели .i"). Например, вы не можете сказать

    print SvPV_nolen(sv)

    но вы должны сказать

    print Perl_sv_2pv_nolen(sv)

Вам может быть полезно иметь «словарь макросов», который вы можете получить, сказав cpp -dM perl.c | sort. Даже тогда cpp не применит эти макросы рекурсивно.

Поддержка макросов gdb

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

Выгрузка структур данных Perl

Один из способов обойти эту проблему макросов — использовать функции выгрузки в dump.c; они немного похожи на внутреннюю функцию Devel::Peek, но также охватывают операторы и другие структуры, к которым нельзя получить доступ из Perl. Рассмотрим пример. Мы воспользуемся $a = $b + $c, которое использовали ранее, но добавим немного контекста: $b = "6XXXX"; $c = 2.3;. Где лучше остановиться и начать изучение?

А как насчет pp_add, функции, которую мы изучали ранее, для реализации оператора +:

(gdb) break Perl_pp_add
Breakpoint 1 at 0x46249f: file pp_hot.c, line 309.

Обратите внимание, что мы используем Perl_pp_add, а не pp_add — см. "Внутренние функции" в perlguts. После установки точки останова, мы можем запустить нашу программу:

(gdb) run -e '$b = "6XXXX"; $c = 2.3; $a = $b + $c'

Много мусора пройдет мимо, пока gdb читает соответствующие исходные файлы и библиотеки, а затем:

Breakpoint 1, Perl_pp_add () at pp_hot.c:309
309         dSP; dATARGET; tryAMAGICbin(add,opASSIGN);
(gdb) step
311           dPOPTOPnnrl_ul;
(gdb)

Мы рассматривали этот фрагмент кода ранее, и говорили, что dPOPTOPnnrl_ul организует размещение двух NV в left и right — давайте немного расширим его:

#define dPOPTOPnnrl_ul  NV right = POPn; \
                        SV *leftsv = TOPs; \
                        NV left = USE_LEFT(leftsv) ? SvNV(leftsv) : 0.0

POPn берет SV из верхней части стека и получает его NV либо непосредственно (если SvNOK установлено), либо вызывая функцию sv_2nv. TOPs берет следующий SV из верхней части стека — да, POPn использует TOPs — но не удаляет его. Затем мы используем SvNV для получения NV из leftsv таким же способом, как и ранее — да, POPn использует SvNV.

Поскольку у нас нет NV для $b, нам придётся использовать sv_2nv для его преобразования. Если мы снова выполним шаг, то окажемся там:

(gdb) step
Perl_sv_2nv (sv=0xa0675d0) at sv.c:1669
1669        if (!sv)
(gdb)

Теперь мы можем использовать Perl_sv_dump для изучения SV:

(gdb) print Perl_sv_dump(sv)
SV = PV(0xa057cc0) at 0xa0675d0
REFCNT = 1
FLAGS = (POK,pPOK)
PV = 0xa06a510 "6XXXX"\0
CUR = 5
LEN = 6
$1 = void

Мы знаем, что получим 6 от этого, поэтому давайте закончим подпрограмму:

(gdb) finish
Run till exit from #0  Perl_sv_2nv (sv=0xa0675d0) at sv.c:1671
0x462669 in Perl_pp_add () at pp_hot.c:311
311           dPOPTOPnnrl_ul;

Мы также можем выгрузить этот оператор: текущий оператор всегда хранится в PL_op, и мы можем выгрузить его с помощью Perl_op_dump. Это даст нам вывод, похожий на B::Debug.

(gdb) print Perl_op_dump(PL_op)
{
13  TYPE = add  ===> 14
    TARG = 1
    FLAGS = (SCALAR,KIDS)
    {
        TYPE = null  ===> (12)
          (was rv2sv)
        FLAGS = (SCALAR,KIDS)
        {
11          TYPE = gvsv  ===> 12
            FLAGS = (SCALAR)
            GV = main::b
        }
    }

# завершить позже #

Использование gdb для просмотра определённых частей программы

В примере выше вы знали, что искать Perl_pp_add, но что если таких вызовов много, или вы не знаете, какой оператор вам нужен?

Один из способов — вставить редкий вызов где-то рядом с тем, что вы ищете. Например, вы можете добавить study перед вашим методом:

study;

А в gdb выполните:

(gdb) break Perl_pp_study

Затем переходите, пока не достигнете того, что ищете. Это хорошо работает в цикле, если вам нужно прерываться только на определённых итерациях:

for my $c (1..100) {
    study if $c == 50;
}

Использование gdb для просмотра работы анализатора/лексера

Если вы хотите увидеть, что делает perl при разборе/лексическом анализе вашего кода, вы можете использовать BEGIN {}:

print "Before\n";
BEGIN { study; }
print "After\n";

А в gdb:

(gdb) break Perl_pp_study

Если вы хотите увидеть работу анализатора/лексера внутри блоков if и подобных, вам нужно будет поступить немного хитрее:

if ($a && $b && do { BEGIN { study } 1 } && $c) { ... }

СТАТИЧЕСКИЙ АНАЛИЗ ИСТОЧНИКА

Существуют различные инструменты для анализа исходного кода C статически, в отличие от динамически, то есть без выполнения кода. Можно обнаружить утечки ресурсов, неопределённое поведение, несоответствия типов, проблемы с переносимостью, пути кода, которые могут привести к нелегальным обращениям к памяти, и другие подобные проблемы, просто проанализировав код C и рассмотрев полученный граф, что он говорит об исполнении и потоках данных. Фактически, именно так компиляторы C знают, как выдавать предупреждения о сомнительном коде.

lint

Хороший старый инспектор качества кода C, lint, доступен на нескольких платформах, но имейте в виду, что существует несколько различных реализаций его разными поставщиками, что означает, что флаги не идентичны на разных платформах.

В Makefile есть целевой lint, но вам может потребоваться поиграть с флагами (см. выше).

Coverity

Coverity (http://www.coverity.com/) — продукт, похожий на lint, и в качестве тестовой площадки для своего продукта они периодически проверяют несколько открытых проектов, предоставляя учётные записи разработчикам открытого кода для баз данных дефектов.

Для проекта perl5 есть настройка Coverity: https://scan.coverity.com/projects/perl5

HP-UX cadvise (Консультант по коду)

HP имеет продукт статического анализатора C/C++ для HP-UX под названием Code Advisor. (Ссылка не приводится здесь, потому что URL ужасно длинный и, похоже, ужасно нестабилен; воспользуйтесь поисковой системой по своему выбору, чтобы найти её.) Рекомендуется использовать рецепт cadvise_cc с Configure ... -Dcc=./cadvise_cc (см. "Руководство пользователя" cadvise); а также использование +wall.

cpd (детектора копирования-вставки)

Инструмент cpd обнаруживает код, скопированный и вставленный. Если одна часть скопированного и вставленного кода изменяется, все другие места, вероятно, тоже должны быть изменены. Поэтому такой код, вероятно, следует преобразовать в подпрограмму или макрос.

cpd (http://pmd.sourceforge.net/cpd.html) является частью проекта pmd (http://pmd.sourceforge.net/). pmd изначально был написан для статического анализа кода Java, но позже его часть cpd была расширена для обработки также C и C++.

Скачайте pmd-bin-X.Y.zip () со страницы SourceForge, извлеките pmd-X.Y.jar, а затем запустите его на исходном коде следующим образом:

java -cp pmd-X.Y.jar net.sourceforge.pmd.cpd.CPD \
 --minimum-tokens 100 --files /some/where/src --language c > cpd.txt

В случае возникновения проблем с памятью используйте параметр -Xmx:

java -Xmx512M ...

Предупреждения gcc

Хотя о проблемах с несоответствием и покрытием предупреждений gcc (например, -Wall не означает "все предупреждения", или некоторые общие проблемы переносимости не покрываются -Wall, или -ansi и -pedantic — плохо определённый набор предупреждений, и так далее) можно написать много, gcc всё ещё является полезным инструментом для поддержания чистоты кодирования.

-Wall включён по умолчанию.

-ansi (и его помощник -pedantic) было бы неплохо всегда включать, но, к сожалению, они не безопасны на всех платформах, например, могут вызвать фатальные конфликты с системными заголовками (Solaris — яркий пример). Если используется Configure -Dgccansipedantic, фронтенд cflags выбирает -ansi -pedantic для платформ, где они известны как безопасные.

Добавлены следующие дополнительные флаги:

  • -Wendif-labels

  • -Wextra

  • -Wc++-compat

  • -Wwrite-strings

  • -Werror=declaration-after-statement

  • -Werror=pointer-arith

Следующие флаги было бы неплохо добавить, но для начала нужно очистить их аугеев конюшенный:

  • -Wshadow

  • -Wstrict-prototypes

-Wtraditional — ещё один пример раздражающей тенденции gcc объединять множество предупреждений под одним переключателем (его было бы невозможно развернуть на практике, потому что он сильно жаловался бы), но он содержит некоторые предупреждения, которые было бы полезно иметь доступными самостоятельно, например, предупреждение о строковых константах внутри макросов, содержащих аргументы макроса: это поведение отличалось до ANSI, чем в ANSI, и некоторые компиляторы C всё ещё находятся в процессе перехода, AIX — пример.

Предупреждения других компиляторов C

Другие компиляторы C (да, существуют другие компиляторы C, помимо gcc) часто включают свои режимы "строгий ANSI" или "строгий ANSI с некоторыми расширениями переносимости", например, Sun Workshop имеет режим -Xa (хотя неявно), или DEC (в наши дни HP...) имеет режим -std1.

ДЕБАГГЕРЫ ПАМЯТИ

ПРИМЕЧАНИЕ 1: Запуск под старыми отладчиками памяти, такими как Purify, valgrind или Third Degree, значительно замедляет выполнение: секунды превращаются в минуты, минуты — в часы. Например, на Perl 5.8.1, ext/Encode/t/Unicode.t занимает чрезвычайно много времени для завершения под e.g. Purify, Third Degree и valgrind. Под valgrind он занимает более шести часов, даже на быстром компьютере. Сказанный тест должен делать что-то, что довольно недружелюбно относится к дебагерам памяти. Если вы не хотите ждать, вы можете просто убить процесс perl. Примерно valgrind замедляет выполнение в 10 раз, AddressSanitizer — в 2 раза.

ПРИМЕЧАНИЕ 2: Чтобы свести к минимуму ложные срабатывания утечки памяти (см. "PERL_DESTRUCT_LEVEL" для получения дополнительной информации), необходимо установить переменную окружения PERL_DESTRUCT_LEVEL в 2. Например, вот так:

env PERL_DESTRUCT_LEVEL=2 valgrind ./perl -Ilib ...

ПРИМЕЧАНИЕ 3: Известны утечки памяти, когда в eval или require есть ошибки компиляции, обнаружение S_doeval в стеке вызовов — хороший признак таких ошибок. К сожалению, исправление этих утечек нетривиально, но их нужно исправить в конечном итоге.

ПРИМЕЧАНИЕ 4: DynaLoader не очистит себя полностью, если Perl не скомпилирован с опцией Configure -Accflags=-DDL_UNLOAD_ALL_AT_EXIT.

valgrind

Инструмент valgrind можно использовать для выявления как утечек памяти, так и нелегальных обращений к куче. По состоянию на версию 3.3.0, Valgrind поддерживает только Linux на x86, x86-64 и PowerPC, а также Darwin (OS X) на x86 и x86-64. Для запуска тестов под valgrind можно использовать специальную цель "test.valgrind". Обнаруженные ошибки и утечки памяти регистрируются в файлах с именем testfile.valgrind, и по умолчанию вывод отображается в строке.

Пример использования:

make test.valgrind

Поскольку valgrind добавляет значительную нагрузку, тесты будут выполняться гораздо дольше. Для ускорения этого процесса тесты valgrind поддерживают параллельное выполнение:

TEST_JOBS=9 make test.valgrind

Обратите внимание, что два вышеприведённых вызова будут очень подробными, так как по умолчанию включены проверка достижимой памяти и утечек. Если вы хотите увидеть только чистые ошибки, попробуйте:

VG_OPTS='-q --leak-check=no --show-reachable=no' TEST_JOBS=9 \
    make test.valgrind

Valgrind также предоставляет инструмент cachegrind, который вызывается для perl так:

VG_OPTS=--tool=cachegrind make test.valgrind

Поскольку системные библиотеки (в первую очередь glibc) также вызывают ошибки, valgrind позволяет подавлять такие ошибки с помощью файлов подавления. В стандартном файле подавления, поставляемом с valgrind, уже подавлено множество таких ошибок. Некоторые дополнительные подавления определены в t/perl.supp.

Для получения valgrind и дополнительной информации см

http://valgrind.org/

AddressSanitizer

AddressSanitizer — это расширение clang и gcc, включённое в clang начиная с версии v3.1 и gcc начиная с версии v4.8. Оно проверяет некорректные указатели кучи, глобальные указатели, указатели стека и ошибки использования памяти после освобождения, и достаточно быстро, чтобы вы могли легко скомпилировать свои отладочные или оптимизированные perl-программы с его помощью. Однако оно не проверяет утечки памяти. AddressSanitizer доступен для Linux, Mac OS X и скоро появится для Windows.

Чтобы скомпилировать perl с AddressSanitizer, вызов Configure должен выглядеть следующим образом:

sh Configure -des -Dcc=clang \
   -Accflags=-faddress-sanitizer -Aldflags=-faddress-sanitizer \
   -Alddlflags=-shared\ -faddress-sanitizer

где эти аргументы означают:

  • -Dcc=clang

    Этот аргумент следует заменить полным путём к вашему исполняемому файлу clang, если он не находится в вашей переменной среды PATH.

  • -Accflags=-faddress-sanitizer

    Компилировать исходные файлы perl и расширений с AddressSanitizer.

  • -Aldflags=-faddress-sanitizer

    Связывать исполняемый файл perl с AddressSanitizer.

  • -Alddlflags=-shared\ -faddress-sanitizer

    Связывать динамические расширения с AddressSanitizer. Вы должны вручную указать -shared, потому что использование -Alddlflags=-shared помешает Configure установить значение по умолчанию для lddlflags, которое обычно содержит -shared (по крайней мере, в Linux).

См. также http://code.google.com/p/address-sanitizer/wiki/AddressSanitizer.

ПРОФИЛИРОВАНИЕ

В зависимости от вашей платформы существуют различные способы профилирования Perl.

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

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

Второй метод разбивает сгенерированный код на базовые блоки. Базовые блоки — это участки кода, в которые попадают только в начале и покидают только в конце. Например, условный переход начинает базовый блок. Профилирование базовых блоков обычно осуществляется путём инструментирования кода путём добавления вход в базовый блок #nnnn кода учёта в сгенерированный код. Во время выполнения кода счётчики базовых блоков затем обновляются соответствующим образом. Ограничение заключается в том, что добавленный дополнительный код может исказить результаты: снова, инструменты профилирования обычно пытаются исключить собственное влияние из результатов.

Профилирование с помощью gprof

gprof — это инструмент профилирования, доступный на многих платформах Unix, который использует статистическое временное отслеживание. Вы можете создать профилируемую версию perl, скомпилировав её с помощью gcc со флагом -pg. Вы можете либо изменить config.sh, либо снова запустить Configure. Запуск профилируемой версии Perl создаст выходной файл под названием gmon.out, который содержит данные профилирования, собранные во время выполнения.

Быстрый совет:

$ sh Configure -des -Dusedevel -Accflags='-pg' \
    -Aldflags='-pg' -Alddlflags='-pg -shared' \
    && make perl
$ ./perl ... # creates gmon.out in current directory
$ gprof ./perl > out
$ less out

(вероятно, вам нужно добавить -shared в строку <-Alddlflags> до тех пор, пока RT #118199 не будет решен)

Инструмент gprof затем может отобразить собранные данные различными способами. Обычно gprof понимает следующие опции:

  • -a

    Исключить статически определённые функции из профиля.

  • -b

    Исключить подробные описания в профиле.

  • -e routine

    Исключить заданную функцию и её потомков из профиля.

  • -f routine

    Отобразить только заданную функцию и её потомков в профиле.

  • -s

    Генерировать сводный файл под названием gmon.sum, который затем можно использовать для последующих запусков gprof для накопления данных за несколько запусков.

  • -z

    Отобразить функции, которые не используются.

Для более подробного объяснения доступных команд и форматов вывода см. вашу локальную документацию по gprof.

Профилирование с помощью GCC gcov

Профилирование базовых блоков официально доступно в gcc 3.0 и более поздних версиях. Вы можете скомпилировать профилируемую версию perl, используя gcc со флагами -fprofile-arcs -ftest-coverage. Вы можете либо изменить config.sh, либо снова запустить Configure.

Быстрый совет:

$ sh Configure -des -Dusedevel -Doptimize='-g' \
    -Accflags='-fprofile-arcs -ftest-coverage' \
    -Aldflags='-fprofile-arcs -ftest-coverage' \
    -Alddlflags='-fprofile-arcs -ftest-coverage -shared' \
    && make perl
$ rm -f regexec.c.gcov regexec.gcda
$ ./perl ...
$ gcov regexec.c
$ less regexec.c.gcov

(вероятно, вам нужно добавить -shared в строку <-Alddlflags> до тех пор, пока RT #118199 не будет решен)

Запуск профилируемой версии Perl вызовет генерацию выходных данных профиля. Для каждого исходного файла будет создан сопровождающий файл .gcda.

Для отображения результатов используется утилита gcov (которая должна быть установлена, если у вас установлен gcc 3.0 или более поздней версии). gcov запускается на исходных файлах, например так

gcov sv.c

что приведёт к созданию sv.c.gcov. Файлы .gcov содержат исходный код, снабжённый аннотациями с относительными частотами выполнения, указанными маркерами «#». Если вы хотите сгенерировать файлы .gcov для всех профилируемых объектных файлов, вы можете запустить что-то вроде этого:

for file in `find . -name \*.gcno`
do sh -c "cd `dirname $file` && gcov `basename $file .gcno`"
done

Полезные опции gcov включают -b, которая подведёт итоги покрытия базовых блоков, ветвлений и вызовов функций, и -c, которая вместо относительных частот будет использовать фактические счётчики. Для получения дополнительной информации об использовании gcov и профилировании базовых блоков с помощью gcc, см. последний справочник по GNU CC. Начиная с gcc 4.8, он находится по адресу http://gcc.gnu.org/onlinedocs/gcc/Gcov-Intro.html#Gcov-Intro

РАЗЛИЧНЫЕ ПРИЁМЫ

PERL_DESTRUCT_LEVEL

Если вы хотите самостоятельно выполнить тесты с помощью, например, valgrind, обратите внимание, что по умолчанию perl не явно очищает всю выделенную им память (например, глобальные области памяти), а вместо этого позволяет выходу () всей программы «заняться» такими выделениями, также известными как «глобальное уничтожение объектов».

Существует способ сообщить perl о полном очищении: установите переменную среды PERL_DESTRUCT_LEVEL в ненулевое значение. Врапер t/TEST устанавливает это значение в 2, и это то, что вам нужно сделать, если вы не хотите видеть «глобальные утечки»: Например, для запуска под valgrind

env PERL_DESTRUCT_LEVEL=2 valgrind ./perl -Ilib t/foo/bar.t

(Примечание: модуль mod_perl apache также использует эту переменную среды для своих целей и расширил её семантику. Для получения дополнительной информации см. документацию по mod_perl. Также, запущенные потоки выполняют эквивалент установки этой переменной в значение 1.)

Если в конце выполнения вы получите сообщение N scalars leaked, вы можете перекомпилировать с -DDEBUG_LEAKING_SCALARS, (Configure -Accflags=-DDEBUG_LEAKING_SCALARS), что заставит вывести адреса всех этих утечек SVs вместе с деталями о том, где каждый SV был первоначально выделен. Эта информация также отображается Devel::Peek. Обратите внимание, что дополнительные данные, записанные с каждым SV, увеличивают использование памяти, поэтому не следует использовать его в производственных средах. Он также преобразует new_SV() из макроса в реальную функцию, поэтому вы можете использовать свой любимый отладчик, чтобы выяснить, где эти надоедливые SVs были выделены.

Если вы заметите, что утечка памяти происходит во время выполнения, но ни valgrind, ни -DDEBUG_LEAKING_SCALARS ничего не найдут, вы, вероятно, пропускаете SVs, которые всё ещё доступны и будут должным образом очищены во время уничтожения интерпретатора. В таких случаях использование переключателя -Dm может указать вам на источник утечки. Если исполняемый файл был скомпилирован с -DDEBUG_LEAKING_SCALARS, -Dm выведет выделения SV в дополнение к выделениям памяти. Каждое выделение SV имеет уникальный серийный номер, который будет записан при создании и уничтожении SV. Итак, если вы выполняете утечку кода в цикле, вам нужно найти SVs, которые создаются, но никогда не уничтожаются между каждым циклом. Если такой SV найден, установите условную точку останова внутри new_SV() и сделайте так, чтобы она останавливалась только тогда, когда PL_sv_serial равно серийному номеру пропускаемого SV. Таким образом, вы поймаете интерпретатор ровно в том состоянии, где выделен пропускаемый SV, что в большинстве случаев достаточно, чтобы найти источник утечки.

Так как -Dm использует слой PerlIO для вывода, он сам по себе выделит довольно много SVs, которые скрыты для предотвращения рекурсии. Вы можете обойти слой PerlIO, если используете ведение журнала SV, предоставляемое -DPERL_MEM_LOG вместо этого.

PERL_MEM_LOG

Если скомпилирован с -DPERL_MEM_LOG (-Accflags=-DPERL_MEM_LOG), как выделение памяти, так и выделения SV проходят через функции ведения журнала, что удобно для установки точек останова.

Если -DPERL_MEM_LOG_NOIMPL (-Accflags=-DPERL_MEM_LOG_NOIMPL) также не скомпилирован, функции ведения журнала читают $ENV{PERL_MEM_LOG}, чтобы определить, следует ли регистрировать событие и, если да, то как:

$ENV{PERL_MEM_LOG} =~ /m/           Log all memory ops
$ENV{PERL_MEM_LOG} =~ /s/           Log all SV ops
$ENV{PERL_MEM_LOG} =~ /t/           include timestamp in Log
$ENV{PERL_MEM_LOG} =~ /^(\d+)/      write to FD given (default is 2)

Ведение журнала памяти несколько похоже на -Dm, но независимо от -DDEBUGGING и на более высоком уровне; все операции Newx(), Renew() и Safefree() регистрируются вместе с файлом исходного кода вызывающей стороны и номером строки (и именем C-функции, если это поддерживается компилятором C). В отличие от этого, -Dm находится непосредственно в точке malloc(). Ведение журнала SV аналогично.

Поскольку ведение журнала не использует PerlIO, все выделения SV регистрируются, и никаких дополнительных выделений SV не вводятся при включении ведения журнала. Если скомпилировано с -DDEBUG_LEAKING_SCALARS, также регистрируется серийный номер каждого выделения SV.

DDD через gdb

Те, кто отлаживает perl с помощью переднего плана DDD через gdb, могут найти это полезным:

Вы можете расширить меню сокращений для преобразования данных, чтобы, например, отобразить значение IV SV одним щелчком мыши, не вводя никаких значений. Для этого просто отредактируйте файл ~/.ddd/init и добавьте после:

! Display shortcuts.
Ddd*gdbDisplayShortcuts: \
/t ()   // Convert to Bin\n\
/d ()   // Convert to Dec\n\
/x ()   // Convert to Hex\n\
/o ()   // Convert to Oct(\n\

следующие две строки:

((XPV*) (())->sv_any )->xpv_pv  // 2pvx\n\
((XPVIV*) (())->sv_any )->xiv_iv // 2ivx

Теперь вы можете выполнять запросы ivx и pvx или вставить туда преобразование «sv_peek»:

Perl_sv_peek(my_perl, (SV*)()) // sv_peek

(my_perl предназначен для многопоточных сборок.) Просто помните, что каждая строка, кроме последней, должна заканчиваться \n\

В качестве альтернативы вы можете редактировать файл init интерактивно, нажав правой кнопкой мыши -> Новый дисплей -> Редактировать меню

Примечание: вы можете определить до 20 сокращений преобразования в разделе gdb.

C трассировка стека

На некоторых платформах Perl поддерживает получение трассировки стека на уровне C (аналогично тому, что делают символьные отладчики, такие как gdb).

Трассировка стека возвращает трассировку стека кадров вызовов C, с именами символов (имена функций), именами объектов (например, «perl») и, если это возможно, также местоположениями исходного кода (файл:строка).

Поддерживаемые платформы: Linux и OS X (некоторые *BSD могут работать частично, но пока не были протестированы).

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

Эта функция должна быть включена с помощью Configure -Dusecbacktrace.

END_OF_DOCUMENT_MARKER

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

Статические функции могут быть невидимыми для трассировки стека.

Местоположения исходного кода, даже если они доступны, часто могут отсутствовать или быть вводящими в заблуждение, если компилятор, например, встроил код. Оптимизатор может значительно затруднить сопоставление исходного кода и объектного кода.

Linux

У вас должен быть установлен библиотека BFD (-lbfd), в противном случае perl не сможет связаться. Библиотека BFD обычно распространяется как часть GNU binutils.

Краткое описание: Configure ... -Dusecbacktrace и вам понадобится -lbfd.

OS X

Местоположения исходного кода поддерживаются только при установленных Developer Tools. (Библиотека BFD не нужна.)

Краткое описание: Configure ... -Dusecbacktrace и установка Developer Tools будет полезной.

В качестве дополнительной возможности для тестирования функции можно включить автоматическое вывода трассировки стека непосредственно перед выводом сообщения об ошибке или вызовом croak (die), добавив -Accflags=-DUSE_C_BACKTRACE_ON_ERROR в Configure.

Если указанная дополнительная функция не включена, никакая информация о функциональности трассировки стека не отображается, кроме уровня Perl/XS.

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

Получение трассировки стека с уровня Perl (например, с помощью XS-расширения) будет значительно менее интересным, чем хотелось бы: обычно вы увидите runops, entersub, и не многое другое. Данный API предназначен для вызова внутри реализации Perl, а не из Perl-уровня выполнения.

C API для трассировки стека выглядит следующим образом:

get_c_backtrace
free_c_backtrace
get_c_backtrace_dump
dump_c_backtrace

Отравление

Если вы видите в отладчике область памяти, загадочно заполненную 0xABABABAB или 0xEFEFEFEF, это может быть следствием использования макросов Poison(), см. perlclib.

Только для чтения optrees

В ithreads optree является только для чтения. Если вы хотите это обеспечить, чтобы проверить доступ для записи из дефектного кода, скомпилируйте с -Accflags=-DPERL_DEBUG_READONLY_OPS для включения кода, который выделяет оперативную память через mmap и делает её только для чтения при её прикреплении к подпрограмме. Любой доступ для записи к op приводит к SIGBUS и прерыванию.

Этот код предназначен только для разработки и может быть не переносимым даже на все варианты Unix. Кроме того, это решение на 80%, так как оно не способно сделать все ops только для чтения. В частности, оно не относится к op-блокам, принадлежащим BEGIN блокам.

Однако, как решение на 80%, оно всё ещё эффективно, поскольку в прошлом помогло выявить ошибки.

Когда bool не является bool?

В компиляторах до C99 bool определяется как эквивалент char. Следовательно, присваивание любого типа большего размера к bool небезопасно и может быть усечено. Макрос cBOOL существует для правильного приведения типа; вы также можете заметить, что его использование короче и понятнее, чем запись эквивалентного условного выражения в развернутом виде.

На платформах и компиляторах, где bool действительно является булевым значением (C++, C99), легко забыть приведение типа. Вы можете заставить bool быть char, скомпилировав с -Accflags=-DPERL_BOOL_AS_CHAR. Вы также можете запустить Configure с чем-то вроде

-Accflags='-Wconversion -Wno-sign-conversion -Wno-shorten-64-to-32'

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

Макросы TRUE и FALSE доступны для ситуаций, где их использование прояснит намерение. (Однако они всегда просто означают те же целые числа 1 и 0, соответственно, поэтому их использование не обязательно.)

Цели .i

Вы можете расширить макросы в файле foo.c, сказав

make foo.i

что расширит макросы с помощью cpp. Не пугайтесь результатов.

АВТОР

Этот документ первоначально написал Nathan Torkington, и его поддерживает рассылка perl5-porters.

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlhacktips

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API