class: center, middle # Интерактивная отладка Perl .align-right[ **отладчик perl5db.pl** ] --- ## Зачем? * Исследование работы незнакомой программы * Пошаговая интерактиная отладка * REPL (Read-eval-print loop) среда --- ## Запуск * `perl -d script.pl` * `perl -de0` * Установить Term::ReadLine, что бы редактировать вводимую команду --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- ## Еще команды * **|** *dbcmd* Выполняет команду отладчика, перенаправля $DB::OUT в пейджер * **||** *dbcmd* Тоже, но выполняет select $DB::OUT * **a** [*line*] *expr* Добавляет действие, выполняемое, когда отладчик достигает строки *line* * **A** [*line*|*****] Удаляет действия * **y** [[*level*] *pattern*] Дампит локальные переменные, на уровне стека *level* * **Y** [[*package*] *pattern*] Дампит переменные пакета * **X** [*patter*] Тоже, в текущем пакете * **M** Выводит список загруженных файлов * **t** [[*max-level*] *expr*] Трассировка. Отладчик печатает каждую выполняемую строку. *max-level* Ограничивает максимальную глубину стека * **o** [*opt*|*opt*?|*opt*=*val*] Устанавливает, или выводит оцию отладчика. Без параметров выводит все опции * **=** [*alias* *value*] Создает алиас для команды. Без парамеров выводи все алиасы. * **Ctrl+C** SIGINT во время выполнения программы приостанавливает отладчик на текущей строке * **<**, **<<**, **>**, **>>** Добавляют и удаляют действия (perl выражение) на остановку отладчика и продолжение работы программы * **{**, **{{**, **}**, **}}** Также, только действия в командах отладчика * **rerun** [*n*|-*n*] "Шаг назад". Выполняет скрипт заново до указанной команды в истории. * **save** *file* Сохраняет историю комманд в файл * **source** *file* Выполняет комманды отладчика из файла * **H** [-*number*|*****] Показывает, очищает историю команд * **!** [*number*|-*number*|*pattern*] Повторяет последнюю команду по номеру или шаблону --- ## Опции и переменные отладчика * o pager - Пейджер для команд *|* и *||* * o frame, $frame - Выводить информацию о входе и выходе из процедур * o inhibit_exit - Выход из отлачика по завершению программы * o CreateTTY - Когда создавать новый TTY * 0x1 - На fork() * 0x2 - Когда perl процесс запущен из под другого perl процесса, работающего под отладчиком * 0x1 - При старте * o NonStop - Не останавливатся при старте программы * o AutoTrace, $trace - Включает/выключает трассировку * $single - При установке переменной, дебагер останавливается. Полезно для остановки дебагера из программы * $OUT, $IN - Ввод и вывод отладчика * %alias - алиасы команд Переменная окружения PERLDB_OPTS задает значение опций отладчика. Файл `.perldb` в $HOME или текущем каталоге выполняется при запуске отладчки. Полезен для установки своих настроек. --- class: center, middle # Рецепты --- ## Переопредялем пейджер Используем vim в качестве более удобного пейджера `/usr/local/bin/perldbpg:` ```shell #!/bin/sh file=$(mktemp /tmp/db-XXXXXXX) cat > "$file" exec vim -c'se sw=3' -c'se fdm=indent' -c'se fdl=1000' $@ -- "$file" < /dev/tty; ``` ``` o pager='|perldbpg' ``` --- class: screenshot  --- class: screenshot  --- .header[ ## Отладка форков Терминальные мультиплексоры ] .left-column[
] .right-column[ * tmux * Богатый функцонал * Удобное API * Способ организации терминалов - вкладки, каждая вкладка разбивается на несколько областей. Есть простенький тайлинг. * screen * Богатый функцонал * Способ организации терминалов - экран разбивается на несколько областей, в каждой области можно переключатся на нужный терминал * dvtm * Простой функционал * Тайлинговая организация окн терминалов. Пожалуй наиболее удобная в контексте отладки форков ] --- ### DB::get_fork_TTY ```perl sub DB::get_fork_TTY { # создать терминал, и вернуть путь к нему my $tty = qx(tmux new-window -PF '#{pane_tty}' -n FORK 'sleep 1000000'); chomp $tty; return $tty; } ``` ```perl sub DB::get_fork_TTY { # или, если api мультиплексора не умеет возвращать терминал my $fifo = qx(mktemp -u); chomp $fifo; qx(mkfifo $fifo); # создаем именованый пайп qx(tmux new-window -n FORK 'tty > $fifo; sleep 1000000'); # команда пишет tty в пайп open my $fh, '<', $fifo; my $tty = <$fh>; # читаем строку с tty close $fh; unlink $fifo; chomp $tty; return $tty; } ``` --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- ## Визуализация местонахождения Проблема: консольный дебагер весьма мощный, но не хватает визуализации местонахождения. Сложно сказать, где мы находимся в данный момент, плохо видно контекст. Решение: опция `LineInfo` задает отладчику файл, или комаду, в которую писать информацию о текущем местоположении вместо $DB::OUT. Можно написать плагин к редактору, который будет ее читать и перескакивать на нужные строки. --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- class: screenshot  --- ## Отладка программ со встраиваемым Perl Самый простой способ включить дебагер - переменная окружения PERL5OPT=-d ```shell PERL5OPT=-d uwsgi ... ``` Альтернативный - с помощью установки флагов переменной `$^P` при инициализации Perl программы и загрузке дебагера вручную. Или с помощью модуля `Enbugger`. В нужном месте прописать установку переменной `$DB::single=1` для остановки #### Отладка mod_perl mod_perl переопределяет поведение блоков END в Perl (код в них выполняется после отработки каждого http запроса). Это ломает отладчик. ```shell sed 's/END /sub db_END /g' /usr/lib/perl5/5.8.8/perl5db.pl > /usr/lib/perl5/5.8.8/perl5db-apache.pl PERL5DB='BEGIN { require "perl5db-apache.pl" }' PERL5OPT=-d httpd -X ``` Так же можно воспользоваться модулем `Apache::DB` --- Можно спрятать настройки в httpd.conf, например ```
use Apache::DB(); Apache::DB->init;
``` запуск: ```shell httpd -X -DPERLDB ``` --- ## Проблемы с отладкой кода из eval + #line В некоторых проектах используется генерация кода с помощью eval, с указанием исходного файла с помощью `#line`. ```perl my $filename = 'source-file.pl'; my $eval = qq{ sub foo { #line 1 "$filename" ... } }; eval $eval; foo(); ``` Отладчик в perl 5.8 корректно отображает номер текущей строки и файл, но не показвает код по `v`, `l` и не может ставить брейкпойнты. --- Лечится: ```perl sub _eval_dblines { my($file, $eval, $remove_before, $remove_after) = @_; my $dbline = $main::{"_<$file"} = $main::{"_<$eval"}; ${$dbline} = $file; splice @{$dbline}, 0, $remove_before if $remove_before; splice @{$dbline}, -$remove_after if $remove_after; ${$dbline}[0] = undef; } my $filename = 'source-file.pl'; my $eval = qq{ sub foo { #line 1 "$filename" ... } }; $eval = "BEGIN { _eval_dblines(q[$filename], __FILE__, 3, 1) };" . $eval if $^P; eval $eval; foo(); ``` --- ## Интересные модули **Еще отладчики** * perl5db.pl Стандартный perl отладчик, входящий в состав perl * Devel::trepan Новый модульный отладчик * Devel::ebug Мультиинтерфейсный отладчик (Консоль, GUI, Web) * Devel::sdb форк perl5db.pl, с отображением выполняемого кода * Devel::hdb Отладчик с Web интерфейсом (REST и HTML) * Devel::PDB Отладчик c TUI интерфейсом * Devel::ptkdb Отладчик с GUI интерфейсом (Perl/Tk) * Enbugger Включает отладчик или профайлер рантайм. С помощью gdb можно подключится к любом perl процессу без какой-либо модификации программы * Regexp::Debugger Аддский PurePerl модуль для интерактиной отладки регексов * Tie::Watch Полезен для гибкого наблюдения за чтением/записью в переменные * Debug::Fork::Tmux Привязка get_fork_TTY к tmux --- Миниотладчик .small[ ```perl # Devel/simpledb.pm -- perl -d:simpledb script.pl package DB; use strict; use warnings; use Term::ReadLine; use Data::Dumper; our $TERM = Term::ReadLine->new(); our $OUT = $TERM->OUT; our $STOP = 1 << 30; our $NONSTOP = 0; sub DB::DB { my($package, $file, $line) = caller(0); return if $NONSTOP; local $NONSTOP = 1; print $OUT "$line:" . $main::{"_<$file"}->[$line]; while(1) { my $command = $TERM->readline("$$> "); chomp($command); if($command =~ /^x\b(.*)/) { # выполнить perl код, вывести результат my @result = eval("package $package; \$^D = \$^D | \$DB::STOP; " . $1); if($@) { print $OUT "!!! $@"; } else { print $OUT Data::Dumper->new([@result])->Terse(1)->Dump; } } elsif($command =~ /^n\b/) { # следующая строка last; } elsif($command =~ /^q\b/) { # выход exit; } }; } 1; ``` ] --- class: center, middle # END