本記事はリライト中です。
FORTRANで自作したsplit関数をGDBデバッグします。
FORTRANデバッグその1
splitする文字列はこれです。
STRING='1-3,5,7-12,13,14'
まず、カンマでsplitして、次にハイフンでsplitします。
結果は、このようになりました。
takk@deb9:~$ ./a.out 1-3,5,7-12,13,14 SSIZE1= 5 [1-3 ] SSIZE2= 5 [5 ] SSIZE2= 4 [7-12 ] SSIZE2= 4 [13 ] SSIZE2= 3 [14 ] SSIZE2= 4 takk@deb9:~$
カンマは4箇所ありますのでsplitすると、split後の文字列の個数SSIZE1が5となります。
その後、1-3に含まれるハイフンでsplitすると、SSIZE2は2となるはずなのですが、なぜか5と表示されています。これをデバッグします。
-gオプションをつけてビルド後、GDBを起動します。
takk@deb9:~$ gfortran -g split2.f takk@deb9:~$ gdb a.out ~ (gdb)
listコマンドでソースの1行目~20行目を確認します。
(gdb) l 1,20 warning: Source file is more recent than executable. 1 PROGRAM MAIN 2 CHARACTER*100 STRING 3 CHARACTER*10 SSTRING1(10),SSTRING2(10) 4 INTEGER I,SSIZE1,SSIZE2 5 6 STRING='1-3,5,7-12,13,14' 7 WRITE(*,*) STRING 8 9 CALL SPLIT(STRING,',', SSTRING1,SSIZE1) 10 WRITE(*,1100) SSIZE1 11 12 DO 100 I=1,SSIZE1 13 14 WRITE(*,1300) SSTRING1(I) 15 16 CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2) 17 WRITE(*,1200) SSIZE2 18 19 100 CONTINUE 20 1100 FORMAT('SSIZE1=',I3) (gdb)
ハイフン指定するときのSPLITの呼び出しでブレイクしたいので、b 16してrun
(gdb) b 16 Breakpoint 1 at 0xf5a: file split2.f, line 16. (gdb) r Starting program: /home/takk/a.out 1-3,5,7-12,13,14 SIZE1= 5 [1-3 ] Breakpoint 1, MAIN__ () at split2.f:16 16 CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2) (gdb)
SPLIT関数へ渡す引数を確認します。SSTRING1配列は初回はI=1ですので、SSTRING(1)について値を確認します。
(gdb) p sstring1(1) $1 = '1-3 ' (gdb)
カンマで分割された文字列が格納されるので1-3となっています。ただ、1-3の後に配列サイズ分空白が入ってるのが以前から気になっていましたが、置いておきます。
次にsplit関数内を見ていくので、ステップインします。
(gdb) s split (src=..., sep=..., dest=..., split_size=32767, _src=10, _sep=1, _dest=10) at split2.f:34 34 IND = 0 (gdb)
先ほど見たばかりですが、引数についてもう一度確認してみます。
引数として渡された文字列はsrc変数となっていますので、p srcします。
(gdb) p src $2 = '1-3 5 7-12 13 14 \377\377\000\000\000\000\210v\335\367\377\177\000\000 IUUUU\000\000\300\345\377\377\377\177', '\000' <repeats 18 times>, '\340\344\377\377' (gdb)
おかしいです。1-3だけのはずが、元の文字列すべて表示されてしまいます。
もしかして後に続く文字列は全部表示されているのでしょうか。
この場所でブレイクポイントを設定します。
(gdb) b Breakpoint 2 at 0x555555554a71: file split2.f, line 34. (gdb)
ブレイクポイントまで実行。
(gdb) c Continuing. SIZE2= 3 [5 ] Breakpoint 1, MAIN__ () at split2.f:16 16 CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2) (gdb)
同じようにsstring1(2)を確認。
(gdb) p sstring1(2) $3 = '5 ' (gdb)
想定通りです。ではSPLIT関数内部に移動して再度引数を確認します。
(gdb) s Breakpoint 2, split (src=..., sep=..., dest=..., split_size=3, _src=10, _sep=1, _dest=10) at split2.f:34 34 IND = 0 (gdb) p src $4 = '5 7-12 13 14 \377\377\000\000\000\000\210v\335\367\377\177\000\000 IUUUU\000\000\300\345\377\377\377\177', '\000' <repeats 18 times>, '\340\344\377\377\003\000\000\000\005\000\000\000\002\000' (gdb)
予想通り、次は1-3の文字列は表示されず、5と、それ以降の文字列が表示されました。
FORTRANデバッグその2
自作のSPLIT関数を呼び出しするときに、文字列
‘1-3 ‘を引数で渡しているのに関数内部の方宣言の文字列サイズが大きいことが原因っぽいですが、実験プログラムを作って確認します。
takk@deb9:~$ cat test.f PROGRAM MAIN CHARACTER*10 AA AA='ABCDEFGHIJ' CALL TEST(AA) END SUBROUTINE TEST(SRC) CHARACTER*100 SRC WRITE(*,*) SRC RETURN END takk@deb9:~$
このようなプログラムになりました。TEST関数に渡す文字列サイズは10Byteで、TEST関数の引数はサイズを100Byteにしています。
ビルドします。
takk@deb9:~$ gfortran -g test.f test.f:4:18: CALL TEST(AA) 1 Warning: Character length of actual argument shorter than of dummy argument ‘sr ’ (10/100) at (1) takk@deb9:~$
実引数と仮引数のサイズが違うというwarningが出ました。
サイズを合わせれば、warningは出なくなるのでしょうか。
CHARACTER*10 AA
10を100に変更して、
CHARACTER*100 AA
再度ビルドします。
takk@deb9:~$ gfortran -g test.f takk@deb9:~$
warningは取れました。
もしかして、SPLIT関数のプログラムもサイズが違うから動作がおかしいのかもしれません。
文字列のサイズを、10/100混在していたのを100で統一したプログラムです。
takk@deb9:~$ cat split3.f PROGRAM MAIN CHARACTER*100 STRING CHARACTER*100 SSTRING1(10),SSTRING2(10) INTEGER I,SSIZE1,SSIZE2 STRING='1-3,5,7-12,13,14' WRITE(*,*) STRING CALL SPLIT(STRING,',', SSTRING1,SSIZE1) WRITE(*,1100) SSIZE1 DO 100 I=1,SSIZE1 WRITE(*,1300) SSTRING1(I) CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2) WRITE(*,1200) SSIZE2 100 CONTINUE 1100 FORMAT('SSIZE1=',I3) 1200 FORMAT(' SSIZE2=',I3) 1300 FORMAT('[',A,']') STOP END SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE) CHARACTER*100 SRC CHARACTER*1 SEP CHARACTER*100 DEST(10) INTEGER IND,S1,S2,RSLT,SPLIT_SIZE IND = 0 S2=-1 I = 1 20100 RSLT = INDEX(SRC(IND+1:),SEP) IF(RSLT.NE.0) THEN S1 = IND+1 IND = IND + RSLT S2 = IND-1 DEST(I) = SRC(S1:S2) I = I + 1 GOTO 20100 END IF DEST(I) = SRC(S2+2:) SPLIT_SIZE = I RETURN END takk@deb9:~$
ビルドして実行します。
takk@deb9:~$ gfortran split3.f takk@deb9:~$ ./a.out 1-3,5,7-12,13,14 SSIZE1= 5 [1-3 ] SSIZE2= 2 [5 ] SSIZE2= 1 [7-12 ] SSIZE2= 2 [13 ] SSIZE2= 1 [14 ] SSIZE2= 1 takk@deb9:~$
ぴったり数字が合いました。これを使えばcutの-bオプションが作れそうです。
FORTRANデバッグその3
SPLIT関数が直ったかどうか、コマンドにして確認してみます。
takk@deb9:~$ cat split3.f PROGRAM MAIN CHARACTER*100 ARGS CALL GETARG(1,ARGS) IF(ARGS(1:2).EQ.'-b') THEN CALL B_OPT(ARGS(3:)) END IF STOP END SUBROUTINE B_OPT(B_OPT_STRING) CHARACTER*100 B_OPT_STRING CHARACTER*100 SSTRING1(10),SSTRING2(10) INTEGER I,SSIZE1,SSIZE2 WRITE(*,*) B_OPT_STRING CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) WRITE(*,11100) SSIZE1 DO 10100 I=1,SSIZE1 WRITE(*,11300) SSTRING1(I) CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2) WRITE(*,11200) SSIZE2 10100 CONTINUE 11100 FORMAT('SSIZE1=',I3) 11200 FORMAT(' SSIZE2=',I3) 11300 FORMAT(' STRING:',A) STOP END SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE) CHARACTER*100 SRC CHARACTER*1 SEP CHARACTER*100 DEST(10) INTEGER IND,S1,S2,RSLT,SPLIT_SIZE IND = 0 S2=-1 I = 1 20100 RSLT = INDEX(SRC(IND+1:),SEP) IF(RSLT.NE.0) THEN S1 = IND+1 IND = IND + RSLT S2 = IND-1 DEST(I) = SRC(S1:S2) I = I + 1 GOTO 20100 END IF DEST(I) = SRC(S2+2:) SPLIT_SIZE = I RETURN END takk@deb9:~$
使い方は、cutコマンドの-bオプションを指定するように./a.out -bの後に文字列を指定すればよいです。
テスト用のスクリプトを組みます。テスト内容は適当です。
takk@deb9:~$ cat split3-test.f cmd=./a.out $cmd -b1 echo $cmd -b5 echo $cmd -b1,2 echo $cmd -b1,3,5 echo $cmd -b2,4,5 echo $cmd -b1-2 echo $cmd -b2-5 echo $cmd -b1-2,3 echo $cmd -b1,2-4,5 echo $cmd -b1-3,4-5 echo takk@deb9:~$
スクリプト実行の結果に文字列のサイズ分空白で出てしまって見にくいので、空白はすべてtrでカットして実行しました。
takk@deb9:~$ . split3-test.sh | tr -d ' ' 1 SSIZE1=1 STRING:1 SSIZE2=1 5 SSIZE1=1 STRING:5 SSIZE2=1 1,2 SSIZE1=2 STRING:1 SSIZE2=1 STRING:2 SSIZE2=1 1,3,5 SSIZE1=3 STRING:1 SSIZE2=1 STRING:3 SSIZE2=1 STRING:5 SSIZE2=1 2,4,5 SSIZE1=3 STRING:2 SSIZE2=1 STRING:4 SSIZE2=1 STRING:5 SSIZE2=1 1-2 SSIZE1=1 STRING:1-2 SSIZE2=2 2-5 SSIZE1=1 STRING:2-5 SSIZE2=2 1-2,3 SSIZE1=2 STRING:1-2 SSIZE2=2 STRING:3 SSIZE2=1 1,2-4,5 SSIZE1=3 STRING:1 SSIZE2=1 STRING:2-4 SSIZE2=2 STRING:5 SSIZE2=1 1-3,4-5 SSIZE1=2 STRING:1-3 SSIZE2=2 STRING:4-5 SSIZE2=2 takk@deb9:~$
いわゆるprintfデバッグというものでしょう。 結果も見にくいですが、一応合ってそうです。
FORTRANデバッグその4
前回はprintfテストをしたので、今回はGDBのスクリプトでテストします。
まず、対象プログラムからテスト用コードを取っ払います。
takk@deb9:~$ cat -n split4.f 1 PROGRAM MAIN 2 CHARACTER*100 ARGS 3 CALL GETARG(1,ARGS) 4 IF(ARGS(1:2).EQ.'-b') THEN 5 CALL B_OPT(ARGS(3:)) 6 END IF 7 STOP 8 END 9 10 SUBROUTINE B_OPT(B_OPT_STRING) 11 CHARACTER*100 B_OPT_STRING 12 CHARACTER*100 SSTRING1(10),SSTRING2(10) 13 INTEGER I,SSIZE1,SSIZE2 14 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) 16 DO 10100 I=1,SSIZE1 17 CALL SPLIT(SSTRING1(I),'-', SSTRING2,SSIZE2) 18 10100 CONTINUE 19 STOP 20 END 21 22 SUBROUTINE SPLIT(SRC,SEP,DEST,SPLIT_SIZE) 23 CHARACTER*100 SRC 24 CHARACTER*1 SEP 25 CHARACTER*100 DEST(10) 26 INTEGER IND,S1,S2,RSLT,SPLIT_SIZE 27 IND = 0 28 S2=-1 29 I = 1 30 20100 RSLT = INDEX(SRC(IND+1:),SEP) 31 IF(RSLT.NE.0) THEN 32 S1 = IND+1 33 IND = IND + RSLT 34 S2 = IND-1 35 DEST(I) = SRC(S1:S2) 36 I = I + 1 37 GOTO 20100 38 END IF 39 DEST(I) = SRC(S2+2:) 40 SPLIT_SIZE = I 41 RETURN 42 END takk@deb9:~$
次に-gオプションをつけてビルド。
takk@deb9:~$ gfortran -g split4.f takk@deb9:~$
次はGDBのスクリプトを作成します。GDBで使うコマンドを列挙したテキストファイルを作ればよいです。
takk@deb9:~$ cat test1 break B_OPT set args -b1,2 run next echo "B_OPT_STRING:" print B_OPT_STRING echo "SSIZE1:" print SSIZE1 set args -b1,2,3,4,5 run next echo "B_OPT_STRING:" print B_OPT_STRING echo "SSIZE1:" print SSIZE1 set args -b1,2,3,4,5,7,8,9,10 run next echo "B_OPT_STRING:" print B_OPT_STRING echo "SSIZE1:" print SSIZE1 takk@deb9:~$
GDBのスクリプト名はtest1としました。
実行は、-xオプションをつけてファイル名を指定します。
takk@deb9:~$ gdb a.out -x test1
実行すると、結果が表示されます。
Reading symbols from a.out...done. Breakpoint 1 at 0x979: file split4.f, line 15. Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) 16 DO 10100 I=1,SSIZE1 "B_OPT_STRING:"$1 = '1,2', ' ' <repeats 95 times>, '\000\000' "SSIZE1:"$2 = 2 Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15 ---Type <return> to continue, or q <return> to quit---
Enterキー待ちになるので、確認したら、Enterを押します。
---Type <return> to continue, or q <return> to quit--- 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) 16 DO 10100 I=1,SSIZE1 "B_OPT_STRING:"$3 = '1,2,3,4,5', ' ' <repeats 89 times>, '\000\000' "SSIZE1:"$4 = 5 Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) 16 DO 10100 I=1,SSIZE1 "B_OPT_STRING:"$5 = '1,2,3,4,5,7,8,9,10', ' ' <repeats 80 times>, '\000\000' "SSIZE1:"$6 = 9 (gdb)
結果の続きが表示されます。
FORTRANデバッグその5
FORTRANでデバッグ続きです。
man gdbを見てみるとこのような一文がありました。
GDB では C, C++, Modula-2 などで書かれたプログラムのデバッグが行なえま す。 GNU Fortran コンパイラが完成すれば Fortran もサポートされます。
Fortranコンパイラが完成すればサポートされると書いてありますが、デバッグできててますね。FORTRANと書くとFORTRAN77以前のことで、Fortranと書くと90以降のことを言うらしいですが、90や95のプログラムでもGDB動かせるようです。
ちなみに使用しているGDBのバージョンはこれです。今は8があるのでちょっと古めです。
takk@deb9:~$ gdb --version GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
FORTRANプログラムの文字列変数をGDBで表示するとき、文字列が格納されていない領域が全部スペースなのでとても見にくいです。これをなんとか解消してデバッグしやすくならないでしょうか。
Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) (gdb) p B_OPT_STRING $1 = '1,2,3,4,5', ' ' <repeats 89 times>, '\000\000' (gdb)
思いつくのは、先頭の空白の位置に0(NULL)を入れることぐらいです。
GDBから見ると普通の文字列が格納されているように見えます、きっと。
FORTRANのプログラムを、INDEXで空白を探し、代わりに0を格納するように改造してみます。
文字列に0を代入するのもエスケープシーケンスというものがあるかどうかも現状は分からないので、これまでで覚えたものを使ってやってみます。
takk@deb9:~$ cat -n split5.f 1 PROGRAM MAIN 2 CHARACTER*100 ARGS 3 INTEGER*1 A(100),SP_LOC 4 EQUIVALENCE (A,ARGS) 5 CALL GETARG(1,ARGS) 6 SP_LOC=INDEX(ARGS,' ') 7 A(SP_LOC)=0 8 IF(ARGS(1:2).EQ.'-b') THEN 9 CALL B_OPT(ARGS(3:)) 10 END IF 11 STOP 12 END
3行目で文字列の途中にNULLを入れるための、配列変数Aを用意してます。
この配列変数AをEQUIVALENCEで文字列ARGSと結合させ、INDEXで見つけた空白の位置に、A(位置)=0とすることで、文字列ARGSに(GDBから見て)終端コードである空白を埋め込んでいます。
では-gオプションをつけて、ビルド後、GDBを起動してみましょう。
takk@deb9:~$ gfortran -g split5.f takk@deb9:~$ gdb a.out
引数に”-b1,2,3,4,5″を設定し、mainでブレイクさせます。
(gdb) set args -b1,2,3,4,5 (gdb) b main Breakpoint 1 at 0xdb4: file split5.f, line 12. (gdb) run Starting program: /home/takk/a.out -b1,2,3,4,5 Breakpoint 1, main (argc=2, argv=0x7fffffffe7e2) at split5.f:12 12 END (gdb)
NULL埋め込み後、printfで文字列を確認します。
(gdb) step MAIN__ () at split5.f:5 5 CALL GETARG(1,ARGS) (gdb) next 6 SP_LOC=INDEX(ARGS,' ') (gdb) n 7 A(SP_LOC)=0 (gdb) n 8 IF(ARGS(1:2).EQ.'-b') THEN (gdb) printf "%s\n",&args -b1,2,3,4,5 (gdb)
NULLを埋め込む前だと、いらない空白がたくさん出てしまっていましたが、必要な分だけ表示されました。
FORTRANデバッグその6
前回はGDBデバッグで文字列を見やすくするため、デバッグ対象であるFORTRANプログラムを修正しました。ナンセンスなことですが、早く覚えるためできるだけたくさんFORTRANのプログラミングをしていこうと思ってます。たまたま思いつきでFORTRANプログラミングできましたが、いつもうまくいくとは限りません。あまりFORTRANに根気を使うと疲れてしまって、勉強も続かないので、FORTRAN修得までの長い道のりのモチベーション維持のため、たまには使い慣れた言語を使ってみます。
さてFORTRANのプログラムを改造せずに、文字列をきれいに表示する方法として、今回はGDBでPythonスクリプトを使います。まずはPythonをGDBで使えるようにインストール。
root@deb9:~# apt-get install gdb-python2
これで、GDBでPythonスクリプト使えます。さっそく試してみましょう。
takk@deb9:~$ gdb a.out ~ (gdb) pi print 'HELLO' HELLO (gdb)
素晴らしい。これもうGDBも思いのままです。
GDBスクリプトの代わりにPythonスクリプトでGDB自動化してみましょう。こんなスクリプトを使います。
takk@deb9:~$ cat -n test1.py 1 gdb.execute('set args -b1,2,3,4,5') 2 gdb.execute('break B_OPT') 3 gdb.execute('run') takk@deb9:~$
executeの中の文字列は、GDBスクリプトそのままです。
FORTRANプログラムを-g付きでビルドして、GDB起動します。
takk@deb9:~$ gfortran -g split4.f takk@deb9:~$ gdb a.out
さきほどのPythonスクリプトを実行しましょう。
(gdb) source test1.py Breakpoint 1 at 0x979: file split4.f, line 15. Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) (gdb)
さて、問題の残りが空白で埋められる文字列変数ですが、Pythonスクリプトを使って、きれいにトリムしたいと思います。
(gdb) p b_opt_string $1 = '1,2,3,4,5', ' ' <repeats 89 times>, '\000\000' (gdb)
さきほどのスクリプトに4~6行目を追加しました。FORTRAN77ではすぐには書けない文字列処理も、Pythonではお茶の子さいさい。
takk@deb9:~$ cat -n test2.py 1 gdb.execute('set args -b1,2,3,4,5') 2 gdb.execute('break B_OPT') 3 gdb.execute('run') 4 s=gdb.execute('p B_OPT_STRING',to_string=True) 5 ss=(s.split("'"))[1] 6 print ss takk@deb9:~$
再度GDBを起動します。今度は、gdb起動時に、-xオプションでPythonスクリプトも一緒に実行してしまいましょう。
takk@deb9:~$ gdb a.out -x test2.py ~ Breakpoint 1 at 0x979: file split4.f, line 15. Breakpoint 1, b_opt (b_opt_string=..., _b_opt_string=98) at split4.f:15 15 CALL SPLIT(B_OPT_STRING,',', SSTRING1,SSIZE1) 1,2,3,4,5 (gdb)
シングルクォートをPythonでスプリットしてリストの2番目を表示しました。
-bオプションで指定した文字列が、そのままさくっと表示されましたね。
コメント