Perl 디버깅

Perl 디버깅 (perldebug)

이 문서는 Perl에 내장된 명령줄 디버거 사용법을 다뤄요. -d 스위치로 디버거를 켜고, 중단점·단계 실행·표현식 평가·변수 검사까지 어떻게 쓰는지 강사가 옆에서 알려 주듯 안내해 드릴게요.

시작하기 전에, use strict;use warnings;는 써 보고 했어요? 디버거가 처음이라면 입문 튜토리얼인 perldebtut를 먼저 읽는 걸 추천해요. 디버거가 어떻게 구현되는지 깊이 알고 싶다면 perldebguts를 보세요. 기술적인 세부 사용법은 perl5db.pl 문서를 참고해요.

출처: perldoc - perldebug

본문

Perl 디버거 (The Perl Debugger)

-d 스위치로 Perl을 실행하면 스크립트가 소스 디버거 아래에서 실행돼요. 이건 대화형 Perl 환경처럼 동작해서, 소스 검사·중단점 설정·스택 역추적·변수 값 변경 등을 할 수 있어요. 꽤 편리해서 Perl 구문을 시험해 보려고 디버거만 따로 띄우기도 해요:

$ perl -d -e 42

Perl에서 디버거는 일반 컴파일 환경에서처럼 별도 프로그램이 아니에요. 대신 -d 플래그가 컴파일러에게 파서 트리에 소스 정보를 넣으라고 지시해요. 따라서 디버거가 동작하려면 코드가 먼저 제대로 컴파일돼야 해요. 인터프리터가 시작되면 디버거를 담은 특별한 Perl 라이브러리 파일을 미리 로드해요.

프로그램은 첫 실행 가능 문장 바로 직전에 멈추고 디버거 명령을 입력받아요. 디버거가 줄을 보여 줄 때는 앞으로 실행할 줄을 보여 준다는 점을 기억하세요(방금 실행한 줄이 아니에요).

디버거가 모르는 명령은 현재 패키지에서 Perl 코드로 (eval로) 바로 실행돼요. 디버거는 자신의 상태를 DB 패키지에 저장해요. 그 eval은 암묵적 스코프에 묶이므로, 새로 만든 렉시컬 변수나 수정된 캡처 버퍼 내용은 eval이 끝나면 사라져요. 같은 스코프에서 실험하려면 한 줄에 다 담아서 입력하세요.

디버거 프롬프트에 입력한 텍스트는 앞뒤 공백이 먼저 제거돼요. 만약 디버거 명령이 프로그램의 함수 이름과 겹치면, ;+를 앞에 붙이거나 괄호·중괄호로 감싸서 함수로 해석되게 하세요.

디버거 호출하기 (Calling the Debugger)

디버거를 호출하는 방법은 여러 가지예요:

  • perl -d program_name : 지정한 프로그램을 디버깅.
  • perl -d -e 0 : -e로 임의의 표현식을 대화형으로 제공.
  • perl -d:ptkdb program_name : Devel::ptkdb GUI로 디버깅.
  • perl -dt threaded_program_name : 스레드를 써서 디버깅(실험적).

-d 스위치로 실행하면 변수 $^P가 참(true) 값이 돼요. 코드가 디버거 아래 실행 중인지 확인할 때 유용해요:

if ( $^P ) {
    # running under the debugger
}

$^P에 대한 자세한 정보는 perlvar를 보세요.

디버거 명령 (Debugger Commands)

대화형 디버거는 다음 명령들을 이해해요.

  • h : 요약 도움말 출력.
  • h [command] : 특정 디버거 명령에 대한 도움말.
  • h h : 전체 도움말 페이지 출력(꽤 깁니다). 화면을 넘어가면 |h h처럼 파이프 기호를 앞에 붙여 페이저로 보세요. 페이저는 o pager=...로 바꿀 수 있어요.
  • p expr : 현재 패키지에서 print {$DB::OUT} expr과 같아요. Perl의 print이므로 중첩 데이터 구조·객체는 덤프하지 않아요(x 명령과 다름). DB::OUT 파일핸들은 STDOUT이 어디로 리다이렉트되든 /dev/tty로 열려요.
  • x [maxdepth] expr : 리스트 컨텍스트로 표현식을 평가하고 예쁘게 출력해요. 중첩 구조는 재귀적으로 찍어요. 해시를 덤프할 땐 x %h보다 x \%h가 좋아요. maxdepth는 숫자 N으로, dumpDepth가 일시적으로 N으로 설정된 것처럼 N 레벨 깊이만 덤프해요.
  • V [pkg [vars]] : 패키지(기본값 main)의 변수를 데이터 프리티프린터로 표시해요. 타입 지정자($ 등)는 넣지 말고 심볼 이름만 넣어요: V DB filename line. ~pattern!pattern은 양·음 정규식 필터예요. 각 변수에 x를 호출하는 것과 유사해요.
  • X [vars] : V currentpackage [vars]와 같아요.
  • y [level [vars]] : 현재 스코프나 level 단계 위의 렉시컬 변수를 표시해요. PadWalker 0.08 이상 필요해요.
  • T : 스택 역추적(backtrace)을 생성해요. 아래에서 자세히 설명할게요.
  • s [expr] : 단계 실행. 서브루틴 호출 안으로 들어가면서 다음 문장 시작까지 실행해요.
  • n [expr] : 다음(next). 서브루틴 호출을 건너뛰고 다음 문장 시작까지 실행해요. 함수 호출이 포함된 표현식이면 각 문장 전에 멈춰요.
  • r : 현재 서브루틴의 반환까지 계속 실행. PrintRet 옵션이 기본값으로 설정돼 있으면 반환 값을 덤프해요.
  • <CR> : 마지막 n이나 s 명령 반복.
  • c [line|sub] : 계속 실행. 지정한 줄이나 서브루틴에 1회성 중단점을 넣어요.
  • l : 다음 창(window)의 줄들 출력. l min+incrmin부터 incr+1줄, l min-maxmin부터 max까지, l line은 한 줄, l subname은 서브루틴의 첫 창.
  • - : 이전 창의 줄들 출력.
  • v [line] : 현재 줄 주변 몇 줄 보기.
  • . : 디버거 내부 포인터를 마지막 실행 줄로 되돌리고 그 줄 출력.
  • f filename : 다른 파일이나 eval 문장 보기로 전환. %INC의 값이 아닌 전체 경로명이 아니면 정규식으로 취급돼요. f (eval 7)처럼 eval 문자열에 접근할 수 있어요.
  • /pattern/ : 정방향으로 패턴(Perl 정규식) 검색(기본 대소문자 무시).
  • ?pattern? : 역방향으로 검색(기본 대소문자 무시).
  • L [abw] : 액션·중단점·워치 표현식 나열(기본 전부).
  • S [[!]regex] : 정규식과 [일치하지 않는] 서브루틴 이름 나열.
  • t [n] : 추적 모드 토글. 선택 인자는 추적할 최대 레벨.
  • t [n] expr : expr의 실행을 추적. 예시는 perldebguts.
  • b : 현재 줄에 중단점.
  • b [line] [condition] : 지정 줄 앞에 중단점. 조건은 매번 평가돼 참일 때만 멈춰요. 조건에 if는 쓰지 않아요: b 237 $x > 30. b . $n > 100은 현재 줄에.
  • b [file]:[line] [condition] : 다른 파일의 줄 앞에 중단점: b lib/MyModule.pm:237 $x > 30.
  • b subname [condition] : 이름 붙은 서브루틴의 첫 줄 앞에 중단점.
  • b postpone subname [condition] : 컴파일된 후 서브루틴 첫 줄에 중단점.
  • b load filename : %INC의 값인 전체 경로의 파일 첫 실행 줄 앞에 중단점.
  • b compile subname : 서브루틴 컴파일 후 첫 문장 앞에 중단점.
  • B line : 줄의 중단점 삭제. B * : 모든 중단점 삭제.
  • disable [file]:[line] / disable [line] : 중단점 비활성화(삭제는 아님). enable로 다시 켜요.
  • a [line] command : 줄 실행 전에 액션 설정. 디버거의 순서: ① 이 줄 중단점 확인 ② 필요하면 줄 출력(추적) ③ 그 줄의 액션 수행 ④ 중단점·단계에서 사용자에게 물음 ⑤ 줄 평가. 예: a 53 print "DB FOUND $foo\n".
  • A line : 줄의 액션 삭제. A * : 모든 액션 삭제.
  • w expr : 전역 워치 표현식 추가. 감시 중인 전역이 바뀌면 멈추고 옛·새 값을 보여줘요. W expr 삭제, W * 모두 삭제.
  • o : 모든 옵션 표시. o booloption ... : 불리언 옵션을 1로. o anyoption? ... : 값 표시. o option=value ... : 값 설정. 값에 공백이 있으면 따옴표로 감싸요: o pager="less -MQeicsNfr".
  • < ? : 프롬프트 전 Perl 명령 액션 나열. < [ command ] : 매 프롬프트 전 실행될 액션 설정. < * : 전부 삭제. << command : 액션 추가.
  • > ? / > command / > * / >> command : 프롬프트 후 액션에 대한 같은 기능.
  • { ? : 프롬프트 전 디버거 명령 나열. { [ command ] : 설정. { * : 삭제. {{ command : 추가. 블록을 실수로 입력한 것처럼 보이면 경고가 나요; 블록을 의도했다면 ;{ ... }do { ... }로 쓰세요.
  • ! number : 이전 명령 반복(기본 직전 명령). ! -number : number번째 이전 명령. ! pattern : pattern으로 시작한 마지막 명령 반복.
  • !! cmd : 서브프로세스에서 cmd 실행(DB::IN에서 읽고 DB::OUT에 씀). $ENV{SHELL} 셸을 사용해요.
  • source file : 파일에서 디버거 명령을 읽고 실행.
  • H -number : 마지막 n개 명령 표시(한 글자보다 긴 명령만). number 생략 시 전부.
  • q 또는 ^D : 종료. 디버거를 나가는 유일한 지원 방법이에요. exit를 두 번 치면 될 수도 있어요. 스크립트 끝까지 단계로 넘기려면 inhibit_exit 옵션을 0으로.
  • R : exec()로 새 세션을 띄워 디버거 재시작. history·중단점·액션·옵션·-w -I -e는 유지되지만 일부 내부 설정은 잃을 수 있어요.
  • |dbcmd : DB::OUT을 페이저로 파이프하며 명령 실행. ||dbcmd : DB::OUT을 select하기도 하는 같은 명령.
  • = [alias value] : 명령 별명 정의. 예: = quit q. 또는 현재 별명 나열.
  • command : 명령을 Perl 문장으로 실행(끝에 세미콜론 자동 추가). 디버거 명령과 헷갈릴 수 있으면 앞에 ;도 붙여요.
  • m expr : 평가된 표현식의 결과에 호출 가능한 메서드 나열.
  • M : 로드된 모든 모듈과 버전 표시.
  • man [manpage] : 시스템 기본 문서 뷰어 호출. man debug처럼 perlXXX 형태로 재시도되기도 해요. man이 없는 시스템에선 perldoc을 호출해요.

설정 가능한 옵션 (Configurable Options)

디버거는 o 명령으로 설정하는 많은 옵션이 있어요. .perldb 파일(./.perldb 또는 ~/.perldb, Unix 기준)이나 환경 변수에서도 설정해요.

  • recallCommand, ShellBang : 명령 재호출·셸 실행 문자. 기본 둘 다 !.
  • pager : 파이프된 명령의 출력 프로그램. 기본 $ENV{PAGER}. 디버거는 현재 터미널 특성으로 bold·밑줄을 쓰므로 페이저가 이스케이프를 그대로 통과시키지 않으면 읽기 어려울 수 있어요.
  • tkRunning : 프롬프트 중 Tk 실행.
  • signalLevel, warnLevel, dieLevel : 상세 수준. 기본은 예외·경고를 건드리지 않아요(안전 모드). 1로 올리면 경고·예외 시 역추적. dieLevel 2는 예외 핸들러를 가로채요.
  • AutoTrace : 추적 모드(t 명령과 유사하지만 PERLDB_OPTS에 넣을 수 있음).
  • LineInfo : 줄 번호 정보를 보낼 파일·파이프(예: |visual_perl_db). 에디터·비주얼 디버거 연동에 사용.
  • inhibit_exit : 0이면 스크립트 끝을 단계로 넘기는 것 허용.
  • PrintRet : 설정되면 r 명령 후 반환 값 출력(기본).
  • ornaments : 명령줄 화면 모양(Term::ReadLine). 현재 비활성화할 방법이 없어요.
  • frame : 서브루틴 진입·퇴장 메시지 출력 제어. frame & 2가 거짓이면 진입 시만. frame & 4면 인자·컨텍스트·호출자 정보, frame & 8이면 오버로드된 stringify·tied FETCH, frame & 16이면 반환 값 출력.
  • maxTraceLen : frame의 4번 비트가 설정될 때 인자 목록을 자르는 길이.
  • windowSize : 코드 목록 창 크기(기본 10줄).
  • arrayDepth, hashDepth : 처음 N개 요소만 출력(전부는 '').
  • dumpDepth : 구조 덤프 재귀 깊이 제한(기본 무한).
  • compactDump, veryCompact : 배열·해시 출력 스타일.
  • globPrint : glob 내용 출력 여부.
  • DumpDBFiles : 디버깅 파일 배열 덤프.
  • DumpPackages : 패키지 심볼 테이블 덤프.
  • DumpReused : 재사용 주소 내용 덤프.
  • quote, HighBit, undefPrint : 문자열 덤프 스타일. quote 기본 auto.
  • UsageOnly : 패키지별 메모리 사용 덤프.
  • HistFile : 히스토리 파일 경로. HistSize : 저장 줄 수.

rc 파일을 읽은 후 디버거는 $ENV{PERLDB_OPTS} 환경 변수를 "o ..." 줄의 나머지처럼 파싱해요. TTY, noTTY, ReadLine, NonStop 초기화 옵션을 여기 둘 수 있어요.

  • TTY : 디버깅 I/O에 쓸 TTY.
  • noTTY : 설정되면 NonStop 모드로 TTY에 연결하지 않아요. 중단되면 TTY 옵션에 지정된 TTY에 연결.
  • ReadLine : 거짓이면 ReadLine을 쓰는 앱을 디버깅하기 위해 readline 지원을 끔.
  • NonStop : 설정되면 중단될 때까지 비대화형 모드.

예시:

$ PERLDB_OPTS="NonStop frame=2" perl -d myprogram
$ PERLDB_OPTS="NonStop LineInfo=listing frame=2" perl -d myprogram
$ ( PERLDB_OPTS="NonStop frame=1 AutoTrace LineInfo=tperl.out"
    perl -d myprogram )

디버거 입출력 (Debugger Input/Output)

  • 프롬프트(Prompt) : DB<8>처럼 보이는데 숫자는 명령 번호예요. !17은 17번 명령을 반복. 꺾쇠의 깊이는 디버거 중첩 깊이를 나타내요.
  • 여러 줄 명령(Multiline commands) : 서브루틴 정의처럼 여러 줄을 입력하려면 줄바꿈을 백슬래시로 이스케이프해요:
DB<1> for (1..4) {         \
cont:     print "ok\n";   \
cont: }
  • 스택 역추적(Stack backtrace) : T 명령 출력 예:
$ = main::infested called from file 'Ambulation.pm' line 10
@ = Ambulation::legs(1, 2, 3, 4) called from file 'camel_flea'
                                                         line 7

왼쪽 문자는 호출 컨텍스트로 $는 스칼라, @는 리스트, .는 void 컨텍스트예요. &foo로 호출되면 인자가 없이 표시돼요. 활성 use 문 안에서 T를 실행하면 require 프레임과 eval 프레임이 모두 포함돼요.

  • 줄 목록 형식(Line Listing Format) :
  DB<<13>> l
101:        @i{@i} = ();
102:b       @isa{@i,$pack} = ()
103             if(exists $i{$prevpack} || exists $isa{$pack});
...
107==>      if(exists $isa{$pack});

중단 가능한 줄은 :, 중단점 있는 줄은 b, 액션 있는 줄은 a, 곧 실행할 줄은 ==>로 표시돼요. 디버거 목록의 코드는 원래 소스와 다르게 보일 수 있어요(줄 지시어·외부 소스 필터 때문).

컴파일 타임 문장 디버깅 (Debugging Compile-Time Statements)

BEGIN·UNITCHECK·CHECK 블록이나 use 문 같은 컴파일 타임 실행 문장은 디버거가 멈추지 않아요(require·INIT는 멈춤). 하지만 Perl 코드에서 다음과 같이 디버거로 제어를 넘길 수 있어요:

$DB::single = 1;

$DB::single = 1s 명령, 2n 명령, $DB::trace = 1t 명령과 같아요. 모듈 로드에 중단점을 걸려면 b load f:/perllib/lib/Carp.pm처럼 하고 R로 재시작. b compile subname도 쓸 수 있어요.

디버거 커스터마이징 (Debugger Customization)

o 명령, PERLDB_OPTS 환경 변수, 커스터마이즈 파일(rc 파일)로 동작을 바꿔요. .perldb 파일에 별명을 정의할 수 있어요:

$DB::alias{'len'}  = 's/^len(.*)/p length($1)/';
$DB::alias{'stop'} = 's/^stop (at|in)/b/';
$DB::alias{'quit'} = 's/^quit(\s*)/exit/';

옵션 변경은 parse_options("NonStop=1 LineInfo=db.out AutoTrace=1 frame=2");처럼 해요. 코드는 DB 패키지에서 실행돼요. afterinit 서브루틴을 정의하면 초기화 후 호출돼요. 보안상 .perldb는 수퍼유저나 현재 사용자가 소유해야 하고 소유자만 쓸 수 있어야 해요.

@DB::typeahead에 명령을 추가해 디버거 입력을 흉내 낼 수도 있어요(비지원 인터페이스). 디버거 자체를 뜯어고치려면 perl5db.pl을 복사해서 고치고 PERL5DB 환경 변수로 지정해요:

BEGIN { require "myperl5db.pl" }

Readline 지원 / 히스토리 (Readline Support / History)

기본 제공 히스토리는 느낌표(!)로 시작하는 검사 정도로 단순해요. Term::ReadKeyTerm::ReadLine::* 모듈을 설치하면 GNU readline 같은 편집 기능이 생겨요. 기본 명령줄 완성도 있고, PadWalker가 있으면 현재 스코프 렉시컬 변수도 완성돼요.

에디터 지원 (Editor Support for Debugging)

GNU emacs가 있으면 Perl 디버거와 연동해 통합 개발 환경처럼 쓸 수 있어요. vi 사용자는 vim·gvim을 살펴보세요. 하지만 Perl을 진짜로 파싱할 수 있는 건 Perl뿐이라는 점을 기억하세요.

Perl 프로파일러 (The Perl Profiler)

대체 디버거를 지정하려면 -d에 콜론과 패키지 인자를 줘요. 예를 들어 Devel::NYTProf 프로파일러로:

$ perl -d:NYTProf mycode.pl

스크립트가 끝나면 프로파일 데이터베이스를 만들고, 프로파일러 도구로 리포트로 만들 수 있어요. 자세한 건 perlperf를 보세요.

정규식 디버깅 (Debugging Regular Expressions)

use re 'debug'는 Perl 정규식 엔진이 어떻게 동작하는지 자세히 보여줘요. 이 방대한 출력을 이해하려면 정규식 매칭의 일반 개념과 Perl이 정규식을 내부적으로 오토마타로 컴파일하는 방식을 알아야 해요. 자세한 건 perldebguts를 보세요.

메모리 사용 디버깅 (Debugging Memory Usage)

Perl은 자체 메모리 사용을 보고하는 내부 지원이 있지만 꽤 고급 개념이에요. 자세한 건 perldebguts.

알려진 버그 (BUGS)

  • Perl이 컴파일하지 않은 함수(C·C++ 확장)는 스택 프레임 정보를 얻거나 디버깅할 수 없어요.
  • 서브루틴에서 @_를 바꾸면(stack backtrace) 스택 역추적에 원래 값이 안 나와요.
  • -W 스위치와는 디버거가 경고에서 자유롭지 않아 동작하지 않아요.
  • 느린 syscall(wait·accept·키보드/소켓 read) 중에 $SIG{INT} 핸들러를 안 만들었으면 CTRL-C로 디버거로 돌아올 수 없어요.

더 알아보기 (Learn more)

더 깊이 보려면 다음 문서를 함께 읽어 보세요.