Spec-Zone.ru › Perl 5.30

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 трассировка стека
    • Яд
    • Только для чтения optrees
    • Когда bool не является bool?
    • Цели .i
  • АВТОР

НАЗВАНИЕ

perlhacktips - Советы по взлому ядра Perl на C

ОПИСАНИЕ

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

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

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

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

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

  • Отказ от компиляции с потоками

    Компиляция с потоками (-Duseithreads) полностью переписывает прототипы функций Perl. Вам лучше попробовать ваши изменения с этим. Связанное с этим — различие между "Perl_-без" и "Perl_-подобными" 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) будет кодовой точкой, которая означает «маленькая латинская буква с острым ударением» на любой платформе, на которой вы работаете (на платформах 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 случаях для строк без UTF8, но отличается в каждом случае, когда строка закодирована в UTF-8. Единственные другие кодовые точки, которые обладают какой-то общностью во всех 4 кодовых страницах, — это пара 0xDC и 0xFC. Вместе они представляют верхний и нижний регистры LATIN LETTER U WITH DIAERESIS, но верхний и нижний регистр могут быть переставлены: 0xDC — это заглавная буква в Latin1, а 0xFC — строчная буква, в то время как 0xFC — это заглавная буква в EBCDIC, а 0xDC — строчная. Этот факт можно использовать для написания тестов, не чувствительных к регистру, которые одинаковы во всех 4 кодовых страницах.

  • Предполагая, что кодовая страница — только 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

    Нельзя переносить «стеки» директив cpp. Например, в приведенном выше примере требуется два отдельных #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 в функции с переменным числом аргументов (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 включает в C-компилятор флаг -g, чтобы он генерировал отладочную информацию, которая позволит нам шагать по исполняемой программе и видеть, в какой 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
1396    dSP; dATARGET; bool useleft; SV *svl, *svr;
(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. Это даст нам выходные данные, похожие на модуль CPAN 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 занимает чрезвычайно много времени для завершения под, например, 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 не явно очищает всю выделенную память (такие как глобальные области памяти), а вместо этого предоставляет для этого выход exit() всей программы, также известный как «глобальное уничтожение объектов».

Есть способ указать 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.

Также -Dusecbacktrace позволяет сохранить отладочную информацию при компиляции/связывании (часто: -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 для включения кода, который выделяет память op через mmap и устанавливает её в режим только для чтения при присоединении к подпрограмме. Любая попытка записи в op приводит к SIGBUS и прерыванию.

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

Однако, как решение на 80%, оно по-прежнему эффективно, так как в прошлом помогало обнаружить ошибки.

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

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

На платформах и компиляторах, где bool действительно является булевым значением (C++, C99), легко забыть о приведении типов. Вы можете принудительно сделать bool булевым значением, скомпилировав с -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. Не пугайтесь результатов.

АВТОР

Этот документ изначально был написан Натаном Торкинтоном и поддерживается почтовым списком 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.30.3/perlhacktips

Spec-Zone.ru

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