Perl スクリプトを gdb でデバッグ 2011/10/15  YAPC::Asia Tokyo 2011 @  大岡山 株式会社ディー・エヌ・エー 樋口 証
Perl実行中のプロセスをデバッグ <ul><li>今どこの perl コードを実行してるのか知りたい </li></ul><ul><ul><li>どこかで固まってるが場所がわからない </li></ul></ul><ul><ul><li>無限...
作った <ul><li>https:// github.com/ahiguti/gdbperl </li></ul><ul><li>使いかた : </li></ul><ul><ul><li>実行中プロセスのプロセス id を指定して実行 $ g...
使用例 <ul><li>use IO::Socket::INET; sub baz {   while (1) {   my $sd = new IO::Socket::INET( </li></ul><ul><li>  PeerAddr =>...
gdbperl.pl による呼出し履歴出力例 <ul><li>[10] IO::Socket::connect() <- /home/user/perl5/perlbrew/perls/perl-5.15.3/lib/5.15.3/i686-l...
どうやっているか? <ul><li>gdb -p でプロセスにアタッチ </li></ul><ul><li>gdb にコマンドを送って perl インタプリタの内部状態を調べる </li></ul><ul><li>gdbperl.pl 2411...
gdbperl.pl の動作条件 <ul><li>gdb が必要 </li></ul><ul><li>perl が「 -g 」付きでコンパイルされている </li></ul><ul><ul><li>C のソースレベルデバッグできないといけないか...
perl の caller() 関数 <ul><li>呼出し履歴を取得する関数 </li></ul><ul><li>引数として、呼出しの深さを指定 </li></ul><ul><li>pp_ctl.c に定義されている。 C の関数名は pp_...
pp_caller のキモ <ul><li>cx = caller_cx(count, &dbcx); </li></ul><ul><ul><li>指定された深さの呼出し履歴のポインタ </li></ul></ul><ul><li>stashn...
perl インタプリタ内部構造について <ul><li>プリプロセッサマクロを大量に使用 </li></ul><ul><li>gdb で内部を見るにはマクロを頑張って展開する </li></ul><ul><li>スレッド付きと無しでは内部構造が...
perl インタプリタインスタンスの取得 <ul><li>perl がスレッド付きでコンパイルされているとインタプリタが複数インスタンスに </li></ul><ul><li>my_perl という名前なので、 C の呼出し履歴から探す </l...
現在実行中の命令 <ul><li>スレッド付きのときは  my_perl->Icurcop </li></ul><ul><li>スレッド無しのときは  PL_curcop  ( グローバル変数 ) </li></ul>
現在実行中の命令 ( スレッド付き ) <ul><li>(gdb) p *my_perl->Icurcop $2 = {op_next = 0x823c930, op_sibling = 0x8260d38, op_ppaddr = 0x80d...
現在実行中の命令 ( スレッド無し ) <ul><li>(gdb) p *PL_curcop $2 = {op_next = 0x8184fe8, op_sibling = 0x81908f8, op_ppaddr = 0x80d00d0 <P...
ファイル名と行番号 <ul><li>(gdb) p PL_curcop->cop_filegv->sv_u.svu_gp->gp_sv->sv_u.svu_pv $3 = 0x818acd0 &quot;/home/user/perl5/per...
呼出し履歴 <ul><li>スレッド付き : </li></ul><ul><li>my_perl->Icurstackinfo->si_cxstack[ 深さ ] </li></ul><ul><li>スレッド無し : </li></ul><ul...
呼出し元ファイル名と行番号 <ul><li>(gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_filegv->sv_u.svu_gp->gp_sv->sv_u...
呼出し元パッケージ名 <ul><li>(gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blku_oldcop->cop_stash->sv_any->xhv_max $88 = 511 (g...
呼出しの種類 (sub, eval, loop 等 ) <ul><li>(gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_subst.sbu_type & 0xf $90 = 8 #define CX...
呼出し先パッケージ名 <ul><li>(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_...
呼出し先関数名 <ul><li>(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...
呼出しの引数 <ul><li>引数は配列(AV)の形で保持されている </li></ul><ul><li>配列要素はスカラ(SV) </li></ul><ul><li>スカラには数値(IV)、浮動小数点数(NV)、文字列(PV)などの型がある ...
引数の数 <ul><li>(gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_any->xav_fill $94 = 3 これを更に +1...
スカラ値 (SV) の型 <ul><li>(gdb) p (PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[0]).sv_fl...
整数型 (IV) のときの値取得 <ul><li>(gdb) p ((XPVIV*)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_ar...
浮動小数点数 (NV) のときの値取得 <ul><li>(gdb) p ((XPVNV*)(PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu...
文字列 (PV) のときの値取得 <ul><li>(gdb) p (PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_u.svu_array[2]).s...
まとめ等 <ul><li>perl のコードも頑張れば gdb でデバッグできる </li></ul><ul><ul><li>実行中プロセスもデバッグ可 </li></ul></ul><ul><li>ただし自動化しないときつい </li></u...
Upcoming SlideShare
Loading in …5
×

How to debug a perl script using gdb

2,912 views

Published on

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,912
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
9
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

How to debug a perl script using gdb

  1. 1. Perl スクリプトを gdb でデバッグ 2011/10/15 YAPC::Asia Tokyo 2011 @ 大岡山 株式会社ディー・エヌ・エー 樋口 証
  2. 2. Perl実行中のプロセスをデバッグ <ul><li>今どこの perl コードを実行してるのか知りたい </li></ul><ul><ul><li>どこかで固まってるが場所がわからない </li></ul></ul><ul><ul><li>無限ループしているようだが場所がわからない </li></ul></ul><ul><ul><li>クラッシュするが perl の呼出し履歴がわからない </li></ul></ul>
  3. 3. 作った <ul><li>https:// github.com/ahiguti/gdbperl </li></ul><ul><li>使いかた : </li></ul><ul><ul><li>実行中プロセスのプロセス id を指定して実行 $ gdbperl.pl 24113 </li></ul></ul><ul><ul><li>コアファイルを指定して実行 $ gdbperl.pl core.24113 /usr/bin/perl </li></ul></ul><ul><li>実行すると perl の呼出し履歴などを表示する </li></ul><ul><ul><li>呼出し元のファイル名と行番号 </li></ul></ul><ul><ul><li>呼出し先の関数名と引数 </li></ul></ul>
  4. 4. 使用例 <ul><li>use IO::Socket::INET; sub baz { while (1) { my $sd = new IO::Socket::INET( </li></ul><ul><li> 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;); </li></ul>
  5. 5. gdbperl.pl による呼出し履歴出力例 <ul><li>[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) </li></ul><ul><li>[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) </li></ul><ul><li>[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) </li></ul><ul><li>[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) </li></ul><ul><li>[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) </li></ul><ul><li>[5] IO::Socket::INET::new(&quot;PeerAddr&quot;, &quot;10.1.0.0:80&quot;) <- ./t.pl:7(main) </li></ul><ul><li>[4] (loop) <- ./t.pl:6(main) </li></ul><ul><li>[3] main::baz(35) <- ./t.pl:13(main) </li></ul><ul><li>[2] main::bar(35, 5.9800000000000004, &quot;xyz&quot;, &quot;abc&quot;) <- ./t.pl:17(main) </li></ul><ul><li>[1] main::foo(&quot;abc&quot;) <- ./t.pl:20(main) </li></ul>
  6. 6. どうやっているか? <ul><li>gdb -p でプロセスにアタッチ </li></ul><ul><li>gdb にコマンドを送って perl インタプリタの内部状態を調べる </li></ul><ul><li>gdbperl.pl 24113 verbose_gdb=1 のようにやると gdb との会話内容が見れる </li></ul>
  7. 7. gdbperl.pl の動作条件 <ul><li>gdb が必要 </li></ul><ul><li>perl が「 -g 」付きでコンパイルされている </li></ul><ul><ul><li>C のソースレベルデバッグできないといけないから </li></ul></ul><ul><ul><li>perlbrew install 5.15.3 -D ccflags='-g‘ </li></ul></ul><ul><li>perl 5.8 ~ 5.13 </li></ul><ul><li>少なくとも GNU/Linux と MacOSX で動作 </li></ul>
  8. 8. perl の caller() 関数 <ul><li>呼出し履歴を取得する関数 </li></ul><ul><li>引数として、呼出しの深さを指定 </li></ul><ul><li>pp_ctl.c に定義されている。 C の関数名は pp_caller </li></ul><ul><li>この関数と同等のことを gdb 上からやれば、実行中のプロセスの呼出し履歴が調べられる </li></ul>
  9. 9. pp_caller のキモ <ul><li>cx = caller_cx(count, &dbcx); </li></ul><ul><ul><li>指定された深さの呼出し履歴のポインタ </li></ul></ul><ul><li>stashname = CopSTASHPV(cx->blk_oldcop); </li></ul><ul><ul><li>実行中の命令のパッケージ名 </li></ul></ul><ul><li>mPUSHs(newSVpv(OutCopFILE(cx->blk_oldcop), 0)); </li></ul><ul><ul><li>実行中の命令のファイル名 </li></ul></ul><ul><li>mPUSHi((I32)CopLINE(cx->blk_oldcop)); </li></ul><ul><ul><li>行番号 </li></ul></ul><ul><li>GV * const cvgv = CvGV(dbcx->blk_sub.cv); </li></ul><ul><li>gv_efullname3(sv, cvgv, NULL); </li></ul><ul><ul><li>呼出された関数の名前 </li></ul></ul><ul><li>AV * const ary = cx->blk_sub.argarray; </li></ul><ul><ul><li>呼出しの引数 </li></ul></ul>
  10. 10. perl インタプリタ内部構造について <ul><li>プリプロセッサマクロを大量に使用 </li></ul><ul><li>gdb で内部を見るにはマクロを頑張って展開する </li></ul><ul><li>スレッド付きと無しでは内部構造が大幅に異なる </li></ul><ul><li>バージョンによっても大幅に異なる </li></ul><ul><li>以降は 5.14 スレッド無しの場合を中心に解説する </li></ul>
  11. 11. perl インタプリタインスタンスの取得 <ul><li>perl がスレッド付きでコンパイルされているとインタプリタが複数インスタンスに </li></ul><ul><li>my_perl という名前なので、 C の呼出し履歴から探す </li></ul><ul><li>スレッド無しのときはインタプリタがグローバルに定義されているので 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); </li></ul>
  12. 12. 現在実行中の命令 <ul><li>スレッド付きのときは my_perl->Icurcop </li></ul><ul><li>スレッド無しのときは PL_curcop ( グローバル変数 ) </li></ul>
  13. 13. 現在実行中の命令 ( スレッド付き ) <ul><li>(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 '001', op_private = 0 '000', 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} </li></ul>
  14. 14. 現在実行中の命令 ( スレッド無し ) <ul><li>(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 '001', op_private = 0 '000', cop_line = 8, cop_stash = 0x816df58, cop_filegv = 0x816e168, cop_hints = 0, cop_seq = 1058, cop_warnings = 0x0, cop_hints_hash = 0x0} </li></ul>
  15. 15. ファイル名と行番号 <ul><li>(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 </li></ul>
  16. 16. 呼出し履歴 <ul><li>スレッド付き : </li></ul><ul><li>my_perl->Icurstackinfo->si_cxstack[ 深さ ] </li></ul><ul><li>スレッド無し : </li></ul><ul><li>PL_curstackinfo->si_cxstack[ 深さ ] </li></ul><ul><li>履歴の最大長 : </li></ul><ul><ul><li>*cur_stackinfo->si_cxix </li></ul></ul>
  17. 17. 呼出し元ファイル名と行番号 <ul><li>(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 </li></ul>
  18. 18. 呼出し元パッケージ名 <ul><li>(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; </li></ul>
  19. 19. 呼出しの種類 (sub, eval, loop 等 ) <ul><li>(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 </li></ul>
  20. 20. 呼出し先パッケージ名 <ul><li>(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; </li></ul>
  21. 21. 呼出し先関数名 <ul><li>(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“ </li></ul>
  22. 22. 呼出しの引数 <ul><li>引数は配列(AV)の形で保持されている </li></ul><ul><li>配列要素はスカラ(SV) </li></ul><ul><li>スカラには数値(IV)、浮動小数点数(NV)、文字列(PV)などの型がある </li></ul>
  23. 23. 引数の数 <ul><li>(gdb) p PL_curstackinfo->si_cxstack[2].cx_u.cx_blk.blk_u.blku_sub.argarray->sv_any->xav_fill $94 = 3 これを更に +1 した値が引数の数 </li></ul>
  24. 24. スカラ値 (SV) の型 <ul><li>(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 */ </li></ul><ul><li>          ...   } svtype; </li></ul>
  25. 25. 整数型 (IV) のときの値取得 <ul><li>(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 </li></ul>
  26. 26. 浮動小数点数 (NV) のときの値取得 <ul><li>(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 </li></ul>
  27. 27. 文字列 (PV) のときの値取得 <ul><li>(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; </li></ul>
  28. 28. まとめ等 <ul><li>perl のコードも頑張れば gdb でデバッグできる </li></ul><ul><ul><li>実行中プロセスもデバッグ可 </li></ul></ul><ul><li>ただし自動化しないときつい </li></ul><ul><ul><li>gdbperl.pl </li></ul></ul><ul><li>perl のバージョンとスレッド有無によって内部が大幅に異なる </li></ul><ul><li>実運用環境でもデバッグシンボルは付けておきましょう </li></ul>

×