Perl 디버거 내부
Perl 디버거 내부 (perldebguts)
이 문서는 디버거 사용법을 알려주는 perldebug가 아니에요. 여기서는 디버거 내부의 낮은 수준 세부 사항을 다뤄요. Perl 내부에 아주 밝지 않은 사람에겐 어렵거나 이해 불가능한 내용이 많다는 점을 미리 말씀드릴게요(caveat lector).
본문
디버거 내부 (Debugger Internals)
Perl은 디버깅 환경을 만드는 데 쓰는 컴파일-타임·런-타임 특수 훅을 갖고 있어요. 이 훅을 perlrun에 설명된 perl -Dxxx 명령과 혼동하면 안 돼요(그건 특별히 빌드한 Perl에서만 쓸 수 있어요).
예를 들어 DB 패키지에서 내장 caller 함수를 부르면, 해당 스택 프레임이 호출될 때 받은 인자가 @DB::args 배열로 복사돼요. 이런 메커니즘은 -d 스위치로 Perl을 호출할 때 활성화돼요. 구체적으로 다음 기능들이 켜져요:
$ENV{PERL5DB}의 내용(없으면BEGIN {require 'perl5db.pl'})이 프로그램 첫 줄 앞에 삽입돼요.-dt스위치를 주면PERL5DB_THREADED=1이 환경에 설정돼요.- 각 배열
@{"_<$filename"}은 Perl이 컴파일한 파일$filename의 줄들을 담아요.eval문자열(서브루틴 포함)도 마찬가지고, 그$filename은(eval 34)처럼 보여요. 이 배열의 값은 숫자 컨텍스트에서 마법이에요: 줄이 중단 불가능할 때만 0과 같다고 비교돼요. - 각 해시
%{"_<$filename"}은 줄 번호를 키로 하는 중단점·액션을 담아요. 개별 항목은 설정 가능해요. Perl은 불리언 참만 신경 쓰며, perl5db.pl이 쓰는 값은"$break_condition\0$action"형태예요. - 각 스칼라
$ {"_<$filename"}은$filename을 담아요. require된 파일이 컴파일된 후 실행 전에,DB::postponed서브루틴이 있으면DB::postponed(*{"_<$filename"})이 호출돼요.- 각 서브루틴
subname컴파일 후$DB::postponed{subname}존재를 검사하고, 있으면DB::postponed(subname)호출. %DB::sub해시는 키가 서브루틴 이름이고 값이filename:startline-endline형태인 역추적용 해시예요.- 실행이 중단점을 둘 수 있는 지점에 도달하면,
$DB::trace·$DB::single·$DB::signal중 하나라도 참이면DB::DB()서브루틴이 호출돼요. 이 변수들은local불가능해요.DB::DB()내부(그리고 그 안에서 부른 함수)에서는 비활성화돼요(단,$^D & (1<<30)이 참이면 제외). - 실행이 서브루틴 호출에 도달하면
&DB::sub(args)가 대신 호출되고$DB::sub에 호출된 서브루틴이 설정돼요. goto로 non-XS 서브루틴에 들어가고$^P의 0x80 비트가 설정돼 있으면&DB::goto호출.- 표준 디버거의
&DB::sub는$DB::deep변수에 의존해요(디버거 재귀 깊이 제한).$DB::deep이 없으면 서브루틴 호출이 불가능해요.
나만의 디버거 작성하기 (Writing Your Own Debugger)
PERL5DB 환경 변수로 디버거를 정의할 수 있어요. 최소한의 "동작하는" 디버거는 한 줄이에요:
sub DB::DB {}
$ PERL5DB="sub DB::DB {}" perl -d your-script
조금 더 유용한 버전은 이러해요:
sub DB::DB {print ++$i; scalar <STDIN>}
실제로 유용한 디버거는 서브루틴 호출의 순번과 이름을 찍어요:
{
package DB;
sub DB {}
sub sub {print ++$i, " $sub\n"; &$sub}
}
시작 시 디버거는 rc 파일(./.perldb 또는 ~/.perldb)을 읽고 옵션을 설정해요. &afterinit 서브루틴도 여기 정의할 수 있고, 디버거 초기화가 끝난 뒤 실행돼요. 그다음 PERLDB_OPTS 환경 변수를 읽어 o ... 명령의 인자처럼 처리해요.
디버거 내부 변수 (Debugger Internal Variables)
@DB::dbline:@{"::_<current_file"}의 별명. 현재 선택된 파일의 줄들을 담아요.%DB::dbline:%{"::_<current_file"}의 별명. 현재 파일의 줄 번호별 중단점·액션.
디버거 커스터마이즈 함수 (Debugger Customization Functions)
DB::parse_options(string): perldebug의 옵션을 파싱.DB::dump_trace(skip[,count]): 지정 프레임을 건너뛰고 호출 프레임 목록 반환. 각 항목은context·sub·args·file·line키의 해시 참조.DB::print_trace(FH, skip[, count[, short]]): 호출자 프레임 정보를 형식화해 출력.
이 문서(perldebug 포함)에 문서화되지 않은 변수·함수는 내부 전용으로 간주하며 통보 없이 바뀔 수 있어요.
프레임 나열 출력 예시 (Frame Listing Output Examples)
frame 옵션으로 프레임 정보 출력을 제어할 수 있어요. 예를 들어 이 표현식 트레이스를:
$ perl -de 42
...
main::(-e:1): 0
DB<1> sub foo { 14 }
DB<2> sub bar { 3 }
DB<3> t print foo() * bar()
main::((eval 172):3): print foo() + bar();
main::foo((eval 168):2):
main::bar((eval 170):2):
42
option frame=2를 설정한 결과와 비교해 보세요:
DB<5> t print foo() * bar()
3: foo() * bar()
entering main::foo
2: sub foo { 14 };
exited main::foo
entering main::bar
2: sub bar { 3 };
exited main::bar
42
급할 때 frame의 비트 의미: 비트 2가 설정되면 서브루틴 퇴장 시에도 줄 출력. 비트 4는 인자·호출자 정보, 비트 8은 tied·참조여도 인자 출력. 비트 16은 반환 값까지 출력. 패키지 컴파일 시 Package lib/Carp.pm. 같은 줄이 들여쓰기와 함께 출력돼요.
정규식 디버깅 (Debugging Regular Expressions)
정규식 디버깅 출력을 켜는 두 가지 방법이 있어요.
-DDEBUGGING으로 빌드된 Perl이라면 명령줄 -Dr 플래그(더 상세히는 -Drv)를 써요. 그렇지 않으면 use re 'debug'를 써요(컴파일·실행 타임 모두에 영향). Perl 5.9.5부터 이 프래그마는 렉시컬 스코프예요.
컴파일-타임 출력 (Compile-time Output)
Compiling REx '[bc]d(ef*g)+h[ij]k$'
size 45 Got 364 bytes for offset annotations.
first at 1
rarest char g at 0
1: ANYOF[bc](12)
12: EXACT <d>(14)
14: CURLYX[0] {1,32767}(28)
...
anchored 'de' at 1 floating 'gh' at 3..2147483647 (checking floating)
stclass 'ANYOF[bc]' minlen 7
첫 줄은 정규식의 컴파일 전 형태, 두 번째는 컴파일된 크기와 오프셋/길이 테이블 바이트 수, 그다음은 첫 매칭 노드의 라벨·id를 보여줘요. 최적화 정보 줄에는 anchored·floating·stclass·minlen·noscan·isall·GPOS·plus·implicit·with eval·anchored(TYPE) 같은 필드가 나올 수 있어요. 이 최적화 정보는 확실히 매칭되지 않을 문자열에 대해 (느린) 정규식 엔진 진입을 피하는 데 쓰여요. isall 플래그가 설정되면 엔진 호출 자체를 피할 수 있어요.
노드 목록은 id: TYPE OPTIONAL-INFO (next-id) 형식이에요. 전체 노드 타입(END, SUCCEED, SBOL/MBOL/SEOL/MEOL/EOS/GPOS, BOUND 계열, REG_ANY, ANYOF 계열, POSIX 클래스, BRANCH, EXACT 계열, TRIE/AHOCORASICK, NOTHING/TAIL, STAR/PLUS/CURLY 계열, WHILEM, OPEN/CLOSE/REF, LONGJMP/BRANCHJ, IFMATCH/UNLESSM/SUSPEND/IFTHEN/GROUPP, EVAL, MINMOD/LOGICAL, GOSUB, 백트래킹 동사들, OPTIMIZED/PSEUDO 등)의 자세한 설명이 원문에 이어져요.
그다음은 오프셋/길이 테이블 덤프가 나와요. offset[length] 쌍의 목록으로, 각 노드가 컴파일 전 정규식의 어느 문자 위치에서 시작·얼마나 길게 있는지 보여줘요. 0[0] 항목은 대응 노드가 없다는 뜻이에요.
실행-타임 출력 (Run-time Output)
매칭 중에 디버깅이 켜져 있어도 실행-타임 출력이 없을 수 있어요. 이는 정규식 엔진에 진입하지 않았고 최적화기가 전부 처리했다는 뜻이에요. 엔진에 진입했다면:
Matching '[bc]d(ef*g)+h[ij]k$' against 'abcdefg__gh__'
Setting an EVAL scope, savestack=3
2 <ab> <cdefg__gh_> | 1: ANYOF
3 <abc> <defg__gh_> | 11: EXACT <d>
...
가장 중요한 정보는 현재 테스트 중인 컴파일된 정규식의 노드예요. 줄 형식은 STRING-OFFSET <PRE-STRING> <POST-STRING> |ID: TYPE이에요. TYPE 정보는 백트래킹 레벨에 따라 들여쓰기돼요.
Perl 메모리 사용 디버깅 (Debugging Perl Memory Usage)
Perl은 메모리 사용에 있어 꽤 낭비적이에요. 정수는 최소 20바이트, float은 24바이트, 문자열은 32바이트(경험치, 32비트 기준)를 넘지 못해요. 두 세 가지 방식으로 접근하는 변수는 20바이트가 더 늘 수 있어요. 반대로 sub foo; 한 줄이 버전에 따라 최대 500바이트를 차지할 수도 있어요. 소스→컴파일 코드는 대략 8배 bloating된다는 경험적 추정이 있어요.
-DL 커맨드라인 스위치는 Perl 5.6.0 경에 폐기됐어요(-DDEBUGGING 빌드에서만 가능했음). 요즘엔 Purify나 valgrind 같은 malloc 디버깅 도구를 쓰는 걸 권장해요(PERL_MEM_LOG 참고). 데이터 구조가 쓰는 메모리를 알려면 CPAN의 Devel::Size를 설치해요(size()와 total_size()의 차이에 주의). Perl의 malloc으로 빌드했다면 $ENV{PERL_DEBUG_MSTATS}를 설정해 분석할 수 있어요.
$ENV{PERL_DEBUG_MSTATS} 사용 (Using $ENV{PERL_DEBUG_MSTATS})
Perl의 malloc을 쓰고 필요한 스위치로 컴파일된 perl이라면, $ENV{PERL_DEBUG_MSTATS} > 1이면 컴파일 후, >= 1이면 프로그램 종료 전에 메모리 사용 통계를 출력해요:
$ PERL_DEBUG_MSTATS=2 perl -e "require Carp"
Memory allocation statistics after compilation: (buckets 4(4)..8188(8192)
14216 free: 130 117 28 7 9 0 2 2 1 0 0
...
이 형식의 설명:
buckets SMALLEST(APPROX)..GREATEST(APPROX): Perl의 malloc()은 버킷 단위 할당을 써요. 각 요청은 가장 가까운 버킷 크기로 올림돼요. 각 버킷은 메모리 점유량과 담을 수 있는 최대 사용자 데이터 크기 두 가지가 있어요.- Free/Used :
SMALLEST와GREATEST사이 각 크기 버킷의 개수. 디버깅 빌드에선 일부 버킷이 음의 사용 가능 크기를 가질 수 있어요(쓸 수 없음). Total sbrk(): perl이 sbrk(2)로 가져온 총 메모리와 sbrk(2) 호출 횟수, 그리고 반환 청크의 연속성에 대한 판단. 양수면 계속 연속 메모리가 공급될 가능성이 높다고 가정.pad: 버킷 정렬에 필요한 sbrk 메모리.heads: 작은 버킷의 오버헤드 영역 총 크기.chain: 큰 버킷을 쪼갠 나머지 링크드리스트 총 크기.tail: sbrk했지만 아직 안 쓴 부분.
표준 Devel::Peek 모듈의 mstat() 함수로 실행 중 임의 지점에서 통계를 요청할 수 있어요.
함께 보기 (SEE ALSO)
perldebug, perl5db.pl, perlguts, perlrun, re, Devel::DProf.
더 알아보기 (Learn more)
더 깊이 보려면 다음 문서를 함께 읽어 보세요.