FORTRAN(自作splitのデバッグ)

旧2-5. Fortran毎日学習

本記事はリライト中です。

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オプションで指定した文字列が、そのままさくっと表示されましたね。

コメント

タイトルとURLをコピーしました