本記事はリライト中です。
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オプションで指定した文字列が、そのままさくっと表示されましたね。


コメント