Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Perl スクリプトを gdb でデバッグ 2011/10/15  YAPC::Asia Tokyo 2011 @  大岡山 株式会社ディー・エヌ・エー 樋口 証
Perl実行中のプロセスをデバッグ 今どこの perl コードを実行してるのか知りたい どこかで固まってるが場所がわからない 無限ループしているようだが場所がわからない クラッシュするが perl の呼出し履歴がわからない
作った https:// github.com/ahiguti/gdbperl 使いかた : 実行中プロセスのプロセス id を指定して実行 $ gdbperl.pl 24113 コアファイルを指定して実行 $ gdbperl.pl core.24113 /usr/bin/perl 実行すると perl の呼出し履歴などを表示する 呼出し元のファイル名と行番号 呼出し先の関数名と引数
使用例 use IO::Socket::INET; sub baz {   while (1) {   my $sd = new IO::Socket::INET(   PeerAddr => '10.1.0.0:80'); #  ここで固まる   sleep(1);   } } sub bar {   baz($_[0]); } sub foo {   bar(35, 5.98, "xyz", @_); } foo("abc");
gdbperl.pl による呼出し履歴出力例 [10] IO::Socket::connect() <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:257(IO::Socket::INET) [9] IO::Socket::INET::connect() <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:232(IO::Socket::INET) [8] (loop) <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:178(IO::Socket::INET) [7] IO::Socket::INET::configure(ref, ref) <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket.pm:48(IO::Socket) [6] IO::Socket::new(&quot;IO::Socket::INET&quot;, &quot;PeerAddr&quot;, &quot;10.1.0.0:80&quot;) <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:37(IO::Socket::INET) [5] IO::Socket::INET::new(&quot;PeerAddr&quot;, &quot;10.1.0.0:80&quot;) <- ./t.pl:7(main) [4] (loop) <- ./t.pl:6(main) [3] main::baz(35) <- ./t.pl:13(main) [2] main::bar(35, 5.9800000000000004, &quot;xyz&quot;, &quot;abc&quot;) <- ./t.pl:17(main) [1] main::foo(&quot;abc&quot;) <- ./t.pl:20(main)
どうやっているか? gdb -p でプロセスにアタッチ gdb にコマンドを送って perl インタプリタの内部状態を調べる gdbperl.pl 24113 verbose_gdb=1  のようにやると gdb との会話内容が見れる
gdbperl.pl の動作条件 gdb が必要 perl が「 -g 」付きでコンパイルされている C のソースレベルデバッグできないといけないから perlbrew install 5.15.3 -D ccflags='-g‘ perl 5.8  ~  5.13 少なくとも GNU/Linux と MacOSX で動作
perl の caller() 関数 呼出し履歴を取得する関数 引数として、呼出しの深さを指定 pp_ctl.c に定義されている。 C の関数名は pp_caller この関数と同等のことを gdb 上からやれば、実行中のプロセスの呼出し履歴が調べられる
pp_caller のキモ cx = caller_cx(count, &dbcx); 指定された深さの呼出し履歴のポインタ stashname = CopSTASHPV(cx->blk_oldcop); 実行中の命令のパッケージ名 mPUSHs(newSVpv(OutCopFILE(cx->blk_oldcop), 0)); 実行中の命令のファイル名 mPUSHi((I32)CopLINE(cx->blk_oldcop)); 行番号 GV * const cvgv = CvGV(dbcx->blk_sub.cv); gv_efullname3(sv, cvgv, NULL); 呼出された関数の名前 AV * const ary = cx->blk_sub.argarray; 呼出しの引数
perl インタプリタ内部構造について プリプロセッサマクロを大量に使用 gdb で内部を見るにはマクロを頑張って展開する スレッド付きと無しでは内部構造が大幅に異なる バージョンによっても大幅に異なる 以降は 5.14 スレッド無しの場合を中心に解説する
perl インタプリタインスタンスの取得 perl がスレッド付きでコンパイルされているとインタプリタが複数インスタンスに my_perl という名前なので、 C の呼出し履歴から探す スレッド無しのときはインタプリタがグローバルに定義されているので my_perl は不要 (gdb) bt #0  0xffffe410 in __kernel_vsyscall () #1  0xb7de2021 in connect () at ../sysdeps/unix/sysv/linux #2  0x0810f623 in Perl_pp_bind () at pp_sys.c:2475 #3  0x080ce533 in Perl_runops_standard () at run.c:41 #4  0x08073c40 in S_run_body ( my_perl=0x816b008 ) at perl.c #5  perl_run (my_perl=0x816b008) at perl.c:2252 #6  0x0805e99d in main (argc=2, argv=0xbf9fe104, env=0xbf9 (gdb) frame 4 #4  0x08073c40 in S_run_body (my_perl=0x816b008) at perl.c 2334  CALLRUNOPS(aTHX);
現在実行中の命令 スレッド付きのときは  my_perl->Icurcop スレッド無しのときは  PL_curcop  ( グローバル変数 )
現在実行中の命令 ( スレッド付き ) (gdb) p *my_perl->Icurcop $2 = {op_next = 0x823c930, op_sibling = 0x8260d38, op_ppaddr = 0x80dd030 <Perl_pp_nextstate>, op_targ = 0, op_type = 181, op_opt = 1, op_latefree = 0, op_latefreed = 0, op_attached = 0, op_spare = 0, op_flags = 1 '01', op_private = 0 '00', cop_line =  114 , cop_stashpv = 0x8260df0 &quot; IO::Socket &quot;, cop_file = 0x8260d90 &quot; /home/user/perl5/perlbrew/perls/5.14.2-thr/lib/5.14.2/i686-linux-thread-multi/IO/Socket.pm &quot;, cop_hints = 1794, cop_seq = 719, cop_warnings = 0x0, cop_hints_hash = 0x0}
現在実行中の命令 ( スレッド無し ) (gdb) p *PL_curcop $2 = {op_next = 0x8184fe8, op_sibling = 0x81908f8, op_ppaddr = 0x80d00d0 <Perl_pp_nextstate>, op_targ = 0, op_type = 183, op_opt = 1, op_latefree = 0, op_latefreed = 0, op_attached = 0, op_spare = 0, op_flags = 1 '01', op_private = 0 '00', cop_line = 8, cop_stash = 0x816df58, cop_filegv = 0x816e168, cop_hints = 0, cop_seq = 1058, cop_warnings = 0x0, cop_hints_hash = 0x0}
ファイル名と行番号 (gdb) p PL_curcop->cop_filegv->sv_u.svu_gp->gp_sv->sv_u.svu_pv $3 = 0x818acd0 &quot;/home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket.pm&quot; (gdb) p PL_curcop->cop_line $4 = 114
呼出し履歴 スレッド付き : my_perl->Icurstackinfo->si_cxstack[ 深さ ] スレッド無し : PL_curstackinfo->si_cxstack[ 深さ ] 履歴の最大長 : *cur_stackinfo->si_cxix
呼出し元ファイル名と行番号 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_filegv->sv_u.svu_gp->gp_sv->sv_u.svu_pv $86 = 0x817a238 &quot;./t.pl&quot; (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_line $87 = 17
呼出し元パッケージ名 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_stash->sv_any->xhv_max $88 = 511 (gdb) p (char *)((struct xpvhv_aux *)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_stash->sv_u.svu_hash+511+1))->xhv_name_u.xhvnameu_name->hek_key $89 = 0x817686c &quot;main&quot;
呼出しの種類 (sub, eval, loop 等 ) (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_subst.sbu_type & 0xf $90 = 8 #define CXt_NULL  0 #define CXt_WHEN  1 #define CXt_BLOCK  2 #define CXt_GIVEN  3 #define CXt_LOOP_FOR  4 #define CXt_LOOP_PLAIN  5 #define CXt_LOOP_LAZYSV 6 #define CXt_LOOP_LAZYIV 7 #define CXt_SUB  8 #define CXt_FORMAT  9 #define CXt_EVAL  10 #define CXt_SUBST  11
呼出し先パッケージ名 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.cv->sv_any->xcv_gv->sv_any.xnv_u.xgv_stash->sv_any->xhv_max $91 = 511 (gdb) p (char *)((struct xpvhv_aux *)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.cv->sv_any->xcv_gv->sv_any.xnv_u.xgv_stash->sv_u.svu_hash+511+1))->xhv_name_u.xhvnameu_name->hek_key $92 = 0x817686c &quot;main&quot;
呼出し先関数名 (gdb) p (char *)PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.cv->sv_any->xcv_gv->sv_any.xiv_u.xivu_namehek->hek_key $93 = 0x822e2dc &quot;bar“
呼出しの引数 引数は配列(AV)の形で保持されている 配列要素はスカラ(SV) スカラには数値(IV)、浮動小数点数(NV)、文字列(PV)などの型がある
引数の数 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_any->xav_fill $94 = 3 これを更に +1 した値が引数の数
スカラ値 (SV) の型 (gdb) p (PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[0]).sv_flags $95 = 134222082 この値を 0xf でマスクする typedef enum {   SVt_NULL,  /* 0 */   SVt_BIND,  /* 1 */   SVt_IV,  /* 2 */   SVt_NV,  /* 3 */   SVt_PV,  /* 4 */           ...   } svtype;
整数型 (IV) のときの値取得 (gdb) p ((XPVIV*)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[0]).sv_any)->xiv_u.xivu_iv $96 = 35
浮動小数点数 (NV) のときの値取得 (gdb) p ((XPVNV*)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[1]).sv_any)->xnv_u.xnv_nv $98 = 5.9800000000000004
文字列 (PV) のときの値取得 (gdb) p (PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[2]).sv_u.svu_pv $100 = 0x818ea88 &quot;xyz&quot;
まとめ等 perl のコードも頑張れば gdb でデバッグできる 実行中プロセスもデバッグ可 ただし自動化しないときつい gdbperl.pl perl のバージョンとスレッド有無によって内部が大幅に異なる 実運用環境でもデバッグシンボルは付けておきましょう

More Related Content

How to debug a perl script using gdb

  • 1. Perl スクリプトを gdb でデバッグ 2011/10/15 YAPC::Asia Tokyo 2011 @ 大岡山 株式会社ディー・エヌ・エー 樋口 証
  • 2. Perl実行中のプロセスをデバッグ 今どこの perl コードを実行してるのか知りたい どこかで固まってるが場所がわからない 無限ループしているようだが場所がわからない クラッシュするが perl の呼出し履歴がわからない
  • 3. 作った https:// github.com/ahiguti/gdbperl 使いかた : 実行中プロセスのプロセス id を指定して実行 $ gdbperl.pl 24113 コアファイルを指定して実行 $ gdbperl.pl core.24113 /usr/bin/perl 実行すると perl の呼出し履歴などを表示する 呼出し元のファイル名と行番号 呼出し先の関数名と引数
  • 4. 使用例 use IO::Socket::INET; sub baz { while (1) { my $sd = new IO::Socket::INET( PeerAddr => '10.1.0.0:80'); # ここで固まる sleep(1); } } sub bar { baz($_[0]); } sub foo { bar(35, 5.98, &quot;xyz&quot;, @_); } foo(&quot;abc&quot;);
  • 5. gdbperl.pl による呼出し履歴出力例 [10] IO::Socket::connect() <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:257(IO::Socket::INET) [9] IO::Socket::INET::connect() <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:232(IO::Socket::INET) [8] (loop) <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:178(IO::Socket::INET) [7] IO::Socket::INET::configure(ref, ref) <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket.pm:48(IO::Socket) [6] IO::Socket::new(&quot;IO::Socket::INET&quot;, &quot;PeerAddr&quot;, &quot;10.1.0.0:80&quot;) <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket/INET.pm:37(IO::Socket::INET) [5] IO::Socket::INET::new(&quot;PeerAddr&quot;, &quot;10.1.0.0:80&quot;) <- ./t.pl:7(main) [4] (loop) <- ./t.pl:6(main) [3] main::baz(35) <- ./t.pl:13(main) [2] main::bar(35, 5.9800000000000004, &quot;xyz&quot;, &quot;abc&quot;) <- ./t.pl:17(main) [1] main::foo(&quot;abc&quot;) <- ./t.pl:20(main)
  • 6. どうやっているか? gdb -p でプロセスにアタッチ gdb にコマンドを送って perl インタプリタの内部状態を調べる gdbperl.pl 24113 verbose_gdb=1 のようにやると gdb との会話内容が見れる
  • 7. gdbperl.pl の動作条件 gdb が必要 perl が「 -g 」付きでコンパイルされている C のソースレベルデバッグできないといけないから perlbrew install 5.15.3 -D ccflags='-g‘ perl 5.8 ~ 5.13 少なくとも GNU/Linux と MacOSX で動作
  • 8. perl の caller() 関数 呼出し履歴を取得する関数 引数として、呼出しの深さを指定 pp_ctl.c に定義されている。 C の関数名は pp_caller この関数と同等のことを gdb 上からやれば、実行中のプロセスの呼出し履歴が調べられる
  • 9. pp_caller のキモ cx = caller_cx(count, &dbcx); 指定された深さの呼出し履歴のポインタ stashname = CopSTASHPV(cx->blk_oldcop); 実行中の命令のパッケージ名 mPUSHs(newSVpv(OutCopFILE(cx->blk_oldcop), 0)); 実行中の命令のファイル名 mPUSHi((I32)CopLINE(cx->blk_oldcop)); 行番号 GV * const cvgv = CvGV(dbcx->blk_sub.cv); gv_efullname3(sv, cvgv, NULL); 呼出された関数の名前 AV * const ary = cx->blk_sub.argarray; 呼出しの引数
  • 10. perl インタプリタ内部構造について プリプロセッサマクロを大量に使用 gdb で内部を見るにはマクロを頑張って展開する スレッド付きと無しでは内部構造が大幅に異なる バージョンによっても大幅に異なる 以降は 5.14 スレッド無しの場合を中心に解説する
  • 11. perl インタプリタインスタンスの取得 perl がスレッド付きでコンパイルされているとインタプリタが複数インスタンスに my_perl という名前なので、 C の呼出し履歴から探す スレッド無しのときはインタプリタがグローバルに定義されているので my_perl は不要 (gdb) bt #0 0xffffe410 in __kernel_vsyscall () #1 0xb7de2021 in connect () at ../sysdeps/unix/sysv/linux #2 0x0810f623 in Perl_pp_bind () at pp_sys.c:2475 #3 0x080ce533 in Perl_runops_standard () at run.c:41 #4 0x08073c40 in S_run_body ( my_perl=0x816b008 ) at perl.c #5 perl_run (my_perl=0x816b008) at perl.c:2252 #6 0x0805e99d in main (argc=2, argv=0xbf9fe104, env=0xbf9 (gdb) frame 4 #4 0x08073c40 in S_run_body (my_perl=0x816b008) at perl.c 2334 CALLRUNOPS(aTHX);
  • 12. 現在実行中の命令 スレッド付きのときは my_perl->Icurcop スレッド無しのときは PL_curcop ( グローバル変数 )
  • 13. 現在実行中の命令 ( スレッド付き ) (gdb) p *my_perl->Icurcop $2 = {op_next = 0x823c930, op_sibling = 0x8260d38, op_ppaddr = 0x80dd030 <Perl_pp_nextstate>, op_targ = 0, op_type = 181, op_opt = 1, op_latefree = 0, op_latefreed = 0, op_attached = 0, op_spare = 0, op_flags = 1 '01', op_private = 0 '00', cop_line = 114 , cop_stashpv = 0x8260df0 &quot; IO::Socket &quot;, cop_file = 0x8260d90 &quot; /home/user/perl5/perlbrew/perls/5.14.2-thr/lib/5.14.2/i686-linux-thread-multi/IO/Socket.pm &quot;, cop_hints = 1794, cop_seq = 719, cop_warnings = 0x0, cop_hints_hash = 0x0}
  • 14. 現在実行中の命令 ( スレッド無し ) (gdb) p *PL_curcop $2 = {op_next = 0x8184fe8, op_sibling = 0x81908f8, op_ppaddr = 0x80d00d0 <Perl_pp_nextstate>, op_targ = 0, op_type = 183, op_opt = 1, op_latefree = 0, op_latefreed = 0, op_attached = 0, op_spare = 0, op_flags = 1 '01', op_private = 0 '00', cop_line = 8, cop_stash = 0x816df58, cop_filegv = 0x816e168, cop_hints = 0, cop_seq = 1058, cop_warnings = 0x0, cop_hints_hash = 0x0}
  • 15. ファイル名と行番号 (gdb) p PL_curcop->cop_filegv->sv_u.svu_gp->gp_sv->sv_u.svu_pv $3 = 0x818acd0 &quot;/home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-linux/IO/Socket.pm&quot; (gdb) p PL_curcop->cop_line $4 = 114
  • 16. 呼出し履歴 スレッド付き : my_perl->Icurstackinfo->si_cxstack[ 深さ ] スレッド無し : PL_curstackinfo->si_cxstack[ 深さ ] 履歴の最大長 : *cur_stackinfo->si_cxix
  • 17. 呼出し元ファイル名と行番号 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_filegv->sv_u.svu_gp->gp_sv->sv_u.svu_pv $86 = 0x817a238 &quot;./t.pl&quot; (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_line $87 = 17
  • 18. 呼出し元パッケージ名 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_stash->sv_any->xhv_max $88 = 511 (gdb) p (char *)((struct xpvhv_aux *)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_stash->sv_u.svu_hash+511+1))->xhv_name_u.xhvnameu_name->hek_key $89 = 0x817686c &quot;main&quot;
  • 19. 呼出しの種類 (sub, eval, loop 等 ) (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_subst.sbu_type & 0xf $90 = 8 #define CXt_NULL 0 #define CXt_WHEN 1 #define CXt_BLOCK 2 #define CXt_GIVEN 3 #define CXt_LOOP_FOR 4 #define CXt_LOOP_PLAIN 5 #define CXt_LOOP_LAZYSV 6 #define CXt_LOOP_LAZYIV 7 #define CXt_SUB 8 #define CXt_FORMAT 9 #define CXt_EVAL 10 #define CXt_SUBST 11
  • 20. 呼出し先パッケージ名 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.cv->sv_any->xcv_gv->sv_any.xnv_u.xgv_stash->sv_any->xhv_max $91 = 511 (gdb) p (char *)((struct xpvhv_aux *)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.cv->sv_any->xcv_gv->sv_any.xnv_u.xgv_stash->sv_u.svu_hash+511+1))->xhv_name_u.xhvnameu_name->hek_key $92 = 0x817686c &quot;main&quot;
  • 21. 呼出し先関数名 (gdb) p (char *)PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.cv->sv_any->xcv_gv->sv_any.xiv_u.xivu_namehek->hek_key $93 = 0x822e2dc &quot;bar“
  • 22. 呼出しの引数 引数は配列(AV)の形で保持されている 配列要素はスカラ(SV) スカラには数値(IV)、浮動小数点数(NV)、文字列(PV)などの型がある
  • 23. 引数の数 (gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_any->xav_fill $94 = 3 これを更に +1 した値が引数の数
  • 24. スカラ値 (SV) の型 (gdb) p (PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[0]).sv_flags $95 = 134222082 この値を 0xf でマスクする typedef enum { SVt_NULL, /* 0 */ SVt_BIND, /* 1 */ SVt_IV, /* 2 */ SVt_NV, /* 3 */ SVt_PV, /* 4 */           ...   } svtype;
  • 25. 整数型 (IV) のときの値取得 (gdb) p ((XPVIV*)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[0]).sv_any)->xiv_u.xivu_iv $96 = 35
  • 26. 浮動小数点数 (NV) のときの値取得 (gdb) p ((XPVNV*)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[1]).sv_any)->xnv_u.xnv_nv $98 = 5.9800000000000004
  • 27. 文字列 (PV) のときの値取得 (gdb) p (PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[2]).sv_u.svu_pv $100 = 0x818ea88 &quot;xyz&quot;
  • 28. まとめ等 perl のコードも頑張れば gdb でデバッグできる 実行中プロセスもデバッグ可 ただし自動化しないときつい gdbperl.pl perl のバージョンとスレッド有無によって内部が大幅に異なる 実運用環境でもデバッグシンボルは付けておきましょう